diff --git a/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs b/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs index 12ab718..d2751d9 100644 --- a/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs +++ b/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs @@ -116,7 +116,7 @@ public Task ShutdownAsync() public Task SetSelectionAsync(string path, int startLine, int startColumn, int endLine, int endColumn) => Proxy.SetSelectionAsync(path, startLine, startColumn, endLine, endColumn); public Task InsertTextAsync(string text) => Proxy.InsertTextAsync(text); - public Task ReplaceTextAsync(string oldText, string newText) => Proxy.ReplaceTextAsync(oldText, newText); + public Task ReplaceTextAsync(string oldText, string newText) => Proxy.ReplaceTextAsync(oldText, newText); public Task GoToLineAsync(int line) => Proxy.GoToLineAsync(line); public Task> FindAsync(string searchText, bool matchCase, bool wholeWord) => Proxy.FindAsync(searchText, matchCase, wholeWord); diff --git a/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs b/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs index 63f37ba..12b0b27 100644 --- a/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs +++ b/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs @@ -164,8 +164,8 @@ public async Task ReplaceTextAsync( [Description("The exact text to find (case-sensitive).")] string oldText, [Description("The replacement text. Use empty string to delete matches.")] string newText) { - var success = await _rpcClient.ReplaceTextAsync(oldText, newText); - return success ? "Text replaced" : "Text not found or no active document"; + var count = await _rpcClient.ReplaceTextAsync(oldText, newText); + return count > 0 ? $"Replaced {count} occurrence(s)" : "Text not found or no active document"; } [McpServerTool(Name = "editor_goto_line", Destructive = false, Idempotent = true)] diff --git a/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs b/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs index e7f7eb4..523ece7 100644 --- a/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs +++ b/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs @@ -26,7 +26,7 @@ public interface IVisualStudioRpc Task SetSelectionAsync(string path, int startLine, int startColumn, int endLine, int endColumn); Task InsertTextAsync(string text); - Task ReplaceTextAsync(string oldText, string newText); + Task ReplaceTextAsync(string oldText, string newText); Task GoToLineAsync(int line); Task> FindAsync(string searchText, bool matchCase, bool wholeWord); diff --git a/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs b/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs index 29a9f5d..ff7dc4d 100644 --- a/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs +++ b/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs @@ -22,7 +22,7 @@ public interface IVisualStudioService Task SetSelectionAsync(string path, int startLine, int startColumn, int endLine, int endColumn); Task InsertTextAsync(string text); - Task ReplaceTextAsync(string oldText, string newText); + Task ReplaceTextAsync(string oldText, string newText); Task GoToLineAsync(int line); Task> FindAsync(string searchText, bool matchCase = false, bool wholeWord = false); diff --git a/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs b/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs index c9bf80a..544f190 100644 --- a/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs +++ b/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs @@ -176,7 +176,7 @@ public async Task RequestShutdownAsync() public Task SetSelectionAsync(string path, int startLine, int startColumn, int endLine, int endColumn) => _vsService.SetSelectionAsync(path, startLine, startColumn, endLine, endColumn); public Task InsertTextAsync(string text) => _vsService.InsertTextAsync(text); - public Task ReplaceTextAsync(string oldText, string newText) => _vsService.ReplaceTextAsync(oldText, newText); + public Task ReplaceTextAsync(string oldText, string newText) => _vsService.ReplaceTextAsync(oldText, newText); public Task GoToLineAsync(int line) => _vsService.GoToLineAsync(line); public Task> FindAsync(string searchText, bool matchCase, bool wholeWord) => _vsService.FindAsync(searchText, matchCase, wholeWord); diff --git a/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs b/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs index f966b54..68fbb6f 100644 --- a/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs +++ b/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs @@ -371,7 +371,7 @@ public async Task InsertTextAsync(string text) return true; } - public async Task ReplaceTextAsync(string oldText, string newText) + public async Task ReplaceTextAsync(string oldText, string newText) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var dte = await GetDteAsync(); @@ -379,27 +379,32 @@ public async Task ReplaceTextAsync(string oldText, string newText) var doc = dte.ActiveDocument; if (doc == null) { - return false; + return 0; } var textDoc = doc.Object("TextDocument") as TextDocument; if (textDoc == null) { - return false; + return 0; } - var editPoint = textDoc.StartPoint.CreateEditPoint(); - var content = editPoint.GetText(textDoc.EndPoint); - var newContent = content.Replace(oldText, newText); + var count = 0; + var searchPoint = textDoc.StartPoint.CreateEditPoint(); + EditPoint matchEnd = null; - if (content != newContent) + while (searchPoint.FindPattern(oldText, (int)vsFindOptions.vsFindOptionsMatchCase, ref matchEnd)) { - editPoint.Delete(textDoc.EndPoint); - editPoint.Insert(newContent); - return true; + count++; + searchPoint = matchEnd; } - return false; + if (count > 0) + { + TextRanges tags = null; + textDoc.ReplacePattern(oldText, newText, (int)vsFindOptions.vsFindOptionsMatchCase, ref tags); + } + + return count; } public async Task GoToLineAsync(int line)