| 1 | #nullable enable |
| 2 | |
| 3 | using CascadeIDE.Features.Workspace; |
| 4 | using CascadeIDE.Services; |
| 5 | using CascadeIDE.Services.Intercom; |
| 6 | |
| 7 | namespace CascadeIDE.Features.WorkspaceNavigation.Application; |
| 8 | |
| 9 | /// <summary><c>[[workspace.correspondence.code_anchors]]</c> → explicit reverse entries.</summary> |
| 10 | public static class WorkspaceCorrespondenceCodeAnchorsLoader |
| 11 | { |
| 12 | public const string DefaultKind = "documents"; |
| 13 | |
| 14 | public static IReadOnlyList<WorkspaceExplicitCodeAnchor> LoadFromWorkspaceToml( |
| 15 | RepositoryWorkspaceToml? workspaceToml, |
| 16 | string workspaceRoot) |
| 17 | { |
| 18 | var entries = workspaceToml?.Workspace?.Correspondence?.CodeAnchors; |
| 19 | if (entries is not { Count: > 0 }) |
| 20 | return []; |
| 21 | |
| 22 | var root = workspaceRoot.Trim(); |
| 23 | var list = new List<WorkspaceExplicitCodeAnchor>(); |
| 24 | |
| 25 | foreach (var row in entries) |
| 26 | { |
| 27 | if (!TryToExplicit(row, out var explicitAnchor)) |
| 28 | continue; |
| 29 | |
| 30 | list.Add(explicitAnchor with { DocPath = NormalizeDoc(explicitAnchor.DocPath) }); |
| 31 | } |
| 32 | |
| 33 | return list; |
| 34 | } |
| 35 | |
| 36 | private static bool TryToExplicit(RepositoryCorrespondenceCodeAnchorToml row, out WorkspaceExplicitCodeAnchor explicitAnchor) |
| 37 | { |
| 38 | explicitAnchor = new WorkspaceExplicitCodeAnchor("", new CodeAnchor(""), DefaultKind, DocReverseAnchorResolver.ProvenanceWorkspaceToml); |
| 39 | |
| 40 | var doc = (row.Doc ?? "").Trim(); |
| 41 | if (doc.Length == 0) |
| 42 | return false; |
| 43 | |
| 44 | var kind = string.IsNullOrWhiteSpace(row.Kind) ? DefaultKind : row.Kind.Trim(); |
| 45 | |
| 46 | if (!string.IsNullOrWhiteSpace(row.Bracket)) |
| 47 | { |
| 48 | if (!BracketCodeReferenceParser.TryParse(row.Bracket, out var reference, out _) |
| 49 | || string.IsNullOrWhiteSpace(reference.File)) |
| 50 | return false; |
| 51 | |
| 52 | explicitAnchor = new WorkspaceExplicitCodeAnchor( |
| 53 | doc, |
| 54 | new CodeAnchor( |
| 55 | reference.File!.Replace('\\', '/'), |
| 56 | reference.LineStart ?? row.LineStart, |
| 57 | reference.LineEnd ?? row.LineEnd, |
| 58 | reference.MemberKey ?? row.MemberKey, |
| 59 | reference.ScopeKind is null ? null : $"{reference.ScopeKind}:{reference.ScopeIndexInParent}"), |
| 60 | kind, |
| 61 | DocReverseAnchorResolver.ProvenanceWorkspaceToml); |
| 62 | return true; |
| 63 | } |
| 64 | |
| 65 | var file = (row.File ?? "").Trim(); |
| 66 | if (file.Length == 0) |
| 67 | return false; |
| 68 | |
| 69 | explicitAnchor = new WorkspaceExplicitCodeAnchor( |
| 70 | doc, |
| 71 | new CodeAnchor( |
| 72 | file.Replace('\\', '/'), |
| 73 | row.LineStart, |
| 74 | row.LineEnd, |
| 75 | string.IsNullOrWhiteSpace(row.MemberKey) ? null : row.MemberKey.Trim()), |
| 76 | kind, |
| 77 | DocReverseAnchorResolver.ProvenanceWorkspaceToml); |
| 78 | return true; |
| 79 | } |
| 80 | |
| 81 | private static string NormalizeDoc(string doc) => |
| 82 | doc.Replace('\\', '/').Trim().TrimStart('/'); |
| 83 | } |
| 84 | |