diff --git a/metakernel/_metakernel.py b/metakernel/_metakernel.py index 5cb8fe29..4f09e2d3 100644 --- a/metakernel/_metakernel.py +++ b/metakernel/_metakernel.py @@ -600,6 +600,13 @@ async def do_complete(self, code: str, cursor_pos: int) -> dict[str, Any]: "metadata": {}, } + # When the cursor sits after a non-identifier character (e.g. `/`, `-`) the + # id_regex matches empty string, setting cursor_start to 0. Path completions + # are already stripped of their prefix, so we only need to append them at the + # cursor position — not replace from the beginning of the line. + if not info["obj"] and info["path_matches"]: + content["cursor_start"] = content["cursor_end"] + matches = info["path_matches"] if info["magic"]: diff --git a/tests/test_metakernel.py b/tests/test_metakernel.py index 2f4f0e83..f0eb0c4b 100644 --- a/tests/test_metakernel.py +++ b/tests/test_metakernel.py @@ -156,6 +156,30 @@ def test_ls_path_complete() -> None: assert comp["matches"] == ["ipython/"], comp +@pytest.mark.skipif( + sys.platform == "win32", reason="path completion format differs on Windows" +) +def test_path_complete_trailing_slash() -> None: + """Cursor after '/' must not replace the whole line (issue #432).""" + kernel = get_kernel() + text = "%cd /" + comp = asyncio.run(kernel.do_complete(text, len(text))) + assert comp["cursor_start"] == comp["cursor_end"] == len(text), comp + assert len(comp["matches"]) > 0, "expected filesystem entries under /" + + +@pytest.mark.skipif( + sys.platform == "win32", reason="path completion format differs on Windows" +) +def test_path_complete_trailing_dash() -> None: + """Cursor after '-' in a path must not replace the whole line (issue #432).""" + kernel = get_kernel() + text = "%ls /tmp/nonexistent-" + comp = asyncio.run(kernel.do_complete(text, len(text))) + # Even with no matches, cursor_start must not be 0 when there is a path prefix + assert comp["cursor_start"] != 0 or comp["matches"] == [], comp + + def test_history() -> None: kernel = get_kernel() asyncio.run(kernel.do_execute("!ls", False))