Forge
csharpdeeb25a2
1using System.Text.Json;
2using CascadeIDE.Contracts;
3
4namespace CascadeIDE.Features.IdeMcp.Application;
5
6/// <summary>
7/// Application-level orchestrator helpers for IDE MCP editor actions.
8/// Keeps MCP payload shaping out of MainWindowViewModel.
9/// </summary>
10[ApplicationOrchestrator]
11public static class IdeMcpEditorOrchestrator
12{
13 /// <summary>Снимок открытой вкладки для <see cref="BuildGetOpenDocumentTextResponse"/> (без зависимости от ViewModels).</summary>
14 public readonly record struct OpenDocumentTabSnapshot(string FilePath, string Content, bool IsDirty);
15
16 /// <summary>
17 /// Разрешить путь, найти вкладку по тем же правилам, что и обозреватель, вернуть JSON для MCP <c>get_open_document_text</c>.
18 /// </summary>
19 public static string BuildGetOpenDocumentTextResponse(
20 string? filePathArgument,
21 string? currentFilePath,
22 IReadOnlyList<OpenDocumentTabSnapshot> openTabs,
23 int? maxChars)
24 {
25 var target = string.IsNullOrWhiteSpace(filePathArgument) ? currentFilePath : filePathArgument.Trim();
26 if (string.IsNullOrEmpty(target))
27 return SerializeOpenDocumentMissingPathError();
28
29 foreach (var tab in openTabs)
30 {
31 if (EditorTextCoordinateUtilities.PathsReferToSameFile(tab.FilePath, target))
32 return SerializeOpenDocumentText(tab.FilePath, tab.Content, tab.IsDirty, maxChars);
33 }
34
35 return SerializeOpenDocumentNotOpenError(target);
36 }
37
38 /// <summary>
39 /// Maps 1-based line/column range to a selection span in <paramref name="editorText"/>.
40 /// Returns false if either offset is invalid (e.g. out of range).
41 /// </summary>
42 public static bool TryComputeSelectionSpan(
43 string? editorText,
44 int startLine,
45 int startColumn,
46 int endLine,
47 int endColumn,
48 out int selectionStart,
49 out int selectionLength)
50 {
51 var text = editorText ?? "";
52 var start = EditorTextCoordinateUtilities.LineColumnToOffset(text, startLine, startColumn);
53 var end = EditorTextCoordinateUtilities.LineColumnToOffset(text, endLine, endColumn);
54 if (start < 0 || end < 0)
55 {
56 selectionStart = 0;
57 selectionLength = 0;
58 return false;
59 }
60
61 selectionStart = start;
62 selectionLength = Math.Max(0, end - start);
63 return true;
64 }
65
66 public static string SerializeEditorState(Services.EditorStateDto dto) =>
67 JsonSerializer.Serialize(dto);
68
69 public static string SerializeEditorContentRange(string? filePath, int startLine, int endLine, string? content) =>
70 JsonSerializer.Serialize(new
71 {
72 file_path = filePath,
73 start_line = startLine,
74 end_line = endLine,
75 content = content ?? ""
76 });
77
78 public static string SerializeOpenDocumentMissingPathError() =>
79 JsonSerializer.Serialize(new { error = "no_path", message = "file_path не задан и нет текущего открытого файла." });
80
81 public static string SerializeOpenDocumentNotOpenError(string filePathRequested) =>
82 JsonSerializer.Serialize(new
83 {
84 error = "not_open",
85 message = "Файл не среди открытых вкладок.",
86 file_path_requested = filePathRequested
87 });
88
89 /// <summary>Замена диапазона (1-based line/column) в <paramref name="text"/>.</summary>
90 public static bool TryReplaceTextRange(
91 string text,
92 int startLine,
93 int startColumn,
94 int endLine,
95 int endColumn,
96 string newText,
97 out string updated)
98 {
99 updated = text;
100 var start = EditorTextCoordinateUtilities.LineColumnToOffset(text, startLine, startColumn);
101 var end = EditorTextCoordinateUtilities.LineColumnToOffset(text, endLine, endColumn);
102 if (start < 0 || end < 0)
103 return false;
104
105 updated = text[..start] + (newText ?? "") + text[end..];
106 return true;
107 }
108
109 public static string SerializeOpenDocumentText(
110 string? filePath,
111 string? fullText,
112 bool isDirty,
113 int? maxChars)
114 {
115 var source = fullText ?? "";
116 var len = source.Length;
117 var truncated = false;
118 var text = source;
119 if (maxChars is > 0 && len > maxChars.Value)
120 {
121 text = source[..maxChars.Value];
122 truncated = true;
123 }
124
125 return JsonSerializer.Serialize(new
126 {
127 file_path = filePath,
128 length = len,
129 truncated,
130 is_dirty = isDirty,
131 text
132 });
133 }
134}
135
View only · write via MCP/CIDE