Forge
csharpdeeb25a2
1using System.Text.Json;
2using Avalonia.Input;
3using CascadeIDE.Features.Shell.Application;
4using CascadeIDE.Models.Shell;
5using CommunityToolkit.Mvvm.Input;
6
7namespace CascadeIDE.ViewModels;
8
9/// <summary>
10/// Аккордный слой ADR 0060: корень <c>cascade_chord</c> из hotkeys.toml (по умолчанию Ctrl+K), затем тот же хвост мелодии, что после <c>c:</c>.
11/// Однозначный обычный alias (например <c>so</c>) исполняется без Enter при отсутствии более длинного alias-префикса; параметрические (<c>wai:</c>, <c>els:</c>:…) — только по Enter или из палитры.
12/// При конфликте префиксов (<c>gs</c> vs <c>gsu</c>) — точный хвост или Enter.
13/// </summary>
14public partial class MainWindowViewModel
15{
16 private CascadeChordIntentSession? _cascadeChordSession;
17
18 private CascadeChordIntentSession ChordSession =>
19 _cascadeChordSession ??= new CascadeChordIntentSession(
20 NotifyCascadeChordOverlayProperties,
21 () => CurrentFilePath,
22 () => EditorText,
23 async (commandId, argsJson) =>
24 {
25 try
26 {
27 IReadOnlyDictionary<string, JsonElement>? args = IdeCommandRegistry.ParseArgs(argsJson);
28 await IdeMcp.ExecuteCommandAsync(commandId, args, CancellationToken.None)
29 .ConfigureAwait(false);
30 }
31 catch
32 {
33 // Исключения из хендлеров команд логируются внутри исполнителя; аккорд уже сброшен.
34 }
35 });
36
37 /// <summary>Связанные проекции оверлея/лампы Command при смене фазы или хвоста мелодии (ADR 0060).</summary>
38 private static readonly string[] CascadeChordOverlayPresentationNames =
39 [
40 nameof(IsCascadeChordOverlayVisible),
41 nameof(CascadeChordOverlayHintText),
42 nameof(CascadeChordOverlaySuggestions),
43 nameof(CascadeChordOverlayInputText),
44 nameof(CascadeChordOverlayFindBarLine),
45 nameof(CascadeChordOverlayFindBarOpacity),
46 nameof(CascadeChordOverlayNoMatches),
47 nameof(CascadeChordOverlayCompactFooter),
48 nameof(CascadeChordHudMelodyText),
49 nameof(IsCascadeChordHudMirrorPlaceholderVisible),
50 nameof(IsCascadeChordHudMirrorMelodyVisible),
51 nameof(IsCascadeChordDropdownOpen),
52 nameof(HasChordDropdownItems),
53 nameof(CascadeChordHudWatermark),
54 nameof(CommandArmedLampToolTip),
55 ];
56
57 /// <summary>Показать оверлей с подсказками (ADR 0060 §6) пока активна машина аккорда.</summary>
58 public bool IsCascadeChordOverlayVisible => ChordSession.IsOverlayVisible;
59
60 /// <summary>Текст подсказки для текущего шага машины аккорда.</summary>
61 public string CascadeChordOverlayHintText => ChordSession.OverlayHintText;
62
63 /// <summary>Команды, подходящие под префикс (выпадающий список, до 25).</summary>
64 public IReadOnlyList<CascadeChordOverlaySuggestion> CascadeChordOverlaySuggestions =>
65 ChordSession.OverlaySuggestions;
66
67 /// <summary>Выпадающий список под полем ввода: пока armed, есть префикс и совпадения или явное «нет совпадений».</summary>
68 public bool IsCascadeChordDropdownOpen => ChordSession.IsDropdownOpen;
69
70 /// <summary>Есть строки в выпадающем списке (для «нет совпадений» — отдельный текст).</summary>
71 public bool HasChordDropdownItems => ChordSession.HasDropdownItems;
72
73 /// <summary>Подпись поля ввода рядом с CMD: в покое и при armed.</summary>
74 public string CascadeChordHudWatermark => ChordSession.HudWatermark;
75
76 /// <summary>Набранный хвост для визуального поля ввода в оверлее (find-bar style).</summary>
77 public string CascadeChordOverlayInputText => ChordSession.OverlayInputText;
78
79 /// <summary>Хвост мелодии в полоске HUD (зеркало; только чтение из сессии).</summary>
80 public string CascadeChordHudMelodyText => ChordSession.HudMelodyTextGet();
81
82 /// <summary>Плейсхолдер зеркала: покой (Ctrl+K…) или пустой armed до первой буквы.</summary>
83 public bool IsCascadeChordHudMirrorPlaceholderVisible =>
84 !IsCascadeChordOverlayVisible || string.IsNullOrEmpty(CascadeChordHudMelodyText);
85
86 /// <summary>Текущий набранный хвост поверх placeholder.</summary>
87 public bool IsCascadeChordHudMirrorMelodyVisible =>
88 IsCascadeChordOverlayVisible && !string.IsNullOrEmpty(CascadeChordHudMelodyText);
89
90 /// <summary>Одна строка для полоски: плейсхолдер при пустом хвосте или набранный хвост (без двух <c>IsVisible</c>).</summary>
91 public string CascadeChordOverlayFindBarLine => ChordSession.OverlayFindBarLine;
92
93 /// <summary>Плейсхолдер в полоске — чуть приглушить через прозрачность текста.</summary>
94 public double CascadeChordOverlayFindBarOpacity => ChordSession.OverlayFindBarOpacity;
95
96 /// <summary>Нижняя строка подсказки в полосе.</summary>
97 public string CascadeChordOverlayCompactFooter => ChordSession.OverlayCompactFooter;
98
99 /// <summary>При вводе неверного префикса до сброса (редко видно — машина часто сразу выходит).</summary>
100 public bool CascadeChordOverlayNoMatches => ChordSession.OverlayNoMatches;
101
102 /// <summary>Подсказка для лампы Command на тулбаре: в покое — кратко; при armed — полный контекст аккорда.</summary>
103 public string CommandArmedLampToolTip => ChordSession.CommandArmedLampToolTip;
104
105 private void NotifyCascadeChordOverlayProperties()
106 {
107 foreach (var name in CascadeChordOverlayPresentationNames)
108 OnPropertyChanged(name);
109 }
110
111 /// <summary>Popup закрыт кликом вне списка (Avalonia light dismiss).</summary>
112 public void NotifyCascadeChordDropdownDismissed() => ChordSession.NotifyDropdownDismissed();
113
114 /// <summary>Выбор строки из выпадающего списка.</summary>
115 public void PickCascadeChordSuggestion(CascadeChordOverlaySuggestion? item) => ChordSession.PickSuggestion(item);
116
117 /// <summary>Esc или явная отмена: сброс сессии аккорда.</summary>
118 public void CancelCascadeChord() => ChordSession.Cancel();
119
120 /// <summary>Явный Enter (программно): как из туннеля — точный alias или выход из фазы.</summary>
121 public void OnCascadeChordHudEnter() => ChordSession.OnHudEnterFromView();
122
123 /// <summary>Корень аккорда (hotkeys.toml <c>cascade_chord</c>): обрабатывается tunnel KeyDown главного окна (как палитра Ctrl+Q).</summary>
124 [RelayCommand]
125 private void CascadeChordRoot() => ChordSession.BeginRoot();
126
127 /// <summary>
128 /// Обрабатывает аккорд Cascade: возвращает <see langword="true"/>, если событие поглощено (в т.ч. корень Ctrl+K).
129 /// Вызывать из tunnel <see cref="Views.MainWindow"/> до <see cref="MainWindowHotkeyService.TryHandleTunnelShortcuts"/>.
130 /// </summary>
131 public bool TryConsumeCascadeChordKeyDown(KeyEventArgs e) =>
132 ChordSession.TryConsumeKeyDown(e);
133}
134
View only · write via MCP/CIDE