This repository has been archived by the owner on Mar 24, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bash-complete
executable file
·267 lines (257 loc) · 6.39 KB
/
bash-complete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#!/usr/bin/bash
# Ensure that bash-completion is properly set up
. /usr/share/bash-completion/bash_completion
# bash function that gets the completion function for a given command if it exists
get_completion_function() {
local candidates=$(complete | grep $1)
# Unfortunately, I do not know how to handle this line-wise. Therefore,
# I have to do a dirty little hack:
# Handle the whole output word-wise, whenever we find "complete" and after
# the loop has finished, we know that we have finished a line :(
for candidate in $candidates # automatically splits by ' '
do
if [ "$nextIsFunction" == "1" ]
then
local localFunctionName="$candidate"
local nextIsFunction=0
fi
if [ "$candidate" == "-F" ]
then
local nextIsFunction=1
fi
if [ "$candidate" == "complete" ]
then
if [ "$lastWord" == "$1" ]
then
# Success (not last line)
functionName=$localFunctionName
return 0
fi
fi
local lastWord="$candidate"
done
if [ "$lastWord" == "$1" ]
then
# Success (last line)
functionName=$localFunctionName
return 0
fi
return 1
}
load_completion_function() {
local candidates=$(complete | grep -- -D)
# See above
for candidate in $candidates # automatically splits by ' '
do
if [ "$nextIsFunction" == "1" ]
then
local functionName="$candidate"
local hasFunction=1
local nextIsFunction=0
if [ "$isDefault" == "1" ]
then
$functionName $1 "" $1 # call loader function
return 0
fi
fi
if [ "$candidate" == "-F" ]
then
local nextIsFunction=1
fi
if [ "$candidate" == "-D" ]
then
local isDefault=1
if [ "$hasFunction" == "1" ]
then
$functionName $1 "" $1 # call loader function
return 0
fi
fi
if [ "$candidate" == "complete" ]
then
local hasFunction=0
local isDefault=0
fi
done
return 1
}
# Attention: load_and_get... must not be called with $(...) syntax because it
# changes bash state and $(...) seems to be executed in its own ... scope?
# Therefore, the name of the completionFunction is not "returned" by echo but
# by setting a global variable instead. Yeah, bash scripting :)
load_and_get_completion_function() {
if get_completion_function $1
then
completionFunction=$functionName
return 0
fi
load_completion_function $1
if get_completion_function $1
then
completionFunction=$functionName
return 0
fi
return 1
}
# If we are in the first word, we complete commands or fallback to directories
# because those could expand to a command path (bash default seems to be to
# only return commands from PATH if there are any found and directories otherwise)
if [ $1 == 0 ]
then
commands=$(compgen -c $2) # Try commands first
if [ "$commands" ]
then
echo $commands
exit
else
dirs=$(compgen -d $2) # Try directories as fallback
slashdirs=()
for dir in $dirs
do
slashdirs+=($dir"/") # Append / to each directory
done
echo ${slashdirs[@]}
fi
exit # Do not try function completion
fi
if load_and_get_completion_function $2
then
# Since my bash-scripting skills are so lacking, I have to do everything step
# by step here:
# From all arguments, extract the current word position (first argument
# after this script's name, i.e. $1) and the current command-line which should
# by completed (all following arguments)
allArgs=($@)
argLen=${#allArgs[@]}
wordPos=${allArgs[0]}
argsWithoutPos=(${allArgs[@]:1:$argLen})
# Set the 6 environment variables which are used by the completion scripts
COMP_LINE=${argsWithoutPos[@]}
COMP_POINT=0 # We have to calculate COMP_POINT by counting all words until $wordPos
wordIter=0
while [ $wordIter -le $wordPos ]
do
word=${argsWithoutPos[$wordIter]}
wordLen=${#word}
COMP_POINT=$((COMP_POINT+wordLen))
if [ $wordIter -gt 0 ] # For all following words, include the whitespace between the two words
then
COMP_POINT=$((COMP_POINT+1))
fi
((wordIter++))
done
COMP_WORDS=(${argsWithoutPos[@]})
COMP_CWORD=$wordPos
COMP_KEY=9 # we hold those two ...
COMP_TYPE=33 # ... fix for the moment
COMPREPLY=() # Reset COMPREPLY ... :(
# Set the three arguments to the completion function
cmdname=$2
curWord=${argsWithoutPos[$wordPos]}
prevWordPos=$((wordPos-1))
prevWord=${argsWithoutPos[$prevWordPos]}
# Call the completion function
: '# Output debug info
echo $completionFunction
echo $cmdname
echo $curWord
echo $prevWord
'
# If we have found a meaningful completion function, call it. Otherwise,
# do file and directory completion.
if [ "$completionFunction" != "_minimal" ]
then
$completionFunction "$cmdname" "$curWord" "$prevWord"
echo ${COMPREPLY[@]}
else # file & directory completion
results=()
files=$(compgen -f $curWord)
for file in $files
do
results+=($file)
done
dirs=$(compgen -d $curWord) # Try directories as fallback
for dir in $dirs
do
results+=($dir"/") # Append / to each directory
done
echo ${results[@]}
fi
else
exit 1
fi
#
# Starting here: Just testing stuff
#
# Register a function _custom_complete() for (hopefully) nonexistent command
# 'asdf' which just prints the variables/parameters
: '
_custom_complete() {
echo "" # Just a newline for nicer output
echo "COMP_LINE='"$COMP_LINE"'"
echo "COMP_POINT='"$COMP_POINT"'"
echo "COMP_KEY='"$COMP_KEY"'"
echo "COMP_TYPE='"$COMP_TYPE"'"
echo "COMP_WORDS='"${COMP_WORDS[@]}"'"
echo "COMP_CWORD='"$COMP_CWORD"'"
echo "p1='"$1"'"
echo "p2='"$2"'"
echo "p3='"$3"'"
}
complete -F _custom_complete asdf
'
# Try to manually call the _known_hosts function which provides completion for
# dig, ftp ...
: '
COMP_LINE="ftp "
COMP_POINT=4
COMP_KEY=9
COMP_TYPE=33
COMP_WORDS=(ftp )
COMP_CWORD=1
_known_hosts ftp "" ftp
length=${#COMPREPLY[@]}
for reply in ${COMPREPLY[@]}
do
#suppress output of: echo $reply
true # bash does not like empty loops?
done
echo $length
'
# Try to dynamically load the completion code for git
: '
echo "Completion function for git before calling _completion_loader:"
complete | grep git
COMP_LINE="git "
COMP_POINT=4
COMP_KEY=9
COMP_TYPE=33
COMP_WORDS=(git )
COMP_CWORD=1
_completion_loader git "" git
echo "Completion function for git after calling _completion_loader:"
complete | grep git
'
# Let's call it :)
: '
COMP_LINE="git ch file"
COMP_POINT=6
COMP_KEY=9
COMP_TYPE=33
COMP_WORDS=(git ch file)
COMP_CWORD=1
COMPREPLY=() # Reset COMPREPLY ... :(
__git_wrap__git_main git ch git
echo ${COMPREPLY[@]}
'
# Let's try something more
: '
COMP_LINE="cd ~/"
COMP_POINT=5
COMP_KEY=9
COMP_TYPE=33
COMP_WORDS=(cd ~/)
COMP_CWORD=1
_cd cd ~/ cd
echo ${COMPREPLY[@]}
'