| 1 | using CascadeIDE.Services; |
| 2 | using Microsoft.CodeAnalysis; |
| 3 | |
| 4 | namespace CascadeIDE.Features.Editor.Application.Monaco; |
| 5 | |
| 6 | /// <summary>Diagnostic strips → line-first CECB decorations and EOL inlays.</summary> |
| 7 | public static class MonacoEditorDiagnosticsMapper |
| 8 | { |
| 9 | private const int MaxInlineMessageLength = 120; |
| 10 | |
| 11 | public static IReadOnlyList<CideEditorDecoration> ToDecorations(IReadOnlyList<EditorDiagnosticStrip> strips) |
| 12 | { |
| 13 | if (strips.Count == 0) |
| 14 | return Array.Empty<CideEditorDecoration>(); |
| 15 | |
| 16 | var list = new List<CideEditorDecoration>(strips.Count); |
| 17 | foreach (var group in strips.GroupBy(s => s.Line1).OrderBy(g => g.Key)) |
| 18 | { |
| 19 | var items = group.ToList(); |
| 20 | var primary = items.OrderByDescending(s => SeverityRank(s.Severity)).First(); |
| 21 | var className = primary.Severity switch |
| 22 | { |
| 23 | DiagnosticSeverity.Error => "squiggly-error", |
| 24 | DiagnosticSeverity.Warning => "squiggly-warning", |
| 25 | _ => "squiggly-info", |
| 26 | }; |
| 27 | var hover = items.Count == 1 |
| 28 | ? $"{primary.Id}: {primary.Message}" |
| 29 | : string.Join("\n", items.Select(s => $"{s.Id}: {s.Message}")); |
| 30 | |
| 31 | list.Add(new CideEditorDecoration( |
| 32 | StartOffset: 0, |
| 33 | Length: 0, |
| 34 | ClassName: className, |
| 35 | HoverMessage: hover, |
| 36 | IsWholeLine: true, |
| 37 | StartLine: group.Key, |
| 38 | StartColumn: 1, |
| 39 | EndLine: group.Key)); |
| 40 | } |
| 41 | |
| 42 | return list; |
| 43 | } |
| 44 | |
| 45 | /// <summary>Trailing diagnostic labels at EOL (VS / Error Lens style).</summary> |
| 46 | public static IReadOnlyList<CideEditorInlayHint> ToDiagnosticInlays(IReadOnlyList<EditorDiagnosticStrip> strips) |
| 47 | { |
| 48 | if (strips.Count == 0) |
| 49 | return Array.Empty<CideEditorInlayHint>(); |
| 50 | |
| 51 | var list = new List<CideEditorInlayHint>(strips.Count); |
| 52 | foreach (var group in strips.GroupBy(s => s.Line1).OrderBy(g => g.Key)) |
| 53 | { |
| 54 | var items = group.ToList(); |
| 55 | var primary = items.OrderByDescending(s => SeverityRank(s.Severity)).First(); |
| 56 | var label = FormatInlineLabel(primary, items.Count); |
| 57 | var kind = primary.Severity switch |
| 58 | { |
| 59 | DiagnosticSeverity.Error => "diagnostic-error", |
| 60 | DiagnosticSeverity.Warning => "diagnostic-warning", |
| 61 | _ => "diagnostic-info", |
| 62 | }; |
| 63 | list.Add(new CideEditorInlayHint(group.Key, 1, label, kind, AtEndOfLine: true)); |
| 64 | } |
| 65 | |
| 66 | return list; |
| 67 | } |
| 68 | |
| 69 | private static string FormatInlineLabel(EditorDiagnosticStrip primary, int countOnLine) |
| 70 | { |
| 71 | var message = primary.Message.Trim(); |
| 72 | if (message.Length > MaxInlineMessageLength) |
| 73 | message = message[..(MaxInlineMessageLength - 1)] + "…"; |
| 74 | |
| 75 | var label = $" {primary.Id}: {message}"; |
| 76 | if (countOnLine > 1) |
| 77 | label += $" (+{countOnLine - 1})"; |
| 78 | return label; |
| 79 | } |
| 80 | |
| 81 | private static int SeverityRank(DiagnosticSeverity severity) => severity switch |
| 82 | { |
| 83 | DiagnosticSeverity.Error => 3, |
| 84 | DiagnosticSeverity.Warning => 2, |
| 85 | DiagnosticSeverity.Info => 1, |
| 86 | _ => 0, |
| 87 | }; |
| 88 | } |
| 89 | |