| 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 | 
 |