Forge
csharpdeeb25a2
1using System.Collections.ObjectModel;
2using System.Diagnostics.CodeAnalysis;
3using CascadeIDE.Features.Search.Application;
4using CascadeIDE.Features.Shell.Application;
5using CascadeIDE.Models;
6using CascadeIDE.IdeDisplay;
7using CascadeIDE.IdeDisplay.CommandPalette;
8using CommunityToolkit.Mvvm.ComponentModel;
9using CommunityToolkit.Mvvm.Input;
10
11namespace CascadeIDE.ViewModels;
12
13/// <summary>Палитра команд.</summary>
14public partial class MainWindowViewModel
15{
16 private const int CommandPalettePageStep = 8;
17
18 private HotkeyGestureMap? _hotkeyGestureMap;
19
20 private HotkeyGestureMap HotkeyGestureMap => _hotkeyGestureMap ??= HotkeyGestureMap.Load();
21
22 private readonly CommandPaletteGoToAsyncHandle _commandPaletteGoTo = new();
23
24 private readonly IIdsSurfaceCompositor<CommandPaletteSurfaceIntent, CommandPaletteSurfaceSnapshot> _commandPaletteSurfaceCompositor =
25 new CommandPaletteSurfaceCompositor();
26 private CommandPaletteSurfaceSnapshot _commandPaletteSurfaceSnapshot = CommandPaletteSurfaceSnapshot.Empty;
27
28 [ObservableProperty]
29 public partial bool IsCommandPaletteOpen { get; set; }
30
31 [ObservableProperty]
32 public partial string CommandPaletteQuery { get; set; } = "";
33
34 [ObservableProperty]
35 public partial int CommandPaletteSelectedIndex { get; set; } = -1;
36
37 /// <summary>IDS v0 снимок оверлея Ctrl+Q (канал -> композитор -> поверхность).</summary>
38 public CommandPaletteSurfaceSnapshot CommandPaletteSurfaceSnapshot
39 {
40 get => _commandPaletteSurfaceSnapshot;
41 private set => SetProperty(ref _commandPaletteSurfaceSnapshot, value);
42 }
43
44 /// <summary>Подсказка внизу палитры; жест «выделить запрос» из <c>hotkeys.toml</c> (<c>toggle_command_palette</c>).</summary>
45 public string CommandPaletteFooterHint
46 {
47 get
48 {
49 var h = HotkeyGestureMap.GetDisplayHint("toggle_command_palette");
50 var melody = IntentMelodyAliases.SampleAliasesForFooter(8);
51 return CommandPaletteChromeProjection.FooterHint(h, melody);
52 }
53 }
54
55 /// <summary>Плейсхолдер поля поиска; примеры melody из <c>IntentMelody/intent-catalog.toml</c>.</summary>
56 [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Привязка Avalonia к экземпляру MainWindowViewModel (DataContext).")]
57 public string CommandPalettePlaceholderText
58 {
59 get
60 {
61 var melody = IntentMelodyAliases.SampleAliasesForFooter(6);
62 return CommandPaletteChromeProjection.QueryPlaceholder(melody);
63 }
64 }
65
66 public ObservableCollection<IdeCommandPaletteRowViewModel> FilteredCommandPaletteEntries { get; } = new();
67
68 partial void OnCommandPaletteQueryChanged(string value) => RefreshCommandPaletteFilter();
69
70 partial void OnCommandPaletteSelectedIndexChanged(int value) => RefreshCommandPaletteSurfaceSnapshot();
71
72 partial void OnIsCommandPaletteOpenChanged(bool value)
73 {
74 if (!value)
75 {
76 _commandPaletteGoTo.Cancel();
77 CommandPaletteSurfaceSnapshot = CommandPaletteSurfaceSnapshot.Empty;
78 return;
79 }
80
81 RefreshCommandPaletteSurfaceSnapshot();
82 }
83
84 [RelayCommand]
85 private void ToggleCommandPalette()
86 {
87 IsCommandPaletteOpen = !IsCommandPaletteOpen;
88 if (!IsCommandPaletteOpen)
89 return;
90 CommandPaletteQuery = "";
91 RefreshCommandPaletteFilter();
92 CommandPaletteSelectedIndex = CommandPaletteSelectionProjection.InitialSelectedIndex(
93 FilteredCommandPaletteEntries.Count);
94 }
95
96 [RelayCommand]
97 private void CloseCommandPalette() => IsCommandPaletteOpen = false;
98
99 [RelayCommand]
100 private async Task ExecuteCommandPaletteSelectionAsync()
101 {
102 if (CommandPaletteSelectedIndex < 0 || CommandPaletteSelectedIndex >= FilteredCommandPaletteEntries.Count)
103 return;
104 var row = FilteredCommandPaletteEntries[CommandPaletteSelectedIndex];
105 if (row.RowKind == IdeCommandPaletteRowKind.Hint)
106 return;
107 if (row.RowKind == IdeCommandPaletteRowKind.GoTo)
108 {
109 IsCommandPaletteOpen = false;
110 if (string.IsNullOrEmpty(row.NavigateFilePath))
111 return;
112 await IdeCommandPaletteExecutionOrchestrator
113 .RunSelectionAsync(row, IdeMcp, _ideMcpHost.Executor, CancellationToken.None)
114 .ConfigureAwait(false);
115 return;
116 }
117
118 if (!row.IsAvailable)
119 return;
120 IsCommandPaletteOpen = false;
121 await IdeCommandPaletteExecutionOrchestrator
122 .RunSelectionAsync(row, IdeMcp, _ideMcpHost.Executor, CancellationToken.None)
123 .ConfigureAwait(false);
124 }
125
126 [RelayCommand]
127 private void CommandPaletteMoveSelection(int delta)
128 {
129 if (CommandPaletteSelectionProjection.TryMoveCircular(
130 CommandPaletteSelectedIndex,
131 delta,
132 FilteredCommandPaletteEntries.Count,
133 out var next))
134 CommandPaletteSelectedIndex = next;
135 }
136
137 /// <summary>Прокрутка списка страницей (PgUp/PgDn).</summary>
138 [RelayCommand]
139 private void CommandPalettePageMove(int directionSign)
140 {
141 if (CommandPaletteSelectionProjection.TryPageMove(
142 CommandPaletteSelectedIndex,
143 directionSign,
144 CommandPalettePageStep,
145 FilteredCommandPaletteEntries.Count,
146 out var next))
147 CommandPaletteSelectedIndex = next;
148 }
149
150 private void RefreshCommandPaletteFilter() =>
151 IdeCommandPaletteFilterOrchestrator.RefreshCommandPaletteFilter(
152 () => CommandPaletteQuery,
153 FilteredCommandPaletteEntries,
154 HotkeyGestureMap,
155 UiModeFamily,
156 Workspace.SolutionPath,
157 Workspace.SolutionRoots,
158 CurrentFilePath,
159 EditorText,
160 _commandPaletteGoTo,
161 i => CommandPaletteSelectedIndex = i,
162 () => CommandPaletteSelectedIndex,
163 RefreshCommandPaletteSurfaceSnapshot,
164 () => CommandPaletteGoToSearchBackendFactory.Resolve(
165 CommandPaletteGoToSearchBackendNormalizer.Parse(_settings.CommandPalette.GoToSearch.Backend),
166 _hybridIndex,
167 _settings.HybridIndex.ScopeMode,
168 _settings.HybridIndex.Enabled),
169 _workspaceFileIndex,
170 InvalidateWorkspaceFileIndex);
171
172 /// <summary>Обновить подписи доступности при смене UI-режима с открытой палитрой.</summary>
173 private void RefreshCommandPaletteIfOpen()
174 {
175 if (IsCommandPaletteOpen)
176 RefreshCommandPaletteFilter();
177 }
178
179 private void RefreshCommandPaletteSurfaceSnapshot()
180 {
181 if (!IsCommandPaletteOpen)
182 {
183 CommandPaletteSurfaceSnapshot = CommandPaletteSurfaceSnapshot.Empty;
184 return;
185 }
186
187 var intent = new CommandPaletteSurfaceIntent(
188 CommandPaletteQuery,
189 CommandPaletteSelectedIndex,
190 FilteredCommandPaletteEntries);
191 CommandPaletteSurfaceSnapshot = _commandPaletteSurfaceCompositor.Compose(intent);
192 }
193}
194
View only · write via MCP/CIDE