diff --git a/src/prompt_toolkit/completion/filesystem.py b/src/prompt_toolkit/completion/filesystem.py index 5da254d8e..18bad5e9d 100644 --- a/src/prompt_toolkit/completion/filesystem.py +++ b/src/prompt_toolkit/completion/filesystem.py @@ -112,7 +112,11 @@ def __init__(self) -> None: super().__init__( only_directories=False, min_input_len=1, - get_paths=lambda: os.environ.get("PATH", "").split(os.pathsep), + get_paths=lambda: ( + [p or os.curdir for p in os.environ["PATH"].split(os.pathsep)] + if "PATH" in os.environ + else [""] + ), file_filter=lambda name: os.access(name, os.X_OK), expanduser=True, ) diff --git a/tests/test_completion.py b/tests/test_completion.py index 8b3541af0..ba04dd3e1 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -6,8 +6,11 @@ import tempfile from contextlib import contextmanager +import pytest + from prompt_toolkit.completion import ( CompleteEvent, + ExecutableCompleter, FuzzyWordCompleter, NestedCompleter, PathCompleter, @@ -467,3 +470,57 @@ def create_completer(deduplicate: bool): ) ) assert len(completions) == 5 + + +@pytest.mark.parametrize( + "path_entries, expected", + [ + ([""], ["local"]), + (["", "bin"], ["external", "local"]), + (["bin", ""], ["external", "local"]), + (["bin", "", "other"], ["external", "local"]), + (["."], ["local"]), + (["bin"], ["external"]), + ], +) +def test_executablecompleter_empty_path_entries( + tmp_path, monkeypatch, path_entries, expected +): + (tmp_path / "bin").mkdir() + (tmp_path / "other").mkdir() + for name in ("tool-local", "bin/tool-external"): + executable = tmp_path / name + executable.write_text("#!/bin/sh\nexit 0\n") + executable.chmod(0o755) + monkeypatch.chdir(tmp_path) + monkeypatch.setenv("PATH", os.pathsep.join(path_entries)) + + completions = list( + ExecutableCompleter().get_completions(Document("tool-"), CompleteEvent()) + ) + + assert [c.text for c in completions] == expected + assert [c.display_text for c in completions] == ["tool-" + e for e in expected] + assert all(c.start_position == 0 for c in completions) + + +@pytest.mark.parametrize("prefix", ["plain", "relative", "absolute", "home"]) +def test_executablecompleter_missing_path(tmp_path, monkeypatch, prefix): + executable = tmp_path / "tool-local" + executable.write_text("#!/bin/sh\nexit 0\n") + executable.chmod(0o755) + monkeypatch.chdir(tmp_path) + monkeypatch.delenv("PATH", raising=False) + + monkeypatch.setenv("HOME", str(tmp_path)) + monkeypatch.setenv("USERPROFILE", str(tmp_path)) + text = { + "plain": "tool-", + "relative": "./tool-", + "absolute": str(tmp_path / "tool-"), + "home": "~/tool-", + }[prefix] + completions = list( + ExecutableCompleter().get_completions(Document(text), CompleteEvent()) + ) + assert [c.text for c in completions] == ([] if prefix == "plain" else ["local"])