From 215b2d81029749abf80b4f6070c37df44c41bc1e Mon Sep 17 00:00:00 2001 From: Evan Date: Sat, 25 Nov 2023 16:40:22 +1100 Subject: [PATCH 1/4] Fix continuation chars in zsh --- .../bash_completion.d/_python-argcomplete | 14 ++++++++++---- argcomplete/shell_integration.py | 11 ++++++++++- test/test.py | 19 ++++++++++++------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/argcomplete/bash_completion.d/_python-argcomplete b/argcomplete/bash_completion.d/_python-argcomplete index 1e4c66ce..2cbc3962 100644 --- a/argcomplete/bash_completion.d/_python-argcomplete +++ b/argcomplete/bash_completion.d/_python-argcomplete @@ -139,9 +139,6 @@ _python_argcomplete_global() { __python_argcomplete_expand_tilde_by_ref executable else if [[ "$service" != "-default-" ]]; then - # TODO: this may not be sufficient - see https://zsh.sourceforge.io/Doc/Release/Completion-System.html - # May need to call _complete with avoid-completer=_python-argcomplete or something like that - _default return fi executable="${words[1]}" @@ -208,7 +205,15 @@ _python_argcomplete_global() { _ARGCOMPLETE_SHELL="zsh" \ _ARGCOMPLETE_SUPPRESS_SPACE=1 \ __python_argcomplete_run "$executable" "${(@)req_argv[1, ${ARGCOMPLETE}-1]}")) - _describe "$executable" completions + local nosort=() + local nospace=() + if is-at-least 5.8; then + nosort=(-o nosort) + fi + if [[ "${completions-}" =~ ([^\\]): && "${BASH_REMATCH[2]}" =~ [=/:] ]]; then + nospace=(-S '') + fi + _describe "$executable" completions "${nosort[@]}" "${nospace[@]}" else COMPREPLY=($(IFS="$IFS" \ COMP_LINE="$COMP_LINE" \ @@ -234,5 +239,6 @@ _python_argcomplete_global() { if [[ -z "${ZSH_VERSION-}" ]]; then complete -o default -o bashdefault -D -F _python_argcomplete_global else + autoload is-at-least compdef _python_argcomplete_global -P '*' fi diff --git a/argcomplete/shell_integration.py b/argcomplete/shell_integration.py index 53b8e182..73214bb6 100644 --- a/argcomplete/shell_integration.py +++ b/argcomplete/shell_integration.py @@ -42,7 +42,15 @@ _ARGCOMPLETE_SHELL="zsh" \ _ARGCOMPLETE_SUPPRESS_SPACE=1 \ __python_argcomplete_run ${script:-${words[1]}})) - _describe "${words[1]}" completions -o nosort + local nosort=() + local nospace=() + if is-at-least 5.8; then + nosort=(-o nosort) + fi + if [[ "${completions-}" =~ ([^\\]): && "${match[1]}" =~ [=/:] ]]; then + nospace=(-S '') + fi + _describe "${words[1]}" completions "${nosort[@]}" "${nospace[@]}" else local SUPPRESS_SPACE=0 if compopt +o nospace 2> /dev/null; then @@ -67,6 +75,7 @@ if [[ -z "${ZSH_VERSION-}" ]]; then complete %(complete_opts)s -F _python_argcomplete%(function_suffix)s %(executables)s else + autoload is-at-least compdef _python_argcomplete%(function_suffix)s %(executables)s fi """ diff --git a/test/test.py b/test/test.py index d94e55ac..2eea3e5e 100755 --- a/test/test.py +++ b/test/test.py @@ -77,6 +77,8 @@ def bash_repl(command="bash"): def zsh_repl(command="zsh"): sh = _repl_sh(command, ["--no-rcs", "-V"], non_printable_insert="%(!..)") sh.run_command("autoload compinit; compinit -u") + # Require two tabs to print all options (some tests rely on this). + sh.run_command("setopt BASH_AUTO_LIST") return sh @@ -1256,9 +1258,6 @@ def setUp(self): path = ":".join([os.path.join(BASE_DIR, "scripts"), TEST_DIR, "$PATH"]) sh.run_command("export PATH={0}".format(path)) sh.run_command("export PYTHONPATH={0}".format(BASE_DIR)) - if self.repl_provider == bash_repl: - # Disable the "python" module provided by bash-completion - sh.run_command("complete -r python python2 python3") output = sh.run_command(self.install_cmd) self.assertEqual(output, "") # Register a dummy completion with an external argcomplete script @@ -1313,16 +1312,13 @@ class TestZsh(TestBashZshBase, unittest.TestCase): "test_parse_special_characters_dollar", "test_comp_point", # FIXME "test_completion_environment", # FIXME - "test_continuation", # FIXME - "test_wordbreak_chars", # FIXME ] def repl_provider(self): return zsh_repl() -@unittest.skipIf(BASH_MAJOR_VERSION < 4, "complete -D not supported") -class TestBashGlobal(TestBash): +class TestBashZshGlobalBase(TestBashZshBase): install_cmd = 'eval "$(activate-global-python-argcomplete --dest=-)"' def test_python_completion(self): @@ -1396,6 +1392,15 @@ def test_console_script_package_wheel(self): self._test_console_script(package=True, wheel=True) +@unittest.skipIf(BASH_MAJOR_VERSION < 4, "complete -D not supported") +class TestBashGlobal(TestBash, TestBashZshGlobalBase): + pass + + +class TestZshGlobal(TestZsh, TestBashZshGlobalBase): + pass + + class Shell: def __init__(self, shell): self.child = pexpect.spawn(shell, encoding="utf-8") From 906a964161071f26340e549ca5f2f4a0dd51d0ee Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 26 Nov 2023 00:15:31 +1100 Subject: [PATCH 2/4] Fix completion in zsh special contexts --- argcomplete/bash_completion.d/_python-argcomplete | 8 ++++---- test/test.py | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/argcomplete/bash_completion.d/_python-argcomplete b/argcomplete/bash_completion.d/_python-argcomplete index 2cbc3962..cdb60a96 100644 --- a/argcomplete/bash_completion.d/_python-argcomplete +++ b/argcomplete/bash_completion.d/_python-argcomplete @@ -138,9 +138,6 @@ _python_argcomplete_global() { req_argv=( "" "${COMP_WORDS[@]:1}" ) __python_argcomplete_expand_tilde_by_ref executable else - if [[ "$service" != "-default-" ]]; then - return - fi executable="${words[1]}" req_argv=( "${words[@]:1}" ) fi @@ -240,5 +237,8 @@ if [[ -z "${ZSH_VERSION-}" ]]; then complete -o default -o bashdefault -D -F _python_argcomplete_global else autoload is-at-least - compdef _python_argcomplete_global -P '*' + # Replace only the default completer (_default). + # There are many other special contexts we don't want to override. + # https://zsh.sourceforge.io/Doc/Release/Completion-System.html + compdef _python_argcomplete_global -default- fi diff --git a/test/test.py b/test/test.py index 2eea3e5e..1be4cfb1 100755 --- a/test/test.py +++ b/test/test.py @@ -1321,6 +1321,15 @@ def repl_provider(self): class TestBashZshGlobalBase(TestBashZshBase): install_cmd = 'eval "$(activate-global-python-argcomplete --dest=-)"' + def test_redirection_completion(self): + with TempDir(prefix="test_dir_py", dir="."): + self.sh.run_command("cd " + os.getcwd()) + self.sh.run_command("echo failure > ./foo.txt") + self.sh.run_command("echo success > ./foo.\t") + with open("foo.txt") as f: + msg = f.read() + self.assertEqual(msg, "success\n") + def test_python_completion(self): self.sh.run_command("cd " + TEST_DIR) self.assertEqual(self.sh.run_command("python3 ./prog basic f\t"), "foo\r\n") From a0dc5e3621380ce1d30808035764c048a8b31d47 Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 26 Nov 2023 01:59:08 +1100 Subject: [PATCH 3/4] Remove pre-installed argcomplete on macos --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2644ff1a..cc36b5fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,9 @@ jobs: run: | [[ $(uname) == Linux ]] && sudo apt-get install --yes rpm tcsh fish zsh [[ $(uname) == Darwin ]] && brew install bash tcsh fish + # Some runners have python-argcomplete preinstalled + # as a dependency of pipx, which interferes with the tests. + [[ $(uname) == Darwin ]] && brew uninstall --ignore-dependencies python-argcomplete || true python -m pip install --quiet --upgrade codecov - run: make install - run: make lint From 8459dae05d1798866c9653f7272d24f90273396b Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 26 Nov 2023 12:33:37 +1100 Subject: [PATCH 4/4] Re-enable skipped macOS tests The linked workaround seems no longer relevant. The Homebrew documentation no longer mentions the flag. --- test/test.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/test.py b/test/test.py index 1be4cfb1..b741a585 100755 --- a/test/test.py +++ b/test/test.py @@ -1369,9 +1369,6 @@ def _test_console_script(self, package=False, wheel=False): command = "pip install {} --target .".format(test_package) if not wheel: command += " --no-binary :all:" - if sys.platform == "darwin": - # Work around https://stackoverflow.com/questions/24257803 - command += ' --install-option="--prefix="' install_output = self.sh.run_command(command) self.assertEqual(self.sh.run_command("echo $?"), "0\r\n", install_output) command = "test-module" @@ -1380,22 +1377,18 @@ def _test_console_script(self, package=False, wheel=False): command += " a\t" self.assertEqual(self.sh.run_command(command), "arg\r\n") - @unittest.skipIf(os.uname()[0] == "Darwin", "Skip test that fails on MacOS") def test_console_script_module(self): """Test completing a console_script for a module.""" self._test_console_script() - @unittest.skipIf(os.uname()[0] == "Darwin", "Skip test that fails on MacOS") def test_console_script_package(self): """Test completing a console_script for a package.""" self._test_console_script(package=True) - @unittest.skipIf(os.uname()[0] == "Darwin", "Skip test that fails on MacOS") def test_console_script_module_wheel(self): """Test completing a console_script for a module from a wheel.""" self._test_console_script(wheel=True) - @unittest.skipIf(os.uname()[0] == "Darwin", "Skip test that fails on MacOS") def test_console_script_package_wheel(self): """Test completing a console_script for a package from a wheel.""" self._test_console_script(package=True, wheel=True)