Forge
csharpdeeb25a2
1#nullable enable
2using CascadeIDE.Cockpit.Graph;
3
4namespace CascadeIDE.Services;
5
6/// <summary>
7/// Короткий сценарный API для <b>карты намерений</b> (CodeNavigation) поверх <see cref="GraphDocumentBlueprint"/> (те же виды шагов, что и у
8/// <see cref="CascadeIDE.Services.CodeNavigation.CodeNavigationControlFlowSubgraphBuilder"/>):
9/// <list type="bullet">
10/// <item><description><see cref="DrawFunction"/> — <c>call_step</c>, ребро <c>Call</c>;</description></item>
11/// <item><description><see cref="DrawLoopCall"/> — <c>call_step</c>, ребро <c>LoopCall</c> (вызов в теле цикла);</description></item>
12/// <item><description><see cref="DrawCondition"/> — <c>condition_step</c>, рёбра <c>ConditionalCall</c>;</description></item>
13/// <item><description><see cref="DrawReturn"/> — <c>exit_step</c>, рёбра <c>Exit</c> (выход из метода).</description></item>
14/// </list>
15/// В wire-формате ещё бывают <c>Merge</c>, <c>MultiBranch</c> и т.д.; для них по-прежнему можно собрать граф руками через blueprint или JSON.
16/// </summary>
17public sealed class ControlFlowSceneSketch
18{
19 private const string DefaultAnchorLabel = "file";
20
21 private readonly GraphDocumentBlueprint _blueprint;
22 private readonly Dictionary<string, string> _stepKeyToNodeId = new(StringComparer.Ordinal);
23
24 public ControlFlowSceneSketch(
25 string anchorPath,
26 string anchorRationale = "sketch",
27 int maxNodes = 64,
28 int maxEdges = 128)
29 {
30 AnchorPath = anchorPath;
31 _blueprint = new GraphDocumentBlueprint(
32 anchorPath,
33 maxNodes,
34 maxEdges,
35 DefaultAnchorLabel,
36 anchorRationale,
37 GraphKind.CodeIntent);
38 }
39
40 public string AnchorPath { get; }
41
42 /// <summary>Якорь subgraph (id узла <c>n0</c>).</summary>
43 public string AnchorNodeId => _blueprint.AnchorNodeId;
44
45 /// <summary>
46 /// Последовательный шаг вызова: <paramref name="fromStepKey"/> — ключ уже добавленного шага или <c>null</c> (от якоря).
47 /// </summary>
48 public string DrawFunction(string? fromStepKey, string toStepKey) =>
49 DrawCallStep(fromStepKey, toStepKey, "Call", "Call");
50
51 /// <summary>
52 /// Вызов в контексте цикла (<c>LoopCall</c>), как вызов внутри <c>for</c>/<c>while</c> в билдере по AST.
53 /// </summary>
54 public string DrawLoopCall(string? fromStepKey, string toStepKey) =>
55 DrawCallStep(fromStepKey, toStepKey, "LoopCall", "LoopCall");
56
57 private string DrawCallStep(string? fromStepKey, string toStepKey, string edgeKind, string relatedKind)
58 {
59 ArgumentException.ThrowIfNullOrWhiteSpace(toStepKey);
60 if (_stepKeyToNodeId.ContainsKey(toStepKey))
61 throw new InvalidOperationException($"Step key '{toStepKey}' is already used.");
62
63 var fromId = ResolveFromKey(fromStepKey);
64 var id = _blueprint.TryAddNode(
65 "call_step",
66 AnchorPath,
67 toStepKey,
68 "",
69 $"call {toStepKey}",
70 toStepKey,
71 assignControlFlowLegendIndex: true);
72 if (id is null)
73 throw new InvalidOperationException("Node cap reached.");
74
75 _blueprint.AddEdges([fromId], id, edgeKind, relatedKind);
76 _stepKeyToNodeId[toStepKey] = id;
77 return id;
78 }
79
80 /// <summary>
81 /// Ветвление: от шага <paramref name="fromStepKey"/> — узел условия, затем два шага <paramref name="thenStepKey"/> и <paramref name="elseStepKey"/>.
82 /// Текст условия для легенды — <paramref name="conditionLegend"/> (например предикат исходного <c>if</c>).
83 /// </summary>
84 public string DrawCondition(
85 string fromStepKey,
86 string thenStepKey,
87 string elseStepKey,
88 string? conditionLegend = null)
89 {
90 ArgumentException.ThrowIfNullOrWhiteSpace(fromStepKey);
91 ArgumentException.ThrowIfNullOrWhiteSpace(thenStepKey);
92 ArgumentException.ThrowIfNullOrWhiteSpace(elseStepKey);
93 if (string.Equals(thenStepKey, elseStepKey, StringComparison.Ordinal))
94 throw new InvalidOperationException("Then and else step keys must differ.");
95
96 foreach (var k in new[] { thenStepKey, elseStepKey })
97 {
98 if (_stepKeyToNodeId.ContainsKey(k))
99 throw new InvalidOperationException($"Step key '{k}' is already used.");
100 }
101
102 var fromId = ResolveFromKey(fromStepKey);
103 var leg = string.IsNullOrWhiteSpace(conditionLegend) ? "if condition" : conditionLegend.Trim();
104
105 var condId = _blueprint.TryAddNode(
106 "condition_step",
107 AnchorPath,
108 "IF",
109 "",
110 "if condition",
111 leg,
112 assignControlFlowLegendIndex: true);
113 if (condId is null)
114 throw new InvalidOperationException("Node cap reached.");
115
116 _blueprint.AddEdges([fromId], condId, "ConditionalCall", "ConditionalCall");
117
118 var thenId = AddCallBranch(thenStepKey);
119 var elseId = AddCallBranch(elseStepKey);
120 _blueprint.AddEdges([condId], thenId, "ConditionalCall", "ConditionalCall");
121 _blueprint.AddEdges([condId], elseId, "ConditionalCall", "ConditionalCall");
122
123 _stepKeyToNodeId[thenStepKey] = thenId;
124 _stepKeyToNodeId[elseStepKey] = elseId;
125 return condId;
126 }
127
128 /// <summary>
129 /// Выход из метода (<c>exit_step</c>): узел «return», рёбра <c>Exit</c>. Опционально зарегистрировать <paramref name="exitStepKey"/>, если на этот шаг ссылаются дальше (редко).
130 /// </summary>
131 public string DrawReturn(string fromStepKey, string? exitStepKey = null)
132 {
133 ArgumentException.ThrowIfNullOrWhiteSpace(fromStepKey);
134 if (exitStepKey is not null && _stepKeyToNodeId.ContainsKey(exitStepKey))
135 throw new InvalidOperationException($"Step key '{exitStepKey}' is already used.");
136
137 var fromId = ResolveFromKey(fromStepKey);
138 var id = _blueprint.TryAddNode(
139 "exit_step",
140 AnchorPath,
141 "RET",
142 "",
143 "return",
144 "return",
145 assignControlFlowLegendIndex: true);
146 if (id is null)
147 throw new InvalidOperationException("Node cap reached.");
148
149 _blueprint.AddEdges([fromId], id, "Exit", "Exit");
150 if (!string.IsNullOrWhiteSpace(exitStepKey))
151 _stepKeyToNodeId[exitStepKey!] = id;
152 return id;
153 }
154
155 /// <summary>
156 /// Документ для <see cref="CascadeIDE.Features.WorkspaceNavigation.Application.CodeNavigationMapCompositor"/> (<see cref="GraphDocument"/> + <c>CodeNavigationMapLevelKind.ControlFlow</c>):
157 /// раскладка идёт через Intent → Declutter → Layout, без обхода пайплайна (ADR 0055).
158 /// </summary>
159 public GraphDocument ToDocument() => _blueprint.ToDocument();
160
161 private string AddCallBranch(string stepKey)
162 {
163 var id = _blueprint.TryAddNode(
164 "call_step",
165 AnchorPath,
166 stepKey,
167 "",
168 $"call {stepKey}",
169 stepKey,
170 assignControlFlowLegendIndex: true);
171 if (id is null)
172 throw new InvalidOperationException("Node cap reached.");
173 return id;
174 }
175
176 private string ResolveFromKey(string? fromStepKey)
177 {
178 if (fromStepKey is null)
179 return _blueprint.AnchorNodeId;
180 if (_stepKeyToNodeId.TryGetValue(fromStepKey, out var id))
181 return id;
182 throw new InvalidOperationException($"Unknown step key '{fromStepKey}'.");
183 }
184}
185
View only · write via MCP/CIDE