| 1 | using CascadeIDE.Services; |
| 2 | using CascadeIDE.ViewModels; |
| 3 | |
| 4 | namespace CascadeIDE.Features.Editor.Application.Monaco; |
| 5 | |
| 6 | /// <summary>Maps navigation-map CFG nodes to Monaco CodeLens (ADR 0163 M9).</summary> |
| 7 | public static class MonacoEditorCodeLensComposer |
| 8 | { |
| 9 | public static IReadOnlyList<CideEditorCodeLensItem> FromNavigationScene( |
| 10 | string? filePath, |
| 11 | CodeNavigationMapGraphSceneVm? scene) |
| 12 | { |
| 13 | if (string.IsNullOrWhiteSpace(filePath) || scene is null || scene.IsEmpty) |
| 14 | return []; |
| 15 | |
| 16 | var list = new List<CideEditorCodeLensItem>(scene.Nodes.Count); |
| 17 | foreach (var node in scene.Nodes) |
| 18 | { |
| 19 | if (!EditorTextCoordinateUtilities.PathsReferToSameFile(node.FullPath, filePath)) |
| 20 | continue; |
| 21 | if (node.LineStart is not int line || line < 1) |
| 22 | continue; |
| 23 | |
| 24 | var title = node.LegendIndex is int idx |
| 25 | ? $"{idx}: {node.Label}" |
| 26 | : node.Label; |
| 27 | if (string.IsNullOrWhiteSpace(title)) |
| 28 | continue; |
| 29 | |
| 30 | list.Add(new CideEditorCodeLensItem(node.Id, line, 1, title.Trim())); |
| 31 | } |
| 32 | |
| 33 | return list; |
| 34 | } |
| 35 | |
| 36 | public static CodeNavigationMapNodeNavigatePayload? TryResolveNavigation( |
| 37 | string lensId, |
| 38 | CodeNavigationMapGraphSceneVm? scene) |
| 39 | { |
| 40 | if (string.IsNullOrWhiteSpace(lensId) || scene is null) |
| 41 | return null; |
| 42 | |
| 43 | foreach (var node in scene.Nodes) |
| 44 | { |
| 45 | if (!string.Equals(node.Id, lensId, StringComparison.Ordinal)) |
| 46 | continue; |
| 47 | return new CodeNavigationMapNodeNavigatePayload( |
| 48 | node.FullPath, |
| 49 | node.LineStart, |
| 50 | node.LineEnd, |
| 51 | node.LegendLine, |
| 52 | node.Kind); |
| 53 | } |
| 54 | |
| 55 | return null; |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | public static class MonacoEditorInlayMapper |
| 60 | { |
| 61 | public static IReadOnlyList<CideEditorInlayHint> ToHints( |
| 62 | string sourceText, |
| 63 | IReadOnlyList<EditorTrailingInlayPart> parts) |
| 64 | { |
| 65 | if (parts.Count == 0) |
| 66 | return []; |
| 67 | |
| 68 | var list = new List<CideEditorInlayHint>(parts.Count); |
| 69 | foreach (var part in parts) |
| 70 | { |
| 71 | if (string.IsNullOrWhiteSpace(part.Label)) |
| 72 | continue; |
| 73 | var (line, column) = LineColumnFromOffset(sourceText, part.AnchorOffset); |
| 74 | var kind = part.Label.TrimEnd().EndsWith(':') ? "parameter" : "type"; |
| 75 | list.Add(new CideEditorInlayHint(line, column, part.Label, kind)); |
| 76 | } |
| 77 | |
| 78 | return list; |
| 79 | } |
| 80 | |
| 81 | private static (int line, int column) LineColumnFromOffset(string text, int offset) |
| 82 | { |
| 83 | if (string.IsNullOrEmpty(text) || offset < 0) |
| 84 | return (1, 1); |
| 85 | offset = Math.Min(offset, text.Length); |
| 86 | var line = 1; |
| 87 | var lineStart = 0; |
| 88 | for (var i = 0; i < offset; i++) |
| 89 | { |
| 90 | if (text[i] == '\n') |
| 91 | { |
| 92 | line++; |
| 93 | lineStart = i + 1; |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | return (line, offset - lineStart + 1); |
| 98 | } |
| 99 | } |
| 100 | |