diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0a57aa9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +# AGENTS.md + +## Notes + +- `CHANGELOG.md` is generated using `git changelog`; do not edit it manually. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9b269..0cefcdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,203 +1,161 @@ Changelog ======================================== -v0.7.4 - 2026-02-19 ----------------------------------------- - -- Fix completion escaping [`dce16c3`](https://github.com/bashly-framework/completely/commit/dce16c3) -- Refactor template filter function [`834ed78`](https://github.com/bashly-framework/completely/commit/834ed78) -- Update completions installer paths [`338d567`](https://github.com/bashly-framework/completely/commit/338d567) -- Compare [`v0.7.3..v0.7.4`](https://github.com/bashly-framework/completely/compare/v0.7.3..v0.7.4) - - -v0.7.3 - 2025-09-24 ----------------------------------------- - -- Add support for reading config from stdin and writing to stdout [`764fb36`](https://github.com/bashly-framework/completely/commit/764fb36) -- Fix repeating final completion [`0029c4e`](https://github.com/bashly-framework/completely/commit/0029c4e) -- Refactor installer with new io / string builders [`cb91679`](https://github.com/bashly-framework/completely/commit/cb91679) -- Add support for `generate --install` [`c5a877e`](https://github.com/bashly-framework/completely/commit/c5a877e) -- Compare [`v0.7.2..v0.7.3`](https://github.com/bashly-framework/completely/compare/v0.7.2..v0.7.3) - - v0.7.2 - 2025-08-04 ---------------------------------------- -- Fix JSON schema [`3cc3ef6`](https://github.com/bashly-framework/completely/commit/3cc3ef6) -- Drop support for Ruby 3.0 and 3.1 [`7ff8bd8`](https://github.com/bashly-framework/completely/commit/7ff8bd8) -- Compare [`v0.7.1..v0.7.2`](https://github.com/bashly-framework/completely/compare/v0.7.1..v0.7.2) +- Fix JSON schema +- Drop support for Ruby 3.0 and 3.1 v0.7.1 - 2025-04-04 ---------------------------------------- -- Add support for modifying the `complete` command options [`690c264`](https://github.com/bashly-framework/completely/commit/690c264) -- Compare [`v0.7.0..v0.7.1`](https://github.com/bashly-framework/completely/compare/v0.7.0..v0.7.1) +- Add support for modifying the `complete` command options v0.7.0 - 2024-11-29 ---------------------------------------- -- Update instructions and template for obtaining list of git branches [`8cdfabd`](https://github.com/bashly-framework/completely/commit/8cdfabd) -- Add support for nested configuration [`742e3cd`](https://github.com/bashly-framework/completely/commit/742e3cd) -- Add `completely init --nested` and explain nested syntax in the README [`f1e17ed`](https://github.com/bashly-framework/completely/commit/f1e17ed) -- Compare [`v0.6.3..v0.7.0`](https://github.com/bashly-framework/completely/compare/v0.6.3..v0.7.0) +- Update instructions and template for obtaining list of git branches +- Add support for nested configuration +- Add `completely init --nested` and explain nested syntax in the README v0.6.3 - 2024-07-05 ---------------------------------------- -- Allow using colon, semicolon and equal sign in completions [`a9e6a6e`](https://github.com/bashly-framework/completely/commit/a9e6a6e) -- Check output with shfmt [`3f7ae7e`](https://github.com/bashly-framework/completely/commit/3f7ae7e) -- Revert wordbreak (colon) patch [`24f9d3d`](https://github.com/bashly-framework/completely/commit/24f9d3d) -- Compare [`v0.6.2..v0.6.3`](https://github.com/bashly-framework/completely/compare/v0.6.2..v0.6.3) +- Allow using colon, semicolon and equal sign in completions +- Check output with shfmt +- Revert wordbreak (colon) patch v0.6.2 - 2024-02-08 ---------------------------------------- -- Update possible completions installation directories [`2bc93a7`](https://github.com/bashly-framework/completely/commit/2bc93a7) -- Build docker images automatically [`4d20dfd`](https://github.com/bashly-framework/completely/commit/4d20dfd) -- Compare [`v0.6.1..v0.6.2`](https://github.com/bashly-framework/completely/compare/v0.6.1..v0.6.2) +- Update possible completions installation directories +- Build docker images automatically v0.6.1 - 2023-06-23 ---------------------------------------- -- Add ability to uninstall a completion script [`67b6715`](https://github.com/bashly-framework/completely/commit/67b6715) -- Compare [`v0.6.0..v0.6.1`](https://github.com/bashly-framework/completely/compare/v0.6.0..v0.6.1) +- Add ability to uninstall a completion script v0.6.0 - 2023-06-23 ---------------------------------------- -- Refactor install command and add an Installer model [`b1341fa`](https://github.com/bashly-framework/completely/commit/b1341fa) -- Drop support for Ruby 2.7 [`151eff1`](https://github.com/bashly-framework/completely/commit/151eff1) -- Change exception classes [`3ce16ac`](https://github.com/bashly-framework/completely/commit/3ce16ac) -- Compare [`v0.5.4..v0.6.0`](https://github.com/bashly-framework/completely/compare/v0.5.4..v0.6.0) +- Refactor install command and add an Installer model +- Drop support for Ruby 2.7 +- Change exception classes v0.5.4 - 2023-04-21 ---------------------------------------- -- Add `completely install` command [`2fbd879`](https://github.com/bashly-framework/completely/commit/2fbd879) -- Compare [`v0.5.3..v0.5.4`](https://github.com/bashly-framework/completely/compare/v0.5.3..v0.5.4) +- Add `completely install` command v0.5.3 - 2023-01-31 ---------------------------------------- -- Upgrade dependencies [`211166a`](https://github.com/bashly-framework/completely/commit/211166a) -- Compare [`v0.5.2..v0.5.3`](https://github.com/bashly-framework/completely/compare/v0.5.2..v0.5.3) +- Upgrade dependencies v0.5.2 - 2022-12-02 ---------------------------------------- -- Improve test command output and allow multiple complines in one run [`e924571`](https://github.com/bashly-framework/completely/commit/e924571) -- Compare [`v0.5.1..v0.5.2`](https://github.com/bashly-framework/completely/compare/v0.5.1..v0.5.2) +- Improve test command output and allow multiple complines in one run v0.5.1 - 2022-11-28 ---------------------------------------- -- Refactor with rubocop [`42b996d`](https://github.com/bashly-framework/completely/commit/42b996d) -- Fix broken script when wildcards follow the first word [`63b77d1`](https://github.com/bashly-framework/completely/commit/63b77d1) -- Show warning when running the test command on an invalid file [`de7ede0`](https://github.com/bashly-framework/completely/commit/de7ede0) -- Compare [`v0.5.0..v0.5.1`](https://github.com/bashly-framework/completely/compare/v0.5.0..v0.5.1) +- Refactor with rubocop +- Fix broken script when wildcards follow the first word +- Show warning when running the test command on an invalid file v0.5.0 - 2022-09-04 ---------------------------------------- -- Add docker release [`39acd6e`](https://github.com/bashly-framework/completely/commit/39acd6e) -- Fix shellcheck SC2162 in the generated script [`9e703ec`](https://github.com/bashly-framework/completely/commit/9e703ec) -- Fix shellcheck SC2124 in the generated script [`2d23c51`](https://github.com/bashly-framework/completely/commit/2d23c51) -- Hide flag completion unless input ends with a hyphen [`c15d705`](https://github.com/bashly-framework/completely/commit/c15d705) -- Compare [`v0.4.3..v0.5.0`](https://github.com/bashly-framework/completely/compare/v0.4.3..v0.5.0) +- Add docker release +- Fix shellcheck SC2162 in the generated script +- Fix shellcheck SC2124 in the generated script +- Hide flag completion unless input ends with a hyphen v0.4.3 - 2022-07-14 ---------------------------------------- -- Fix file/folder completion when they contain spaces [`9dea691`](https://github.com/bashly-framework/completely/commit/9dea691) -- Compare [`v0.4.2..v0.4.3`](https://github.com/bashly-framework/completely/compare/v0.4.2..v0.4.3) +- Fix file/folder completion when they contain spaces v0.4.2 - 2022-05-27 ---------------------------------------- -- Allow keeping the test script with --keep [`20d9b15`](https://github.com/bashly-framework/completely/commit/20d9b15) -- Compare [`v0.4.1..v0.4.2`](https://github.com/bashly-framework/completely/compare/v0.4.1..v0.4.2) +- Allow keeping the test script with --keep v0.4.1 - 2022-05-21 ---------------------------------------- -- Remove support for arbitrary script test to fix zsh incompatibilities [`9e3e6d9`](https://github.com/bashly-framework/completely/commit/9e3e6d9) -- Compare [`v0.4.0..v0.4.1`](https://github.com/bashly-framework/completely/compare/v0.4.0..v0.4.1) +- Remove support for arbitrary script test to fix zsh incompatibilities v0.4.0 - 2022-05-21 ---------------------------------------- -- Improve template [`8172be2`](https://github.com/bashly-framework/completely/commit/8172be2) -- Refactor CLI commands [`1fced36`](https://github.com/bashly-framework/completely/commit/1fced36) -- Add Tester class for testing any completions script [`986f4d1`](https://github.com/bashly-framework/completely/commit/986f4d1) -- Add tester CLI command [`09e91ee`](https://github.com/bashly-framework/completely/commit/09e91ee) -- Add support for middle wildcard for --flag args completions [`8d25207`](https://github.com/bashly-framework/completely/commit/8d25207) -- Add COMPLETELY_DEBUG environment setting [`44c00a1`](https://github.com/bashly-framework/completely/commit/44c00a1) -- Allow setting the CONFIG_PATH argument via the COMPLETELY_CONFIG_PATH environment variable [`8ef65e1`](https://github.com/bashly-framework/completely/commit/8ef65e1) -- Allow setting the SCRIPT_PATH argument via the COMPLETELY_SCRIPT_PATH environment variable [`a484ff4`](https://github.com/bashly-framework/completely/commit/a484ff4) -- Compare [`v0.3.1..v0.4.0`](https://github.com/bashly-framework/completely/compare/v0.3.1..v0.4.0) +- Improve template +- Refactor CLI commands +- Add Tester class for testing any completions script +- Add tester CLI command +- Add support for middle wildcard for --flag args completions +- Add COMPLETELY_DEBUG environment setting +- Allow setting the CONFIG_PATH argument via the COMPLETELY_CONFIG_PATH environment variable +- Allow setting the SCRIPT_PATH argument via the COMPLETELY_SCRIPT_PATH environment variable v0.3.1 - 2022-02-20 ---------------------------------------- -- Fix Psych 4 errors for Ruby 3.1 [`2fb9a73`](https://github.com/bashly-framework/completely/commit/2fb9a73) -- Compare [`v0.3.0..v0.3.1`](https://github.com/bashly-framework/completely/compare/v0.3.0..v0.3.1) +- Fix Psych 4 errors for Ruby 3.1 v0.3.0 - 2022-01-28 ---------------------------------------- -- Fix generated script for zsh compatibility [`d19369b`](https://github.com/bashly-framework/completely/commit/d19369b) -- Compare [`v0.2.0..v0.3.0`](https://github.com/bashly-framework/completely/compare/v0.2.0..v0.3.0) +- Fix generated script for zsh compatibility v0.2.0 - 2021-09-03 ---------------------------------------- -- Improve generated code to support local completions [`3518434`](https://github.com/bashly-framework/completely/commit/3518434) -- Compare [`v0.1.3..v0.2.0`](https://github.com/bashly-framework/completely/compare/v0.1.3..v0.2.0) +- Improve generated code to support local completions v0.1.3 - 2021-07-21 ---------------------------------------- -- Fix function name when only spaced patterns are configured [`2e14ec2`](https://github.com/bashly-framework/completely/commit/2e14ec2) -- Compare [`v0.1.2..v0.1.3`](https://github.com/bashly-framework/completely/compare/v0.1.2..v0.1.3) +- Fix function name when only spaced patterns are configured v0.1.2 - 2021-07-20 ---------------------------------------- -- Add ability to generate a function that prints the script [`22de124`](https://github.com/bashly-framework/completely/commit/22de124) -- Compare [`v0.1.1..v0.1.2`](https://github.com/bashly-framework/completely/compare/v0.1.1..v0.1.2) +- Add ability to generate a function that prints the script v0.1.1 - 2021-07-20 ---------------------------------------- -- Fix missing VERSION error [`e6f0ac1`](https://github.com/bashly-framework/completely/commit/e6f0ac1) -- Compare [`v0.1.0..v0.1.1`](https://github.com/bashly-framework/completely/compare/v0.1.0..v0.1.1) +- Fix missing VERSION error v0.1.0 - 2021-07-20 ---------------------------------------- -- Initial version [`bcd598c`](https://github.com/bashly-framework/completely/commit/bcd598c) -- Compare [`v0.1.0`](https://github.com/bashly-framework/completely/compare/v0.1.0) +- Initial version diff --git a/Dockerfile b/Dockerfile index 554eb3d..4345097 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,6 @@ FROM dannyben/alpine-ruby:3.3.3 ENV PS1="\n\n>> completely \W \$ " WORKDIR /app -RUN gem install completely --version 0.7.4 +RUN gem install completely --version 0.7.3 ENTRYPOINT ["completely"] \ No newline at end of file diff --git a/README.md b/README.md index c35d6db..6203e99 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,13 @@ mygit: The `2> /dev/null` is used so that if the command is executed in a directory without a git repository, it will still behave as expected. +### Completion scope and limitations + +- Completion words are treated as whitespace-delimited tokens. +- Literal completion phrases that contain spaces are not supported as a single completion item. +- Quotes and other special shell characters in literal completion words are not escaped automatically. +- Dynamic `$(...)` completion commands should output plain whitespace-delimited words. + ### Suggesting flag arguments Adding a `*` wildcard in the middle of a pattern can be useful for suggesting diff --git a/lib/completely/pattern.rb b/lib/completely/pattern.rb index 1c6f6b7..5bd0609 100644 --- a/lib/completely/pattern.rb +++ b/lib/completely/pattern.rb @@ -49,21 +49,17 @@ def compgen @compgen ||= compgen! end + def filename_action? + actions.include?('-A file') || actions.include?('-A directory') + end + private def compgen! result = [] result << actions.join(' ').to_s if actions.any? - result << %[-W "$(#{function_name} #{quoted_words.join ' '})"] if words.any? + result << %[-W "$(#{function_name} "#{words.join ' '}")"] if words.any? result.any? ? result.join(' ') : nil end - - def quoted_words - @quoted_words ||= words.map { |word| %("#{escape_for_double_quotes word}") } - end - - def escape_for_double_quotes(word) - word.gsub(/["\\]/, '\\\\\&') - end end end diff --git a/lib/completely/templates/template.erb b/lib/completely/templates/template.erb index 8b46f92..3110fd3 100644 --- a/lib/completely/templates/template.erb +++ b/lib/completely/templates/template.erb @@ -5,10 +5,9 @@ # Modifying it manually is not recommended <%= function_name %>_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } <%= function_name %>() { @@ -59,6 +60,9 @@ % patterns.each do |pattern| % next if pattern.empty? <%= pattern.case_string %>) +% if pattern.filename_action? + compopt -o filenames 2>/dev/null +% end while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen <%= pattern.compgen %> -- "$cur") ;; diff --git a/lib/completely/version.rb b/lib/completely/version.rb index 407dc87..026d7c7 100644 --- a/lib/completely/version.rb +++ b/lib/completely/version.rb @@ -1,3 +1,3 @@ module Completely - VERSION = '0.7.4' + VERSION = '0.7.3' end diff --git a/spec/README.md b/spec/README.md index 25343ef..c00362f 100644 --- a/spec/README.md +++ b/spec/README.md @@ -2,19 +2,29 @@ ## Running tests +You can run specs with `rspec` as usual. + +We recommend using [`respec`][2], which wraps common spec workflows: + ```bash -$ rspec +rspec # or -$ run spec -# or, to run just tests in a given file -$ run spec zsh -# or, to run just specs tagged with :focus -$ run spec :focus +respec ``` You might need to prefix the commands with `bundle exec`, depending on the way Ruby is installed. +Useful helper shortcuts: + +```bash +# script quality checks (shellcheck + shfmt generated script tests) +respec tagged script_quality + +# integration behavior suite +respec only integration +``` + ## Interactive Approvals Some tests may prompt you for an interactive approval of changes. This @@ -29,4 +39,5 @@ ZSH compatibility test is done by running the completely tester script inside a zsh container. This is all done automatically by `spec/completely/zsh_spec.rb`. -[1]: https://github.com/dannyben/rspec_approvals \ No newline at end of file +[1]: https://github.com/dannyben/rspec_approvals +[2]: https://github.com/DannyBen/respec diff --git a/spec/approvals/cli/generated-script b/spec/approvals/cli/generated-script index 8e7ee14..ded53a7 100644 --- a/spec/approvals/cli/generated-script +++ b/spec/approvals/cli/generated-script @@ -5,10 +5,9 @@ # Modifying it manually is not recommended _mygit_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ _mygit_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _mygit_completions() { @@ -58,15 +59,16 @@ _mygit_completions() { ;; 'status'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur") ;; 'init'*) + compopt -o filenames 2>/dev/null while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur") ;; *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur") ;; esac diff --git a/spec/approvals/cli/generated-script-alt b/spec/approvals/cli/generated-script-alt index 23eb09b..3856f04 100644 --- a/spec/approvals/cli/generated-script-alt +++ b/spec/approvals/cli/generated-script-alt @@ -5,10 +5,9 @@ # Modifying it manually is not recommended _mycomps_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ _mycomps_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _mycomps() { @@ -58,15 +59,16 @@ _mycomps() { ;; 'status'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "--help" "--verbose" "--branch" "-b")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "--help --verbose --branch -b")" -- "$cur") ;; 'init'*) + compopt -o filenames 2>/dev/null while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mycomps_filter "--bare")" -- "$cur") ;; *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "-h -v --help --version init status")" -- "$cur") ;; esac diff --git a/spec/approvals/cli/generated-wrapped-script b/spec/approvals/cli/generated-wrapped-script index 875142e..32613f4 100644 --- a/spec/approvals/cli/generated-wrapped-script +++ b/spec/approvals/cli/generated-wrapped-script @@ -6,10 +6,9 @@ give_comps() { echo $'# Modifying it manually is not recommended' echo $'' echo $'_mygit_completions_filter() {' - echo $' local words=("$@")' + echo $' local words="$1"' echo $' local cur=${COMP_WORDS[COMP_CWORD]}' echo $' local result=()' - echo $' local want_options=0' echo $'' echo $' # words the user already typed (excluding the command itself)' echo $' local used=()' @@ -17,26 +16,28 @@ give_comps() { echo $' used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")' echo $' fi' echo $'' - echo $' # Completing an option: offer everything.' - echo $' # Completing a non-option: drop options and already-used words.' - echo $' [[ "${cur:0:1}" == "-" ]] && want_options=1' - echo $' for word in "${words[@]}"; do' - echo $' if ((!want_options)); then' + echo $' if [[ "${cur:0:1}" == "-" ]]; then' + echo $' # Completing an option: offer everything (including options)' + echo $' echo "$words"' + echo $'' + echo $' else' + echo $' # Completing a non-option: offer only non-options,' + echo $' # and don\'t re-offer ones already used earlier in the line.' + echo $' for word in $words; do' echo $' [[ "${word:0:1}" == "-" ]] && continue' echo $'' + echo $' local seen=0' echo $' for u in "${used[@]}"; do' echo $' if [[ "$u" == "$word" ]]; then' - echo $' continue 2' + echo $' seen=1' + echo $' break' echo $' fi' echo $' done' - echo $' fi' - echo $'' - echo $' # compgen -W expects shell-escaped words in one space-delimited string.' - echo $' printf -v word \'%q\' "$word"' - echo $' result+=("$word")' - echo $' done' + echo $' ((!seen)) && result+=("$word")' + echo $' done' echo $'' - echo $' echo "${result[*]}"' + echo $' echo "${result[*]}"' + echo $' fi' echo $'}' echo $'' echo $'_mygit_completions() {' @@ -59,15 +60,16 @@ give_comps() { echo $' ;;' echo $'' echo $' \'status\'*)' - echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")' + echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur")' echo $' ;;' echo $'' echo $' \'init\'*)' + echo $' compopt -o filenames 2>/dev/null' echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")' echo $' ;;' echo $'' echo $' *)' - echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")' + echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur")' echo $' ;;' echo $'' echo $' esac' diff --git a/spec/approvals/cli/test/completely-tester-1.sh b/spec/approvals/cli/test/completely-tester-1.sh index 7d141a9..5deacf8 100644 --- a/spec/approvals/cli/test/completely-tester-1.sh +++ b/spec/approvals/cli/test/completely-tester-1.sh @@ -13,10 +13,9 @@ fi # Modifying it manually is not recommended _mygit_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -24,26 +23,28 @@ _mygit_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _mygit_completions() { @@ -66,15 +67,16 @@ _mygit_completions() { ;; 'status'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur") ;; 'init'*) + compopt -o filenames 2>/dev/null while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur") ;; *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur") ;; esac diff --git a/spec/approvals/cli/test/completely-tester-2.sh b/spec/approvals/cli/test/completely-tester-2.sh index 73555b9..ec78932 100644 --- a/spec/approvals/cli/test/completely-tester-2.sh +++ b/spec/approvals/cli/test/completely-tester-2.sh @@ -13,10 +13,9 @@ fi # Modifying it manually is not recommended _mygit_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -24,26 +23,28 @@ _mygit_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _mygit_completions() { @@ -66,15 +67,16 @@ _mygit_completions() { ;; 'status'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur") ;; 'init'*) + compopt -o filenames 2>/dev/null while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur") ;; *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur") ;; esac diff --git a/spec/approvals/cli/test/completely-tester.sh b/spec/approvals/cli/test/completely-tester.sh index a3235d5..499ca18 100644 --- a/spec/approvals/cli/test/completely-tester.sh +++ b/spec/approvals/cli/test/completely-tester.sh @@ -13,10 +13,9 @@ fi # Modifying it manually is not recommended _mygit_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -24,26 +23,28 @@ _mygit_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _mygit_completions() { @@ -66,15 +67,16 @@ _mygit_completions() { ;; 'status'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur") ;; 'init'*) + compopt -o filenames 2>/dev/null while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur") ;; *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur") ;; esac diff --git a/spec/approvals/cli/uninstall/dry b/spec/approvals/cli/uninstall/dry index c9791f4..088c7c9 100644 --- a/spec/approvals/cli/uninstall/dry +++ b/spec/approvals/cli/uninstall/dry @@ -1 +1 @@ -rm -f /home/USER/.local/share/bash-completion/completions/completely-test +rm -f /home/vagrant/.local/share/bash-completion/completions/completely-test diff --git a/spec/approvals/completions/function b/spec/approvals/completions/function index ffaa11c..3bd1845 100644 --- a/spec/approvals/completions/function +++ b/spec/approvals/completions/function @@ -6,10 +6,9 @@ send_completions() { echo $'# Modifying it manually is not recommended' echo $'' echo $'_completely_completions_filter() {' - echo $' local words=("$@")' + echo $' local words="$1"' echo $' local cur=${COMP_WORDS[COMP_CWORD]}' echo $' local result=()' - echo $' local want_options=0' echo $'' echo $' # words the user already typed (excluding the command itself)' echo $' local used=()' @@ -17,26 +16,28 @@ send_completions() { echo $' used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")' echo $' fi' echo $'' - echo $' # Completing an option: offer everything.' - echo $' # Completing a non-option: drop options and already-used words.' - echo $' [[ "${cur:0:1}" == "-" ]] && want_options=1' - echo $' for word in "${words[@]}"; do' - echo $' if ((!want_options)); then' + echo $' if [[ "${cur:0:1}" == "-" ]]; then' + echo $' # Completing an option: offer everything (including options)' + echo $' echo "$words"' + echo $'' + echo $' else' + echo $' # Completing a non-option: offer only non-options,' + echo $' # and don\'t re-offer ones already used earlier in the line.' + echo $' for word in $words; do' echo $' [[ "${word:0:1}" == "-" ]] && continue' echo $'' + echo $' local seen=0' echo $' for u in "${used[@]}"; do' echo $' if [[ "$u" == "$word" ]]; then' - echo $' continue 2' + echo $' seen=1' + echo $' break' echo $' fi' echo $' done' - echo $' fi' - echo $'' - echo $' # compgen -W expects shell-escaped words in one space-delimited string.' - echo $' printf -v word \'%q\' "$word"' - echo $' result+=("$word")' - echo $' done' + echo $' ((!seen)) && result+=("$word")' + echo $' done' echo $'' - echo $' echo "${result[*]}"' + echo $' echo "${result[*]}"' + echo $' fi' echo $'}' echo $'' echo $'_completely_completions() {' @@ -51,7 +52,8 @@ send_completions() { echo $'' echo $' case "$compline" in' echo $' \'generate\'*)' - echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help" "--force")" -- "$cur")' + echo $' compopt -o filenames 2>/dev/null' + echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help --force")" -- "$cur")' echo $' ;;' echo $'' echo $' \'init\'*)' @@ -59,7 +61,7 @@ send_completions() { echo $' ;;' echo $'' echo $' *)' - echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help" "--version" "init" "generate")" -- "$cur")' + echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help --version init generate")" -- "$cur")' echo $' ;;' echo $'' echo $' esac' diff --git a/spec/approvals/completions/script b/spec/approvals/completions/script index bb33f7b..5d1a3aa 100644 --- a/spec/approvals/completions/script +++ b/spec/approvals/completions/script @@ -5,10 +5,9 @@ # Modifying it manually is not recommended _completely_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ _completely_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _completely_completions() { @@ -50,7 +51,8 @@ _completely_completions() { case "$compline" in 'generate'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help" "--force")" -- "$cur") + compopt -o filenames 2>/dev/null + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help --force")" -- "$cur") ;; 'init'*) @@ -58,7 +60,7 @@ _completely_completions() { ;; *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help" "--version" "init" "generate")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help --version init generate")" -- "$cur") ;; esac diff --git a/spec/approvals/completions/script-complete-options b/spec/approvals/completions/script-complete-options index 8e6347c..a0dff27 100644 --- a/spec/approvals/completions/script-complete-options +++ b/spec/approvals/completions/script-complete-options @@ -5,10 +5,9 @@ # Modifying it manually is not recommended _mygit_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ _mygit_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _mygit_completions() { @@ -50,7 +51,7 @@ _mygit_completions() { case "$compline" in *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "status" "commit")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "status commit")" -- "$cur") ;; esac diff --git a/spec/approvals/completions/script-only-spaces b/spec/approvals/completions/script-only-spaces index f980115..6e845b2 100644 --- a/spec/approvals/completions/script-only-spaces +++ b/spec/approvals/completions/script-only-spaces @@ -5,10 +5,9 @@ # Modifying it manually is not recommended _completely_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ _completely_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _completely_completions() { @@ -50,7 +51,8 @@ _completely_completions() { case "$compline" in 'generate'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help" "--force")" -- "$cur") + compopt -o filenames 2>/dev/null + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help --force")" -- "$cur") ;; 'init'*) diff --git a/spec/approvals/completions/script-with-debug b/spec/approvals/completions/script-with-debug index 7a3673c..afc0053 100644 --- a/spec/approvals/completions/script-with-debug +++ b/spec/approvals/completions/script-with-debug @@ -5,10 +5,9 @@ # Modifying it manually is not recommended _completely_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ _completely_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _completely_completions() { diff --git a/spec/completely/commands/install_spec.rb b/spec/completely/commands/install_spec.rb index 6e2a93f..b1351a7 100644 --- a/spec/completely/commands/install_spec.rb +++ b/spec/completely/commands/install_spec.rb @@ -52,7 +52,7 @@ expect { subject.execute %w[install completely-test --dry] } .to output_approval('cli/install/dry') - .except(%r[/home/([^/]+)], '/home/USER') + .except(%r{/home/([^/]+)}, '/home/USER') end end @@ -65,7 +65,7 @@ expect { subject.execute %w[install completely-test - --dry] } .to output_approval('cli/install/stdin-dry') .except(/cp [^\s]*completely-[^\s]*/, 'cp ') - .except(%r[/home/([^/]+)], '/home/USER') + .except(%r{/home/([^/]+)}, '/home/USER') end end diff --git a/spec/completely/commands/uninstall_spec.rb b/spec/completely/commands/uninstall_spec.rb index ec34739..930bc95 100644 --- a/spec/completely/commands/uninstall_spec.rb +++ b/spec/completely/commands/uninstall_spec.rb @@ -38,7 +38,9 @@ expect(mock_installer).not_to receive(:uninstall) expect { subject.execute %w[uninstall completely-test --dry] } - .to output_approval('cli/uninstall/dry').diff(20) + .to output_approval('cli/uninstall/dry') + .except(%r{/home/([^/]+)}, '/home/USER') + .diff(20) end end diff --git a/spec/completely/pattern_spec.rb b/spec/completely/pattern_spec.rb index 93952a4..05f10e6 100644 --- a/spec/completely/pattern_spec.rb +++ b/spec/completely/pattern_spec.rb @@ -37,6 +37,20 @@ end end + describe '#filename_action?' do + it 'returns true when file or directory actions exist' do + expect(subject.filename_action?).to be true + end + + context 'when file and directory actions are not present' do + let(:completions) { %w[--message --help ] } + + it 'returns false' do + expect(subject.filename_action?).to be false + end + end + end + describe '#prefix' do it 'returns the first word of the pattern' do expect(subject.prefix).to eq 'git' @@ -105,7 +119,7 @@ describe '#compgen' do it 'returns a line of compgen arguments' do - expect(subject.compgen).to eq '-A file -A user -W "$(_filter "--message" "--help")"' + expect(subject.compgen).to eq '-A file -A user -W "$(_filter "--message --help")"' end context 'when there are no words for -W' do @@ -120,15 +134,7 @@ let(:completions) { %w[--message --help] } it 'omits the -A arguments' do - expect(subject.compgen).to eq '-W "$(_filter "--message" "--help")"' - end - end - - context 'when words include spaces and quotes' do - let(:completions) { ['hello world', 'one"quote'] } - - it 'shell-escapes words before passing them to the filter' do - expect(subject.compgen).to eq '-W "$(_filter "hello world" "one\"quote")"' + expect(subject.compgen).to eq '-W "$(_filter "--message --help")"' end end diff --git a/spec/fixtures/tester/default.bash b/spec/fixtures/tester/default.bash index c4df327..a5a878f 100644 --- a/spec/fixtures/tester/default.bash +++ b/spec/fixtures/tester/default.bash @@ -5,10 +5,9 @@ # Modifying it manually is not recommended _cli_completions_filter() { - local words=("$@") + local words="$1" local cur=${COMP_WORDS[COMP_CWORD]} local result=() - local want_options=0 # words the user already typed (excluding the command itself) local used=() @@ -16,26 +15,28 @@ _cli_completions_filter() { used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}") fi - # Completing an option: offer everything. - # Completing a non-option: drop options and already-used words. - [[ "${cur:0:1}" == "-" ]] && want_options=1 - for word in "${words[@]}"; do - if ((!want_options)); then + if [[ "${cur:0:1}" == "-" ]]; then + # Completing an option: offer everything (including options) + echo "$words" + + else + # Completing a non-option: offer only non-options, + # and don't re-offer ones already used earlier in the line. + for word in $words; do [[ "${word:0:1}" == "-" ]] && continue + local seen=0 for u in "${used[@]}"; do if [[ "$u" == "$word" ]]; then - continue 2 + seen=1 + break fi done - fi - - # compgen -W expects shell-escaped words in one space-delimited string. - printf -v word '%q' "$word" - result+=("$word") - done + ((!seen)) && result+=("$word") + done - echo "${result[*]}" + echo "${result[*]}" + fi } _cli_completions() { @@ -50,19 +51,19 @@ _cli_completions() { case "$compline" in 'command childcommand'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "--quiet" "--verbose" "-q" "-v")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "--quiet --verbose -q -v")" -- "$cur") ;; 'command subcommand'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "--force" "--quiet")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "--force --quiet")" -- "$cur") ;; 'command'*) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "subcommand" "childcommand")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "subcommand childcommand")" -- "$cur") ;; *) - while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "--help" "--version" "command" "conquer")" -- "$cur") + while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_cli_completions_filter "--help --version command conquer")" -- "$cur") ;; esac