Forge
csharp4405de34
1namespace CascadeIDE.Services.Roslyn;
2
3/// <summary>MSBuildWorkspace accepts .sln/.csproj — not .slnx or folder workspace roots.</summary>
4public static class RoslynEditorWorkspacePath
5{
6 public static string? Resolve(string? workspaceSolutionPath, string filePath, string? workspaceRoot = null)
7 {
8 if (string.IsNullOrWhiteSpace(filePath))
9 return null;
10
11 if (!string.IsNullOrWhiteSpace(workspaceSolutionPath))
12 {
13 var normalized = Path.GetFullPath(workspaceSolutionPath.Trim());
14 var ext = Path.GetExtension(normalized);
15 if (ext.Equals(".sln", StringComparison.OrdinalIgnoreCase)
16 || ext.Equals(".csproj", StringComparison.OrdinalIgnoreCase)
17 || ext.Equals(".fsproj", StringComparison.OrdinalIgnoreCase))
18 return normalized;
19
20 if (ext.Equals(".slnx", StringComparison.OrdinalIgnoreCase))
21 {
22 var fromSlnx = TryResolveCsprojFromSlnx(normalized, filePath);
23 if (!string.IsNullOrWhiteSpace(fromSlnx))
24 return fromSlnx;
25 }
26 }
27
28 return TryFindNearestCsproj(filePath, workspaceRoot);
29 }
30
31 private static string? TryResolveCsprojFromSlnx(string slnxPath, string filePath)
32 {
33 var slnxDir = Path.GetDirectoryName(slnxPath);
34 if (string.IsNullOrWhiteSpace(slnxDir) || !File.Exists(slnxPath))
35 return null;
36
37 var fullFile = Path.GetFullPath(filePath);
38 string? first = null;
39 try
40 {
41 var xml = System.Xml.Linq.XDocument.Load(slnxPath);
42 foreach (var el in xml.Descendants())
43 {
44 var rel = el.Attribute("Path")?.Value?.Trim();
45 if (string.IsNullOrWhiteSpace(rel) || !rel.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
46 continue;
47
48 var candidate = Path.GetFullPath(Path.Combine(slnxDir, rel.Replace('/', Path.DirectorySeparatorChar)));
49 if (!File.Exists(candidate))
50 continue;
51
52 first ??= candidate;
53 var projDir = Path.GetDirectoryName(candidate);
54 if (!string.IsNullOrWhiteSpace(projDir)
55 && fullFile.StartsWith(projDir + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
56 return candidate;
57 }
58 }
59 catch
60 {
61 return TryFindNearestCsproj(filePath, slnxDir);
62 }
63
64 return first ?? TryFindNearestCsproj(filePath, slnxDir);
65 }
66
67 private static string? TryFindNearestCsproj(string filePath, string? searchRoot)
68 {
69 var dir = Path.GetDirectoryName(Path.GetFullPath(filePath));
70 var root = string.IsNullOrWhiteSpace(searchRoot) ? null : Path.GetFullPath(searchRoot.Trim());
71
72 while (!string.IsNullOrEmpty(dir))
73 {
74 string[] projects;
75 try
76 {
77 projects = Directory.GetFiles(dir, "*.csproj");
78 }
79 catch
80 {
81 break;
82 }
83
84 if (projects.Length == 1)
85 return projects[0];
86
87 if (projects.Length > 1)
88 {
89 var sibling = projects.FirstOrDefault(p =>
90 string.Equals(Path.GetDirectoryName(p), dir, StringComparison.OrdinalIgnoreCase));
91 if (!string.IsNullOrWhiteSpace(sibling))
92 return sibling;
93 return projects[0];
94 }
95
96 if (root is not null
97 && !dir.StartsWith(root, StringComparison.OrdinalIgnoreCase)
98 && !string.Equals(dir, root, StringComparison.OrdinalIgnoreCase))
99 break;
100
101 var parent = Directory.GetParent(dir);
102 if (parent is null)
103 break;
104 dir = parent.FullName;
105 }
106
107 return null;
108 }
109}
110
View only · write via MCP/CIDE