Skip to content

Commit 3117a04

Browse files
Yoshihiro TakaharaYoshihiro Takahara
authored andcommitted
feat: add suggestion to use delete when patch with empty content
- Change response key from 'reason' to 'hint' for deletion suggestions - Add test case for empty content patch
1 parent 7769cda commit 3117a04

File tree

2 files changed

+85
-23
lines changed

2 files changed

+85
-23
lines changed

src/mcp_text_editor/text_editor.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -219,19 +219,10 @@ async def edit_file_contents(
219219
Args:
220220
file_path (str): Path to the file to edit
221221
expected_hash (str): Expected hash of the file before editing
222-
patches (List[EditPatch]): List of patches to apply
223-
- start (int): Starting line number (1-based, optional, default: 1)
224-
- end (Optional[int]): Ending line number (inclusive)
225-
- contents (str): New content to insert
226-
Edit file contents with hash-based conflict detection and multiple patches (supporting new file creation).
227-
228-
Args:
229-
file_path (str): Path to the file to edit (parent directories are created automatically)
230-
expected_hash (str): Expected hash of the file before editing (empty string for new files)
231222
patches (List[Dict[str, Any]]): List of patches to apply, each containing:
232223
- start (int): Starting line number (1-based)
233224
- end (Optional[int]): Ending line number (inclusive)
234-
- contents (str): New content to insert
225+
- contents (str): New content to insert (if empty string, consider using delete_text_file_contents instead)
235226
- range_hash (str): Expected hash of the content being replaced
236227
237228
Returns:
@@ -269,9 +260,14 @@ async def edit_file_contents(
269260
encoding = "utf-8"
270261
else:
271262
# Read current file content and verify hash
272-
current_content, _, _, current_hash, total_lines, _ = (
273-
await self.read_file_contents(file_path, encoding=encoding)
274-
)
263+
(
264+
current_content,
265+
_,
266+
_,
267+
current_hash,
268+
total_lines,
269+
_,
270+
) = await self.read_file_contents(file_path, encoding=encoding)
275271

276272
# Treat empty file as new file
277273
if not current_content:
@@ -396,6 +392,15 @@ async def edit_file_contents(
396392
else:
397393
contents = patch["contents"]
398394

395+
# Check if this is a deletion (empty content)
396+
if not contents.strip():
397+
return {
398+
"result": "ok",
399+
"file_hash": current_hash, # Return current hash since no changes made
400+
"hint": "For content deletion, please consider using delete_text_file_contents instead of patch with empty content",
401+
"suggestion": "delete",
402+
}
403+
399404
new_content = contents if contents.endswith("\n") else contents + "\n"
400405
new_lines = new_content.splitlines(keepends=True)
401406

@@ -471,11 +476,16 @@ async def insert_text_file_contents(
471476
}
472477

473478
try:
474-
current_content, _, _, current_hash, total_lines, _ = (
475-
await self.read_file_contents(
476-
file_path,
477-
encoding=encoding,
478-
)
479+
(
480+
current_content,
481+
_,
482+
_,
483+
current_hash,
484+
total_lines,
485+
_,
486+
) = await self.read_file_contents(
487+
file_path,
488+
encoding=encoding,
479489
)
480490

481491
if current_hash != file_hash:
@@ -563,11 +573,16 @@ async def delete_text_file_contents(
563573
self._validate_file_path(request.file_path)
564574

565575
try:
566-
current_content, _, _, current_hash, total_lines, _ = (
567-
await self.read_file_contents(
568-
request.file_path,
569-
encoding=request.encoding or "utf-8",
570-
)
576+
(
577+
current_content,
578+
_,
579+
_,
580+
current_hash,
581+
total_lines,
582+
_,
583+
) = await self.read_file_contents(
584+
request.file_path,
585+
encoding=request.encoding or "utf-8",
571586
)
572587

573588
# Check for conflicts

tests/test_patch_text_file.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,53 @@ async def test_patch_text_file_middle(tmp_path):
5353
assert updated_content == "line1\nnew line2\nnew line3\nline4\nline5\n"
5454

5555

56+
@pytest.mark.asyncio
57+
async def test_patch_text_file_empty_content(tmp_path):
58+
"""Test patching with empty content suggests using delete_text_file_contents."""
59+
# Create a test file
60+
file_path = os.path.join(tmp_path, "test.txt")
61+
editor = TextEditor()
62+
63+
# Create initial content
64+
content = "line1\nline2\nline3\nline4\nline5\n"
65+
with open(file_path, "w", encoding="utf-8") as f:
66+
f.write(content)
67+
68+
# Get file hash and range hash
69+
file_info = await editor.read_multiple_ranges(
70+
[{"file_path": str(file_path), "ranges": [{"start": 2, "end": 3}]}]
71+
)
72+
73+
# Extract file and range hashes
74+
file_content = file_info[str(file_path)]
75+
file_hash = file_content["file_hash"]
76+
range_hash = file_content["ranges"][0]["range_hash"]
77+
78+
# Patch the file with empty content
79+
patch = {
80+
"start": 2,
81+
"end": 3,
82+
"contents": "",
83+
"range_hash": range_hash,
84+
}
85+
result = await editor.edit_file_contents(
86+
str(file_path),
87+
file_hash,
88+
[patch],
89+
)
90+
91+
# Verify that the operation suggests using delete_text_file_contents
92+
assert result["result"] == "ok"
93+
assert result["file_hash"] == file_hash
94+
assert "delete_text_file_contents" in result["hint"]
95+
assert result["suggestion"] == "delete"
96+
97+
# Verify file content remains unchanged
98+
with open(file_path, "r", encoding="utf-8") as f:
99+
updated_content = f.read()
100+
assert updated_content == content
101+
102+
56103
@pytest.mark.asyncio
57104
async def test_patch_text_file_errors(tmp_path):
58105
"""Test error handling when patching a file."""

0 commit comments

Comments
 (0)