diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs index 125d8d78f4..e5fddf7ad2 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/AppExtensionHost.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -25,6 +25,8 @@ public abstract partial class AppExtensionHost : IExtensionHost public ObservableCollection StatusMessages { get; } = []; + public virtual bool SupportsDockBands { get; set; } + public static void SetHostHwnd(ulong hostHwnd) => _hostingHwnd = hostHwnd; public void DebugLog(string message) diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs index 04046eea0c..e01d4f3868 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs @@ -212,27 +212,28 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa return; } - var more = model.MoreCommands; - if (more is not null) - { - MoreCommands = more - .Select(item => - { - return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel(); - }) - .ToList(); - } + BuildAndInitMoreCommands(); - // Here, we're already theoretically in the async context, so we can - // use Initialize straight up - MoreCommands - .OfType() - .ToList() - .ForEach(contextItem => - { - contextItem.SlowInitializeProperties(); - }); + // var more = model.MoreCommands; + // if (more is not null) + // { + // MoreCommands = more + // .Select(item => + // { + // return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel(); + // }) + // .ToList(); + // } + // // Here, we're already theoretically in the async context, so we can + // // use Initialize straight up + // MoreCommands + // .OfType() + // .ToList() + // .ForEach(contextItem => + // { + // contextItem.SlowInitializeProperties(); + // }); if (!string.IsNullOrEmpty(model.Command?.Name)) { _defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext) @@ -382,36 +383,36 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa break; case nameof(model.MoreCommands): - var more = model.MoreCommands; - if (more is not null) - { - var newContextMenu = more - .Select(item => - { - return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel(); - }) - .ToList(); - lock (MoreCommands) - { - ListHelpers.InPlaceUpdateList(MoreCommands, newContextMenu); - } - - newContextMenu - .OfType() - .ToList() - .ForEach(contextItem => - { - contextItem.InitializeProperties(); - }); - } - else - { - lock (MoreCommands) - { - MoreCommands.Clear(); - } - } + // var more = model.MoreCommands; + // if (more is not null) + // { + // var newContextMenu = more + // .Select(item => + // { + // return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel(); + // }) + // .ToList(); + // lock (MoreCommands) + // { + // ListHelpers.InPlaceUpdateList(MoreCommands, newContextMenu); + // } + // newContextMenu + // .OfType() + // .ToList() + // .ForEach(contextItem => + // { + // contextItem.InitializeProperties(); + // }); + // } + // else + // { + // lock (MoreCommands) + // { + // MoreCommands.Clear(); + // } + // } + BuildAndInitMoreCommands(); UpdateProperty(nameof(SecondaryCommand)); UpdateProperty(nameof(SecondaryCommandName)); UpdateProperty(nameof(HasMoreCommands)); @@ -513,6 +514,68 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa base.SafeCleanup(); Initialized |= InitializedState.CleanedUp; } + + /// + /// * Does call SlowInitializeProperties on the created items. + /// * does NOT call UpdateProperty ; caller must do that. + /// + private void BuildAndInitMoreCommands() + { + var model = _commandItemModel.Unsafe; + if (model is null) + { + return; + } + + var more = model.MoreCommands; + List results = []; + if (more is not null) + { + foreach (var item in more) + { + if (item is ICommandContextItem contextItem) + { + var contextItemViewModel = new CommandContextItemViewModel(contextItem, PageContext); + contextItemViewModel.SlowInitializeProperties(); + results.Add(contextItemViewModel); + } + else + { + results.Add(new SeparatorViewModel()); + } + } + + // MoreCommands = more + // .Select(item => + // { + // return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel(); + // }) + // .ToList(); + } + + if (PageContext.TryGetTarget(out var pageContext)) + { + if (pageContext.ExtensionSupportsPinning) + { + // test: just add a bunch of separators + results.Add(new SeparatorViewModel()); + results.Add(new SeparatorViewModel()); + results.Add(new SeparatorViewModel()); + } + } + + // var oldMoreCommands = MoreCommands; + // MoreCommands = results; + List? freedItems; + lock (MoreCommands) + { + ListHelpers.InPlaceUpdateList(MoreCommands, results, out freedItems); + } + + freedItems.OfType() + .ToList() + .ForEach(c => c.SafeCleanup()); + } } [Flags] diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/GlobalLogPageContext.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/GlobalLogPageContext.cs index 228ccc5f4b..8bf504272f 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/GlobalLogPageContext.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/GlobalLogPageContext.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -8,6 +8,8 @@ public class GlobalLogPageContext : IPageContext { public TaskScheduler Scheduler { get; private init; } + bool IPageContext.ExtensionSupportsPinning => false; + public void ShowException(Exception ex, string? extensionHint) { /*do nothing*/ } diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs index 61e0b1e7f3..6d3cad8af7 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs @@ -79,6 +79,8 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext public IconInfoViewModel Icon { get; protected set; } + bool IPageContext.ExtensionSupportsPinning => ExtensionHost.SupportsDockBands; + public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost) : base((IPageContext?)null) { @@ -267,6 +269,8 @@ public interface IPageContext void ShowException(Exception ex, string? extensionHint = null); TaskScheduler Scheduler { get; } + + bool ExtensionSupportsPinning { get; } } public interface IPageViewModelFactoryService diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs index f4efbb4e46..a7d47c75eb 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs @@ -165,6 +165,7 @@ public sealed class CommandProviderWrapper if (model is ICommandProvider3 supportsDockBands) { + ExtensionHost.SupportsDockBands = true; var bands = supportsDockBands.GetDockBands(); if (bands is not null) { @@ -213,7 +214,16 @@ public sealed class CommandProviderWrapper Func make = (ICommandItem? i, TopLevelType t) => { CommandItemViewModel commandItemViewModel = new(new(i), pageContext); - TopLevelViewModel topLevelViewModel = new(commandItemViewModel, t, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider, i); + TopLevelViewModel topLevelViewModel = new( + item: commandItemViewModel, + topLevelType: t, + extensionHost: ExtensionHost, + commandProviderId: ProviderId, + settings: settings, + providerSettings: providerSettings, + serviceProvider: serviceProvider, + commandItem: i/*, + providerSupportsPinning: SupportsDockBands*/); topLevelViewModel.InitializeProperties(); return topLevelViewModel; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs index e1643fc59e..ae42562fa5 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs @@ -33,6 +33,8 @@ public sealed partial class DockViewModel : IDisposable, public ObservableCollection AllItems => _topLevelCommandManager.DockBands; + bool IPageContext.ExtensionSupportsPinning => false; + public DockViewModel( TopLevelCommandManager tlcManager, SettingsModel settings, diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs index c3bfdf7eca..a0ee21860b 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs @@ -60,6 +60,8 @@ public partial class TopLevelCommandManager : ObservableObject, } } + bool IPageContext.ExtensionSupportsPinning => false; + public async Task LoadBuiltinsAsync() { var s = new Stopwatch(); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs index c6496780f4..6d995174aa 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs @@ -28,6 +28,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx private readonly string _commandProviderId; + // private readonly bool _providerSupportsPinning; private string IdFromModel => IsFallback && !string.IsNullOrWhiteSpace(_fallbackId) ? _fallbackId : _commandItemViewModel.Command.Id; private string _fallbackId = string.Empty; @@ -58,6 +59,8 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx public IconInfoViewModel IconViewModel => _commandItemViewModel.Icon; + // public bool ProviderSupportsPinning => _providerSupportsPinning; + ////// ICommandItem public string Title => _commandItemViewModel.Title; @@ -204,12 +207,15 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx SettingsModel settings, ProviderSettings providerSettings, IServiceProvider serviceProvider, - ICommandItem? commandItem) + ICommandItem? commandItem)/*, + bool providerSupportsPinning*/ { _serviceProvider = serviceProvider; _settings = settings; _providerSettings = providerSettings; _commandProviderId = commandProviderId; + + // _providerSupportsPinning = providerSupportsPinning; _commandItemViewModel = item; IsFallback = topLevelType == TopLevelType.Fallback; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs index 27922f9b7b..b88d34e5cd 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsCommandProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -167,7 +167,7 @@ public partial class AllAppsCommandProvider : CommandProvider } // ... Now, combine those two - List both = bestAppMatch is null ? nameMatches : [.. nameMatches, bestAppMatch]; + var both = bestAppMatch is null ? nameMatches : [.. nameMatches, bestAppMatch]; if (both.Count == 1) { @@ -191,7 +191,7 @@ public partial class AllAppsCommandProvider : CommandProvider public override ICommandItem? GetCommandItemById(string id) { - if (id == _listItem.Command.Id) + if (id == _listItem.Command?.Id) { return _listItem; }