| 1 | #!/bin/bash
|
| 2 | #
|
| 3 | # Demo of the bash completion API.
|
| 4 | #
|
| 5 | # It's used by core/completion_test.py, and you can run it manually.
|
| 6 | #
|
| 7 | # The reason we use unit tests is that some of the postprocessing in GNU
|
| 8 | # readline is untestable from the "outside".
|
| 9 | #
|
| 10 | # Usage:
|
| 11 | # source testdata/completion/osh-unit.bash
|
| 12 |
|
| 13 | # For testing if ls completion works AUTOMATICALLY
|
| 14 |
|
| 15 | complete -W 'one two' ls
|
| 16 |
|
| 17 | alias ll='ls -l'
|
| 18 | alias ll_classify='ll --classify' # a second level of alias expansion
|
| 19 | alias ll_trailing='ls -l ' # trailing space shouldn't make a difference
|
| 20 |
|
| 21 | alias ll_own_completion='ls -l'
|
| 22 | complete -W 'own words' ll_own_completion
|
| 23 |
|
| 24 |
|
| 25 | argv() {
|
| 26 | spec/bin/argv.py "$@"
|
| 27 | }
|
| 28 |
|
| 29 | # Complete a fixed set of words
|
| 30 | complete_mywords() {
|
| 31 | local first=$1
|
| 32 | local cur=$2
|
| 33 | local prev=$3
|
| 34 | for candidate in one two three bin; do
|
| 35 | if [[ $candidate == $cur* ]]; then
|
| 36 | COMPREPLY+=("$candidate")
|
| 37 | fi
|
| 38 | done
|
| 39 | }
|
| 40 |
|
| 41 | foo() { argv foo "$@"; }
|
| 42 | complete_foo() {
|
| 43 | local first=$1
|
| 44 | local cur=$2
|
| 45 | local prev=$3
|
| 46 |
|
| 47 | echo
|
| 48 | argv args "$@"
|
| 49 |
|
| 50 | # NOTE: If you pass foo 'hi', then the words are quoted! This is a mistake!
|
| 51 | # Also true for \hi and "hi".
|
| 52 | # If you pass foo $x, you get literally $x as a word! It's BEFORE
|
| 53 | # evaluation rather than AFTER evaluation!
|
| 54 | #
|
| 55 | # This is asking the completion function to do something impossible!!!
|
| 56 |
|
| 57 | argv COMP_WORDS "${COMP_WORDS[@]}"
|
| 58 | argv COMP_CWORD "${COMP_CWORD}"
|
| 59 | argv COMP_LINE "${COMP_LINE}"
|
| 60 | # Somehow completion doesn't trigger in the middle of a word, so this is
|
| 61 | # always equal to ${#COMP_LINE} ?
|
| 62 | argv COMP_POINT "${COMP_POINT}"
|
| 63 |
|
| 64 | # This value is used in main bash_completion script.
|
| 65 |
|
| 66 | argv source "${BASH_SOURCE[@]}"
|
| 67 | argv 'source[0]' "${BASH_SOURCE[0]}"
|
| 68 |
|
| 69 | # Test for prefix
|
| 70 | # bin is a dir
|
| 71 | for candidate in one two three bin; do
|
| 72 | if [[ $candidate == $cur* ]]; then
|
| 73 | COMPREPLY+=("$candidate")
|
| 74 | fi
|
| 75 | done
|
| 76 | }
|
| 77 | # dirnames: add dirs if nothing matches
|
| 78 | # plusdirs: always add dirs
|
| 79 | # filenames: adds trailing slash if it is a directory
|
| 80 | complete -F complete_foo -o dirnames -o filenames foo
|
| 81 | complete -F complete_foo -o nospace foo2
|
| 82 |
|
| 83 | complete_filedir() {
|
| 84 | local first=$1
|
| 85 | local cur=$2
|
| 86 | local prev=$3
|
| 87 | COMPREPLY=( $( compgen -d "$cur" ) )
|
| 88 | }
|
| 89 | # from _filedir
|
| 90 | complete -F complete_filedir filedir
|
| 91 |
|
| 92 | complete_bug() {
|
| 93 | # Regression for issue where readline swallows SystemExit.
|
| 94 | comsub=$(echo comsub)
|
| 95 |
|
| 96 | COMPREPLY=(one two three $comsub)
|
| 97 | }
|
| 98 | # isolated bug
|
| 99 | complete -F complete_bug bug
|
| 100 |
|
| 101 | optdemo() { argv "$@"; }
|
| 102 | complete_optdemo() {
|
| 103 | local first=$1
|
| 104 | local cur=$2
|
| 105 | local prev=$3
|
| 106 |
|
| 107 | # Turn this off DYNAMICALLY, so we WILL get a space.
|
| 108 | # TODO: Add unit tests for this case. I only tested it manually.
|
| 109 | compopt +o nospace
|
| 110 |
|
| 111 | # -o nospace doesn't work here, but it's accepted!
|
| 112 | COMPREPLY=( $( compgen -o nospace -d "$cur" ) )
|
| 113 | }
|
| 114 | # Test how the options work. git uses nospace.
|
| 115 | complete -F complete_optdemo -o nospace optdemo
|
| 116 |
|
| 117 | optdynamic() { argv optdynamic "$@"; }
|
| 118 | complete_optdynamic() {
|
| 119 | local first=$1
|
| 120 | local cur=$2
|
| 121 | local prev=$3
|
| 122 |
|
| 123 | # Suppress space on anything that starts with b
|
| 124 | if [[ "$cur" == b* ]]; then
|
| 125 | compopt -o nospace
|
| 126 | fi
|
| 127 | COMPREPLY=( $( compgen -A file "$cur" ) )
|
| 128 | }
|
| 129 | complete -F complete_optdynamic optdynamic
|
| 130 |
|
| 131 | files_func() { argv "$@"; }
|
| 132 | complete_files_func() {
|
| 133 | local first=$1
|
| 134 | local cur=$2
|
| 135 | local prev=$3
|
| 136 |
|
| 137 | # This MESSES up the trailing slashes. Need -o filenames.
|
| 138 | COMPREPLY=( $( compgen -A file "$cur" ) )
|
| 139 | }
|
| 140 | # everything else completes files
|
| 141 | #complete -D -A file
|
| 142 | complete -F complete_files_func files_func # messes up trailing slashes
|
| 143 |
|
| 144 | # Check trailing backslashes for the 'fileuser' command
|
| 145 | # Hm somehow it knows to distinguish. Gah.
|
| 146 | fu_builtin() { argv "$@"; }
|
| 147 | complete -A user -A file fu_builtin
|
| 148 |
|
| 149 | # This behaves the same way as above because of -o filenames.
|
| 150 | # If you have both a dir and a user named root, it gets a trailing slash, which
|
| 151 | # makes sense.
|
| 152 | fu_func() { argv "$@"; }
|
| 153 | complete_users_files() {
|
| 154 | local first=$1
|
| 155 | local cur=$2
|
| 156 |
|
| 157 | COMPREPLY=( $( compgen -A user -A file "$cur" ) )
|
| 158 | }
|
| 159 | complete -F complete_users_files -o filenames fu_func
|
| 160 |
|
| 161 | complete_op_chars() {
|
| 162 | local first=$1
|
| 163 | local cur=$2
|
| 164 |
|
| 165 | for candidate in '$$$' '(((' '```' ';;;'; do
|
| 166 | if [[ $candidate == $cur* ]]; then
|
| 167 | COMPREPLY+=("$candidate")
|
| 168 | fi
|
| 169 | done
|
| 170 | }
|
| 171 |
|
| 172 | # Bash does NOT quote these! So they do not get sent into commands. It is
|
| 173 | # conflating shell syntax and tool syntax.
|
| 174 | op_chars() { argv "$@"; }
|
| 175 | complete -F complete_op_chars op_chars
|
| 176 |
|
| 177 | # Aha but this does the right thing! I think OSH should do this all the time!
|
| 178 | # User-defined functions shouldn't be responsible for quoting.
|
| 179 | op_chars_filenames() { argv "$@"; }
|
| 180 | complete -F complete_op_chars -o filenames op_chars_filenames
|
| 181 |
|
| 182 | # This shows that x is evaluated at RUNTIME, not at registration time!
|
| 183 | W_runtime() { argv "$@"; }
|
| 184 | complete -W 'foo $x $(echo --$x)' W_runtime
|
| 185 |
|
| 186 | W_runtime_error() { argv "$@"; }
|
| 187 | complete -W 'foo $(( 1 / 0 )) bar' W_runtime_error
|
| 188 |
|
| 189 | F_runtime_error() { argv "$@"; }
|
| 190 | complete_F_runtime_error() {
|
| 191 | COMPREPLY=( foo bar )
|
| 192 | COMPREPLY+=( $(( 1 / 0 )) ) # FATAL, but we still have candidates
|
| 193 | }
|
| 194 | complete -F complete_F_runtime_error F_runtime_error
|
| 195 |
|
| 196 | unset_compreply() { argv "$@"; }
|
| 197 | complete_unset_compreply() {
|
| 198 | unset COMPREPLY
|
| 199 | }
|
| 200 | complete -F complete_unset_compreply unset_compreply
|
| 201 |
|
| 202 | badexit() { argv "$@"; }
|
| 203 | complete_badexit() {
|
| 204 | COMPREPLY=(one two three)
|
| 205 | exit 42
|
| 206 | }
|
| 207 | complete -F complete_badexit badexit
|
| 208 |
|
| 209 | #
|
| 210 | # Test out the "124" protocol for lazy loading of completions.
|
| 211 | #
|
| 212 |
|
| 213 | # https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
|
| 214 | _completion_loader()
|
| 215 | {
|
| 216 | # If the file exists, we will return 124 and reload it.
|
| 217 | # TODO: It would be nice to have a debug_f builtin to trace this!
|
| 218 | . "testdata/completion/${1}_completion.bash" >/dev/null 2>&1 && return 124
|
| 219 | }
|
| 220 | complete -D -F _completion_loader
|
| 221 |
|
| 222 | #
|
| 223 | # Unit tests use this
|
| 224 | #
|
| 225 |
|
| 226 | # For testing interactively
|
| 227 | flagX() { argv "$@"; }
|
| 228 | flagX_bang() { argv "$@"; }
|
| 229 | flagX_prefix() { argv "$@"; }
|
| 230 | prefix_plusdirs() { argv "$@"; }
|
| 231 | flagX_plusdirs() { argv "$@"; }
|
| 232 | prefix_dirnames() { argv "$@"; }
|
| 233 |
|
| 234 | complete -F complete_mywords mywords
|
| 235 | complete -F complete_mywords -o nospace mywords_nospace
|
| 236 |
|
| 237 | # This REMOVES two of them. 'three' should not be removed
|
| 238 | # since it's not an exact match.
|
| 239 | # Hm filtering comes BEFORE the prefix.
|
| 240 | complete -X '@(two|bin|thre)' -F complete_mywords flagX
|
| 241 |
|
| 242 | # Silly negation syntax
|
| 243 | complete -X '!@(two|bin)' -F complete_mywords flagX_bang
|
| 244 |
|
| 245 | complete -P __ -X '@(two|bin|thre)' -F complete_mywords flagX_prefix
|
| 246 |
|
| 247 | complete -P __ -o plusdirs -F complete_mywords prefix_plusdirs
|
| 248 |
|
| 249 | # Filter out bin, is it added back? Yes, it appears to work.
|
| 250 | complete -X '@(two|bin)' -o plusdirs -F complete_mywords flagX_plusdirs
|
| 251 |
|
| 252 | complete -P __ -o dirnames prefix_dirnames
|
| 253 |
|