Forge
csharp4405de34
1#nullable enable
2
3using CascadeIDE.Features.HybridIndex.Application;
4
5namespace CascadeIDE.Services.Intercom;
6
7/// <summary>Infer workspace-relative <c>F:</c> from <c>M:</c> via symbol sidecar (ADR 0128, 0135).</summary>
8public static class IntercomMemberFileInference
9{
10 private const int MaxAmbiguousPathsListed = 5;
11
12 public static bool TryResolveRelativeFile(
13 string? explicitRelativeFile,
14 string? memberKey,
15 string? activeFilePath,
16 string? workspaceRoot,
17 string? solutionPath,
18 string? indexDirectoryRelative,
19 out string relativeFile,
20 out string error)
21 {
22 relativeFile = "";
23 error = "";
24
25 var explicitPath = normalizeRelativePath(explicitRelativeFile);
26 if (!string.IsNullOrWhiteSpace(explicitPath))
27 {
28 relativeFile = explicitPath;
29 return true;
30 }
31
32 if (string.IsNullOrWhiteSpace(memberKey))
33 {
34 var activeOnly = normalizeRelativePath(
35 AttachmentAnchorPaths.ToWorkspaceRelative(activeFilePath, workspaceRoot) ?? activeFilePath);
36 if (string.IsNullOrWhiteSpace(activeOnly))
37 {
38 error = "Не задан файл в ссылке и нет активного файла в редакторе.";
39 return false;
40 }
41
42 relativeFile = activeOnly;
43 return true;
44 }
45
46 var indexDir = HybridIndexIndexDirectoryRelative.ResolveOrDefault(indexDirectoryRelative);
47 var activeRel = normalizeRelativePath(
48 AttachmentAnchorPaths.ToWorkspaceRelative(activeFilePath, workspaceRoot) ?? activeFilePath);
49
50 if (string.IsNullOrWhiteSpace(workspaceRoot))
51 {
52 if (!string.IsNullOrWhiteSpace(activeRel))
53 {
54 relativeFile = activeRel;
55 return true;
56 }
57
58 error = "Нет workspace для поиска члена по solution.";
59 return false;
60 }
61
62 if (!string.IsNullOrWhiteSpace(activeRel)
63 && memberExistsInFile(workspaceRoot, solutionPath, indexDir, activeRel, memberKey))
64 {
65 relativeFile = activeRel;
66 return true;
67 }
68
69 var cache = IntercomAttachResolveCacheContext.From(workspaceRoot, solutionPath, null, indexDir);
70 if (!IntercomSymbolLineIndex.TryFindRelativePathsForMember(cache, memberKey, out var paths))
71 paths = [];
72
73 if (paths.Count == 1)
74 {
75 relativeFile = paths[0];
76 return true;
77 }
78
79 if (paths.Count > 1)
80 {
81 if (!string.IsNullOrWhiteSpace(activeRel)
82 && paths.Any(p => string.Equals(p, activeRel, StringComparison.OrdinalIgnoreCase)))
83 {
84 relativeFile = activeRel;
85 return true;
86 }
87
88 var listed = string.Join(", ", paths.Take(MaxAmbiguousPathsListed));
89 var more = paths.Count > MaxAmbiguousPathsListed ? $" (+{paths.Count - MaxAmbiguousPathsListed})" : "";
90 error =
91 $"Член «{memberKey}» найден в нескольких файлах: {listed}{more}. Укажи F: или открой нужный файл.";
92 return false;
93 }
94
95 if (!string.IsNullOrWhiteSpace(activeRel))
96 {
97 relativeFile = activeRel;
98 return true;
99 }
100
101 error =
102 $"Член «{memberKey}» не найден в symbol index. Открой файл с членом, укажи [F:… M:…] или дождись прогрева индекса.";
103 return false;
104 }
105
106 private static bool memberExistsInFile(
107 string workspaceRoot,
108 string? solutionPath,
109 string indexDir,
110 string relativePath,
111 string memberKey)
112 {
113 var cache = IntercomAttachResolveCacheContext.From(workspaceRoot, solutionPath, relativePath, indexDir);
114 if (!AttachmentAnchorPaths.TryResolveAbsolute(relativePath, workspaceRoot, out var absolute, out _)
115 || !File.Exists(absolute))
116 {
117 return false;
118 }
119
120 return IntercomSymbolLineIndex.TryResolveMemberLines(cache, absolute, memberKey, out _, out _);
121 }
122
123 private static string normalizeRelativePath(string? path) =>
124 string.IsNullOrWhiteSpace(path) ? "" : path.Trim().Replace('\\', '/');
125}
126
View only · write via MCP/CIDE