Forge
csharpdeeb25a2
1using System.ComponentModel;
2using Avalonia;
3using Avalonia.Controls;
4using Avalonia.Threading;
5using Avalonia.VisualTree;
6using CascadeIDE.Features.Documents;
7using CascadeIDE.Features.Editor.Application;
8using CascadeIDE.Features.WorkspaceNavigation.Presentation;
9using CascadeIDE.ViewModels;
10using Microsoft.CodeAnalysis;
11using Microsoft.CodeAnalysis.CSharp;
12using Microsoft.CodeAnalysis.CSharp.Syntax;
13
14namespace CascadeIDE.Views;
15
16public partial class DockDocumentView : UserControl
17{
18 private MainWindowViewModel? _vm;
19 private DockDocumentViewModel? _docVm;
20 private PropertyChangedEventHandler? _vmHandler;
21 private PropertyChangedEventHandler? _navigationMapHandler;
22 private PropertyChangedEventHandler? _documentsHandler;
23
24 private Border? _stickyScrollHost;
25 private TextBlock? _stickyScrollText;
26
27 private IEditorSurfaceAdapter? _editorSurface;
28 private readonly EditorDocumentHudLayer _documentHudLayer = new();
29 private Action<EditorInputDelta>? _stabilizedHudAction;
30
31 public DockDocumentView()
32 {
33 InitializeComponent();
34 DataContextChanged += (_, _) => TrySetup();
35 AttachedToVisualTree += (_, _) => TrySetup();
36 UiScheduler.Default.Post(TrySetup);
37 }
38
39 private void TrySetup()
40 {
41 Teardown();
42
43 _docVm = DataContext as DockDocumentViewModel;
44 if (_docVm is null)
45 return;
46
47 var top = TopLevel.GetTopLevel(this);
48 _vm = top?.DataContext as MainWindowViewModel;
49 if (_vm is null)
50 {
51 for (Visual? v = this; v is not null; v = v.GetVisualParent())
52 {
53 if (v is MainWindow mw)
54 {
55 _vm = mw.DataContext as MainWindowViewModel;
56 break;
57 }
58 }
59 }
60
61 if (_vm is null)
62 return;
63
64 _stickyScrollHost = this.FindControl<Border>("StickyScrollHost");
65 _stickyScrollText = this.FindControl<TextBlock>("StickyScrollText");
66 TrySetupMonacoForward();
67 }
68
69 private Action<EditorInputDelta> StabilizedHudAction =>
70 _stabilizedHudAction ??= OnStabilizedHud;
71
72 private void OnStabilizedHud(EditorInputDelta d) =>
73 _vm?.SetStabilizedEditorHudContext(_documentHudLayer.BuildStabilizedContext(d));
74
75 internal void UpdateStabilizedHudRegistration()
76 {
77 if (_vm is null)
78 return;
79 if (IsActive())
80 _vm.SetActiveEditorStabilizedHudHandler(StabilizedHudAction);
81 else
82 _vm.ClearActiveEditorStabilizedHudHandlerIfEquals(StabilizedHudAction);
83 }
84
85 private void Teardown()
86 {
87 TeardownMonacoForward();
88
89 if (_vm is not null)
90 {
91 _vm.ClearActiveEditorStabilizedHudHandlerIfEquals(StabilizedHudAction);
92 _vm.SetStabilizedEditorHudContext(null);
93 if (_vmHandler is not null)
94 _vm.PropertyChanged -= _vmHandler;
95 if (_navigationMapHandler is not null)
96 {
97 _vm.NavigationMap.PropertyChanged -= _navigationMapHandler;
98 _navigationMapHandler = null;
99 }
100
101 if (_documentsHandler is not null)
102 _vm.Documents.PropertyChanged -= _documentsHandler;
103 }
104
105 _documentHudLayer.ConfigureDiagnostics(null);
106 _editorSurface = null;
107
108 _vm = null;
109 _docVm = null;
110 _vmHandler = null;
111 _documentsHandler = null;
112 _stickyScrollHost = null;
113 _stickyScrollText = null;
114 }
115
116 internal bool IsActive()
117 {
118 if (_vm is null || _docVm is null)
119 return false;
120
121 return ReferenceEquals(_vm.Documents.DockActiveDocument, _docVm)
122 || string.Equals(_vm.CurrentFilePath, _docVm.Doc.FilePath, StringComparison.OrdinalIgnoreCase);
123 }
124
125 internal void PostStabilizedEditorInputIfActive(EditorInputDeltaKind kind)
126 {
127 if (!IsActive() || _vm is null || _editorSurface is null)
128 return;
129 _editorSurface.GetSelection(out var selStart, out var selLen);
130 var d = new EditorInputDelta(_docVm?.Doc.FilePath, _editorSurface.CaretOffset, selStart, selLen, kind);
131 _vm.TryPostEditorStabilizedInput(d);
132 }
133
134 internal static string? BuildStickyScrollLabel(string sourceText, int topLineOneBased)
135 {
136 if (topLineOneBased <= 1 || string.IsNullOrWhiteSpace(sourceText))
137 return null;
138
139 try
140 {
141 var tree = CSharpSyntaxTree.ParseText(sourceText);
142 var root = tree.GetRoot();
143 var line = tree.GetText().Lines[Math.Max(0, Math.Min(topLineOneBased - 1, tree.GetText().Lines.Count - 1))];
144 var token = root.FindToken(line.Start);
145 if (token.RawKind == 0)
146 return null;
147
148 var parts = token.Parent?
149 .AncestorsAndSelf()
150 .Reverse()
151 .Select(n => ToStickyPart(n, tree, topLineOneBased))
152 .Where(static s => !string.IsNullOrWhiteSpace(s))
153 .Distinct(StringComparer.Ordinal)
154 .ToList();
155
156 if (parts is null || parts.Count == 0)
157 return null;
158
159 return string.Join(" > ", parts);
160 }
161 catch
162 {
163 return null;
164 }
165 }
166
167 private static string? ToStickyPart(SyntaxNode node, SyntaxTree tree, int topLineOneBased)
168 {
169 var startLine = RoslynLinePositionMapper.ToEditorLineNumber(tree.GetLineSpan(node.Span).StartLinePosition).Value;
170 if (startLine >= topLineOneBased)
171 return null;
172
173 return node switch
174 {
175 BaseNamespaceDeclarationSyntax n => $"namespace {n.Name}",
176 ClassDeclarationSyntax c => $"class {c.Identifier.Text}",
177 StructDeclarationSyntax s => $"struct {s.Identifier.Text}",
178 InterfaceDeclarationSyntax i => $"interface {i.Identifier.Text}",
179 RecordDeclarationSyntax r => $"record {r.Identifier.Text}",
180 EnumDeclarationSyntax e => $"enum {e.Identifier.Text}",
181 DelegateDeclarationSyntax d => $"delegate {d.Identifier.Text}",
182 MethodDeclarationSyntax m => $"{m.Identifier.Text}()",
183 ConstructorDeclarationSyntax c => $"{c.Identifier.Text}()",
184 PropertyDeclarationSyntax p => p.Identifier.Text,
185 IndexerDeclarationSyntax => "this[]",
186 LocalFunctionStatementSyntax f => $"{f.Identifier.Text}()",
187 _ => null
188 };
189 }
190
191 /// <summary>Reveal строк из карты намерений / MCP (ADR 0130).</summary>
192 public bool TryRevealEditorRange(int startLine, int endLine, int? durationMs)
193 {
194 if (_docVm is null || _vm is null || _monacoHost is null)
195 return false;
196
197 if (durationMs is > 0)
198 {
199 _ = _monacoHost.PushAgentRevealAsync(startLine, endLine, persistent: false, durationMs);
200 return true;
201 }
202
203 _ = _monacoHost.PushRevealRangeAsync(startLine, endLine);
204 return true;
205 }
206
207 public void FocusMonacoEditor() => _monacoHost?.Focus();
208
209 public Task GotoLineColumnAsync(int line, int column, bool select = true) =>
210 _monacoHost is { IsReady: true } host
211 ? host.PushRevealRangeAsync(line, line, column, select)
212 : Task.CompletedTask;
213
214 public Task SetSelectionAsync(int start, int length) =>
215 _monacoHost is { IsReady: true } host
216 ? host.PushSetSelectionAsync(start, length)
217 : Task.CompletedTask;
218
219 public Task SetEpochDimAsync(bool dimmed) =>
220 _monacoHost is { IsReady: true } host
221 ? host.PushEpochDimAsync(dimmed)
222 : Task.CompletedTask;
223
224 public Task RevealAgentRangeAsync(int startLine, int endLine, bool persistent, int? durationMs = null) =>
225 _monacoHost is { IsReady: true } host
226 ? host.PushAgentRevealAsync(startLine, endLine, persistent, durationMs: persistent ? null : (durationMs ?? 3000))
227 : Task.CompletedTask;
228
229 public void ClearAgentReveal() => _ = ClearAgentRevealAsync();
230
231 internal Task PushMonacoDebugVisualsAsync() => PushMonacoDebugOverlayAsync();
232
233 internal string GetEditorTextSnapshot()
234 {
235 if (_monacoHost is null)
236 return _vm?.EditorText ?? _docVm?.Doc.Content ?? "";
237 _monacoHost.Session.ReadSnapshot(out _, out var text, out _, out _, out _);
238 return text;
239 }
240}
241
View only · write via MCP/CIDE