core/tools: show 1-based line range on read_file results

This commit is contained in:
2026-08-11 15:33:12 +08:00
parent 3fbf2dc388
commit 25e6ec3d44
7 changed files with 80 additions and 38 deletions
+4 -4
View File
@@ -19,7 +19,7 @@ def test_edit_lineno_replace_middle(workspace: object) -> None:
_ = _read_lineno("a.txt")
out = call_sync(edit_lineno, "a.txt", 2, 3, "TWO\nTHREE\n")
assert "replaced lines 2-3" in out
assert call_sync(read_file, "a.txt") == "one\nTWO\nTHREE\nfour\n"
assert call_sync(read_file, "a.txt") == "L1-4\none\nTWO\nTHREE\nfour\n"
def test_edit_lineno_requires_all_lines_read(workspace: object) -> None:
@@ -49,7 +49,7 @@ def test_edit_lineno_append_requires_last_line_read(workspace: object) -> None:
_ = call_sync(read_file, "ap.txt", offset=2, limit=1, with_lineno=True)
out = call_sync(edit_lineno, "ap.txt", 4, 4, "d\n")
assert "appended" in out
assert call_sync(read_file, "ap.txt") == "a\nb\nc\nd\n"
assert call_sync(read_file, "ap.txt") == "L1-4\na\nb\nc\nd\n"
def test_edit_lineno_delete_range(workspace: object) -> None:
@@ -58,7 +58,7 @@ def test_edit_lineno_delete_range(workspace: object) -> None:
_ = _read_lineno("b.txt")
out = call_sync(edit_lineno, "b.txt", 2, 2, "")
assert "replaced" in out
assert call_sync(read_file, "b.txt") == "a\nc\n"
assert call_sync(read_file, "b.txt") == "L1-2\na\nc\n"
def test_edit_lineno_append(workspace: object) -> None:
@@ -67,7 +67,7 @@ def test_edit_lineno_append(workspace: object) -> None:
_ = _read_lineno("c.txt")
out = call_sync(edit_lineno, "c.txt", 2, 2, "more\n")
assert "appended" in out
assert call_sync(read_file, "c.txt") == "only\nmore\n"
assert call_sync(read_file, "c.txt") == "L1-2\nonly\nmore\n"
def test_edit_lineno_invalid(workspace: object) -> None:
+11 -8
View File
@@ -19,19 +19,21 @@ def _extract_token(out: str) -> str | None:
def test_write_read_listdir_edit(workspace: object) -> None:
del workspace
assert "wrote" in call_sync(write_file, "notes/a.txt", "hello world")
assert call_sync(read_file, "notes/a.txt") == "hello world"
assert call_sync(read_file, "notes/a.txt") == "L1-1\nhello world"
listing = call_sync(listdir, "notes")
assert "a.txt" in listing
assert "file" in listing
result = call_sync(edit_replace, "notes/a.txt", "world", "there")
assert "replaced" in result
assert call_sync(read_file, "notes/a.txt") == "hello there"
assert call_sync(read_file, "notes/a.txt") == "L1-1\nhello there"
def test_read_file_max_chars_embeds_token(workspace: Path) -> None:
(workspace / "big.txt").write_text("a" * 1000, encoding="utf-8")
out = call_sync(read_file, "big.txt", max_chars=200)
content, _, marker = out.partition("\n...[")
header, _, rest = out.partition("\n")
assert header.startswith("L1-") # 1-based range of the raw slice read
content, _, marker = rest.partition("\n...[")
assert content == "a" * len(content)
assert len(content) < 200
assert "TRUNCATE_TOKEN:" in marker
@@ -46,7 +48,7 @@ def test_read_file_max_chars_embeds_token(workspace: Path) -> None:
def test_read_file_max_chars_small_file_untouched(workspace: Path) -> None:
(workspace / "small.txt").write_text("tiny", encoding="utf-8")
out = call_sync(read_file, "small.txt", max_chars=200)
assert out == "tiny"
assert out == "L1-1\ntiny"
assert "TRUNCATE_TOKEN:" not in out
@@ -54,7 +56,7 @@ def test_edit_replace_first_only(workspace: object) -> None:
del workspace
_ = call_sync(write_file, "t.txt", "aa aa")
result = call_sync(edit_replace, "t.txt", "aa", "bb")
assert call_sync(read_file, "t.txt") == "bb aa"
assert call_sync(read_file, "t.txt") == "L1-1\nbb aa"
assert "1 of 2" in result or "1 of 2 matches" in result
assert "remain" in result
@@ -63,7 +65,7 @@ def test_edit_replace_max_replaces(workspace: object) -> None:
del workspace
_ = call_sync(write_file, "t.txt", "aa aa aa")
result = call_sync(edit_replace, "t.txt", "aa", "bb", max_replaces=2)
assert call_sync(read_file, "t.txt") == "bb bb aa"
assert call_sync(read_file, "t.txt") == "L1-1\nbb bb aa"
assert "2 of 3" in result
assert "1 remain" in result
@@ -72,7 +74,7 @@ def test_edit_replace_all_matches(workspace: object) -> None:
del workspace
_ = call_sync(write_file, "t.txt", "aa aa")
result = call_sync(edit_replace, "t.txt", "aa", "bb", max_replaces=10)
assert call_sync(read_file, "t.txt") == "bb bb"
assert call_sync(read_file, "t.txt") == "L1-1\nbb bb"
assert "all 2 matches" in result
@@ -91,7 +93,8 @@ def test_edit_missing_old_string(workspace: object) -> None:
def test_read_offset_limit(workspace: object) -> None:
del workspace
_ = call_sync(write_file, "lines.txt", "a\nb\nc\nd\n")
assert call_sync(read_file, "lines.txt", offset=1, limit=2) == "b\nc\n"
# offset is 0-based: offset=1 skips line 1, so the read starts at 1-based line 2.
assert call_sync(read_file, "lines.txt", offset=1, limit=2) == "L2-3\nb\nc\n"
def test_read_with_lineno(workspace: object) -> None:
+6 -6
View File
@@ -11,7 +11,7 @@ def test_copy_file(workspace: object) -> None:
_ = call_sync(write_file, "a.txt", "hello")
out = call_sync(copy_path, "a.txt", "b.txt")
assert "copied" in out
assert call_sync(read_file, "b.txt") == "hello"
assert call_sync(read_file, "b.txt") == "L1-1\nhello"
assert (workspace / "a.txt").is_file()
@@ -21,8 +21,8 @@ def test_copy_directory(workspace: object) -> None:
_ = call_sync(write_file, "src/nested/y.txt", "y")
out = call_sync(copy_path, "src", "dst")
assert "directory" in out
assert call_sync(read_file, "dst/x.txt") == "x"
assert call_sync(read_file, "dst/nested/y.txt") == "y"
assert call_sync(read_file, "dst/x.txt") == "L1-1\nx"
assert call_sync(read_file, "dst/nested/y.txt") == "L1-1\ny"
def test_copy_overwrite(workspace: object) -> None:
@@ -32,7 +32,7 @@ def test_copy_overwrite(workspace: object) -> None:
assert "exists" in call_sync(copy_path, "b.txt", "a.txt")
out = call_sync(copy_path, "b.txt", "a.txt", overwrite=True)
assert "copied" in out
assert call_sync(read_file, "a.txt") == "new"
assert call_sync(read_file, "a.txt") == "L1-1\nnew"
def test_move_file(workspace: object) -> None:
@@ -41,7 +41,7 @@ def test_move_file(workspace: object) -> None:
out = call_sync(move_path, "old.txt", "renamed.txt")
assert "moved" in out
assert not (workspace / "old.txt").exists()
assert call_sync(read_file, "renamed.txt") == "data"
assert call_sync(read_file, "renamed.txt") == "L1-1\ndata"
def test_move_directory(workspace: object) -> None:
@@ -50,7 +50,7 @@ def test_move_directory(workspace: object) -> None:
out = call_sync(move_path, "d", "e")
assert "moved" in out
assert not (workspace / "d").exists()
assert call_sync(read_file, "e/a.txt") == "a"
assert call_sync(read_file, "e/a.txt") == "L1-1\na"
def test_delete_file(workspace: object) -> None:
+3 -1
View File
@@ -29,7 +29,9 @@ async def test_get_truncated_file_chains_without_gap(workspace: Path) -> None:
total_read = 0
for _ in range(20):
out = await call_async(get_truncated, token, max_chars=200)
content, _, _ = out.partition("\n...[")
body, _, _ = out.partition("\n...[")
header, _, content = body.partition("\n")
assert header.startswith("L") and "-" in header
assert content == "a" * len(content)
total_read += len(content)
next_token = _extract_token(out)
+2 -2
View File
@@ -33,10 +33,10 @@ def test_new_temporary_workspace_allowlist(workspace: object) -> None:
target = temp / "scratch.txt"
_ = target.write_text("hello-temp", encoding="utf-8")
assert resolve_path(str(target)) == target.resolve()
assert call_sync(read_file, str(target)) == "hello-temp"
assert call_sync(read_file, str(target)) == "L1-1\nhello-temp"
_ = call_sync(write_file, "project.txt", "proj")
assert call_sync(read_file, "project.txt") == "proj"
assert call_sync(read_file, "project.txt") == "L1-1\nproj"
# Sibling under system temp that we did not allowlist still fails.
outside = temp.parent / "not-ours-should-fail"