1 | # Test bash and OSH handling of spaces
|
2 |
|
3 | # This works, you have to escape it FIRST.
|
4 |
|
5 | # So COMPREPLY takes SHELL-ESCAPED strings
|
6 | #
|
7 | # bash does NOT quote COMPREPLY -- it expects it to be already quoted.
|
8 | #
|
9 | # But YSH might do so, because it's a better interface.
|
10 | #
|
11 | # shopt --set ysh_quotes_compreply
|
12 | #
|
13 | # If the user plugin doesn't quote it, then the shell is responsible for it.
|
14 |
|
15 | # This is like 'printf %q' 'cha spaces'
|
16 | #declare -a commands=( cherry checkout 'cha\ spaces')
|
17 |
|
18 | # Test apostrophe
|
19 | #
|
20 | # Newline works! It's because printf %q works.
|
21 |
|
22 | # The $ gets quoted
|
23 | # BUG: bash shows \$file_not_var as a completion candidate, but you can't select it
|
24 | #
|
25 | # - If you type $, you get $'one\ntwo'
|
26 | # - If you type \$, you get $'one\ntwo' as well
|
27 | # This is probably GNU readline evaluation
|
28 | #
|
29 | # This is why YSH distinguishes between completing the shell language, and
|
30 | # completing a command arg. The latter must be shell-quoted.
|
31 |
|
32 | declare -a commands=(
|
33 | cherry checkout
|
34 | 'ch with space' # you have to type 'ch\ ' to select this completion
|
35 | "can't" # apostrophe
|
36 | $'one\ntwo' # newline
|
37 | '$file_not_var'
|
38 | $'mu \u03bc \u4e09 \U0001f618 unicode'
|
39 | )
|
40 |
|
41 | # This has problems because 'check' is a prefix of two things.
|
42 | # You might need to add quotes
|
43 | #declare -a commands=( cherry checkout 'check\ spaces' )
|
44 |
|
45 | __printf_q() {
|
46 | #argv "$@"
|
47 |
|
48 | local cur=$2
|
49 |
|
50 | local -a results
|
51 |
|
52 | # Literal spaces don't work, you have to escape them beforehand
|
53 | for cmd in "${commands[@]}"; do
|
54 | case $cmd in
|
55 | $cur*)
|
56 |
|
57 | # So COMPREPLY is a literal list of SHELL strings, not string argv
|
58 | # words
|
59 |
|
60 | #local quoted=$(printf %q "$cmd")
|
61 |
|
62 | # More efficient version
|
63 | local quoted
|
64 | printf -v quoted %q "$cmd"
|
65 |
|
66 | # This extra space isn't treated as part of the word. But it does
|
67 | # result in an extra space.
|
68 | #results+=( "$quoted " )
|
69 |
|
70 | results+=( "$quoted" )
|
71 |
|
72 | ;;
|
73 | esac
|
74 | done
|
75 |
|
76 | COMPREPLY=( "${results[@]}" )
|
77 | }
|
78 |
|
79 | # This works too
|
80 |
|
81 | __sq() {
|
82 | # Important: cur does NOT include the single quote
|
83 | local cur=$2
|
84 |
|
85 | local -a results
|
86 |
|
87 | for cmd in "${commands[@]}"; do
|
88 | case $cmd in
|
89 | $cur*)
|
90 | # BUG when there's a single quote!
|
91 | local quoted="'$cmd'"
|
92 | #local quoted="$cmd"
|
93 | results+=( "$quoted" )
|
94 | ;;
|
95 | esac
|
96 | done
|
97 |
|
98 | COMPREPLY=( "${results[@]}" )
|
99 | }
|
100 |
|
101 | # TODO: demonstrate how to get it from an external process
|
102 | # Well I think the easiest thing is obviously to implement %q on their side,
|
103 | # and '\n'
|
104 |
|
105 | argv() {
|
106 | python3 -c 'import sys; print(sys.argv[1:])' "$@"
|
107 | }
|
108 |
|
109 | pq-argv() {
|
110 | argv "$@"
|
111 | }
|
112 |
|
113 | sq-argv() {
|
114 | argv "$@"
|
115 | }
|
116 |
|
117 | w1-argv() {
|
118 | argv "$@"
|
119 | }
|
120 |
|
121 | w2-argv() {
|
122 | argv "$@"
|
123 | }
|
124 |
|
125 | c-argv() {
|
126 | argv "$@"
|
127 | }
|
128 |
|
129 | cw-argv() {
|
130 | argv "$@"
|
131 | }
|
132 |
|
133 | q2-argv() {
|
134 | argv "$@"
|
135 | }
|
136 |
|
137 | q3-argv() {
|
138 | argv "$@"
|
139 | }
|
140 |
|
141 | v-argv() {
|
142 | argv "$@"
|
143 | }
|
144 |
|
145 | complete -F __printf_q pq-argv
|
146 | complete -F __sq sq-argv
|
147 |
|
148 | # Hm this doesn't work. It comes across as one candidate.
|
149 | # But it doesn't get shell escaping
|
150 | #complete -W 'word\ with\ spaces w2' w-argv
|
151 |
|
152 | # It comes across as one candidate
|
153 | complete -W "'word with spaces' w2" w1-argv
|
154 |
|
155 | # This works! I think there is a double-eval
|
156 | complete -W "'word\ with\ spaces' w2" w2-argv
|
157 |
|
158 | print-comps() {
|
159 | local cur=$2
|
160 |
|
161 | for cmd in "${commands[@]}"; do
|
162 | case $cmd in
|
163 | $cur*)
|
164 | # More efficient version
|
165 | local quoted
|
166 | printf -v quoted %q "$cmd"
|
167 | echo "$quoted"
|
168 | ;;
|
169 | esac
|
170 | done
|
171 | }
|
172 |
|
173 | complete -C print-comps c-argv
|
174 |
|
175 | #
|
176 | # Try to figure out what -W does
|
177 | #
|
178 |
|
179 | # This doesn't work
|
180 | complete -W '$(print-comps)' cw-argv
|
181 |
|
182 | # Doesn't work either
|
183 | #complete -W "$(print-comps)" cw-argv
|
184 |
|
185 | print-comps-q2() {
|
186 | print-comps | while read -r line; do
|
187 | printf '%q\n' "$line"
|
188 | done
|
189 | }
|
190 |
|
191 | # This works except you have to press $ for $'one\ntwo'
|
192 | complete -W "$(print-comps-q2)" q2-argv
|
193 |
|
194 | # -W is first split by IFS, and then each word is evaluated?
|
195 |
|
196 | print-comps-q3() {
|
197 | ### Complex alternative to printf %q that also works
|
198 |
|
199 | print-comps | while read -r line; do
|
200 | # This is wrong
|
201 | #echo "'$line'"
|
202 |
|
203 | # replace ' --> '\''
|
204 | echo "'${line//"'"/"'\\''"}'"
|
205 | done
|
206 | }
|
207 |
|
208 | test-words() {
|
209 | echo "$(print-comps)"
|
210 | echo
|
211 | echo "$(print-comps-q2)"
|
212 | echo
|
213 | echo "$(print-comps-q3)"
|
214 | echo
|
215 |
|
216 | echo 'Unquoted command sub, with word splitting'
|
217 | echo
|
218 |
|
219 | echo $(print-comps-q2)
|
220 | echo
|
221 |
|
222 | echo $(print-comps-q3)
|
223 | echo
|
224 | }
|
225 |
|
226 | # This works except you have to press $ for $'one\ntwo'
|
227 | complete -W "$(print-comps-q3)" q3-argv
|
228 |
|
229 |
|
230 | print-vars-with-dollar() {
|
231 | local prefix=$1
|
232 | compgen -A variable "$prefix" | while read -r line; do
|
233 | echo '$'$line
|
234 | done
|
235 | }
|
236 |
|
237 | __complete-vars() {
|
238 | #argv "$@"
|
239 | local cur=$2
|
240 |
|
241 | local -a results
|
242 |
|
243 | case $cur in
|
244 | '$'*)
|
245 | local prefix=${cur:1}
|
246 | #echo "prefix=$prefix"
|
247 |
|
248 | # Variables that start with prefix
|
249 | COMPREPLY=( $(print-vars-with-dollar "$prefix") )
|
250 | ;;
|
251 | esac
|
252 | }
|
253 |
|
254 | complete -F __complete-vars v-argv
|
255 |
|
256 |
|
257 | # For testing print-comps
|
258 |
|
259 | if test "$(basename -- $0)" = 'quoting.bash'; then
|
260 | "$@"
|
261 | fi
|
262 |
|