Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions metakernel/_metakernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]:
Expand Down
24 changes: 24 additions & 0 deletions tests/test_metakernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading