/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContextMenuViewModel.cs
270 строк
9 KB
Mike Griese
CmdPal: Fix opening a IPage from a context menu on a dock item (#47991)
21 май 2026, 00:03
Не верифицирован
21 май 2026, 00:03
2fc27b1
Код
Авторство
О чём код?
// 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. using System.Collections.ObjectModel; using System.Runtime.CompilerServices; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Common; using Microsoft.CmdPal.Common.Helpers; using Microsoft.CmdPal.Common.Text; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; using Windows.System; namespace Microsoft.CmdPal.UI.ViewModels; public partial class ContextMenuViewModel : ObservableObject, IRecipient<UpdateCommandBarMessage> { private readonly IFuzzyMatcherProvider _fuzzyMatcherProvider; public ICommandBarContext? SelectedItem { get => field; set { field = value; UpdateContextItems(); } } [ObservableProperty] private partial ObservableCollection<List<IContextItemViewModel>> ContextMenuStack { get; set; } = []; private List<IContextItemViewModel>? CurrentContextMenu => ContextMenuStack.LastOrDefault(); [ObservableProperty] public partial ObservableCollection<IContextItemViewModel> FilteredItems { get; set; } = []; [ObservableProperty] public partial bool FilterOnTop { get; set; } = false; private string _lastSearchText = string.Empty; public ContextMenuViewModel(IFuzzyMatcherProvider fuzzyMatcherProvider) { _fuzzyMatcherProvider = fuzzyMatcherProvider; } public void HookCommandBar() { var messenger = WeakReferenceMessenger.Default; if (!messenger.IsRegistered<UpdateCommandBarMessage>(this)) { messenger.Register<UpdateCommandBarMessage>(this); } } public void UnhookCommandBar() { WeakReferenceMessenger.Default.Unregister<UpdateCommandBarMessage>(this); } public void Receive(UpdateCommandBarMessage message) { SelectedItem = message.ViewModel; } public void UpdateContextItems() { if (SelectedItem is not null) { ContextMenuStack.Clear(); PushContextStack(SelectedItem.AllCommands); } } public void SetSearchText(string searchText) { if (searchText == _lastSearchText) { return; } if (SelectedItem is null) { return; } _lastSearchText = searchText; if (CurrentContextMenu is null) { ListHelpers.InPlaceUpdateList(FilteredItems, []); return; } if (string.IsNullOrEmpty(searchText)) { ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu); return; } var commands = CurrentContextMenu .OfType<CommandContextItemViewModel>() .Where(c => c.ShouldBeVisible); var query = _fuzzyMatcherProvider.Current.PrecomputeQuery(searchText); var newResults = InternalListHelpers.FilterList(commands, in query, ScoreFunction); ListHelpers.InPlaceUpdateList(FilteredItems, newResults); } private int ScoreFunction(in FuzzyQuery query, CommandContextItemViewModel item) { if (string.IsNullOrWhiteSpace(query.Original)) { return 1; } if (string.IsNullOrEmpty(item.Title)) { return 0; } var fuzzyMatcher = _fuzzyMatcherProvider.Current; var title = item.GetTitleTarget(fuzzyMatcher); var subtitle = item.GetSubtitleTarget(fuzzyMatcher); var titleScore = fuzzyMatcher.Score(query, title); var subtitleScore = (fuzzyMatcher.Score(query, subtitle) - 4) / 2; return Max3(titleScore, subtitleScore, 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Max3(int a, int b, int c) { var m = a > b ? a : b; return m > c ? m : c; } /// <summary> /// Generates a mapping of key -> command item for this particular item's /// MoreCommands. (This won't include the primary Command, but it will /// include the secondary one). This map can be used to quickly check if a /// shortcut key was pressed. In case there are duplicate keybindings, the first /// one is used and the rest are ignored. /// </summary> /// <returns>a dictionary of KeyChord -> Context commands, for all commands /// that have a shortcut key set.</returns> private Dictionary<KeyChord, CommandContextItemViewModel> Keybindings() { var result = new Dictionary<KeyChord, CommandContextItemViewModel>(); var menu = CurrentContextMenu; if (menu is null) { return result; } foreach (var item in menu) { if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut) { var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0); var added = result.TryAdd(key, cmd); if (!added) { CoreLogger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'"); } } } return result; } public ContextKeybindingResult? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key) { var keybindings = Keybindings(); // Does the pressed key match any of the keybindings? var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0); return keybindings.TryGetValue(pressedKeyChord, out var item) ? InvokeCommand(item) : null; } public bool CanPopContextStack() { return ContextMenuStack.Count > 1; } public void PopContextStack() { if (ContextMenuStack.Count > 1) { ContextMenuStack.RemoveAt(ContextMenuStack.Count - 1); } OnPropertyChanging(nameof(CurrentContextMenu)); OnPropertyChanged(nameof(CurrentContextMenu)); ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu!); } private void PushContextStack(IEnumerable<IContextItemViewModel> commands) { ContextMenuStack.Add(commands.ToList()); OnPropertyChanging(nameof(CurrentContextMenu)); OnPropertyChanged(nameof(CurrentContextMenu)); ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu!); } public void ResetContextMenu() { while (ContextMenuStack.Count > 1) { ContextMenuStack.RemoveAt(ContextMenuStack.Count - 1); } OnPropertyChanging(nameof(CurrentContextMenu)); OnPropertyChanged(nameof(CurrentContextMenu)); if (CurrentContextMenu is not null) { ListHelpers.InPlaceUpdateList(FilteredItems, CurrentContextMenu!); } } /// <summary> /// Raised after a command is actually invoked (i.e. sent as a <see cref="PerformCommandMessage"/>) /// from this context menu. Not raised when the user navigates into a submenu. /// </summary> public event EventHandler<CommandItemViewModel>? CommandInvoked; /// <summary> /// Raised immediately before the <see cref="PerformCommandMessage"/> is sent. /// Subscribers can decorate the message (for example, to attach an /// <see cref="PerformCommandMessage.OnBeforeShowConfirmation"/> callback). /// Not raised when the user navigates into a submenu. /// </summary> public event EventHandler<PerformCommandMessage>? CommandInvoking; public ContextKeybindingResult InvokeCommand(CommandItemViewModel? command) { if (command is null) { return ContextKeybindingResult.Unhandled; } if (command.HasMoreCommands) { // Display the commands child commands PushContextStack(command.AllCommands); OnPropertyChanging(nameof(FilteredItems)); OnPropertyChanged(nameof(FilteredItems)); return ContextKeybindingResult.KeepOpen; } else { var message = new PerformCommandMessage(command.Command.Model, command.Model); CommandInvoking?.Invoke(this, message); WeakReferenceMessenger.Default.Send(message); UpdateContextItems(); CommandInvoked?.Invoke(this, command); return ContextKeybindingResult.Hide; } } }