| 1 | using CascadeIDE.ViewModels; |
| 2 | using CascadeIDE.Features.IdeMcp.Application; |
| 3 | using CascadeIDE.Features.Workspace.DataAcquisition; |
| 4 | using CascadeIDE.Models; |
| 5 | using CascadeIDE.Features.Editor.Application.Monaco; |
| 6 | using CascadeIDE.Services; |
| 7 | |
| 8 | namespace CascadeIDE.Features.IdeMcp.Application; |
| 9 | |
| 10 | internal sealed partial class MainWindowIdeMcpHost |
| 11 | { |
| 12 | |
| 13 | public void OpenFile(string path) => |
| 14 | IdeMcpEditorDocumentOrchestrator.ScheduleOpenFile( |
| 15 | UiScheduler.Default, |
| 16 | path, |
| 17 | normalizedPath => |
| 18 | { |
| 19 | _host.IsLoadingCurrentFile = true; |
| 20 | try |
| 21 | { |
| 22 | _host.Documents.OpenOrActivateDocument(normalizedPath); |
| 23 | } |
| 24 | finally |
| 25 | { |
| 26 | _host.IsLoadingCurrentFile = false; |
| 27 | } |
| 28 | }); |
| 29 | |
| 30 | public void LoadSolution(string path) => |
| 31 | IdeMcpEditorDocumentOrchestrator.ScheduleLoadSolution(UiScheduler.Default, path, LoadSolution); |
| 32 | |
| 33 | public async Task<string> LoadSolutionAndWaitAsync(string path, CancellationToken cancellationToken) |
| 34 | { |
| 35 | cancellationToken.ThrowIfCancellationRequested(); |
| 36 | await _host.LoadSolutionAsync(path).ConfigureAwait(false); |
| 37 | cancellationToken.ThrowIfCancellationRequested(); |
| 38 | return await UiScheduler.Default.InvokeAsync(() => |
| 39 | { |
| 40 | if (!string.IsNullOrWhiteSpace(_host.Workspace.SolutionLoadError)) |
| 41 | return _host.Workspace.SolutionLoadError.Trim(); |
| 42 | if (string.IsNullOrWhiteSpace(_host.Workspace.SolutionPath) || _host.Workspace.SolutionRoots.Count == 0) |
| 43 | return "Не удалось загрузить решение."; |
| 44 | return "OK"; |
| 45 | }).ConfigureAwait(false); |
| 46 | } |
| 47 | |
| 48 | public void SelectInEditor(string? filePath, int startLine, int startColumn, int endLine, int endColumn) |
| 49 | { |
| 50 | UiScheduler.Default.Post(() => |
| 51 | { |
| 52 | if (!string.IsNullOrEmpty(filePath)) |
| 53 | { |
| 54 | var normalized = CanonicalFilePath.Normalize(filePath); |
| 55 | if (!CanonicalFilePath.Equals(_host.CurrentFilePath, normalized) && File.Exists(normalized)) |
| 56 | { |
| 57 | _host.IsLoadingCurrentFile = true; |
| 58 | try |
| 59 | { |
| 60 | _host.Documents.OpenOrActivateDocument(normalized); |
| 61 | } |
| 62 | finally { _host.IsLoadingCurrentFile = false; } |
| 63 | } |
| 64 | } |
| 65 | var text = _host.EditorText ?? ""; |
| 66 | if (!IdeMcpEditorOrchestrator.TryComputeSelectionSpan( |
| 67 | text, startLine, startColumn, endLine, endColumn, out var start, out var len)) |
| 68 | return; |
| 69 | _host.EditorSelectionStart = start; |
| 70 | _host.EditorSelectionLength = len; |
| 71 | }); |
| 72 | } |
| 73 | |
| 74 | public async Task<string> GetEditorStateAsync(int? maxPreviewChars) |
| 75 | { |
| 76 | var preview = maxPreviewChars ?? 2000; |
| 77 | return await UiScheduler.Default.InvokeAsync(() => |
| 78 | IdeMcpEditorOrchestrator.SerializeEditorState( |
| 79 | _host.McpEditorStateProvider?.Invoke(preview) ?? new Services.EditorStateDto())) |
| 80 | .ConfigureAwait(false); |
| 81 | } |
| 82 | |
| 83 | public async Task<string> GetEditorContentRangeAsync(int startLine, int endLine) |
| 84 | { |
| 85 | return await UiScheduler.Default.InvokeAsync(() => |
| 86 | { |
| 87 | var content = _host.McpEditorContentRangeProvider?.Invoke(startLine, endLine); |
| 88 | return IdeMcpEditorOrchestrator.SerializeEditorContentRange( |
| 89 | _host.CurrentFilePath, startLine, endLine, content); |
| 90 | }) |
| 91 | .ConfigureAwait(false); |
| 92 | } |
| 93 | |
| 94 | public async Task<string> GetOpenDocumentTextAsync(string? filePath, int? maxChars) |
| 95 | { |
| 96 | return await UiScheduler.Default.InvokeAsync(() => |
| 97 | { |
| 98 | var tabs = _host.Documents.CollectIdeMcpOpenDocumentTabSnapshots(); |
| 99 | return IdeMcpEditorOrchestrator.BuildGetOpenDocumentTextResponse( |
| 100 | filePath, _host.CurrentFilePath, tabs, maxChars); |
| 101 | }) |
| 102 | .ConfigureAwait(false); |
| 103 | } |
| 104 | |
| 105 | public Task<string> ApplyEditAsync(string filePath, int startLine, int startColumn, int endLine, int endColumn, string newText) => |
| 106 | UiScheduler.Default.InvokeAsync(() => |
| 107 | _host.Documents.ApplyMcpEditToDocument(filePath, startLine, startColumn, endLine, endColumn, newText)); |
| 108 | |
| 109 | public Task<string> ReadWorkspaceFileAsync(string filePath, int? offset, int? limit, int? maxChars) => |
| 110 | UiScheduler.Default.InvokeAsync(() => |
| 111 | { |
| 112 | var workspace = _host.McpGetWorkspacePath(); |
| 113 | if (!WorkspaceDocumentFileIo.TryResolvePath(workspace, null, filePath, out var full, out var resolveError)) |
| 114 | return System.Text.Json.JsonSerializer.Serialize(new { error = "resolve_failed", message = resolveError }); |
| 115 | |
| 116 | if (!WorkspaceDocumentFileIo.TryReadText(full, offset, limit, maxChars, out var json, out var readError)) |
| 117 | return System.Text.Json.JsonSerializer.Serialize(new { error = "read_failed", message = readError, file_path = full }); |
| 118 | |
| 119 | return json; |
| 120 | }); |
| 121 | |
| 122 | public Task<string> SaveDocumentAsync(string? filePath, string? content) => |
| 123 | UiScheduler.Default.InvokeAsync(() => _host.Documents.SaveDocumentToDisk(filePath, content)); |
| 124 | |
| 125 | public void GoToPosition(string? filePath, int line, int column, int? endLine, int? endColumn) |
| 126 | { |
| 127 | _host.EditorNavigation.TryNavigateGoTo(filePath, line, column, endLine, endColumn, EditorNavigationSource.Mcp); |
| 128 | } |
| 129 | |
| 130 | public void RevealEditorRange(string? filePath, int startLine, int endLine, int? durationMs) |
| 131 | { |
| 132 | _host.EditorNavigation.TryNavigateReveal( |
| 133 | filePath, |
| 134 | startLine, |
| 135 | endLine, |
| 136 | durationMs, |
| 137 | EditorNavigationSource.Mcp); |
| 138 | } |
| 139 | |
| 140 | } |
| 141 | |