diff --git a/src/plyngent/cli/display.py b/src/plyngent/cli/display.py index d838b73..5064837 100644 --- a/src/plyngent/cli/display.py +++ b/src/plyngent/cli/display.py @@ -44,6 +44,12 @@ _PRETTY_TOOLS = frozenset( "run_argv", "run_argv_batch", "fetch", + "edit_replace", + "edit_lineno", + "write_file", + "copy_path", + "move_path", + "delete_path", } ) @@ -384,6 +390,29 @@ def _fetch_pretty(args_json: str, result: str) -> str: return _pretty_line(f"* {method} {status} {url}", (detail, _http_status_fg(status))) +_MUTATOR_VERBS: dict[str, str] = { + "edit_replace": "Edited", + "edit_lineno": "Edited", + "write_file": "Wrote", + "copy_path": "Copied", + "move_path": "Moved", + "delete_path": "Deleted", +} + +_MUTATOR_LOWER_PREFIXES = ("wrote ", "copied ", "moved ", "deleted ") + + +def _mutator_pretty(name: str, result: str) -> str: + """One-line summary for file-mutation tools: ``* Wrote 120 characters to src/x.py``.""" + verb = _MUTATOR_VERBS[name] + if result.startswith("error:"): + return _pretty_line(f"* {verb} ", (f"({result})", "red")) + for prefix in _MUTATOR_LOWER_PREFIXES: + if result.startswith(prefix): + return _pretty_line(f"* {verb} {result[len(prefix) :]}") + return _pretty_line(f"* {verb}: {result}") + + _PRETTY_BUILDERS: dict[str, Callable[[str, str], str]] = { "read_file": _read_file_pretty, "tree": _tree_pretty, @@ -395,6 +424,12 @@ _PRETTY_BUILDERS: dict[str, Callable[[str, str], str]] = { "run_argv": _run_argv_pretty, "run_argv_batch": _run_argv_batch_pretty, "fetch": _fetch_pretty, + "edit_replace": lambda _args, result: _mutator_pretty("edit_replace", result), + "edit_lineno": lambda _args, result: _mutator_pretty("edit_lineno", result), + "write_file": lambda _args, result: _mutator_pretty("write_file", result), + "copy_path": lambda _args, result: _mutator_pretty("copy_path", result), + "move_path": lambda _args, result: _mutator_pretty("move_path", result), + "delete_path": lambda _args, result: _mutator_pretty("delete_path", result), } diff --git a/tests/test_cli/test_display.py b/tests/test_cli/test_display.py index effeccd..10686d3 100644 --- a/tests/test_cli/test_display.py +++ b/tests/test_cli/test_display.py @@ -449,3 +449,33 @@ async def test_pretty_fetch_error(capsys: pytest.CaptureFixture[str]) -> None: ) out = capsys.readouterr().out assert "* GET (error: fetch failed: boom)" in out + + +async def test_pretty_mutators(capsys: pytest.CaptureFixture[str]) -> None: + cases = [ + ("write_file", "wrote 120 characters to src/x.py", "* Wrote 120 characters to src/x.py"), + ( + "edit_replace", + "replaced 1 occurrence in src/a.py ('x' → 'y')", + "* Edited: replaced 1 occurrence in src/a.py ('x' → 'y')", + ), + ( + "edit_lineno", + "replaced lines 3-5 (3 lines; first: 'pass') with 2 lines in src/a.py", + "* Edited: replaced lines 3-5 (3 lines; first: 'pass') with 2 lines in src/a.py", + ), + ("copy_path", "copied file a.txt -> b.txt", "* Copied file a.txt -> b.txt"), + ("move_path", "moved directory src -> dst", "* Moved directory src -> dst"), + ("delete_path", "deleted file tmp/x.txt", "* Deleted file tmp/x.txt"), + ] + for name, content, expected in cases: + await render_events(_aiter([_pretty_call(name, "{}"), _result(content)])) + out = capsys.readouterr().out + assert expected in out, f"{name}: expected {expected!r} in {out!r}" + assert "[tool ok]" not in out + + +async def test_pretty_mutator_error(capsys: pytest.CaptureFixture[str]) -> None: + await render_events(_aiter([_pretty_call("copy_path", "{}"), _result("error: source does not exist: x")])) + out = capsys.readouterr().out + assert "* Copied (error: source does not exist: x)" in out