Forge
csharpdeeb25a2
1#nullable enable
2using System.Collections.ObjectModel;
3using CommunityToolkit.Mvvm.ComponentModel;
4
5namespace CascadeIDE.Features.Chat;
6
7public partial class ChatPanelViewModel
8{
9 public ObservableCollection<ChatSlashSuggestionItem> ChatSlashSuggestions { get; } = [];
10
11 [ObservableProperty]
12 private bool _isChatSlashAutocompleteVisible;
13
14 [ObservableProperty]
15 private int _selectedChatSlashSuggestionIndex = -1;
16
17 [ObservableProperty]
18 private string? _chatSlashPathPrefix;
19
20 [ObservableProperty]
21 private string? _chatSlashNextStepLabel;
22
23 [ObservableProperty]
24 private string? _chatSlashBreadcrumb;
25
26 partial void OnChatInputChanged(string value)
27 {
28 RefreshComposerAutocomplete();
29 RefreshComposerSlashPreview();
30 }
31
32 /// <param name="inputOverride">Текст из TextBox при <c>TextChanged</c> (биндинг может отставать на один тик).</param>
33 /// <param name="caretOverride">Каретка из Skia composer (приоритет над VM, если биндинг отстаёт).</param>
34 public void RefreshChatSlashAutocomplete(string? inputOverride = null, int? caretOverride = null)
35 {
36 var text = inputOverride ?? ChatInput;
37 var caret = Math.Clamp(caretOverride ?? ChatComposerCaretIndex, 0, text.Length);
38 var suggestions = ChatSlashAutocomplete.GetSuggestions(
39 text,
40 _workspaceFileSlashCompletion,
41 _sessionTopicSlashCompletion,
42 _messageAnchorSlashCompletion,
43 caretIndex: caret);
44 ChatSlashSuggestions.Clear();
45 foreach (var s in suggestions)
46 ChatSlashSuggestions.Add(new ChatSlashSuggestionItem(s));
47
48 var visible = ChatSlashSuggestions.Count > 0;
49 if (IsChatSlashAutocompleteVisible != visible)
50 IsChatSlashAutocompleteVisible = visible;
51 else
52 OnPropertyChanged(nameof(IsChatSlashAutocompleteVisible));
53
54 SelectedChatSlashSuggestionIndex = visible ? 0 : -1;
55
56 var hierarchy = ChatSlashAutocomplete.GetHierarchyContext(text, caret);
57 ChatSlashPathPrefix = hierarchy?.PathPrefix;
58 ChatSlashNextStepLabel = hierarchy?.NextStepLabel;
59 ChatSlashBreadcrumb = hierarchy?.Breadcrumb;
60
61 rebuildComposerPopup();
62 }
63
64 public void MoveChatSlashSuggestionSelection(int delta)
65 {
66 if (ChatSlashSuggestions.Count == 0)
67 return;
68
69 if (SelectedChatSlashSuggestionIndex < 0)
70 SelectedChatSlashSuggestionIndex = 0;
71 else
72 {
73 var next = SelectedChatSlashSuggestionIndex + delta;
74 if (next < 0)
75 next = ChatSlashSuggestions.Count - 1;
76 else if (next >= ChatSlashSuggestions.Count)
77 next = 0;
78 SelectedChatSlashSuggestionIndex = next;
79 }
80 }
81
82 public bool TryApplySelectedChatSlashSuggestion() =>
83 TryCommitSelectedChatSlashSuggestion(out _);
84
85 /// <summary>Подставить выбранную подсказку; <paramref name="shouldAutoExecute"/> — сразу отправить open/load.</summary>
86 public bool TryCommitSelectedChatSlashSuggestion(out bool shouldAutoExecute)
87 {
88 shouldAutoExecute = false;
89 if (ChatSlashSuggestions.Count == 0)
90 return false;
91
92 var idx = SelectedChatSlashSuggestionIndex < 0 ? 0 : SelectedChatSlashSuggestionIndex;
93 if (idx >= ChatSlashSuggestions.Count)
94 idx = 0;
95
96 var insertText = ChatSlashSuggestions[idx].InsertText;
97 if (IsCockpitCommandLineOpen)
98 {
99 CockpitCommandLineText = insertText;
100 CockpitCommandLineCaretIndex = insertText.Length;
101 }
102 else if (ChatSlashAutocomplete.TryReplaceSlashLineAtCaret(
103 ChatInput,
104 ChatComposerCaretIndex,
105 insertText,
106 out var newText,
107 out var newCaret))
108 {
109 ChatInput = newText;
110 ChatComposerCaretIndex = newCaret;
111 }
112 else
113 {
114 ChatInput = insertText;
115 ChatComposerCaretIndex = insertText.Length;
116 }
117
118 shouldAutoExecute = ChatSlashCommandParser.ShouldAutoExecuteAfterAutocompleteCommit(insertText);
119 if (shouldAutoExecute)
120 {
121 IsChatSlashAutocompleteVisible = false;
122 }
123 else
124 {
125 // Не гасить popup после сегмента: иначе следующий Enter уходит в Send вместо «start» (ADR 0119 §6).
126 RefreshComposerAutocomplete(
127 inputOverride: IsCockpitCommandLineOpen ? CockpitCommandLineText : ChatInput,
128 caretOverride: IsCockpitCommandLineOpen ? CockpitCommandLineCaretIndex : ChatComposerCaretIndex);
129 }
130
131 return true;
132 }
133
134 public void DismissChatSlashAutocomplete()
135 {
136 IsChatSlashAutocompleteVisible = false;
137 ChatSlashPathPrefix = null;
138 ChatSlashNextStepLabel = null;
139 ChatSlashBreadcrumb = null;
140 }
141}
142
View only · write via MCP/CIDE