Skip to content

Commit 02742e0

Browse files
author
Yoshihiro Takahara
committed
refactor: rename line_start/line_end to start/end
1 parent 887cab6 commit 02742e0

File tree

8 files changed

+165
-173
lines changed

8 files changed

+165
-173
lines changed

src/mcp_text_editor/models.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@ class GetTextFileContentsRequest(BaseModel):
99
"""Request model for getting text file contents."""
1010

1111
file_path: str = Field(..., description="Path to the text file")
12-
line_start: int = Field(1, description="Starting line number (1-based)")
13-
line_end: Optional[int] = Field(None, description="Ending line number (inclusive)")
12+
start: int = Field(1, description="Starting line number (1-based)")
13+
end: Optional[int] = Field(None, description="Ending line number (inclusive)")
1414

1515

1616
class GetTextFileContentsResponse(BaseModel):
1717
"""Response model for getting text file contents."""
1818

1919
contents: str = Field(..., description="File contents")
20-
line_start: int = Field(..., description="Starting line number")
21-
line_end: int = Field(..., description="Ending line number")
20+
start: int = Field(..., description="Starting line number")
21+
end: int = Field(..., description="Ending line number")
2222
hash: str = Field(..., description="Hash of the contents")
2323

2424

2525
class EditPatch(BaseModel):
2626
"""Model for a single edit patch operation."""
2727

