Forge
csharpdeeb25a2
1using Microsoft.CodeAnalysis;
2using Microsoft.CodeAnalysis.CSharp.Syntax;
3using Microsoft.CodeAnalysis.Text;
4
5namespace CascadeIDE.Services;
6
7public sealed partial class CSharpLanguageService
8{
9 /// <summary>Диапазоны подсветки вхождений символа в том же файле (offset, length). Выполнять в фоне.</summary>
10 public IReadOnlyList<TextSpan> GetHighlightSpans(string filePath, string sourceText, int line, int column, CancellationToken ct = default)
11 {
12 if (string.IsNullOrEmpty(filePath) || line < 1 || column < 1)
13 return [];
14
15 var text = SourceText.From(sourceText);
16 var textHash = GetStableHash(text);
17 var cacheKey = (filePath, textHash, line, column);
18 if (_highlightCache.TryGetValue(cacheKey, out var cached))
19 return cached;
20
21 try
22 {
23 var model = GetOrCreateModel(filePath, text, ct);
24 var lines = text.Lines;
25 if (line > lines.Count)
26 return [];
27
28 var lineInfo = lines[line - 1];
29 var colIndex = column - 1;
30 var position = lineInfo.Start + Math.Min(Math.Max(0, colIndex), lineInfo.Span.Length);
31
32 var root = model.SyntaxTree.GetRoot(ct);
33 var token = root.FindToken(position, findInsideTrivia: true);
34 ISymbol? symbol = null;
35 for (var node = token.Parent; node is not null; node = node.Parent)
36 {
37 symbol = model.GetDeclaredSymbol(node, ct) ?? model.GetSymbolInfo(node, ct).Symbol;
38 if (symbol is not null)
39 break;
40 }
41
42 if (symbol is null)
43 {
44 _highlightCache[cacheKey] = [];
45 return [];
46 }
47
48 var spans = new List<TextSpan>();
49 foreach (var node in root.DescendantNodes())
50 {
51 ct.ThrowIfCancellationRequested();
52 if (!TryGetOccurrenceSpan(node, out var occurrenceSpan))
53 continue;
54 if (model.GetSymbolInfo(node, ct).Symbol?.Equals(symbol, SymbolEqualityComparer.Default) != true)
55 continue;
56 spans.Add(occurrenceSpan);
57 }
58
59 TrimCaches(_highlightCache);
60 _highlightCache[cacheKey] = spans;
61 return spans;
62 }
63 catch
64 {
65 return [];
66 }
67 }
68
69 /// <summary>Leaf occurrence span only — not whole <c>new T { … }</c> / invocation trees (VS-style).</summary>
70 private static bool TryGetOccurrenceSpan(SyntaxNode node, out TextSpan span)
71 {
72 switch (node)
73 {
74 case IdentifierNameSyntax id:
75 span = id.Identifier.Span;
76 return true;
77 case GenericNameSyntax gen:
78 span = gen.Identifier.Span;
79 return true;
80 case MemberAccessExpressionSyntax memberAccess:
81 span = memberAccess.Name.Span;
82 return true;
83 default:
84 span = default;
85 return false;
86 }
87 }
88}
89
View only · write via MCP/CIDE