Skip to content

Commit f878f66

Browse files
authored
Merge pull request #18635 from unoplatform/mergify/bp/release/stable/5.5/pr-18606
fix(vs): Ensure that infobars get closed properly (backport #18606)
2 parents 7048f4d + f6c1d3e commit f878f66

File tree

5 files changed

+135
-53
lines changed

5 files changed

+135
-53
lines changed

src/Uno.UI.RemoteControl.VS/Commands/UnoMenuCommand.cs

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.ComponentModel.Design;
44
using System.Diagnostics.CodeAnalysis;
55
using System.Linq;
6+
using System.Management.Instrumentation;
7+
using System.Threading.Tasks;
68
using Microsoft.VisualStudio.Shell;
79
using Uno.UI.RemoteControl.Messaging.IdeChannel;
810
using Uno.UI.RemoteControl.VS.Commands;
@@ -11,62 +13,70 @@
1113

1214
namespace Uno.UI.RemoteControl.VS;
1315

14-
internal sealed class UnoMenuCommand
16+
internal sealed class UnoMenuCommand : IDisposable
1517
{
1618
private readonly AsyncPackage _package;
1719
private OleMenuCommandService CommandService { get; set; }
1820
private IdeChannelClient IdeChannelClient;
19-
21+
private DynamicItemMenuCommand? _dynamicMenuCommand;
22+
private OleMenuCommand? _unoMainMenuItem;
2023
private static readonly Guid UnoStudioPackageCmdSet = new Guid("6c532d75-ee35-4726-a1cd-338c5243e38f");
2124
private static readonly int UnoMainMenu = 0x4100;
2225
private static readonly int DynamicMenuCommandId = 0x4103;
2326

2427
public List<AddMenuItemRequestIdeMessage> CommandList { get; set; } = [];
25-
public static UnoMenuCommand? Instance { get; private set; }
2628

27-
private UnoMenuCommand(AsyncPackage package, IdeChannelClient ideChannelClient, OleMenuCommandService commandService, AddMenuItemRequestIdeMessage cr)
29+
private UnoMenuCommand(
30+
AsyncPackage package
31+
, IdeChannelClient ideChannelClient
32+
, OleMenuCommandService commandService
33+
, AddMenuItemRequestIdeMessage cr)
2834
{
2935
_package = package ?? throw new ArgumentNullException(nameof(_package));
3036
CommandService = commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
3137
IdeChannelClient = ideChannelClient ?? throw new ArgumentNullException(nameof(ideChannelClient));
3238
CommandList.Add(cr);
3339

34-
CommandID dynamicItemRootId = new CommandID(UnoStudioPackageCmdSet, DynamicMenuCommandId);
40+
var dynamicItemRootId = new CommandID(UnoStudioPackageCmdSet, DynamicMenuCommandId);
3541
if (commandService.FindCommand(dynamicItemRootId) is not DynamicItemMenuCommand)
3642
{
37-
DynamicItemMenuCommand dynamicMenuCommand = new DynamicItemMenuCommand(
43+
_dynamicMenuCommand = new DynamicItemMenuCommand(
3844
dynamicItemRootId,
3945
IsValidDynamicItem,
4046
OnInvokedDynamicItem,
4147
OnBeforeQueryStatusDynamicItem);
42-
commandService.AddCommand(dynamicMenuCommand);
48+
commandService.AddCommand(_dynamicMenuCommand);
49+
}
50+
51+
var unoMainMenuId = new CommandID(UnoStudioPackageCmdSet, UnoMainMenu);
52+
if (commandService.FindCommand(unoMainMenuId) is not OleMenuCommand)
53+
{
54+
_unoMainMenuItem = new OleMenuCommand(null, unoMainMenuId);
55+
_unoMainMenuItem.BeforeQueryStatus += OnBeforeQueryStatus;
56+
commandService.AddCommand(_unoMainMenuItem);
57+
}
58+
59+
var dynamicMenuCommandIdId = new CommandID(UnoStudioPackageCmdSet, DynamicMenuCommandId);
60+
if (commandService.FindCommand(dynamicMenuCommandIdId) is DynamicItemMenuCommand dynamicMenuItem)
61+
{
62+
dynamicMenuItem.BeforeQueryStatus += OnBeforeQueryStatus;
4363
}
4464
}
4565

46-
public static async Task InitializeAsync(AsyncPackage package, IdeChannelClient ideChannelClient, AddMenuItemRequestIdeMessage cr)
66+
public static async Task<UnoMenuCommand> InitializeAsync(
67+
AsyncPackage package
68+
, IdeChannelClient ideChannelClient
69+
, AddMenuItemRequestIdeMessage cr)
4770
{
4871
// Switch to the main thread - the call to AddCommand in DynamicMenu's constructor requires the UI thread.
4972
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
5073

51-
if (Instance is null
52-
&& await package.GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService commandService)
74+
if (await package.GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService commandService)
5375
{
54-
Instance = new UnoMenuCommand(package, ideChannelClient, commandService, cr);
55-
56-
CommandID unoMainMenuId = new CommandID(UnoStudioPackageCmdSet, UnoMainMenu);
57-
if (Instance.CommandService.FindCommand(unoMainMenuId) is not OleMenuCommand)
58-
{
59-
var unoMenuItem = new OleMenuCommand(null, unoMainMenuId);
60-
unoMenuItem.BeforeQueryStatus += Instance.OnBeforeQueryStatus;
61-
commandService.AddCommand(unoMenuItem);
62-
}
63-
64-
CommandID dynamicMenuCommandIdId = new CommandID(UnoStudioPackageCmdSet, DynamicMenuCommandId);
65-
if (Instance.CommandService.FindCommand(dynamicMenuCommandIdId) is DynamicItemMenuCommand dynamicMenuItem)
66-
{
67-
dynamicMenuItem.BeforeQueryStatus += Instance.OnBeforeQueryStatus;
68-
}
76+
return new UnoMenuCommand(package, ideChannelClient, commandService, cr);
6977
}
78+
79+
throw new InvalidOperationException("IMenuCommandService is not availabe");
7080
}
7181

7282
private void OnBeforeQueryStatus(object sender, EventArgs e)
@@ -108,10 +118,12 @@ sender is DynamicItemMenuCommand matchedCommand &&
108118
private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
109119
{
110120
ThreadHelper.ThrowIfNotOnUIThread();
121+
111122
if (!CommandList.Any())
112123
{
113124
return;
114125
}
126+
115127
DynamicItemMenuCommand matchedCommand = (DynamicItemMenuCommand)sender;
116128
matchedCommand.Enabled = true;
117129
matchedCommand.Visible = true;
@@ -124,10 +136,24 @@ private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
124136
matchedCommand.MatchedCommandId = 0;
125137
}
126138

127-
private static int GetCurrentPosition(DynamicItemMenuCommand matchedCommand) =>
128-
// The position of the command is the command ID minus the ID of the root dynamic start item.
129-
matchedCommand.MatchedCommandId == 0 ? 0 : matchedCommand.MatchedCommandId - (int)DynamicMenuCommandId;
139+
private static int GetCurrentPosition(DynamicItemMenuCommand matchedCommand)
140+
// The position of the command is the command ID minus the ID of the root dynamic start item.
141+
=> matchedCommand.MatchedCommandId == 0 ? 0 : matchedCommand.MatchedCommandId - (int)DynamicMenuCommandId;
130142

131143
private bool TryGetCommandRequestIdeMessage(DynamicItemMenuCommand matchedCommand, [NotNullWhen(true)] out AddMenuItemRequestIdeMessage result)
132144
=> (result = CommandList.Skip(GetCurrentPosition(matchedCommand)).FirstOrDefault()) != null;
145+
146+
public void Dispose()
147+
{
148+
if (_dynamicMenuCommand is not null)
149+
{
150+
CommandService.RemoveCommand(_dynamicMenuCommand);
151+
}
152+
153+
if (_unoMainMenuItem is not null)
154+
{
155+
_unoMainMenuItem.Enabled = false;
156+
_unoMainMenuItem.Visible = false;
157+
}
158+
}
133159
}

src/Uno.UI.RemoteControl.VS/EntryPoint.ActiveProfileSync.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ private async Task OnDebugFrameworkChangedAsync(string? previousFramework, strin
3838
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
3939

4040
// In this case, a new TargetFramework was selected. We need to file a matching launch profile, if any.
41-
if (GetTargetFrameworkIdentifier(newFramework) is { } targetFrameworkIdentifier)
41+
if (GetTargetFrameworkIdentifier(newFramework) is { } targetFrameworkIdentifier && _debuggerObserver is not null)
4242
{
4343
_debugAction?.Invoke($"OnDebugFrameworkChangedAsync({previousFramework}, {newFramework}, {targetFrameworkIdentifier}, forceReload: {forceReload})");
4444

@@ -134,6 +134,11 @@ private async Task OnDebugProfileChangedAsync(string? previousProfile, string ne
134134
return;
135135
}
136136

137+
if (_debuggerObserver is null)
138+
{
139+
return;
140+
}
141+
137142
var targetFrameworks = await _debuggerObserver.GetActiveTargetFrameworksAsync();
138143
var profiles = await _debuggerObserver.GetLaunchProfilesAsync();
139144

@@ -305,7 +310,7 @@ previousTargetFrameworkIdentifier is WasmTargetFrameworkIdentifier
305310

306311
private async Task OnStartupProjectChangedAsync()
307312
{
308-
if (!await EnsureProjectUserSettingsAsync())
313+
if (!await EnsureProjectUserSettingsAsync() && _debuggerObserver is not null)
309314
{
310315
_debugAction?.Invoke($"The user setting is not yet initialized, aligning framework and profile");
311316

@@ -332,7 +337,8 @@ private async Task OnStartupProjectChangedAsync()
332337
private async Task<bool> EnsureProjectUserSettingsAsync()
333338
{
334339
if (await _asyncPackage.GetServiceAsync(typeof(SVsSolution)) is IVsSolution solution
335-
&& await _dte.GetStartupProjectsAsync() is { Length: > 0 } startupProjects)
340+
&& await _dte.GetStartupProjectsAsync() is { Length: > 0 } startupProjects
341+
&& _debuggerObserver is not null)
336342
{
337343
// Convert DTE project to IVsHierarchy
338344
solution.GetProjectOfUniqueName(startupProjects[0].UniqueName, out var hierarchy);

src/Uno.UI.RemoteControl.VS/EntryPoint.cs

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ public partial class EntryPoint : IDisposable
5858
private bool _closing;
5959
private bool _isDisposed;
6060
private IdeChannelClient? _ideChannelClient;
61-
private ProfilesObserver _debuggerObserver;
62-
private GlobalJsonObserver _globalJsonObserver;
61+
private ProfilesObserver? _debuggerObserver;
62+
private InfoBarFactory? _infoBarFactory;
63+
private GlobalJsonObserver? _globalJsonObserver;
6364
private readonly Func<Task> _globalPropertiesChanged;
64-
private readonly _dispSolutionEvents_BeforeClosingEventHandler _closeHandler;
65-
private readonly _dispBuildEvents_OnBuildBeginEventHandler _onBuildBeginHandler;
66-
private readonly _dispBuildEvents_OnBuildDoneEventHandler _onBuildDoneHandler;
67-
private readonly _dispBuildEvents_OnBuildProjConfigBeginEventHandler _onBuildProjConfigBeginHandler;
65+
private _dispSolutionEvents_BeforeClosingEventHandler? _closeHandler;
66+
private _dispBuildEvents_OnBuildBeginEventHandler? _onBuildBeginHandler;
67+
private _dispBuildEvents_OnBuildDoneEventHandler? _onBuildDoneHandler;
68+
private _dispBuildEvents_OnBuildProjConfigBeginEventHandler? _onBuildProjConfigBeginHandler;
69+
private UnoMenuCommand? _unoMenuCommand;
6870

6971
public EntryPoint(
7072
DTE2 dte2
@@ -80,6 +82,13 @@ DTE2 dte2
8082
globalPropertiesProvider(OnProvideGlobalPropertiesAsync);
8183
_globalPropertiesChanged = globalPropertiesChanged;
8284

85+
_ = ThreadHelper.JoinableTaskFactory.RunAsync(() => InitializeAsync(asyncPackage));
86+
}
87+
88+
private async Task InitializeAsync(AsyncPackage asyncPackage)
89+
{
90+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
91+
8392
SetupOutputWindow();
8493

8594
_closeHandler = () => SolutionEvents_BeforeClosing();
@@ -108,7 +117,13 @@ DTE2 dte2
108117
, OnStartupProjectChangedAsync
109118
, _debugAction);
110119

111-
_globalJsonObserver = new GlobalJsonObserver(asyncPackage, _dte, _debugAction, _infoAction, _warningAction, _errorAction);
120+
if (await _asyncPackage.GetServiceAsync(typeof(SVsShell)) is IVsShell shell
121+
&& await _asyncPackage.GetServiceAsync(typeof(SVsInfoBarUIFactory)) is IVsInfoBarUIFactory infoBarFactory)
122+
{
123+
_infoBarFactory = new InfoBarFactory(infoBarFactory, shell);
124+
125+
_globalJsonObserver = new GlobalJsonObserver(asyncPackage, _dte, _infoBarFactory, _debugAction, _infoAction, _warningAction, _errorAction);
126+
}
112127

113128
_ = _debuggerObserver.ObserveProfilesAsync();
114129

@@ -450,17 +465,17 @@ private async Task OnAddMenuItemRequestIdeMessageAsync(object? sender, AddMenuIt
450465
return;
451466
}
452467

453-
if (UnoMenuCommand.Instance is { } instance)
468+
if (_unoMenuCommand is not null)
454469
{
455470
//ignore when duplicated
456-
if (!instance.CommandList.Contains(cr))
471+
if (!_unoMenuCommand.CommandList.Contains(cr))
457472
{
458-
instance.CommandList.Add(cr);
473+
_unoMenuCommand.CommandList.Add(cr);
459474
}
460475
}
461476
else
462477
{
463-
await UnoMenuCommand.InitializeAsync(_asyncPackage, _ideChannelClient, cr);
478+
_unoMenuCommand = await UnoMenuCommand.InitializeAsync(_asyncPackage, _ideChannelClient, cr);
464479
}
465480
}
466481
catch (Exception e)
@@ -472,13 +487,12 @@ private async Task OnAddMenuItemRequestIdeMessageAsync(object? sender, AddMenuIt
472487

473488
private async Task CreateInfoBarAsync(NotificationRequestIdeMessage e, IVsShell shell, IVsInfoBarUIFactory infoBarFactory)
474489
{
475-
if (_ideChannelClient is null)
490+
if (_ideChannelClient is null || _infoBarFactory is null)
476491
{
477492
return;
478493
}
479-
var factory = new InfoBarFactory(infoBarFactory, shell);
480494

481-
var infoBar = await factory.CreateAsync(
495+
var infoBar = await _infoBarFactory.CreateAsync(
482496
new InfoBarModel(
483497
e.Message,
484498
e.Commands.Select(Commands => new ActionBarItem
@@ -500,12 +514,14 @@ private async Task CreateInfoBarAsync(NotificationRequestIdeMessage e, IVsShell
500514
if (e.ActionItem is ActionBarItem action &&
501515
action.Name is { } command)
502516
{
503-
var cmd =
504-
new CommandRequestIdeMessage(
505-
System.Diagnostics.Process.GetCurrentProcess().Id,
506-
command,
507-
action.ActionContext?.ToString());
517+
var cmd = new CommandRequestIdeMessage(
518+
System.Diagnostics.Process.GetCurrentProcess().Id,
519+
command,
520+
action.ActionContext?.ToString());
521+
508522
await _ideChannelClient.SendToDevServerAsync(cmd, _ct.Token);
523+
524+
infoBar.Close();
509525
}
510526
});
511527
};
@@ -622,7 +638,9 @@ public void Dispose()
622638
_dte.Events.BuildEvents.OnBuildBegin -= _onBuildBeginHandler;
623639
_dte.Events.BuildEvents.OnBuildDone -= _onBuildDoneHandler;
624640
_dte.Events.BuildEvents.OnBuildProjConfigBegin -= _onBuildProjConfigBeginHandler;
625-
_globalJsonObserver.Dispose();
641+
_globalJsonObserver?.Dispose();
642+
_infoBarFactory?.Dispose();
643+
_unoMenuCommand?.Dispose();
626644
}
627645
catch (Exception e)
628646
{

src/Uno.UI.RemoteControl.VS/GlobalJsonObserver.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ internal class GlobalJsonObserver
2323
private readonly Action<string> _infoAction;
2424
private readonly Action<string> _warningAction;
2525
private readonly Action<string> _errorAction;
26+
private readonly InfoBarFactory _infoBarFactory;
2627
private FileSystemWatcher? _fileWatcher;
2728
private readonly JsonSerializerOptions _readerOptions = new() { ReadCommentHandling = JsonCommentHandling.Skip };
2829

2930
public GlobalJsonObserver(
3031
AsyncPackage asyncPackage
3132
, DTE dte
33+
, InfoBarFactory infoBarFactory
3234
, Action<string> debugAction
3335
, Action<string> infoAction
3436
, Action<string> warningAction
@@ -40,6 +42,7 @@ AsyncPackage asyncPackage
4042
_infoAction = infoAction;
4143
_warningAction = warningAction;
4244
_errorAction = errorAction;
45+
_infoBarFactory = infoBarFactory;
4346

4447
_debugAction("GlobalJsonObserver: Starting");
4548

0 commit comments

Comments
 (0)