Forge
csharpdeeb25a2
1#nullable enable
2
3using System.Collections.ObjectModel;
4using System.IO;
5using System.Linq;
6using System.Text.Json;
7using Avalonia.Controls;
8using Avalonia.Threading;
9using CascadeIDE.Cockpit.Cds;
10using CascadeIDE.Cockpit.Channels.TraceFlow;
11using CascadeIDE.Cockpit.Composition.TraceFlow;
12using CascadeIDE.Cockpit.Graph;
13using CascadeIDE.Features.Documents;
14using CascadeIDE.Features.Editor;
15using CascadeIDE.Features.Shell.Application;
16using CascadeIDE.Features.HybridIndex.Application;
17using CascadeIDE.Features.Markdown;
18using CascadeIDE.Features.UiChrome;
19using CascadeIDE.Features.Workspace;
20using CascadeIDE.Features.Workspace.DataAcquisition;
21using CascadeIDE.Features.WorkspaceNavigation.Application;
22using CascadeIDE.Models;
23using CascadeIDE.Services;
24using CascadeIDE.ViewModels;
25using CommunityToolkit.Mvvm.ComponentModel;
26using CommunityToolkit.Mvvm.Input;
27
28namespace CascadeIDE.Features.WorkspaceNavigation.Presentation;
29
30/// <summary>PFD/MFD workspace navigation map (ADR 0039).</summary>
31public sealed partial class WorkspaceNavigationMapViewModel : ObservableObject
32{
33 private readonly IWorkspaceNavigationMapHost _host;
34
35 public WorkspaceNavigationMapViewModel(IWorkspaceNavigationMapHost host)
36 {
37 _host = host;
38 _host.Shell.PropertyChanged += OnShellPropertyChanged;
39 }
40
41 public bool IsPfdRegionExpanded => _host.IsPfdRegionExpanded;
42
43 private void OnShellPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
44 {
45 if (e.PropertyName is "IsPfdRegionExpanded" or null or "")
46 OnPropertyChanged(nameof(IsPfdRegionExpanded));
47 }
48
49 private readonly IGraphDataSource _codeNavigationMapGraphDataSource = new WorkspaceNavigationMapContextJsonDataSource();
50 private readonly TraceFlowChannelCoordinator _traceFlowChannelCoordinator = new(
51 [
52 new CodeFlowTraceChannel(),
53 new UnitTestTraceChannel()
54 ]);
55 private readonly ITraceFlowCdsRouter _traceFlowCdsRouter = new TraceFlowCdsRouter();
56 private readonly ITraceFlowSurfaceCompositor _traceFlowSurfaceCompositor = new TraceFlowSurfaceCompositor();
57 private int? _editorCaretOffset;
58
59 /// <summary>Одноразовый якорь CF после клика по узлу графа (строка для refresh + reveal).</summary>
60 private string? _controlFlowGraphNavigatePath;
61 private int? _controlFlowGraphNavigateLine;
62 private int? _controlFlowGraphNavigateColumn;
63
64 private CancellationTokenSource? _workspaceNavigationMapRefreshCts;
65
66 internal int? EditorCaretOffset => _editorCaretOffset;
67
68 internal void UpdateCodeNavigationMapCaretOffset(int? offset)
69 {
70 _editorCaretOffset = offset;
71 if (_host.Settings.CodeNavigationMap.IsControlFlowDepth)
72 ScheduleWorkspaceNavigationMapRefresh();
73 _host.ScheduleEditorHudBannerRefresh();
74 }
75
76 /// <summary>Связанные файлы для текущего якоря (режим списка).</summary>
77 public ObservableCollection<WorkspaceNavigationMapItemVm> WorkspaceNavigationMapItems { get; } = new();
78
79 /// <summary>Варианты <see cref="CodeNavigationMapPresentationKind"/> для ComboBox.</summary>
80 public string[] CodeNavigationMapPresentationOptions { get; } =
81 [CodeNavigationMapPresentationKind.List, CodeNavigationMapPresentationKind.Graph, CodeNavigationMapPresentationKind.Both];
82
83 /// <summary>Варианты уровня карты: файловый и control flow.</summary>
84 public string[] CodeNavigationMapLevelOptions { get; } =
85 [CodeNavigationMapLevelKind.File, CodeNavigationMapLevelKind.ControlFlow];
86
87 /// <summary><c>auto</c> | <c>vertical</c> | <c>horizontal</c> для <c>[code_navigation_map].control_flow_main_axis</c>.</summary>
88 public string[] CodeNavigationMapControlFlowMainAxisOptions { get; } =
89 [
90 CodeNavigationMapControlFlowMainAxisKind.Auto,
91 CodeNavigationMapControlFlowMainAxisKind.Vertical,
92 CodeNavigationMapControlFlowMainAxisKind.Horizontal,
93 ];
94
95 /// <summary>
96 /// Полный путь якоря подграфа control-flow (как в JSON <c>anchor_path</c>); для кликов и подписей в редакторе.
97 /// </summary>
98 [ObservableProperty]
99 private string? _workspaceNavigationMapCfAnchorFullPath;
100
101 /// <summary>Сцена мини-карты (подграф + укладка по выбранному уровню карты).</summary>
102 [ObservableProperty]
103 [NotifyPropertyChangedFor(nameof(WorkspaceNavigationMapHasRelated))]
104 private CodeNavigationMapGraphSceneVm? _codeNavigationMapGraphScene;
105
106 [ObservableProperty]
107 private double _codeNavigationMapGraphHeight = CodeNavigationMapCompositor.DefaultHeightFile;
108
109 /// <summary>Ширина области компоновки (совпадает с фактической шириной мини-карты в PFD).</summary>
110 [ObservableProperty]
111 private double _codeNavigationMapGraphWidth = CodeNavigationMapCompositor.DefaultWidth;
112
113 /// <summary><c>list</c> | <c>graph</c> | <c>both</c> — синхронизируется с <c>[code_navigation_map]</c>.</summary>
114 [ObservableProperty]
115 [NotifyPropertyChangedFor(nameof(ShowCodeNavigationMapList))]
116 [NotifyPropertyChangedFor(nameof(ShowCodeNavigationMapGraph))]
117 [NotifyPropertyChangedFor(nameof(WorkspaceNavigationMapHasRelated))]
118 [NotifyPropertyChangedFor(nameof(CodeNavigationMapListAreaRowHeight))]
119 [NotifyPropertyChangedFor(nameof(ShowCodeNavigationMapListOnPfd))]
120 [NotifyPropertyChangedFor(nameof(ShowCodeNavigationMapGraphClickHint))]
121 private string _codeNavigationMapPresentation = CodeNavigationMapPresentationKind.List;
122
123 /// <summary><c>file</c> | <c>controlFlow</c> — уровень построения карты (секция <c>[code_navigation_map]</c>).</summary>
124 [ObservableProperty]
125 [NotifyPropertyChangedFor(nameof(ShowCodeNavigationMapGraph))]
126 [NotifyPropertyChangedFor(nameof(ShowCodeNavigationMapGraphClickHint))]
127 [NotifyPropertyChangedFor(nameof(CodeNavigationMapSettingsSummaryLine))]
128 private string _codeNavigationMapLevel = CodeNavigationMapLevelKind.File;
129
130 /// <summary>Синхронизируется с <c>[code_navigation_map].control_flow_main_axis</c>.</summary>
131 [ObservableProperty]
132 [NotifyPropertyChangedFor(nameof(CodeNavigationMapSettingsSummaryLine))]
133 private string _codeNavigationMapControlFlowMainAxis = CodeNavigationMapControlFlowMainAxisKind.Auto;
134
135 /// <summary>Сообщение об ошибке или пустом состоянии (не null).</summary>
136 [ObservableProperty]
137 private string _workspaceNavigationMapStatus = "";
138
139 /// <summary>Заголовок якоря: имя файла или «—».</summary>
140 [ObservableProperty]
141 private string _workspaceNavigationMapAnchorLabel = "—";
142
143 /// <summary>Число связей для бейджа и Skia accent.</summary>
144 [ObservableProperty]
145 [NotifyPropertyChangedFor(nameof(WorkspaceNavigationMapRelatedBadge))]
146 [NotifyPropertyChangedFor(nameof(WorkspaceNavigationMapHasRelated))]
147 private int _workspaceNavigationMapRelatedCount;
148
149 /// <summary>Настройка <c>list</c>/<c>both</c>: список связанных ренерится на странице MFD <see cref="MfdShellPage.RelatedFiles"/>, не в колонке PFD.</summary>
150 public bool ShowCodeNavigationMapList =>
151 CodeNavigationMapPresentationProjection.ShowCodeNavigationMapList(CodeNavigationMapPresentation);
152
153 /// <summary>Списка related на PFD нет (см. <see cref="MfdShellPage.RelatedFiles"/>).</summary>
154 public bool ShowCodeNavigationMapListOnPfd =>
155 CodeNavigationMapPresentationProjection.ShowCodeNavigationMapListOnPfd;
156
157 /// <summary>Показать мини-карту подграфа.</summary>
158 public bool ShowCodeNavigationMapGraph =>
159 CodeNavigationMapPresentationProjection.ShowCodeNavigationMapGraph(CodeNavigationMapPresentation, CodeNavigationMapLevel);
160
161 /// <summary>
162 /// Высота нижней строки Grid под список на PFD: список перенесён в MFD — всегда 0.
163 /// </summary>
164 public GridLength CodeNavigationMapListAreaRowHeight =>
165 CodeNavigationMapPresentationProjection.ListAreaRowUsesStar(ShowCodeNavigationMapList, ShowCodeNavigationMapListOnPfd)
166 ? new GridLength(1, GridUnitType.Star)
167 : new GridLength(0);
168
169 /// <summary>
170 /// Режим <c>file</c>: подсказка «открыть файл» (в control flow клик ведёт к строке, не к файлу).
171 /// </summary>
172 public bool ShowCodeNavigationMapGraphClickHint =>
173 CodeNavigationMapPresentationProjection.ShowCodeNavigationMapGraphClickHint(
174 ShowCodeNavigationMapGraph,
175 CodeNavigationMapLevel,
176 CodeNavigationMapPresentation);
177
178 /// <summary>Короткая подпись к количеству связей для шапки SM.</summary>
179 public string WorkspaceNavigationMapRelatedBadge =>
180 CodeNavigationMapPresentationProjection.WorkspaceNavigationMapRelatedBadge(WorkspaceNavigationMapRelatedCount);
181
182 /// <summary>Есть ли контекст для accent (список или подграф с соседями).</summary>
183 public bool WorkspaceNavigationMapHasRelated =>
184 CodeNavigationMapPresentationProjection.WorkspaceNavigationMapHasRelated(
185 WorkspaceNavigationMapRelatedCount,
186 CodeNavigationMapGraphScene?.Nodes.Count);
187
188 public bool IsControlFlowEditorVirtualSpacingActiveForFile(string? filePath) =>
189 EditorControlFlowLanePolicy.ShouldReserveLane(
190 CodeNavigationMapLevel,
191 WorkspaceNavigationMapCfAnchorFullPath,
192 filePath,
193 CodeNavigationMapGraphScene);
194
195 /// <summary>Список глифов CF для строки gutter активного документа (<c>null</c> если не CF / не совпадает якорь).</summary>
196 public IReadOnlyList<ControlFlowLineVisual>? GetControlFlowGutterLineVisualsForFile(string? filePath)
197 {
198 if (!IsControlFlowEditorVirtualSpacingActiveForFile(filePath))
199 return null;
200
201 return CodeNavigationControlFlowGlyphComposer.BuildGutterLineVisuals(CodeNavigationMapGraphScene!);
202 }
203
204 /// <summary>Открыть связанный файл / code anchor из карты намерений.</summary>
205 [RelayCommand]
206 private void OpenWorkspaceNavigationRelated(object? parameter)
207 {
208 switch (parameter)
209 {
210 case CodeNavigationMapNodeNavigatePayload payload:
211 NavigateWorkspaceNavigationMapNode(payload);
212 return;
213 case string path when !string.IsNullOrWhiteSpace(path):
214 _host.Documents.OpenOrActivateDocument(path);
215 return;
216 }
217 }
218
219 [RelayCommand]
220 private void OpenWorkspaceAdrCorrespondence() => ShowCorrespondencePage();
221
222 [RelayCommand]
223 private async Task OpenWorkspaceFeatureDocsAsync()
224 {
225 var docs = WorkspaceFeatureDocPaths ?? [];
226 if (docs.Length == 0)
227 return;
228
229 var pick = docs.Length == 1
230 ? docs[0]
231 : _host.RequestPickFeatureDocAsync is not null
232 ? await _host.RequestPickFeatureDocAsync("Документация фичи", docs).ConfigureAwait(true)
233 : docs[0];
234
235 if (string.IsNullOrWhiteSpace(pick))
236 return;
237
238 var wsRoot = _host.GetWorkspacePath();
239 if (string.IsNullOrWhiteSpace(wsRoot))
240 return;
241
242 if (!WorkspaceMarkdownPreviewOpener.TryOpenRepoDocument(
243 wsRoot,
244 pick,
245 (title, content, source) => _host.MarkdownPreviewTool.SetContent(title, content, source),
246 out _))
247 return;
248
249 _host.ApplyMfdRegionExpanded(true);
250 _host.TryNavigateToMfdShellPage(MfdShellPage.MarkdownPreview);
251 }
252
253 [RelayCommand]
254 private async Task OpenDocsTemplateAsync(object? parameter)
255 {
256 var wsRoot = _host.GetWorkspacePath();
257 if (string.IsNullOrWhiteSpace(wsRoot))
258 return;
259
260 var workspaceToml = RepositoryWorkspaceTomlLoader.TryLoad(wsRoot);
261 var templates = DocsTemplatesCatalogResolver.ResolveTemplatesFromWorkspaceToml(workspaceToml, wsRoot);
262 if (templates.Count == 0)
263 return;
264
265 var requested = parameter as string;
266 var selectedId = !string.IsNullOrWhiteSpace(requested) ? requested!.Trim() : null;
267 if (string.IsNullOrWhiteSpace(selectedId))
268 {
269 var labels = templates.Select(t => $"{t.Id} — {t.Title}").ToArray();
270 var pick = _host.RequestPickFeatureDocAsync is not null
271 ? await _host.RequestPickFeatureDocAsync("Шаблон документации", labels).ConfigureAwait(true)
272 : labels[0];
273
274 if (string.IsNullOrWhiteSpace(pick))
275 return;
276
277 var dash = pick.IndexOf("—", StringComparison.Ordinal);
278 selectedId = (dash > 0 ? pick[..dash] : pick).Trim();
279 }
280
281 if (string.IsNullOrWhiteSpace(selectedId))
282 return;
283
284 var entry = templates.FirstOrDefault(t => string.Equals(t.Id, selectedId, StringComparison.OrdinalIgnoreCase));
285 if (entry is null)
286 return;
287
288 if (entry.Source == "knowledge" && !string.IsNullOrWhiteSpace(entry.KnowledgeFilePath))
289 {
290 try
291 {
292 var args = new Dictionary<string, System.Text.Json.JsonElement>
293 {
294 ["file_path"] = System.Text.Json.JsonDocument.Parse($"\"{entry.KnowledgeFilePath}\"").RootElement.Clone(),
295 };
296 if (!string.IsNullOrWhiteSpace(entry.KnowledgeRootId))
297 args["knowledge_root_id"] = System.Text.Json.JsonDocument.Parse($"\"{entry.KnowledgeRootId}\"").RootElement.Clone();
298
299 var content = await _host.IdeMcp.ExecuteCommandAsync(Services.IdeCommands.ReadKnowledgeFile, args, CancellationToken.None)
300 .ConfigureAwait(true);
301
302 _host.MarkdownPreviewTool.SetContent($"KB template: {entry.Title}", content ?? "", entry.KnowledgeFilePath);
303 _host.ApplyMfdRegionExpanded(true);
304 _host.TryNavigateToMfdShellPage(MfdShellPage.MarkdownPreview);
305 }
306 catch
307 {
308 // ignore
309 }
310 return;
311 }
312
313 if (!string.IsNullOrWhiteSpace(entry.RepoPath)
314 && WorkspaceMarkdownPreviewOpener.TryOpenRepoDocument(
315 wsRoot,
316 entry.RepoPath,
317 (title, content, source) => _host.MarkdownPreviewTool.SetContent(entry.Title, content, source),
318 out _))
319 {
320 _host.ApplyMfdRegionExpanded(true);
321 _host.TryNavigateToMfdShellPage(MfdShellPage.MarkdownPreview);
322 }
323 }
324
325 private void BeginControlFlowGraphNodeNavigation(string fullPath, int lineOneBased)
326 {
327 _controlFlowGraphNavigatePath = fullPath;
328 _controlFlowGraphNavigateLine = lineOneBased;
329 _controlFlowGraphNavigateColumn = 1;
330
331 if (!EditorTextCoordinateUtilities.PathsReferToSameFile(_host.CurrentFilePath, fullPath))
332 return;
333
334 var text = _host.EditorText;
335 if (WorkspaceNavigationMapOrchestrator.TryOffsetForLine(text, lineOneBased) is int offset)
336 _editorCaretOffset = offset;
337 }
338
339 /// <summary>Не сбрасывать каретку при смене файла, если идёт навигация с CF-графа на ту же цель.</summary>
340 internal bool TryPreserveControlFlowNavigateCaretOnFileChange()
341 {
342 if (_controlFlowGraphNavigateLine is not int line || line < 1
343 || string.IsNullOrEmpty(_controlFlowGraphNavigatePath))
344 return false;
345
346 if (!EditorTextCoordinateUtilities.PathsReferToSameFile(_host.CurrentFilePath, _controlFlowGraphNavigatePath))
347 return false;
348
349 if (WorkspaceNavigationMapOrchestrator.TryOffsetForLine(_host.EditorText, line) is int hostOffset)
350 {
351 _editorCaretOffset = hostOffset;
352 return true;
353 }
354
355 return false;
356 }
357
358 private void ClearControlFlowGraphNodeNavigationAnchor()
359 {
360 _controlFlowGraphNavigatePath = null;
361 _controlFlowGraphNavigateLine = null;
362 _controlFlowGraphNavigateColumn = null;
363 }
364
365 private void NavigateWorkspaceNavigationMapNode(CodeNavigationMapNodeNavigatePayload payload)
366 {
367 try
368 {
369 if (string.IsNullOrWhiteSpace(payload.FullPath))
370 return;
371
372 if (!Features.Documents.SolutionTreePath.TryGetFullPath(payload.FullPath, out var path))
373 {
374 WorkspaceNavigationMapStatus = "Не удалось нормализовать путь файла узла карты.";
375 return;
376 }
377
378 var isCf = string.Equals(
379 CodeNavigationMapLevelKind.Normalize(CodeNavigationMapLevel),
380 CodeNavigationMapLevelKind.ControlFlow,
381 StringComparison.Ordinal);
382
383 if (isCf)
384 {
385 var anchorCf = WorkspaceNavigationMapCfAnchorFullPath;
386 if (!string.IsNullOrEmpty(anchorCf)
387 && !EditorTextCoordinateUtilities.PathsReferToSameFile(path, anchorCf))
388 {
389 WorkspaceNavigationMapStatus =
390 "Карта потока — другой файл; клик отменён (ожидается якорный .cs карты).";
391 return;
392 }
393 }
394
395 if (isCf && payload.LineStart is > 0)
396 BeginControlFlowGraphNodeNavigation(path, payload.LineStart.Value);
397
398 _host.Documents.ActivateDocumentForReveal(path);
399
400 if (payload.LineStart is > 0)
401 {
402 var start = payload.LineStart.Value;
403 var end = payload.LineEnd is > 0 ? payload.LineEnd.Value : start;
404 var revealPath = path;
405 Avalonia.Threading.Dispatcher.UIThread.Post(
406 () => _host.RevealEditorRange(revealPath, start, end, null),
407 Avalonia.Threading.DispatcherPriority.Loaded);
408 if (isCf)
409 ScheduleWorkspaceNavigationMapRefresh();
410 }
411 else if (isCf && string.Equals(payload.Kind, "anchor", StringComparison.OrdinalIgnoreCase))
412 {
413 Avalonia.Threading.Dispatcher.UIThread.Post(
414 () => _host.RevealEditorRange(path, 1, 1, null),
415 Avalonia.Threading.DispatcherPriority.Loaded);
416 }
417
418 if (!string.IsNullOrWhiteSpace(payload.LegendLine))
419 WorkspaceNavigationMapStatus = payload.LegendLine.Trim();
420 }
421 catch (Exception ex)
422 {
423 WorkspaceNavigationMapStatus = $"Навигация с карты: {ex.Message}";
424 }
425 }
426
427 /// <summary>Сводка настроек карты намерений (без ComboBox; смена через палитру или MCP).</summary>
428 public string CodeNavigationMapSettingsSummaryLine =>
429 CodeNavigationMapPresentationProjection.SettingsSummaryLine(
430 CodeNavigationMapPresentation,
431 CodeNavigationMapLevel,
432 _host.Settings.CodeNavigationMap.DetailLevel,
433 _host.Settings.CodeNavigationMap.RelatedGraphLayout,
434 _host.Settings.CodeNavigationMap.NormalizedControlFlowMainAxis);
435
436 /// <summary>Краткая строка ориентации HCI (слой B) рядом с картой; не влияет на Roslyn-граф (ADR 0106).</summary>
437 [ObservableProperty]
438 private string _workspaceNavigationMapHciOrientationLine = "";
439
440 /// <summary>Активные слои correspondence L0–L4 (ADR 0155 §3).</summary>
441 [ObservableProperty]
442 private string _workspaceCorrespondenceLayersLine = "";
443
444 [ObservableProperty]
445 private string _workspaceCorrespondenceLayersTooltip = "";
446
447 /// <summary>Doc correspondence (ADR 0061): какие ADR относятся к текущему файлу по <c>[_host.Workspace.adr.map]</c>.</summary>
448 [ObservableProperty]
449 private string _workspaceAdrCorrespondenceLine = "";
450
451 [ObservableProperty]
452 private string? _workspaceAdrCorrespondenceFirstDocPath;
453
454 [ObservableProperty]
455 private string _workspaceFeatureLine = "";
456
457 /// <summary>Мягкий сигнал “нет доков” (не ошибка): пусто, если всё ок.</summary>
458 [ObservableProperty]
459 private string _workspaceDocsCoverageLine = "";
460
461 [ObservableProperty]
462 private string[] _workspaceFeatureDocPaths = [];
463
464 /// <summary>Команда палитры / MCP: list → graph → both.</summary>
465 public void CycleCodeNavigationMapPresentation() =>
466 CodeNavigationMapPresentation = CodeNavigationMapPresentationProjection.NextPresentationViewAfter(CodeNavigationMapPresentation);
467
468 /// <summary>Команда палитры / MCP: file ↔ controlFlow.</summary>
469 public void CycleCodeNavigationMapLevel() =>
470 CodeNavigationMapLevel = CodeNavigationMapPresentationProjection.ToggledMapLevel(CodeNavigationMapLevel);
471
472 /// <summary>Команда палитры / MCP / слэш: явно <c>file</c> или <c>controlFlow</c>.</summary>
473 public void SetCodeNavigationMapLevel(string level) =>
474 CodeNavigationMapLevel = CodeNavigationMapLevelKind.Normalize(level);
475
476 /// <summary>Команда палитры / MCP: radial → top_down → bottom_up.</summary>
477 public void CycleCodeNavigationMapRelatedGraphLayout()
478 {
479 _host.Settings.CodeNavigationMap.RelatedGraphLayout =
480 CodeNavigationMapPresentationProjection.NextRelatedGraphLayoutAfter(_host.Settings.CodeNavigationMap.RelatedGraphLayout);
481 _host.SaveSettingsIfChanged();
482 ScheduleWorkspaceNavigationMapRefresh();
483 OnPropertyChanged(nameof(CodeNavigationMapSettingsSummaryLine));
484 }
485
486 /// <summary>Команда палитры / MCP: glance → normal → inspect.</summary>
487 public void CycleCodeNavigationMapDetailLevel()
488 {
489 var (_, toml) = CodeNavigationMapPresentationProjection.NextDetailCycle(_host.Settings.CodeNavigationMap.NormalizedDetailLevel);
490 _host.Settings.CodeNavigationMap.DetailLevel = toml;
491 _host.SaveSettingsIfChanged();
492 ScheduleWorkspaceNavigationMapRefresh();
493 OnPropertyChanged(nameof(CodeNavigationMapSettingsSummaryLine));
494 }
495
496 partial void OnCodeNavigationMapPresentationChanged(string value)
497 {
498 var normalized = CodeNavigationMapPresentationKind.Normalize(value);
499 if (ShellSettingsPresentationProjection.ShouldRewriteWithNormalizedValue(value, normalized))
500 {
501 CodeNavigationMapPresentation = normalized;
502 return;
503 }
504
505 _host.Settings.CodeNavigationMap.View = normalized;
506 _host.SaveSettingsIfChanged();
507 ScheduleWorkspaceNavigationMapRefresh();
508 }
509
510 partial void OnCodeNavigationMapLevelChanged(string value)
511 {
512 var normalized = CodeNavigationMapLevelKind.Normalize(value);
513 if (ShellSettingsPresentationProjection.ShouldRewriteWithNormalizedValue(value, normalized))
514 {
515 CodeNavigationMapLevel = normalized;
516 return;
517 }
518
519 _host.Settings.CodeNavigationMap.Depth = normalized;
520 _host.SaveSettingsIfChanged();
521 ScheduleWorkspaceNavigationMapRefresh();
522 }
523
524 partial void OnCodeNavigationMapControlFlowMainAxisChanged(string value)
525 {
526 var normalized = CodeNavigationMapControlFlowMainAxisKind.Normalize(value);
527 if (ShellSettingsPresentationProjection.ShouldRewriteWithNormalizedValue(value, normalized))
528 {
529 CodeNavigationMapControlFlowMainAxis = normalized;
530 return;
531 }
532
533 _host.Settings.CodeNavigationMap.ControlFlowMainAxis = normalized;
534 _host.SaveSettingsIfChanged();
535 ScheduleWorkspaceNavigationMapRefresh();
536 }
537}
538
539
540
View only · write via MCP/CIDE