Skip to content

fix(_comp_command_offset): complete command name when called for it - #1752

Open
ybai08 wants to merge 1 commit into
scop:mainfrom
ybai08:fix/command-offset-cword0
Open

ybai08 wants to merge 1 commit into
scop:mainfrom
ybai08:fix/command-offset-cword0

Conversation

@ybai08

@ybai08 ybai08 commented Oct 18, 2026

Copy link
Copy Markdown

Bash 4.3..5.3 may call the completion function of a meta-command for the command name itself, with COMP_CWORD=0 (as found in #1383, where it was identified as a Bash bug). For A= ./d[TAB]o, the function for do (_comp_command) is called with COMP_LINE=./do, COMP_POINT=3, COMP_WORDS=(./do). _comp_command passes offset 1 to _comp_command_offset, which points past the current word:

This clamps the offset to the current word, so the word is completed as a command name through the existing COMP_CWORD == 0 branch (_comp_compgen_commands). That matches what Bash does with no bash-completion loaded: in both cases ./dofile (an executable) is completed.

Callers other than _comp_command/_comp_root_command go through _comp_initialize, which already returns early for cword <= 0, so they do not reach this path.

Closes #1383
Closes #768

Testing

New test_cword0 cases in test/t/unit/test_unit_command_offset.py emulate the Bash call (COMP_CWORD=0, COMP_WORDS=(./com)) for the two cursor positions above and expect ./completer from the _command_offset fixture dir.

Run on macOS with Bash 5.3.0 built from the GNU release tarball (BASH_COMPLETION_TEST_BASH):

  • Without the bash_completion change, both new cases fail: Unexpected output: [bash: COMP_POINT: substring expression < 0 and Unexpected output: [bash: COMP_WORDS[0]: unbound variable. With it: t/unit/test_unit_command_offset.py 17 passed.
  • Interactive check with pexpect (Bash 5.3, --norc, only bash_completion sourced): A= ./d[TAB]o, A= ./do[TAB] ;, var= ./do[TAB] ; and A= ./do; x with the cursor after ./do now complete ./dofile the same way as Bash without bash-completion; A= ./do[TAB] and nice ./do[TAB] are unchanged.
  • t/unit and 691 of the 730 t/test_*.py files (ssh/scp/rsync, network and database client tests excluded) with -n 8: the same pre-existing macOS failures as on main (test_aclocal, test_tar::test_25, test_vipw, intermittently test_ps/test_cpio, and environment-diff errors from bash: child setpgid ...: Operation not permitted), nothing new.
  • shellcheck -f gcc bash_completion (0.11.0) and shfmt -d bash_completion (3.14.1) are clean; ruff check / ruff format --check (0.15.20) clean on the test file.

Not run: Bash versions other than 5.3 (the CI matrix covers those), test/runLint (needs GNU grep -P), the excluded test files, and prek/mypy.

Side note, unrelated to this change: test_cmd_quoted in the same file fails when run on its own on main (pytest t/unit/test_unit_command_offset.py::TestUnitCommandOffset::test_cmd_quoted), because it relies on test_2[cmd2] having replaced the cmd2 compspec first in the shared class-scoped shell. With -n it can fail depending on how tests are distributed across workers.

Bash 4.3..5.3 may call the completion function of a meta-command for
the command name itself, with COMP_CWORD=0.  For example, `A= ./d[TAB]o`
calls the one for `do` (`_comp_command`) with COMP_LINE=./do and
COMP_POINT=3.  The offset of 1 then points past the current word:
COMP_POINT becomes negative ("COMP_POINT: substring expression < 0"),
and for `A= ./do[TAB] ;` COMP_WORDS becomes empty, so nothing is
completed (or "COMP_WORDS[0]: unbound variable" under nounset).

Clamp the offset to the current word so that it is completed as a
command name, as Bash does without bash-completion loaded.

Closes scop#1383
Closes scop#768
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

COMP_POINT: substring expression < 0 when completing A= ./do COMP_WORDS: bad array subscript

1 participant