diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9368f7492f..45945e68bb2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/bash_completion b/bash_completion index 4e488d44564..1bb79cf7fb2 100644 --- a/bash_completion +++ b/bash_completion @@ -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)) + 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 diff --git a/test/t/unit/test_unit_command_substitution.py b/test/t/unit/test_unit_command_substitution.py new file mode 100644 index 00000000000..e481573e81f --- /dev/null +++ b/test/t/unit/test_unit_command_substitution.py @@ -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"