Forge
csharpdeeb25a2
1using System.Text.RegularExpressions;
2using CascadeIDE.Services;
3
4namespace CascadeIDE.Features.Editor;
5
6/// <summary>Debug-hints редактора: EOL-подсказки по текущей остановке DAP и top-level переменным.</summary>
7public sealed partial class EditorWorkspaceViewModel
8{
9 private static readonly Regex AssignmentRegex = new(
10 @"^\s*(?:(?:var|[A-Za-z_][\w<>,\[\]\?\s]*)\s+)?([A-Za-z_]\w*)\s*=",
11 RegexOptions.Compiled);
12
13 private static readonly Regex ConditionRegex = new(
14 @"^\s*(if|while|switch)\s*\((?<expr>.*)\)",
15 RegexOptions.Compiled);
16
17 private static readonly Regex IdentifierRegex = new(
18 @"\b[_A-Za-z]\w*\b",
19 RegexOptions.Compiled);
20
21 /// <summary>Собрать EOL debug hints для файла: в v1 только текущая исполняемая строка.</summary>
22 public IReadOnlyList<EditorDebugHintStrip> GetEditorDebugHintsForFile(string filePath, string sourceText)
23 {
24 var opts = _host.McpSettings.Editor.DebugHints;
25 if (!opts.Enabled)
26 return [];
27
28 var snapshot = _host.DapDebug.GetSnapshot();
29 if (!snapshot.IsExecutionStopped || string.IsNullOrWhiteSpace(snapshot.StoppedFile))
30 return [];
31
32 var currentLine = GetDebugCurrentLineForFile(filePath);
33 if (currentLine <= 0)
34 return [];
35
36 var lines = sourceText.Split('\n');
37 if (currentLine > lines.Length)
38 return [];
39 var lineText = lines[currentLine - 1].Trim();
40 if (string.IsNullOrWhiteSpace(lineText))
41 return [];
42
43 var variables = snapshot.VariableRootScopes
44 .SelectMany(static s => s.Roots)
45 .Where(static r => !string.IsNullOrWhiteSpace(r.Name))
46 .GroupBy(static r => r.Name, StringComparer.Ordinal)
47 .ToDictionary(static g => g.Key, static g => g.First().Value, StringComparer.Ordinal);
48
49 string? label = null;
50 if (opts.ShowAssignments)
51 label = TryBuildAssignmentLabel(lineText, variables);
52 if (label is null && opts.ShowConditions)
53 label = TryBuildConditionLabel(lineText, variables);
54 if (label is null)
55 label = TryBuildIdentifierSnapshotLabel(lineText, variables);
56
57 if (string.IsNullOrWhiteSpace(label))
58 return [];
59
60 if (InlayHintTrace.IsDebug)
61 InlayHintTrace.LogDebug($"DebugHint.Model line={currentLine} label={label}");
62
63 return [new EditorDebugHintStrip(currentLine, label)];
64 }
65
66 private static string? TryBuildAssignmentLabel(string lineText, IReadOnlyDictionary<string, string> vars)
67 {
68 var m = AssignmentRegex.Match(lineText);
69 if (!m.Success)
70 return null;
71 var name = m.Groups[1].Value;
72 if (!vars.TryGetValue(name, out var value))
73 return null;
74 return $"{name} = {value}";
75 }
76
77 private static string? TryBuildConditionLabel(string lineText, IReadOnlyDictionary<string, string> vars)
78 {
79 var m = ConditionRegex.Match(lineText);
80 if (!m.Success)
81 return null;
82 var expr = m.Groups["expr"].Value;
83 var values = ExtractVariableValues(expr, vars);
84 if (values.Count == 0)
85 return null;
86 return "=> " + string.Join(", ", values);
87 }
88
89 private static string? TryBuildIdentifierSnapshotLabel(string lineText, IReadOnlyDictionary<string, string> vars)
90 {
91 var values = ExtractVariableValues(lineText, vars);
92 if (values.Count == 0)
93 return null;
94 return "=> " + string.Join(", ", values);
95 }
96
97 private static List<string> ExtractVariableValues(string source, IReadOnlyDictionary<string, string> vars)
98 {
99 var values = new List<string>(4);
100 var seen = new HashSet<string>(StringComparer.Ordinal);
101 foreach (Match id in IdentifierRegex.Matches(source))
102 {
103 var name = id.Value;
104 if (!seen.Add(name))
105 continue;
106 if (!vars.TryGetValue(name, out var value))
107 continue;
108 values.Add($"{name}={value}");
109 if (values.Count >= 4)
110 break;
111 }
112
113 return values;
114 }
115}
116
View only · write via MCP/CIDE