Forge
csharp8f5a77c3
1#nullable enable
2using Microsoft.CodeAnalysis;
3using Microsoft.CodeAnalysis.CSharp;
4using Microsoft.CodeAnalysis.CSharp.Syntax;
5
6namespace CascadeIDE.Services.CodeNavigation;
7
8/// <summary>
9/// Область control-flow: тело метода или top-level statements (C# 9+).
10/// </summary>
11internal sealed record CodeNavigationControlFlowScope(
12 SyntaxTree SyntaxTree,
13 SyntaxList<StatementSyntax> Statements,
14 IReadOnlyList<GlobalStatementSyntax> TopLevelStatements,
15 string ScopeLabel)
16{
17 public bool IsTopLevel => TopLevelStatements.Count > 0;
18}
19
20internal static class CodeNavigationControlFlowScopeStatements
21{
22 public static IEnumerable<StatementSyntax> Enumerate(CodeNavigationControlFlowScope scope)
23 {
24 if (scope.IsTopLevel)
25 {
26 foreach (var global in scope.TopLevelStatements)
27 {
28 var statement = UnwrapGlobalStatement(global);
29 if (statement is not null)
30 yield return statement;
31 }
32
33 yield break;
34 }
35
36 foreach (var statement in scope.Statements)
37 yield return statement;
38 }
39
40 private static StatementSyntax? UnwrapGlobalStatement(GlobalStatementSyntax global) =>
41 global.ChildNodes().OfType<StatementSyntax>().FirstOrDefault();
42}
43
44internal static class CodeNavigationControlFlowScopeResolver
45{
46 public static bool TryGetTextPosition(SyntaxNode root, SyntaxTree tree, int? line, int? column, out int position)
47 {
48 position = 0;
49 if (line is null or <= 0)
50 return false;
51
52 var linePos = Math.Max(0, line.Value - 1);
53 var colPos = Math.Max(0, (column ?? 1) - 1);
54 var text = tree.GetText();
55 if (linePos >= text.Lines.Count)
56 return false;
57
58 position = text.Lines[linePos].Start + Math.Min(colPos, text.Lines[linePos].Span.Length);
59 return true;
60 }
61
62 public static CodeNavigationControlFlowScope? TryFindScope(
63 SyntaxNode root,
64 SyntaxTree tree,
65 int? line,
66 int? column)
67 {
68 if (!TryGetTextPosition(root, tree, line, column, out var pos))
69 return null;
70
71 var token = root.FindToken(pos);
72 var method = token.Parent?.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault();
73 if (method is not null)
74 return ScopeFromMethod(method);
75
76 var localFunction = token.Parent?.AncestorsAndSelf().OfType<LocalFunctionStatementSyntax>().FirstOrDefault();
77 if (localFunction is not null)
78 return ScopeFromLocalFunction(localFunction);
79
80 if (root is CompilationUnitSyntax cu)
81 {
82 if (token.Parent?.AncestorsAndSelf().OfType<GlobalStatementSyntax>().Any() == true
83 || cu.Members.OfType<GlobalStatementSyntax>().Any(g => g.Span.Contains(pos)))
84 {
85 return ScopeFromTopLevel(cu);
86 }
87 }
88
89 return null;
90 }
91
92 private static CodeNavigationControlFlowScope? ScopeFromLocalFunction(LocalFunctionStatementSyntax localFunction)
93 {
94 if (localFunction.Body is null)
95 return null;
96
97 return new CodeNavigationControlFlowScope(
98 localFunction.SyntaxTree,
99 localFunction.Body.Statements,
100 [],
101 localFunction.Identifier.Text);
102 }
103
104 private static CodeNavigationControlFlowScope? ScopeFromMethod(MethodDeclarationSyntax method)
105 {
106 if (method.Body is not null)
107 {
108 return new CodeNavigationControlFlowScope(
109 method.SyntaxTree,
110 method.Body.Statements,
111 [],
112 method.Identifier.Text);
113 }
114
115 if (method.ExpressionBody is null)
116 return null;
117
118 var expr = method.ExpressionBody.Expression;
119 var synthetic = SyntaxFactory.ExpressionStatement(expr);
120 return new CodeNavigationControlFlowScope(
121 method.SyntaxTree,
122 SyntaxFactory.SingletonList<StatementSyntax>(synthetic),
123 [],
124 method.Identifier.Text);
125 }
126
127 private static CodeNavigationControlFlowScope? ScopeFromTopLevel(CompilationUnitSyntax cu)
128 {
129 var globals = cu.Members.OfType<GlobalStatementSyntax>().ToList();
130 if (globals.Count == 0)
131 return null;
132
133 return new CodeNavigationControlFlowScope(
134 cu.SyntaxTree,
135 default,
136 globals,
137 "top-level");
138 }
139}
140
View only · write via MCP/CIDE