diff --git a/tests/test_completion/test_completion.py b/tests/test_completion/test_completion.py index e9be4e25d1..3d4ac83217 100644 --- a/tests/test_completion/test_completion.py +++ b/tests/test_completion/test_completion.py @@ -119,9 +119,10 @@ def test_completion_source_zsh(): "_TUTORIAL001_PY310.PY_COMPLETE": "source_zsh", }, ) - assert ( - "compdef _tutorial001_py310py_completion tutorial001_py310.py" in result.stdout - ) + assert "compdef _tutorial001_py310py tutorial001_py310.py" in result.stdout + # #1864: the function name must match the autoloaded filename (_), so + # it must not carry the old `_completion` suffix. + assert "_tutorial001_py310py_completion" not in result.stdout def test_completion_source_fish(): diff --git a/tests/test_completion/test_completion_install.py b/tests/test_completion/test_completion_install.py index f6194695fe..4f9fb4aa21 100644 --- a/tests/test_completion/test_completion_install.py +++ b/tests/test_completion/test_completion_install.py @@ -104,10 +104,10 @@ def test_completion_install_zsh(): assert install_source_path.is_file() install_content = install_source_path.read_text() install_source_path.unlink() - assert ( - "compdef _tutorial001_py310py_completion tutorial001_py310.py" - in install_content - ) + assert "compdef _tutorial001_py310py tutorial001_py310.py" in install_content + # #1864: the function name must match the autoloaded filename (_), so + # it must not carry the old `_completion` suffix. + assert "_tutorial001_py310py_completion" not in install_content @requires_completion_permission diff --git a/tests/test_completion/test_completion_show.py b/tests/test_completion/test_completion_show.py index 18f172e691..3c4b4ded03 100644 --- a/tests/test_completion/test_completion_show.py +++ b/tests/test_completion/test_completion_show.py @@ -68,9 +68,10 @@ def test_completion_source_zsh(): "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) - assert ( - "compdef _tutorial001_py310py_completion tutorial001_py310.py" in result.stdout - ) + assert "compdef _tutorial001_py310py tutorial001_py310.py" in result.stdout + # #1864: the function name must match the autoloaded filename (_), so + # it must not carry the old `_completion` suffix. + assert "_tutorial001_py310py_completion" not in result.stdout def test_completion_source_fish(): diff --git a/typer/_completion_classes.py b/typer/_completion_classes.py index cfae02c9bf..802f698211 100644 --- a/typer/_completion_classes.py +++ b/typer/_completion_classes.py @@ -102,6 +102,10 @@ class ZshComplete(ShellComplete): def source_vars(self) -> dict[str, Any]: return { "complete_func": self.func_name, + # zsh autoloads a completion from a file named after the command + # (e.g. `_prog`), and the function it defines must match that + # filename, so drop the `_completion` suffix here. See #1864. + "zsh_func": self.func_name.removesuffix("_completion"), "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } diff --git a/typer/_completion_shared.py b/typer/_completion_shared.py index 8d2c19715c..063aa7516c 100644 --- a/typer/_completion_shared.py +++ b/typer/_completion_shared.py @@ -33,11 +33,11 @@ class Shells(str, Enum): COMPLETION_SCRIPT_ZSH = """ #compdef %(prog_name)s -%(complete_func)s() { +%(zsh_func)s() { eval $(env _TYPER_COMPLETE_ARGS="${words[1,$CURRENT]}" %(autocomplete_var)s=complete_zsh %(prog_name)s) } -compdef %(complete_func)s %(prog_name)s +compdef %(zsh_func)s %(prog_name)s """ COMPLETION_SCRIPT_FISH = 'complete --command %(prog_name)s --no-files --arguments "(env %(autocomplete_var)s=complete_fish _TYPER_COMPLETE_FISH_ACTION=get-args _TYPER_COMPLETE_ARGS=(commandline -cp) %(prog_name)s)" --condition "env %(autocomplete_var)s=complete_fish _TYPER_COMPLETE_FISH_ACTION=is-args _TYPER_COMPLETE_ARGS=(commandline -cp) %(prog_name)s"' @@ -86,6 +86,11 @@ def get_completion_script(*, prog_name: str, complete_var: str, shell: str) -> s script % { "complete_func": f"_{cf_name}_completion", + # zsh autoloads a completion from a file in $fpath named after the + # command (e.g. `_prog`), and the function it defines must match that + # filename. Use `_{name}` (not `_{name}_completion`) so the packaged + # / autoloaded completion actually registers. See #1864. + "zsh_func": f"_{cf_name}", "prog_name": prog_name, "autocomplete_var": complete_var, }