Forge
csharpdeeb25a2
1#nullable enable
2using System.Collections.Generic;
3using System.Globalization;
4using System.Linq;
5using CascadeIDE.Contracts;
6using CascadeIDE.ViewModels;
7
8namespace CascadeIDE.Features.WorkspaceNavigation.Application;
9
10/// <summary>Визуальный язык узла CF совпадает с <see cref="Views.SkiaKit.Graph.SkiaGraphSceneDrawing"/> BuildNodeGlyph / фигуры.</summary>
11public enum ControlFlowNodeVisualKind
12{
13 Anchor,
14 Circle,
15 Diamond,
16 Exit
17}
18
19/// <summary>Одна метка в gutter / virtual spacing lane: логическая строка 1-based.</summary>
20public sealed record ControlFlowLineVisual(
21 int LineOneBased,
22 ControlFlowNodeVisualKind VisualKind,
23 string NodeKind,
24 string TextGlyph,
25 bool ShowExitArrow,
26 string? ToolTip);
27
28/// <summary>Сборка глифов control-flow для редактора (без дублирования логики Skia).</summary>
29[ComputingUnit]
30public static class CodeNavigationControlFlowGlyphComposer
31{
32 private const string ExitStepKind = "exit_step";
33 private const string ConditionStepKind = "condition_step";
34
35 public static ControlFlowNodeVisualKind ResolveVisualKind(CodeNavigationMapGraphNodeLayout n)
36 {
37 if (n.IsAnchor)
38 return ControlFlowNodeVisualKind.Anchor;
39 if (IsExitNode(n))
40 return ControlFlowNodeVisualKind.Exit;
41 if (IsConditionNode(n) || n.Shape == CodeNavigationMapNodeShape.Condition)
42 return ControlFlowNodeVisualKind.Diamond;
43 return ControlFlowNodeVisualKind.Circle;
44 }
45
46 /// <summary>Текст внутри узла (пусто для exit — стрелка рисуется отдельно).</summary>
47 public static (string TextGlyph, bool ShowExitArrow) BuildTextGlyph(
48 CodeNavigationMapGraphNodeLayout n,
49 CodeNavigationMapGraphSceneVm scene)
50 {
51 var useLegend = scene.UseLegendColumn;
52 var showGlyphs = scene.ShowNodeLegendGlyphs;
53
54 if (n.IsAnchor)
55 return ("A", false);
56 if (string.Equals(n.Kind, "protected_step", StringComparison.OrdinalIgnoreCase))
57 return ("T", false);
58 if (IsExitNode(n))
59 return ("", true);
60 if (n.LegendIndex is int idxLegend && idxLegend > 0 && (useLegend || showGlyphs))
61 return (idxLegend.ToString(CultureInfo.InvariantCulture), false);
62 if (IsConditionNode(n) || n.Shape == CodeNavigationMapNodeShape.Condition)
63 return ("?", false);
64 if (IsHandlerNode(n))
65 return ("!", false);
66 return ("•", false);
67 }
68
69 public static void GetNodeVisual(
70 CodeNavigationMapGraphNodeLayout n,
71 CodeNavigationMapGraphSceneVm scene,
72 out ControlFlowNodeVisualKind kind,
73 out string textGlyph,
74 out bool showExitArrow)
75 {
76 kind = ResolveVisualKind(n);
77 (textGlyph, showExitArrow) = BuildTextGlyph(n, scene);
78 }
79
80 /// <summary>Строка для gutter: <see cref="LineStart"/>, иначе для якоря без строки — первая строка файла.</summary>
81 public static int? TryGetGutterLine(CodeNavigationMapGraphNodeLayout n)
82 {
83 if (n.LineStart is int ls and > 0)
84 return ls;
85 if (n.IsAnchor)
86 return 1;
87 return null;
88 }
89
90 public static IReadOnlyList<ControlFlowLineVisual> BuildGutterLineVisuals(CodeNavigationMapGraphSceneVm scene)
91 {
92 var byLine = new Dictionary<int, ControlFlowLineVisual>();
93
94 foreach (var n in scene.Nodes)
95 {
96 var line = TryGetGutterLine(n);
97 if (line is null)
98 continue;
99 GetNodeVisual(n, scene, out var kind, out var text, out var arrow);
100 var tip = TooltipForNode(n);
101 byLine[line.Value] = new ControlFlowLineVisual(line.Value, kind, n.Kind, text, arrow, tip);
102 }
103
104 return byLine.Keys.OrderBy(k => k).Select(k => byLine[k]).ToList();
105 }
106
107 /// <summary>Текст для ToolTip узла на HUD глифах.</summary>
108 public static string? TooltipForNode(CodeNavigationMapGraphNodeLayout n)
109 {
110 if (!string.IsNullOrWhiteSpace(n.LegendLine))
111 return n.LegendLine.Trim();
112 if (!string.IsNullOrWhiteSpace(n.Label))
113 return n.Label.Trim();
114 return null;
115 }
116
117 private static bool IsExitNode(CodeNavigationMapGraphNodeLayout n) =>
118 string.Equals(n.Kind, ExitStepKind, StringComparison.OrdinalIgnoreCase);
119
120 private static bool IsConditionNode(CodeNavigationMapGraphNodeLayout n) =>
121 string.Equals(n.Kind, ConditionStepKind, StringComparison.OrdinalIgnoreCase);
122
123 private static bool IsHandlerNode(CodeNavigationMapGraphNodeLayout n) =>
124 string.Equals(n.Kind, "handler_step", StringComparison.OrdinalIgnoreCase);
125}
126
View only · write via MCP/CIDE