diff --git a/completions/tmux b/completions/tmux index b3ccafbd294..c2de64e6602 100644 --- a/completions/tmux +++ b/completions/tmux @@ -162,6 +162,25 @@ _comp_cmd_tmux__options() done } +# Complete arguments to a nested command. +# +# @param $1 the arg type of the command, e.g., command or shell-command +# @param $2... args to the command, starting with the command itself, ending +# before the current word to complete +_comp_cmd_tmux__nested_arguments() +{ + local arg_type="$1" + shift + _comp_cmd_tmux__log \ + "Attempting completion for arguments to '$1' of type '$arg_type'" + + case $arg_type in + command) + _comp_cmd_tmux__subcommand "$@" + ;; + esac +} + # Complete arguments to a subcommand. # # @param $@ the subcommand followed by its args, ending before the current word @@ -239,12 +258,28 @@ _comp_cmd_tmux__subcommand() # above. _comp_cmd_tmux__value "$subcommand" "$prev_arg_type" return + elif [[ $arg_type == argument ]]; then + # New-style usage after + # https://github.com/tmux/tmux/commit/68ffe654990764ed5bdb7efb88e4d01b921fd3d5 + # (2025-04-09) ends in `argument ...` + if ((usage_args_index == ${#args[@]} - 2)) && + [[ ${args[-1]-} == *("[")...*("]") ]]; then + _comp_cmd_tmux__nested_arguments \ + "$prev_arg_type" \ + "${subcommand_args[@]:args_index-1}" + return + else + _comp_cmd_tmux__log \ + "'tmux $subcommand' has unsupported 'argument' in usage" + return + fi elif [[ $arg_type == arguments ]]; then - if [[ $prev_arg_type == command ]] && - ((usage_args_index == ${#args[@]} - 1)); then - # The usage ends in `command arguments`, so recurse to the new - # subcommand. - _comp_cmd_tmux__subcommand \ + # Old-style usage before + # https://github.com/tmux/tmux/commit/68ffe654990764ed5bdb7efb88e4d01b921fd3d5 + # (2025-04-09) ends in `arguments` + if ((usage_args_index == ${#args[@]} - 1)); then + _comp_cmd_tmux__nested_arguments \ + "$prev_arg_type" \ "${subcommand_args[@]:args_index-1}" return else diff --git a/test/t/test_tmux.py b/test/t/test_tmux.py index 73ee0a54a9b..621b0a9cce9 100644 --- a/test/t/test_tmux.py +++ b/test/t/test_tmux.py @@ -70,6 +70,15 @@ def test_option_without_value(self, completion): def test_option_multiple_without_value(self, completion): assert "new-session" in completion + # Tests for _comp_cmd_tmux__nested_arguments() + + @pytest.mark.complete( + "tmux bind-key C-a new-window -c ", + cwd="shared/default", + ) + def test_nested_arguments_tmux_subcommand(self, completion): + assert completion == ["bar bar.d/", "foo.d/"] + # Tests for _comp_cmd_tmux__subcommand() @pytest.mark.complete("tmux this-is-not-a-real-subcommand-i-hope ") @@ -93,13 +102,6 @@ def test_subcommand_no_positional_arg_completion(self, completion): def test_subcommand_repetition(self, completion): assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"] - @pytest.mark.complete( - "tmux bind-key C-a new-window -c ", - cwd="shared/default", - ) - def test_subcommand_recursion(self, completion): - assert completion == ["bar bar.d/", "foo.d/"] - @pytest.mark.complete("tmux source-file ", cwd="shared/default") def test_subcommand_positional_arg_1(self, completion): assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"]