Skip to content

fix: completion generation inside $() #1403

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,11 @@ Also, please bear the following coding guidelines in mind:
code is accepted into the distribution, a lot of people will try it
out, so try to do a thorough job of eradicating all the bugs before
you send it to us. If at all practical, **add test cases** to our
test suite (in the test/ dir) that verify that the code does what it
test suite (in the `test/` dir) that verify that the code does what it
is intended to do, fixes issues it intends to fix, etc.

- In addition to running the test suite, there are a few scripts in the test/
dir that catch some common issues, see and use for example runLint.
- In addition to running the test suite, there are a few scripts in the `test/`
dir that catch some common issues, see and use for example `runLint`.

- Make sure you have Python 3.7 or later installed. This is required for
running the development tooling, linters etc. Rest of the development
Expand Down
21 changes: 21 additions & 0 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,27 @@ _comp_initialize()
local redir='@(?(+([0-9])|{[a-zA-Z_]*([a-zA-Z_0-9])})@(>?([>|&])|<?([>&])|<<?([-<]))|&>?(>))'
_comp_get_words -n "$exclude<>&" cur prev words cword

# If the current word is a command substitution $(, reset the completion context to be the
# command line *inside* the substitution.
if [[ $cur == '$('* ]]; then
local inner_line=${cur#\$\(}

# Replace the completion variables with ones that reflect the inner context.
COMP_LINE=$inner_line
read -ra COMP_WORDS <<<"$inner_line"
COMP_CWORD=$((${#COMP_WORDS[@]} - 1))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can become negative if there isn't anything inside the $():

echo $(<TAB>bash: COMP_WORDS: bad array subscript

cur=${COMP_WORDS[COMP_CWORD]}
# Also update words and cword used by the rest of _comp_initialize
words=("${COMP_WORDS[@]}")
cword=$COMP_CWORD
# Handle case where there's only one word in the substitution
if ((COMP_CWORD > 0)); then
prev=${COMP_WORDS[COMP_CWORD - 1]}
else
prev=""
fi
fi

# Complete variable names.
_comp_compgen_variables && return 1

Expand Down
45 changes: 45 additions & 0 deletions test/t/unit/test_unit_command_substitution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest

from conftest import TestUnitBase


@pytest.mark.bashcomp(
cmd=None,
ignore_env=r"^[+-](COMP(_(WORDS|CWORD|LINE|POINT)|REPLY)|"
r"cur|prev|cword|words)=",
)
class TestUnitCommandSubstitution(TestUnitBase):
def test_command_substitution_completion(self, bash):
"""Test that command substitution completion works correctly"""
# Test basic command substitution: $(echo
output = self._test_unit(
"_comp_initialize %s; echo $COMP_LINE,$COMP_CWORD,$cur,$prev",
bash,
"(echo '$(echo')",
1,
"echo '$(echo'",
12,
)
assert output == "echo,0,echo,"

# Test command substitution with arguments: $(ls -l
output = self._test_unit(
"_comp_initialize %s; echo $COMP_LINE,$COMP_CWORD,$cur,$prev",
bash,
"(echo '$(ls -l')",
1,
"echo '$(ls -l'",
13,
)
assert output == "ls -l,1,-l,ls"

# Test that normal completion is not affected
output = self._test_unit(
"_comp_initialize %s; echo $COMP_LINE,$COMP_CWORD,$cur,$prev",
bash,
"(echo hello)",
1,
"echo hello",
10,
)
assert output == "echo hello,1,hello,echo"