Skip to content

Commit 14919df

Browse files
authored
CmdPal: Fix SUI crash ; Allow extensions to be disabled (#38040)
Both `TopLevelCommandItemWrapper` and `TopLevelViewModel` were really the same thing. The latter was from an earlier prototype, and the former is a more correct, safer abstraction. We really should have only ever used the former, but alas, we only used it for the SUI, and it piggy-backed off the latter, and that meant the latter's bugs became the former's. tldr: I made the icon access safe in the SUI. And while I was doing this, because we now have a cleaner VM abstraction here in the host, we can actually cleanly disable extensions, because the `CommandProviderWrapper` knows which `ViewModel`s it made. Closes zadjii-msft#426 Closes zadjii-msft#478 Closes zadjii-msft#577
1 parent 57cbcc2 commit 14919df

34 files changed

+584
-522
lines changed

src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Shell/Microsoft.CmdPal.Ext.Shell.csproj

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
</EmbeddedResource>
2525
</ItemGroup>
2626

27-
2827
<ItemGroup>
29-
<Content Update="Assets\[email protected]">
28+
<None Remove="Assets\Run_V2_2x.svg" />
29+
</ItemGroup>
30+
<ItemGroup>
31+
<Content Update="Assets\Run_V2_2x.svg">
3032
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3133
</Content>
3234
</ItemGroup>

src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal sealed partial class ShellListPage : DynamicListPage
1515

1616
public ShellListPage(SettingsManager settingsManager)
1717
{
18-
Icon = new IconInfo("\uE756");
18+
Icon = Icons.RunV2;
1919
Id = "com.microsoft.cmdpal.shell";
2020
Name = Resources.cmd_plugin_name;
2121
PlaceholderText = Resources.list_placeholder_text;

src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Shell/Properties/Resources.Designer.cs

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Shell/Properties/Resources.resx

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
<value>execute command through command shell</value>
131131
</data>
132132
<data name="cmd_run_as_administrator" xml:space="preserve">
133-
<value>Run as administrator (Ctrl+Shift+Enter)</value>
133+
<value>Run as administrator</value>
134134
</data>
135135
<data name="cmd_command_failed" xml:space="preserve">
136136
<value>Error running the command</value>
@@ -139,7 +139,7 @@
139139
<value>Command not found</value>
140140
</data>
141141
<data name="cmd_run_as_user" xml:space="preserve">
142-
<value>Run as different user (Ctrl+Shift+U)</value>
142+
<value>Run as different user</value>
143143
</data>
144144
<data name="leave_shell_open" xml:space="preserve">
145145
<value>Keep shell open</value>

src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.System/SystemCommandExtensionProvider.cs

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public partial class SystemCommandExtensionProvider : CommandProvider
1818
public SystemCommandExtensionProvider()
1919
{
2020
DisplayName = Resources.Microsoft_plugin_ext_system_page_name;
21+
Id = "System";
2122
_commands = [
2223
new CommandItem(Page)
2324
{

src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.TimeDate/TimeDateCommandsProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public partial class TimeDateCommandsProvider : CommandProvider
2222
public TimeDateCommandsProvider()
2323
{
2424
DisplayName = Resources.Microsoft_plugin_timedate_plugin_name;
25-
25+
Id = "DateTime";
2626
_command = new CommandItem(_timeDateExtensionPage)
2727
{
2828
Icon = _timeDateExtensionPage.Icon,

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs

+4
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ public override void InitializeProperties()
146146
UpdateProperty(nameof(Title));
147147
UpdateProperty(nameof(Subtitle));
148148
UpdateProperty(nameof(Icon));
149+
150+
// Load-bearing: if you don't raise a IsInitialized here, then
151+
// TopLevelViewModel will never know what the command's ID is, so it
152+
// will never be able to load Hotkeys & aliases
149153
UpdateProperty(nameof(IsInitialized));
150154

151155
Initialized |= InitializedState.Initialized;

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs

+47-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using Microsoft.CmdPal.Common.Services;
77
using Microsoft.CmdPal.UI.ViewModels.Models;
88
using Microsoft.CommandPalette.Extensions;
9+
using Microsoft.Extensions.DependencyInjection;
10+
911
using Windows.Foundation;
1012

1113
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -20,9 +22,9 @@ public sealed class CommandProviderWrapper
2022

2123
private readonly TaskScheduler _taskScheduler;
2224

23-
public ICommandItem[] TopLevelItems { get; private set; } = [];
25+
public TopLevelViewModel[] TopLevelItems { get; private set; } = [];
2426

25-
public IFallbackCommandItem[] FallbackItems { get; private set; } = [];
27+
public TopLevelViewModel[] FallbackItems { get; private set; } = [];
2628

2729
public string DisplayName { get; private set; } = string.Empty;
2830

@@ -38,7 +40,13 @@ public sealed class CommandProviderWrapper
3840

3941
public CommandSettingsViewModel? Settings { get; private set; }
4042

41-
public string ProviderId => $"{Extension?.PackageFamilyName ?? string.Empty}/{Id}";
43+
public string ProviderId
44+
{
45+
get
46+
{
47+
return string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
48+
}
49+
}
4250

4351
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
4452
{
@@ -105,21 +113,33 @@ public CommandProviderWrapper(IExtensionWrapper extension, TaskScheduler mainThr
105113
isValid = true;
106114
}
107115

108-
public async Task LoadTopLevelCommands()
116+
private ProviderSettings GetProviderSettings(SettingsModel settings)
117+
{
118+
return settings.GetProviderSettings(this);
119+
}
120+
121+
public async Task LoadTopLevelCommands(IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
109122
{
110123
if (!isValid)
111124
{
112125
return;
113126
}
114127

128+
var settings = serviceProvider.GetService<SettingsModel>()!;
129+
130+
if (!GetProviderSettings(settings).IsEnabled)
131+
{
132+
return;
133+
}
134+
115135
ICommandItem[]? commands = null;
116136
IFallbackCommandItem[]? fallbacks = null;
117137

118138
try
119139
{
120140
var model = _commandProvider.Unsafe!;
121141

122-
var t = new Task<ICommandItem[]>(model.TopLevelCommands);
142+
Task<ICommandItem[]> t = new(model.TopLevelCommands);
123143
t.Start();
124144
commands = await t.ConfigureAwait(false);
125145

@@ -134,6 +154,8 @@ public async Task LoadTopLevelCommands()
134154
Settings = new(model.Settings, this, _taskScheduler);
135155
Settings.InitializeProperties();
136156

157+
InitializeCommands(commands, fallbacks, serviceProvider, pageContext);
158+
137159
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
138160
}
139161
catch (Exception e)
@@ -142,15 +164,33 @@ public async Task LoadTopLevelCommands()
142164
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
143165
Logger.LogError(e.ToString());
144166
}
167+
}
168+
169+
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
170+
{
171+
var settings = serviceProvider.GetService<SettingsModel>()!;
172+
173+
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
174+
{
175+
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
176+
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, serviceProvider);
177+
178+
topLevelViewModel.ItemViewModel.SlowInitializeProperties();
145179

180+
return topLevelViewModel;
181+
};
146182
if (commands != null)
147183
{
148-
TopLevelItems = commands;
184+
TopLevelItems = commands
185+
.Select(c => makeAndAdd(c, false))
186+
.ToArray();
149187
}
150188

151189
if (fallbacks != null)
152190
{
153-
FallbackItems = fallbacks;
191+
FallbackItems = fallbacks
192+
.Select(c => makeAndAdd(c, true))
193+
.ToArray();
154194
}
155195
}
156196

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandViewModel.cs

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public void FastInitializeProperties()
4949
return;
5050
}
5151

52+
Id = model.Id ?? string.Empty;
5253
Name = model.Name ?? string.Empty;
5354
IsFastInitialized = true;
5455
}

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ public override IListItem[] GetItems()
7373
{
7474
return _tlcManager
7575
.TopLevelCommands
76-
.Select(tlc => tlc)
7776
.Where(tlc => !string.IsNullOrEmpty(tlc.Title))
7877
.ToArray();
7978
}
@@ -167,16 +166,17 @@ private int ScoreTopLevelItem(string query, IListItem topLevelOrAppItem)
167166
var id = IdForTopLevelOrAppItem(topLevelOrAppItem);
168167

169168
var extensionDisplayName = string.Empty;
170-
if (topLevelOrAppItem is TopLevelCommandItemWrapper toplevel)
169+
if (topLevelOrAppItem is TopLevelViewModel topLevel)
171170
{
172-
isFallback = toplevel.IsFallback;
173-
if (toplevel.Alias?.Alias is string alias)
171+
isFallback = topLevel.IsFallback;
172+
if (topLevel.HasAlias)
174173
{
174+
var alias = topLevel.AliasText;
175175
isAliasMatch = alias == query;
176176
isAliasSubstringMatch = isAliasMatch || alias.StartsWith(query, StringComparison.CurrentCultureIgnoreCase);
177177
}
178178

179-
extensionDisplayName = toplevel.ExtensionHost?.Extension?.PackageDisplayName ?? string.Empty;
179+
extensionDisplayName = topLevel.ExtensionHost?.Extension?.PackageDisplayName ?? string.Empty;
180180
}
181181

182182
var nameMatch = StringMatcher.FuzzySearch(query, title);
@@ -221,9 +221,9 @@ public void UpdateHistory(IListItem topLevelOrAppItem)
221221

222222
private string IdForTopLevelOrAppItem(IListItem topLevelOrAppItem)
223223
{
224-
if (topLevelOrAppItem is TopLevelCommandItemWrapper toplevel)
224+
if (topLevelOrAppItem is TopLevelViewModel topLevel)
225225
{
226-
return toplevel.Id;
226+
return topLevel.Id;
227227
}
228228
else
229229
{

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/IconDataViewModel.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace Microsoft.CmdPal.UI.ViewModels;
1111

12-
public partial class IconDataViewModel : ObservableObject
12+
public partial class IconDataViewModel : ObservableObject, IIconData
1313
{
1414
private readonly ExtensionObject<IIconData> _model = new(null);
1515

@@ -25,6 +25,8 @@ public partial class IconDataViewModel : ObservableObject
2525
// first. Hence why we're sticking this into an ExtensionObject
2626
public ExtensionObject<IRandomAccessStreamReference> Data { get; private set; } = new(null);
2727

28+
IRandomAccessStreamReference? IIconData.Data => Data.Unsafe;
29+
2830
public IconDataViewModel(IIconData? icon)
2931
{
3032
_model = new(icon);

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/IconInfoViewModel.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace Microsoft.CmdPal.UI.ViewModels;
1010

11-
public partial class IconInfoViewModel : ObservableObject
11+
public partial class IconInfoViewModel : ObservableObject, IIconInfo
1212
{
1313
private readonly ExtensionObject<IIconInfo> _model = new(null);
1414

@@ -28,6 +28,10 @@ public partial class IconInfoViewModel : ObservableObject
2828

2929
public bool IsSet => _model.Unsafe != null;
3030

31+
IIconData? IIconInfo.Dark => Dark;
32+
33+
IIconData? IIconInfo.Light => Light;
34+
3135
public IconInfoViewModel(IIconInfo? icon)
3236
{
3337
_model = new(icon);

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ public record PerformCommandMessage
1818

1919
public bool WithAnimation { get; set; } = true;
2020

21+
public CommandPaletteHost? ExtensionHost { get; private set; }
22+
2123
public PerformCommandMessage(ExtensionObject<ICommand> command)
2224
{
2325
Command = command;
2426
Context = null;
2527
}
2628

27-
public PerformCommandMessage(TopLevelCommandItemWrapper topLevelCommand)
29+
public PerformCommandMessage(TopLevelViewModel topLevelCommand)
2830
{
29-
Command = new(topLevelCommand.Command);
31+
Command = topLevelCommand.CommandViewModel.Model;
3032
Context = null;
33+
ExtensionHost = topLevelCommand.ExtensionHost;
3134
}
3235

3336
public PerformCommandMessage(ExtensionObject<ICommand> command, ExtensionObject<IListItem> context)

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,14 @@ await Task.Run(() =>
106106
{
107107
Logger.LogDebug($"Starting {ExtensionDisplayName} ({ExtensionClassId})");
108108

109-
nint extensionPtr = nint.Zero;
109+
var extensionPtr = nint.Zero;
110110
try
111111
{
112112
// -2147024809: E_INVALIDARG
113113
// -2147467262: E_NOINTERFACE
114114
// -2147024893: E_PATH_NOT_FOUND
115-
Guid guid = typeof(IExtension).GUID;
116-
global::Windows.Win32.Foundation.HRESULT hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out object? extensionObj);
115+
var guid = typeof(IExtension).GUID;
116+
var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out var extensionObj);
117117

118118
if (hr.Value == -2147024893)
119119
{
@@ -179,7 +179,7 @@ public async Task<IEnumerable<T>> GetListOfProvidersAsync<T>()
179179
{
180180
await StartExtensionAsync();
181181

182-
object? supportedProviders = GetExtensionObject()?.GetProvider(_providerTypeMap[typeof(T)]);
182+
var supportedProviders = GetExtensionObject()?.GetProvider(_providerTypeMap[typeof(T)]);
183183
if (supportedProviders is IEnumerable<T> multipleProvidersSupported)
184184
{
185185
return multipleProvidersSupported;

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx

+3
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,7 @@
236236
<data name="builtin_reload_display_title" xml:space="preserve">
237237
<value>Reload Command Palette extensions</value>
238238
</data>
239+
<data name="builtin_disabled_extension" xml:space="preserve">
240+
<value>Disabled</value>
241+
</data>
239242
</root>

0 commit comments

Comments
 (0)