| 1 | using CascadeIDE.Features.Editor.Application.Monaco; |
| 2 | |
| 3 | namespace CascadeIDE.Features.Editor.Application; |
| 4 | |
| 5 | /// <summary>Breakpoint / debug-line / agent-reveal decorations for Monaco host.</summary> |
| 6 | public static class MonacoEditorDebugMapper |
| 7 | { |
| 8 | public static IReadOnlyList<CideEditorDecoration> ToBreakpointDecorations( |
| 9 | string text, |
| 10 | IReadOnlyList<int> breakpointLines) |
| 11 | { |
| 12 | if (breakpointLines.Count == 0) |
| 13 | return Array.Empty<CideEditorDecoration>(); |
| 14 | |
| 15 | var list = new List<CideEditorDecoration>(breakpointLines.Count); |
| 16 | foreach (var line in breakpointLines) |
| 17 | { |
| 18 | if (line < 1) |
| 19 | continue; |
| 20 | var (offset, length) = WholeLineSpan(text, line); |
| 21 | if (length <= 0) |
| 22 | continue; |
| 23 | list.Add(new CideEditorDecoration( |
| 24 | offset, |
| 25 | length, |
| 26 | "cide-breakpoint-line", |
| 27 | "Breakpoint", |
| 28 | IsWholeLine: true, |
| 29 | GlyphMarginClassName: "cide-breakpoint-glyph")); |
| 30 | } |
| 31 | |
| 32 | return list; |
| 33 | } |
| 34 | |
| 35 | public static IReadOnlyList<CideEditorDecoration> ToDebugLineDecoration(string text, int debugLine) |
| 36 | { |
| 37 | if (debugLine < 1) |
| 38 | return Array.Empty<CideEditorDecoration>(); |
| 39 | |
| 40 | var (offset, length) = WholeLineSpan(text, debugLine); |
| 41 | if (length <= 0) |
| 42 | return Array.Empty<CideEditorDecoration>(); |
| 43 | |
| 44 | return |
| 45 | [ |
| 46 | new CideEditorDecoration( |
| 47 | offset, |
| 48 | length, |
| 49 | "cide-debug-line", |
| 50 | "Current debug line", |
| 51 | IsWholeLine: true, |
| 52 | GlyphMarginClassName: "cide-debug-arrow"), |
| 53 | ]; |
| 54 | } |
| 55 | |
| 56 | public static IReadOnlyList<CideEditorDecoration> ToAgentRevealDecorations( |
| 57 | string text, |
| 58 | int startLine, |
| 59 | int endLine) |
| 60 | { |
| 61 | if (startLine < 1 || endLine < startLine) |
| 62 | return Array.Empty<CideEditorDecoration>(); |
| 63 | |
| 64 | var list = new List<CideEditorDecoration>(endLine - startLine + 1); |
| 65 | for (var line = startLine; line <= endLine; line++) |
| 66 | { |
| 67 | var (offset, length) = WholeLineSpan(text, line); |
| 68 | if (length <= 0) |
| 69 | continue; |
| 70 | list.Add(new CideEditorDecoration( |
| 71 | offset, |
| 72 | length, |
| 73 | AgentRevealClassForLine(line, startLine, endLine), |
| 74 | null, |
| 75 | IsWholeLine: true)); |
| 76 | } |
| 77 | |
| 78 | return list; |
| 79 | } |
| 80 | |
| 81 | private static string AgentRevealClassForLine(int line, int startLine, int endLine) |
| 82 | { |
| 83 | const string baseClass = "cide-agent-reveal-line"; |
| 84 | if (startLine == endLine) |
| 85 | return $"{baseClass} cide-agent-reveal-single"; |
| 86 | if (line == startLine) |
| 87 | return $"{baseClass} cide-agent-reveal-top"; |
| 88 | if (line == endLine) |
| 89 | return $"{baseClass} cide-agent-reveal-bottom"; |
| 90 | return $"{baseClass} cide-agent-reveal-middle"; |
| 91 | } |
| 92 | |
| 93 | private static (int Offset, int Length) WholeLineSpan(string text, int lineOneBased) |
| 94 | { |
| 95 | if (string.IsNullOrEmpty(text) || lineOneBased < 1) |
| 96 | return (0, 0); |
| 97 | |
| 98 | var lineStart = 0; |
| 99 | var line = 1; |
| 100 | for (var i = 0; i < text.Length && line < lineOneBased; i++) |
| 101 | { |
| 102 | if (text[i] == '\n') |
| 103 | { |
| 104 | line++; |
| 105 | lineStart = i + 1; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | if (line != lineOneBased) |
| 110 | return (0, 0); |
| 111 | |
| 112 | var lineEnd = text.IndexOf('\n', lineStart); |
| 113 | if (lineEnd < 0) |
| 114 | lineEnd = text.Length; |
| 115 | else |
| 116 | lineEnd++; |
| 117 | |
| 118 | return (lineStart, Math.Max(1, lineEnd - lineStart)); |
| 119 | } |
| 120 | } |
| 121 | |