28-
line_start: int = Field(1, description="Starting line for edit")
29-
line_end: Optional[int] = Field(None, description="Ending line for edit")
28+
start: int = Field(1, description="Starting line for edit")
29+
end: Optional[int] = Field(None, description="Ending line for edit")
3030
contents: str = Field(..., description="New content to insert")
3131
range_hash: Optional[str] = Field(
3232
None, description="Hash of content being replaced. None for insertions"
@@ -70,8 +70,8 @@ class EditTextFileContentsRequest(BaseModel):
7070
"hash": "abc123...",
7171
"patches": [
7272
{
73-
"line_start": 1, # default: 1 (top of file)
74-
"line_end": null, # default: null (end of file)
73+
"start": 1, # default: 1 (top of file)
74+
"end": null, # default: null (end of file)
7575
"contents": "new content"
7676
}
7777
]

src/mcp_text_editor/server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class EditTextFileContentsHandler:
109109
"""Handler for editing text file contents."""
110110

111111
name = "edit_text_file_contents"
112-
description = "A line editor that supports editing text file contents by specifying line ranges and content. It handles multiple patches in a single operation with hash-based conflict detection. File paths must be absolute. IMPORTANT: (1) Before using this tool, you must first get the file's current hash and range hashes and line numbers using get_text_file_contents. (2) To avoid line number shifts affecting your patches, use get_text_file_contents to read the SAME ranges you plan to edit before making changes. different line numbers have different rangehashes.(3) Patches must be specified from bottom to top to handle line number shifts correctly, as edits to lower lines don't affect the line numbers of higher lines. (4) To append content to a file, first get the total number of lines with get_text_file_contents, then specify a patch with line_start = total_lines + 1 and line_end = total_lines. This indicates an append operation and range_hash is not required. Similarly, range_hash is not required for new file creation."
112+
description = "A line editor that supports editing text file contents by specifying line ranges and content. It handles multiple patches in a single operation with hash-based conflict detection. File paths must be absolute. IMPORTANT: (1) Before using this tool, you must first get the file's current hash and range hashes and line numbers using get_text_file_contents. (2) To avoid line number shifts affecting your patches, use get_text_file_contents to read the SAME ranges you plan to edit before making changes. different line numbers have different rangehashes.(3) Patches must be specified from bottom to top to handle line number shifts correctly, as edits to lower lines don't affect the line numbers of higher lines. (4) To append content to a file, first get the total number of lines with get_text_file_contents, then specify a patch with start = total_lines + 1 and end = total_lines. This indicates an append operation and range_hash is not required. Similarly, range_hash is not required for new file creation."
113113

114114
def __init__(self):
115115
self.editor = TextEditor()
@@ -140,20 +140,20 @@ def get_tool_description(self) -> Tool:
140140
"items": {
141141
"type": "object",
142142
"properties": {
143-
"line_start": {
143+
"start": {
144144
"type": "integer",
145145
"default": 1,
146146
"description": "Starting line number (1-based). it should be matched with the start line number when get_text_file_contents is called.",
147147
},
148-
"line_end": {
148+
"end": {
149149
"type": ["integer", "null"],
150150
"default": None,
151151
"description": "Ending line number (null for end of file). it should be matched with the end line number when get_text_file_contents is called.",
152152
},
153153
"contents": {"type": "string"},
154154
"range_hash": {
155155
"type": "string",
156-
"description": "Hash of the content being replaced from line_start to line_end (required except for new files and append operations)",
156+
"description": "Hash of the content being replaced from start to end (required except for new files and append operations)",
157157
},
158158
},
159159
"required": ["contents"],

src/mcp_text_editor/service.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,32 @@ def calculate_hash(content: str) -> str:
1616

1717
@staticmethod
1818
def read_file_contents(
19-
file_path: str, line_start: int = 1, line_end: Optional[int] = None
19+
file_path: str, start: int = 1, end: Optional[int] = None
2020
) -> Tuple[str, int, int]:
2121
"""Read file contents within specified line range."""
2222
with open(file_path, "r", encoding="utf-8") as f:
2323
lines = f.readlines()
2424

2525
# Adjust line numbers to 0-based index
26-
line_start = max(1, line_start) - 1
27-
line_end = len(lines) if line_end is None else min(line_end, len(lines))
26+
start = max(1, start) - 1
27+
end = len(lines) if end is None else min(end, len(lines))
2828

29-
selected_lines = lines[line_start:line_end]
29+
selected_lines = lines[start:end]
3030
content = "".join(selected_lines)
3131

32-
return content, line_start + 1, line_end
32+
return content, start + 1, end
3333

3434
@staticmethod
3535
def validate_patches(patches: List[EditPatch], total_lines: int) -> bool:
3636
"""Validate patches for overlaps and bounds."""
37-
# Sort patches by line_start
38-
sorted_patches = sorted(patches, key=lambda x: x.line_start)
37+
# Sort patches by start
38+
sorted_patches = sorted(patches, key=lambda x: x.start)
3939

4040
prev_end = 0
4141
for patch in sorted_patches:
42-
if patch.line_start <= prev_end:
42+
if patch.start <= prev_end:
4343
return False
44-
patch_end = patch.line_end or total_lines
44+
patch_end = patch.end or total_lines
4545
if patch_end > total_lines:
4646
return False
4747
prev_end = patch_end
@@ -84,8 +84,8 @@ def edit_file_contents(
8484
# Apply patches
8585
new_lines = lines.copy()
8686
for patch in operation.patches:
87-
start_idx = patch.line_start - 1
88-
end_idx = patch.line_end if patch.line_end else len(lines)
87+
start_idx = patch.start - 1
88+
end_idx = patch.end if patch.end else len(lines)
8989
patch_lines = patch.contents.splitlines(keepends=True)
9090
new_lines[start_idx:end_idx] = patch_lines
9191

src/mcp_text_editor/text_editor.py

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -100,37 +100,37 @@ async def read_multiple_ranges(
100100
result[file_path] = {"ranges": [], "file_hash": file_hash}
101101

102102
for range_spec in file_range.ranges:
103-
line_start = max(1, range_spec.start) - 1
103+
start = max(1, range_spec.start) - 1
104104
end_value = range_spec.end
105-
line_end = (
105+
end = (
106106
min(total_lines, end_value)
107107
if end_value is not None
108108
else total_lines
109109
)
110110

111-
if line_start >= total_lines:
111+
if start >= total_lines:
112112
empty_content = ""
113113
result[file_path]["ranges"].append(
114114
{
115115
"content": empty_content,
116-
"start_line": line_start + 1,
117-
"end_line": line_start + 1,
116+
"start": start + 1,
117+
"end": start + 1,
118118
"range_hash": self.calculate_hash(empty_content),
119119
"total_lines": total_lines,
120120
"content_size": 0,
121121
}
122122
)
123123
continue
124124

125-
selected_lines = lines[line_start:line_end]
125+
selected_lines = lines[start:end]
126126
content = "".join(selected_lines)
127127
range_hash = self.calculate_hash(content)
128128

129129
result[file_path]["ranges"].append(
130130
{
131131
"content": content,
132-
"start_line": line_start + 1,
133-
"end_line": line_end,
132+
"start": start + 1,
133+
"end": end,
134134
"range_hash": range_hash,
135135
"total_lines": total_lines,
136136
"content_size": len(content),
@@ -142,36 +142,36 @@ async def read_multiple_ranges(
142142
async def read_file_contents(
143143
self,
144144
file_path: str,
145-
line_start: int = 1,
146-
line_end: Optional[int] = None,
145+
start: int = 1,
146+
end: Optional[int] = None,
147147
encoding: str = "utf-8",
148148
) -> Tuple[str, int, int, str, int, int]:
149149
lines, file_content, total_lines = await self._read_file(
150150
file_path, encoding=encoding
151151
)
152152

153-
if line_end is not None and line_end < line_start:
153+
if end is not None and end < start:
154154
raise ValueError("End line must be greater than or equal to start line")
155155

156-
line_start = max(1, line_start) - 1
157-
line_end = total_lines if line_end is None else min(line_end, total_lines)
156+
start = max(1, start) - 1
157+
end = total_lines if end is None else min(end, total_lines)
158158

159-
if line_start >= total_lines:
159+
if start >= total_lines:
160160
empty_content = ""
161161
empty_hash = self.calculate_hash(empty_content)
162-
return empty_content, line_start, line_start, empty_hash, total_lines, 0
163-
if line_end < line_start:
162+
return empty_content, start, start, empty_hash, total_lines, 0
163+
if end < start:
164164
raise ValueError("End line must be greater than or equal to start line")
165165

166-
selected_lines = lines[line_start:line_end]
166+
selected_lines = lines[start:end]
167167
content = "".join(selected_lines)
168168
content_hash = self.calculate_hash(content)
169169
content_size = len(content.encode(encoding))
170170

171171
return (
172172
content,
173-
line_start + 1,
174-
line_end,
173+
start + 1,
174+
end,
175175
content_hash,
176176
total_lines,
177177
content_size,
@@ -191,17 +191,17 @@ async def edit_file_contents(
191191
file_path (str): Path to the file to edit
192192
expected_hash (str): Expected hash of the file before editing
193193
patches (List[EditPatch]): List of patches to apply
194-
- line_start (int): Starting line number (1-based, optional, default: 1)
195-
- line_end (Optional[int]): Ending line number (inclusive)
194+
- start (int): Starting line number (1-based, optional, default: 1)
195+
- end (Optional[int]): Ending line number (inclusive)
196196
- contents (str): New content to insert
197197
Edit file contents with hash-based conflict detection and multiple patches (supporting new file creation).
198198
199199
Args:
200200
file_path (str): Path to the file to edit (parent directories are created automatically)
201201
expected_hash (str): Expected hash of the file before editing (empty string for new files)
202202
patches (List[Dict[str, Any]]): List of patches to apply, each containing:
203-
- line_start (int): Starting line number (1-based)
204-
- line_end (Optional[int]): Ending line number (inclusive)
203+
- start (int): Starting line number (1-based)
204+
- end (Optional[int]): Ending line number (inclusive)
205205
- contents (str): New content to insert
206206
- range_hash (str): Expected hash of the content being replaced
207207
@@ -277,8 +277,8 @@ async def edit_file_contents(
277277
sorted_patches = sorted(
278278
patch_objects,
279279
key=lambda x: (
280-
-(x.line_start),
281-
-(x.line_end or x.line_start or float("inf")),
280+
-(x.start),
281+
-(x.end or x.start or float("inf")),
282282
),
283283
)
284284

@@ -287,10 +287,10 @@ async def edit_file_contents(
287287
for j in range(i + 1, len(sorted_patches)):
288288
patch1 = sorted_patches[i]
289289
patch2 = sorted_patches[j]
290-
start1 = patch1.line_start
291-
end1 = patch1.line_end or start1
292-
start2 = patch2.line_start
293-
end2 = patch2.line_end or start2
290+
start1 = patch1.start
291+
end1 = patch1.end or start1
292+
start2 = patch2.start
293+
end2 = patch2.end or start2
294294

295295
if (start1 <= end2 and end1 >= start2) or (
296296
start2 <= end1 and end2 >= start1
@@ -305,17 +305,17 @@ async def edit_file_contents(
305305
# Apply patches
306306
for patch in sorted_patches:
307307
# Get line numbers (1-based)
308-
line_start: int
309-
line_end: Optional[int]
308+
start: int
309+
end: Optional[int]
310310
if isinstance(patch, EditPatch):
311-
line_start = patch.line_start
312-
line_end = patch.line_end
311+
start = patch.start
312+
end = patch.end
313313
else:
314-
line_start = patch["line_start"] if "line_start" in patch else 1
315-
line_end = patch["line_end"] if "line_end" in patch else line_start
314+
start = patch["start"] if "start" in patch else 1
315+
end = patch["end"] if "end" in patch else start
316316

317317
# Check for invalid line range
318-
if line_end is not None and line_end < line_start:
318+
if end is not None and end < start:
319319
return {
320320
"result": "error",
321321
"reason": "End line must be greater than or equal to start line",
@@ -337,11 +337,9 @@ async def edit_file_contents(
337337
}
338338

339339
# Calculate line ranges for zero-based indexing
340-
line_start_zero = line_start - 1
341-
line_end_zero = (
342-
len(lines) - 1
343-
if line_end is None
344-
else min(line_end - 1, len(lines) - 1)
340+
start_zero = start - 1
341+
end_zero = (
342+
len(lines) - 1 if end is None else min(end - 1, len(lines) - 1)
345343
)
346344

347345
# Get expected hash for validation
@@ -356,8 +354,8 @@ async def edit_file_contents(
356354
if not os.path.exists(file_path) or not current_content:
357355
# New file or empty file - treat as insertion
358356
is_insertion = True
359-
elif line_start_zero >= len(lines):
360-
# Append mode - line_start exceeds total lines
357+
elif start_zero >= len(lines):
358+
# Append mode - start exceeds total lines
361359
is_insertion = True
362360
else:
363361
# For existing files:
@@ -371,7 +369,7 @@ async def edit_file_contents(
371369
}
372370

373371
# Hash provided - verify content
374-
target_lines = lines[line_start_zero : line_end_zero + 1]
372+
target_lines = lines[start_zero : end_zero + 1]
375373
target_content = "".join(target_lines)
376374
actual_range_hash = self.calculate_hash(target_content)
377375

@@ -381,7 +379,7 @@ async def edit_file_contents(
381379
f"\nExpected: {expected_range_hash}"
382380
f"\nActual: {actual_range_hash}"
383381
f"\nContent: {target_content!r}"
384-
f"\nRange: {line_start_zero}:{line_end_zero + 1}"
382+
f"\nRange: {start_zero}:{end_zero + 1}"
385383
)
386384

387385
# Compare hashes
@@ -396,7 +394,7 @@ async def edit_file_contents(
396394
if actual_range_hash != expected_range_hash:
397395
return {
398396
"result": "error",
399-
"reason": f"Content hash mismatch - Please use get_text_file_contents tool with lines {line_start}-{line_end} to get current content and hashes, then retry with the updated hashes. If you want to append content, set line_start to {len(lines)+1}.",
397+
"reason": f"Content hash mismatch - Please use get_text_file_contents tool with lines {start}-{end} to get current content and hashes, then retry with the updated hashes. If you want to append content, set start to {len(lines)+1}.",
400398
"file_hash": None,
401399
"content": current_content,
402400
}
@@ -414,14 +412,14 @@ async def edit_file_contents(
414412
# Apply changes - line ranges were calculated earlier
415413
if is_insertion:
416414
# Insert at the specified line
417-
lines[line_start_zero:line_start_zero] = new_lines
415+
lines[start_zero:start_zero] = new_lines
418416
else:
419417
# Replace the specified range
420-
lines[line_start_zero : line_end_zero + 1] = new_lines
418+
lines[start_zero : end_zero + 1] = new_lines
421419

422420
# Debug output - shows the operation type
423421
print(
424-
f"Applied patch: line_start={line_start} line_end={line_end} is_insertion={is_insertion} contents={patch.contents!r}"
422+
f"Applied patch: start={start} end={end} is_insertion={is_insertion} contents={patch.contents!r}"
425423
)
426424

427425
# Write the final content back to file

0 commit comments

Comments
 (0)