| 1 | using System.Text; |
| 2 | using Microsoft.CodeAnalysis; |
| 3 | using Microsoft.CodeAnalysis.MSBuild; |
| 4 | |
| 5 | namespace RoslynMcp.ServiceLayer; |
| 6 | |
| 7 | /// <summary>Структура solution: список проектов (имя, путь к .csproj). Только чтение, без загрузки компиляции.</summary> |
| 8 | public static class GetSolutionStructure |
| 9 | { |
| 10 | private static async Task<Solution?> OpenSolutionOrProjectAsync( |
| 11 | MSBuildWorkspace workspace, |
| 12 | string solutionOrProjectPath, |
| 13 | CancellationToken cancellationToken) |
| 14 | { |
| 15 | return await WorkspaceOpen.OpenSolutionOrProjectAsync(workspace, solutionOrProjectPath, cancellationToken).ConfigureAwait(false); |
| 16 | } |
| 17 | |
| 18 | private static StringBuilder BuildSolutionStructureText(string solutionOrProjectPath, Solution solution, CancellationToken cancellationToken) |
| 19 | { |
| 20 | var sb = new StringBuilder(); |
| 21 | sb.AppendLine("# Solution structure"); |
| 22 | sb.AppendLineInvariant($"# Path: {solutionOrProjectPath}"); |
| 23 | sb.AppendLine("# Projects (name, path to .csproj) — use solution_or_project_path in other tools."); |
| 24 | sb.AppendLine(); |
| 25 | |
| 26 | AppendProjects(sb, solution, cancellationToken); |
| 27 | |
| 28 | sb.AppendLineInvariant($"Total: {solution.ProjectIds.Count} project(s)."); |
| 29 | return sb; |
| 30 | } |
| 31 | |
| 32 | private static void AppendProjects(StringBuilder sb, Solution solution, CancellationToken cancellationToken) |
| 33 | { |
| 34 | var index = 0; |
| 35 | foreach (var project in solution.Projects) |
| 36 | { |
| 37 | cancellationToken.ThrowIfCancellationRequested(); |
| 38 | var path = project.FilePath ?? ""; |
| 39 | sb.AppendLineInvariant($"{index}. {project.Name}"); |
| 40 | sb.AppendLineInvariant($" {path}"); |
| 41 | sb.AppendLine(); |
| 42 | index++; |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | public static async Task<string> GetStructureAsync( |
| 47 | string solutionOrProjectPath, |
| 48 | CancellationToken cancellationToken = default) |
| 49 | { |
| 50 | if (!File.Exists(solutionOrProjectPath)) |
| 51 | return $"Error: solution/project not found: {solutionOrProjectPath}"; |
| 52 | |
| 53 | Solution? solution = null; |
| 54 | try |
| 55 | { |
| 56 | var workspace = MSBuildWorkspace.Create(RoslynMcpWorkspaceProperties.MsBuild); |
| 57 | solution = await OpenSolutionOrProjectAsync(workspace, solutionOrProjectPath, cancellationToken).ConfigureAwait(false); |
| 58 | |
| 59 | if (solution is null) |
| 60 | return "Error: failed to open solution."; |
| 61 | |
| 62 | return BuildSolutionStructureText(solutionOrProjectPath, solution, cancellationToken).ToString(); |
| 63 | } |
| 64 | catch (InvalidOperationException ex) when (ex.Message.Contains("slnx") || ex.Message.Contains("Slnx")) |
| 65 | { |
| 66 | return "Error: .slnx format is not supported. Use .sln or .csproj."; |
| 67 | } |
| 68 | finally |
| 69 | { |
| 70 | solution?.Workspace.Dispose(); |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | |