-
Notifications
You must be signed in to change notification settings - Fork 391
feat(tmux): new completion #1364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
At a glance, the test failure looks unrelated. Let me know if I need to do anything about it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general quality of this PR seems very good, but I'm busy this week. I'll look at this later.
From tmux/tmux#259 it doesn't look like the tmux maintainers were interested in having a bash completion script in the upstream repo. However, I added license headers to put these new files under either bash-completion's license or tmux's, in case they want it there in the future. I'm aware of a handful of existing tmux bash completion scripts, below. As far as I can tell, they all hard-code a decent amount of tmux's available options, commands, etc. Some are also abandoned and out of date with more recent versions of tmux. Rather than base this code off of those, I decided to implement completion using tmux's own introspection commands as much as possible. Hopefully that will reduce the ongoing maintenance work and make it stay up to date with most tmux changes automatically. This commit has a relatively minimal set of completions, see the TODO in _comp_cmd_tmux__value(). I have code for more completions in varying states of readiness, but this commit is already pretty large. I'll make follow-up PR(s) for those. I'm willing to maintain this script. (And I'm hoping that the design mentioned above will make that easier.) Existing implementations that I'm aware of: * https://github.com/Bash-it/bash-it/blob/master/completion/available/tmux.completion.bash * https://github.com/Boruch-Baum/tmux_bash_completion * https://github.com/imomaliev/tmux-bash-completion * scop#81 * https://github.com/srsudar/tmux-completion
Thanks, and no rush. (In addition to the changes in comments above, I also added a new test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall to me on a quick glance, thanks!
Left a few remarks, but I'll happily defer remainder of this review to someone else, as I don't use tmux myself (yet, anyway, I guess).
# Log a message to help with debugging. | ||
# If BASH_COMPLETION_DEBUG is set, it will be printed to stderr. | ||
# When running with `set -x`, the _comp_cmd_tmux__log call itself will be | ||
# printed. | ||
# | ||
# @param $1 Message to log | ||
_comp_cmd_tmux__log() | ||
{ | ||
if [[ ${BASH_COMPLETION_DEBUG-} ]]; then | ||
printf 'tmux bash completion: %s\n' "$1" >&2 | ||
fi | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd like to see this as a general purpose public API function rather than a tmux specific one. We'd likely want to make use of it from some other completions, too.
I seem to remember there might have been some related discussion/issue/pull request lately, but can't find it right now. Could be I'm just mistaken though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was #1356. Sorry, I disliked it because no code has used it in the codebase at that time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree adding it "just in case" without any uses would have been premature. But here, we do have a use for it now.
I'm not 100% convinced the debug logging is something we need to keep around after this is in though. So I guess I'm still inclined to either generalize this, or just remove the debug logging from here before merging. No that strong opinions though, can leave it as is if others feel it's better that way. If we do, we could look into generalizing later instead of now when/if a 2nd or 3rd use pops up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No that strong opinions though, can leave it as is if others feel it's better that way. If we do, we could look into generalizing later instead of now when/if a 2nd or 3rd use pops up.
My feeling is this. For now, we can confine this function inside completions/tmux
because the only user is the tmux
completion. When another completion file wants to use this function, we can move the function to the main bash_completion
to avoid duplicates. From the perspective of the actual use in bash-completion, one use case is enough. In that sense, now that we have one for tmux
, I'm not strongly opposed to adding this function as a general utility in bash_completion
. However, from the perspective of abstraction, I usually would like to see at least two use cases to consider the proper interface (which is not too tightly bound to a specific use case).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% convinced the debug logging is something we need to keep around after this is in though.
From an objective point of view, the logged information seems only useful for developing the completion settings, and it doesn't seem useful in the actual situation of completing the command lines. Any outputs in the middle of the programmable completion break the terminal layout, so the completion setting shouldn't output anything to the terminal.
However, this feature would be interesting to me. I have to admit that this is out of scope for this project (bash-completion), but my framework (ble.sh) may utilize this kind of information in the actual situation. If an error message is provided by the completion setting, I can technically show the message below the command line to help users understand what is wrong. (In that case, I would overwrite the _comp_log
function with a ble.sh
version to steal the information).
In that sense, I agree that maintaining the error detection and reporting within bash-completion
seems an unnecessary complication, but at the same time, I find it interesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% convinced the debug logging is something we need to keep around after this is in though.
From an objective point of view, the logged information seems only useful for developing the completion settings, and it doesn't seem useful in the actual situation of completing the command lines.
While it is useful for development, that actually was not the main reason I added it. The issue template below links to guidance to use set -x
, and I wanted to have the debug logs in any bug reports.
bash-completion/.github/ISSUE_TEMPLATE/bug_report.md
Lines 36 to 41 in 90162b0
## Debug trace | |
<!-- | |
See Troubleshooting section in README.md how to generate a debug trace, | |
copy-paste it into a separate file and attach the file here. | |
--> |
Any outputs in the middle of the programmable completion break the terminal layout, so the completion setting shouldn't output anything to the terminal.
In general, I agree. Do you think it should be silent even when $BASH_COMPLETION_DEBUG
is set though? As long as there's a way for me to see the log while developing it and when reading bug reports, I don't care too much about the specifics.
However, this feature would be interesting to me. I have to admit that this is out of scope for this project (bash-completion), but my framework (ble.sh) may utilize this kind of information in the actual situation. If an error message is provided by the completion setting, I can technically show the message below the command line to help users understand what is wrong. (In that case, I would overwrite the
_comp_log
function with able.sh
version to steal the information).
Wouldn't you want different messages for that though, if they're geared more at users understanding why there's no completion, rather than developers fixing a bug? I guess that's what log levels are for in most logging frameworks. For now I'd rather just use something to help debug this completion, but I'd be fine splitting the log messages into info/warning/whatever for users and debug for developers if you or anyone else want to make the log framework support that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue template below links to guidance to use
set -x
, and I wanted to have the debug logs in any bug reports.
I haven't counted the situation with set -x
as an actual situation of completing command lines. Debugging is a part of development. Then, questions are whether it would indeed help debugging (when the words are printed by set -x
in the previous loop) and whether we should always internally process the error reporting even when the user do not have a problem (which is 99.9999% the case. We only receive several reports with the set -x
log per year).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it should be silent even when
$BASH_COMPLETION_DEBUG
is set though?
No. BASH_COMPLETION_DEBUG
is a part of development/debugging.
Wouldn't you want different messages for that though, if they're geared more at users understanding
Yes, only some of the existing messages seem to be useful for that purpose.
but I'd be fine splitting the log messages into info/warning/whatever for users and debug for developers if you or anyone else want to make the log framework support that.
I'd support the info message only after a significant number of completions support logging in a systematic way. However, that is clearly not the scope of this PR. The log level can be introduced when it becomes necessary, so I wouldn't suggest introducing it now.
case "$option_type" in | ||
command) | ||
_comp_compgen_split -l -- \ | ||
"$(LC_ALL=C tmux list-commands -F "#{command_list_name}")" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to pass around the original tmux command invoked instead of hardcoding just tmux
from $PATH
(or as an alias or a function) -- tmux
might not be in $PATH
(or the like) in the first place, or the tmux
there might not be the one that was invoked (maybe another one with a full path to it was).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To add more context, one simple way is to use "$1"
(at the top-level function) or "${comp_args[0]}"
(in the helper functions that are called from the top-level function). A more robust way is to use _comp_dequote "${comp_args[0]}"
as _comp_compgen_help__get_help_lines
is doing:
bash-completion/bash_completion
Lines 1561 to 1569 in 90162b0
local REPLY | |
_comp_dequote "${comp_args[0]-}" || REPLY=${comp_args[0]-} | |
help_cmd=("${REPLY:-false}" "$@") | |
;; | |
esac | |
local REPLY | |
_comp_split -l REPLY "$(LC_ALL=C "${help_cmd[@]}" 2>&1)" && | |
_lines=("${REPLY[@]}") |
Note: I try to slightly change the behavior of _comp_dequote
in 3e088b7 (PR #1357), so that one does not need to prefix || REPLY=${comp_args[0]-}
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After checking the implementation, I think maybe we can just use this function _comp_compgen_help__get_help_lines
for broader purposes (with a small adjustment). Even with the current implementation, we can use _comp_compgen_help__get_help_lines
for the present purpose in the following way:
_comp_compgen_help__get_help_lines -- list-commands -F "#{command_list_name}" &&
REPLY=("${_lines[@]}") &&
_comp_compgen -- -W '"${REPLY[@]}"'
Then, we may consider renaming this function to _comp_run_cmd
and change its output variable from _lines
to REPLY
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_comp_compgen_help__get_help_lines
is private, so I shouldn't use it as is, right?
Should I wait for changes to _comp_compgen_help__get_help_lines
and/or _comp_dequote
, or use "${comp_args[0]}"
/ "$1"
for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_comp_compgen_help__get_help_lines
is private, so I shouldn't use it as is, right?
Yes, so I wrote
Then, we may consider renaming this function to
_comp_run_cmd
and change its output variable from_lines
toREPLY
.
Should I wait for changes to
_comp_compgen_help__get_help_lines
and/or_comp_dequote
, or use"${comp_args[0]}"
/"$1"
for now?
You can change _comp_compgen_help__get_help_lines
if other maintainers agree with it.
_comp_cmd_tmux__subcommand() | ||
{ | ||
local subcommand_args=("$@") | ||
local usage="$(LC_ALL=C tmux list-commands \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded tmux
consideration as above.
_comp_initialize -- "$@" || return | ||
|
||
local usage | ||
usage="$(LC_ALL=C tmux -h 2>&1)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usage="$(LC_ALL=C tmux -h 2>&1)" | |
usage="$(LC_ALL=C "$1" -h 2>&1)" |
# Before https://github.com/tmux/tmux/pull/4455 (merged 2025-04-09), `-h` | ||
# produced usage information because it was an error, so we have to trim | ||
# the error message too. | ||
usage="${usage#$'tmux: unknown option -- h\n'}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usage="${usage#$'tmux: unknown option -- h\n'}" | |
usage=${usage#$'tmux: unknown option -- h\n'} |
If one double-quotes the right-hand side of an assignment, which includes $'...'
inside a variable expansion, it would be affected by shopt -u extquote
. (Somehow extquote
of bash-5.2 doesn't work, but I think it is a 5.2 bug. You can confirm the breakage with bash <= 5.1
).
The solution is just not to quote the right-hand sides of assignments. The right-hand sides are not subject to word splitting and pathname expanaions, so one usually do not have to quote the right-hand sides.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, other occurrences of double quotations for var="$(...)"
, var="${...}"
, and case "$var" in
are also unnecessary. See https://github.com/scop/bash-completion/blob/main/doc/styleguide.md#quoting-of-words. We already have mixed conventions in the existing codebase, but we prefer less quoting for better highlighting support by text editors.
local arg | ||
for arg in "${!options[@]}"; do | ||
_comp_cmd_tmux__log "option: ${arg} ${options["$arg"]}" | ||
done | ||
for arg in "${args[@]}"; do | ||
_comp_cmd_tmux__log "arg: ${arg}" | ||
done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we enclose the entire loop with if [[ ${BASH_COMPLETION_DEBUG-} ]]; then
? We do not need to run the loop when _comp_cmd_tmux__log
is nop, which is the case in production.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/scop/bash-completion/blob/main/README.md#troubleshooting (linked from the bug report template) doesn't mention $BASH_COMPLETION_DEBUG
, so that would prevent those lines from showing up in bug reports. Do you think the README should mention BASH_COMPLETION_DEBUG?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I've written in another reply, I doubt we need these in bug reports at the cost of always running loops even when it is working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or another option is to use [[ ${BASH_COMPLETION_DEBUG-} || -o xtrace ]]
.
;; | ||
"[-"*) | ||
# One option that does take an argument. | ||
if [[ ${words[i + 1]} != *"]" ]]; then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if [[ ${words[i + 1]} != *"]" ]]; then | |
if [[ ${words[i + 1]-} != *"]" ]]; then |
# Repeat from the beginning of args. | ||
usage_args_index=-1 | ||
((args_index--)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious about what situation this is used in. Is there a subcommand whose usage is something like tmux subcommand [<word1> <word2> <word3>] ...repeated-multiword-args...
?
What are the actual tmux
subcommands whose usage contain ...
as a positional argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With tmux 3.5a:
$ tmux list-commands | grep --fixed-strings ...
display-menu (menu) [-MO] [-b border-lines] [-c target-client] [-C starting-choice] [-H selected-style] [-s style] [-S border-style] [-t target-pane][-T title] [-x position] [-y position] name key command ...
send-keys (send) [-FHKlMRX] [-c target-client] [-N repeat-count] [-t target-pane] key ...
source-file (source) [-Fnqv] [-t target-pane] path ...
I'm not too familiar with display-menu
, but I think it takes repeated name key command
triples. Completing the commands might be useful, but I'm not sure. send-keys
can send multiple keys, though completing keyboard keys probably doesn't make sense. source-file
takes multiple files, which is useful to complete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'll look at display-menu
later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refs for the display-menu
usage.
- https://www.reddit.com/r/tmux/comments/bz5b7l/comment/hi1f5xp/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
- https://github.com/tmux/tmux/blob/f0a85d04695bcdc84d6dfbf5f9d3f9757a148365/cmd-display-menu.c#L76-L90
- https://github.com/tmux/tmux/blob/f0a85d04695bcdc84d6dfbf5f9d3f9757a148365/arguments.c#L302-L304
Edit: I think the Fig databse is wrong. tmux menu
is an alias of tmux display-menu
and then
if [[ ${words[i]} =~ (\\*)\;$ ]] && | ||
((${#BASH_REMATCH[1]} % 4 == 1)); then | ||
# end of current command | ||
((subcommand_start = i + 1)) | ||
elif [[ ${words[i]} =~ (\\*)\;\'$ ]] && | ||
((${#BASH_REMATCH[1]} % 2 == 0)); then | ||
# end of current command | ||
((subcommand_start = i + 1)) | ||
elif [[ ${words[i]} =~ (\\*)\;\"$ ]] && | ||
(((${#BASH_REMATCH[1]} + 1) % 4 <= 1)); then | ||
# end of current command | ||
((subcommand_start = i + 1)) | ||
fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of trying to match several possible quoting patterns, can we use _comp_dequote "${words[i]}"
to obtain the unquoted value and match it with (\\*)\;$
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering about the descriptions in man tmux
:
As in these examples, when running tmux from the shell extra care must be taken to properly quote semicolons: 1. Semicolons that should be interpreted as a command separa‐ tor should be escaped according to the shell conventions. For sh(1) this typically means quoted (such as ‘neww ';' splitw’) or escaped (such as ‘neww \\\\; splitw’). 2. Individual semicolons or trailing semicolons that should be interpreted as arguments should be escaped twice: once according to the shell conventions and a second time for tmux; for example: $ tmux neww 'foo\\;' bar $ tmux neww foo\\\\; bar 3. Semicolons that are not individual tokens or trailing an‐ other token should only be escaped once according to shell conventions; for example: $ tmux neww 'foo-;-bar' $ tmux neww foo-\\;-bar
Are the numbers of backslashes correct in the above examples? It seems to me that they should be
neww ';' splitw
neww \; splitw
$ tmux neww 'foo \;' bar
$ tmux neww foo \\\; bar
$ tmux neww 'foo-;-bar'
$ tmux neww foo-\;-bar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of trying to match several possible quoting patterns, can we use
_comp_dequote "${words[i]}"
to obtain the unquoted value and match it with(\\*)\:$
?
I looked at that, but the thing about $(echo xxx >/dev/tty)
looked risky. Looking at it again, does that mean that it's only insecure if the user explicitly used declare -n
, not for a normal environment variable? If using _comp_dequote
in situations like this is the generally accepted practice, I'll switch to that.
bash-completion/bash_completion
Lines 212 to 233 in 90162b0
# Note: This function allows parameter expansions as safe strings, which might | |
# cause unexpected results: | |
# | |
# * This allows execution of arbitrary commands through extra expansions of | |
# array subscripts in name references. For example, | |
# | |
# declare -n v='dummy[$(echo xxx >/dev/tty)]' | |
# echo "$v" # This line executes the command 'echo xxx'. | |
# _comp_dequote '"$v"' # This line also executes it. | |
# | |
# * This may change the internal state of the variable that has side effects. | |
# For example, the state of the random number generator of RANDOM can change: | |
# | |
# RANDOM=1234 # Set seed | |
# echo "$RANDOM" # This produces 30658. | |
# RANDOM=1234 # Reset seed | |
# _comp_dequote '"$RANDOM"' # This line changes the internal state. | |
# echo "$RANDOM" # This fails to reproduce 30658. | |
# | |
# We allow these parameter expansions as a part of safe strings assuming the | |
# referential transparency of the simple parameter expansions and the sane | |
# setup of the variables by the user or other frameworks that the user loads. |
I'm wondering about the descriptions in
man tmux
:
Agreed that the escaping there doesn't look right, so I filed tmux/tmux#4480
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of trying to match several possible quoting patterns, can we use
_comp_dequote "${words[i]}"
to obtain the unquoted value and match it with(\\*)\:$
?
I just realized another difference with that approach. That would prevent something like tmux foo "$(bar) ;" <Tab>
from completing the second subcommand, right? Though I think it would be worth it to simplify the code rather than supporting cases like that which are hopefully rare.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at it again, does that mean that it's only insecure if the user explicitly used
declare -n
, not for a normal environment variable?
Yes.
If using
_comp_dequote
in situations like this is the generally accepted practice,
Many places in the bash-completion codebase still do not use _comp_dequote
, but I actually want to use it in more places to complete quoted words.
Agreed that the escaping there doesn't look right, so I filed tmux/tmux#4480
Thanks!
That would prevent something like
tmux foo "$(bar) ;" <Tab>
from completing the second subcommand, right?
Yes, but the original heuristic list of quoting patterns is incomplete too. E.g., it doesn't work for a different quoting pattern such as cmd';'
, cmd";"
, and "$(bar)"';'
. There is an unlimited number of quoting patterns. It also fails to detect ;
for cmd1='command;'
& tmux "$cmd1"
. With _comp_dequote
, we consistently give up completion for words with command substitutions.
Thanks for the reviews! I'll try to get to these comments soon. |
From tmux/tmux#259 it doesn't look like the tmux maintainers were interested in having a bash completion script in the upstream repo. However, I added license headers to put these new files under either bash-completion's license or tmux's, in case they want it there in the future.
I'm aware of a handful of existing tmux bash completion scripts, below. As far as I can tell, they all hard-code a decent amount of tmux's available options, commands, etc. Some are also abandoned and out of date with more recent versions of tmux. Rather than base this code off of those, I decided to implement completion using tmux's own introspection commands as much as possible. Hopefully that will reduce the ongoing maintenance work and make it stay up to date with most tmux changes automatically.
This commit has a relatively minimal set of completions, see the TODO in _comp_cmd_tmux__value(). I have code for more completions in varying states of readiness, but this commit is already pretty large. I'll make follow-up PR(s) for those.
I'm willing to maintain this script. (And I'm hoping that the design mentioned above will make that easier.)
Existing implementations that I'm aware of: