line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Complete::Bash; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY |
4
|
|
|
|
|
|
|
our $DATE = '2021-07-24'; # DATE |
5
|
|
|
|
|
|
|
our $DIST = 'Complete-Bash'; # DIST |
6
|
|
|
|
|
|
|
our $VERSION = '0.336'; # VERSION |
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
79889
|
use 5.010001; |
|
4
|
|
|
|
|
76
|
|
9
|
4
|
|
|
4
|
|
23
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
116
|
|
10
|
4
|
|
|
4
|
|
22
|
use warnings; |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
146
|
|
11
|
4
|
|
|
4
|
|
8230
|
use Log::ger; |
|
4
|
|
|
|
|
317
|
|
|
4
|
|
|
|
|
23
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
require Exporter; |
14
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
15
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
16
|
|
|
|
|
|
|
point |
17
|
|
|
|
|
|
|
parse_cmdline |
18
|
|
|
|
|
|
|
join_wordbreak_words |
19
|
|
|
|
|
|
|
format_completion |
20
|
|
|
|
|
|
|
); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
our %SPEC; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
$SPEC{':package'} = { |
25
|
|
|
|
|
|
|
v => 1.1, |
26
|
|
|
|
|
|
|
summary => 'Completion routines for bash shell', |
27
|
|
|
|
|
|
|
}; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
sub _expand_tilde { |
30
|
4
|
|
|
4
|
|
14
|
my ($user, $slash) = @_; |
31
|
4
|
|
|
|
|
8
|
my @ent; |
32
|
4
|
50
|
|
|
|
11
|
if (length $user) { |
33
|
0
|
|
|
|
|
0
|
@ent = getpwnam($user); |
34
|
|
|
|
|
|
|
} else { |
35
|
4
|
|
|
|
|
452
|
@ent = getpwuid($>); |
36
|
4
|
|
|
|
|
17
|
$user = $ent[0]; |
37
|
|
|
|
|
|
|
} |
38
|
4
|
50
|
|
|
|
38
|
return $ent[7] . $slash if @ent; |
39
|
0
|
|
|
|
|
0
|
"~$user$slash"; # return as-is when failed |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
sub _add_unquoted { |
43
|
4
|
|
|
4
|
|
1685
|
no warnings 'uninitialized'; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
1042
|
|
44
|
|
|
|
|
|
|
|
45
|
138
|
|
|
138
|
|
315
|
my ($word, $is_cur_word, $after_ws) = @_; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
#say "D:add_unquoted word=$word is_cur_word=$is_cur_word after_ws=$after_ws"; |
48
|
|
|
|
|
|
|
|
49
|
138
|
|
|
|
|
416
|
$word =~ s!^(~)(\w*)(/|\z) | # 1) tilde 2) username 3) optional slash |
50
|
|
|
|
|
|
|
\\(.) | # 4) escaped char |
51
|
|
|
|
|
|
|
\$(\w+) # 5) variable name |
52
|
|
|
|
|
|
|
! |
53
|
|
|
|
|
|
|
$1 ? (not($after_ws) || $is_cur_word ? "$1$2$3" : _expand_tilde($2, $3)) : |
54
|
|
|
|
|
|
|
$4 ? $4 : |
55
|
20
|
100
|
100
|
|
|
148
|
($is_cur_word ? "\$$5" : $ENV{$5}) |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
56
|
|
|
|
|
|
|
!egx; |
57
|
138
|
|
|
|
|
310
|
$word; |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
sub _add_double_quoted { |
61
|
4
|
|
|
4
|
|
34
|
no warnings 'uninitialized'; |
|
4
|
|
|
|
|
16
|
|
|
4
|
|
|
|
|
1865
|
|
62
|
|
|
|
|
|
|
|
63
|
23
|
|
|
23
|
|
58
|
my ($word, $is_cur_word) = @_; |
64
|
|
|
|
|
|
|
|
65
|
23
|
|
|
|
|
81
|
$word =~ s!\\(.) | # 1) escaped char |
66
|
|
|
|
|
|
|
\$(\w+) # 2) variable name |
67
|
|
|
|
|
|
|
! |
68
|
|
|
|
|
|
|
$1 ? $1 : |
69
|
3
|
50
|
|
|
|
19
|
($is_cur_word ? "\$$2" : $ENV{$2}) |
|
|
100
|
|
|
|
|
|
70
|
|
|
|
|
|
|
!egx; |
71
|
23
|
|
|
|
|
51
|
$word; |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
sub _add_single_quoted { |
75
|
20
|
|
|
20
|
|
47
|
my $word = shift; |
76
|
20
|
|
|
|
|
43
|
$word =~ s/\\(.)/$1/g; |
77
|
20
|
|
|
|
|
45
|
$word; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
$SPEC{point} = { |
81
|
|
|
|
|
|
|
v => 1.1, |
82
|
|
|
|
|
|
|
summary => 'Return line with point marked by a marker', |
83
|
|
|
|
|
|
|
description => <<'_', |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
This is a utility function useful for testing/debugging. `parse_cmdline()` |
86
|
|
|
|
|
|
|
expects a command-line and a cursor position (`$line`, `$point`). This routine |
87
|
|
|
|
|
|
|
expects `$line` with a marker character (by default it's the caret, `^`) and |
88
|
|
|
|
|
|
|
return (`$line`, `$point`) to feed to `parse_cmdline()`. |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
Example: |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
point("^foo") # => ("foo", 0) |
93
|
|
|
|
|
|
|
point("fo^o") # => ("foo", 2) |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
_ |
96
|
|
|
|
|
|
|
args_as => 'array', |
97
|
|
|
|
|
|
|
args => { |
98
|
|
|
|
|
|
|
cmdline => { |
99
|
|
|
|
|
|
|
summary => 'Command-line which contains a marker character', |
100
|
|
|
|
|
|
|
schema => 'str*', |
101
|
|
|
|
|
|
|
pos => 0, |
102
|
|
|
|
|
|
|
}, |
103
|
|
|
|
|
|
|
marker => { |
104
|
|
|
|
|
|
|
summary => 'Marker character', |
105
|
|
|
|
|
|
|
schema => ['str*', len=>1], |
106
|
|
|
|
|
|
|
default => '^', |
107
|
|
|
|
|
|
|
pos => 1, |
108
|
|
|
|
|
|
|
}, |
109
|
|
|
|
|
|
|
}, |
110
|
|
|
|
|
|
|
result_naked => 1, |
111
|
|
|
|
|
|
|
}; |
112
|
|
|
|
|
|
|
sub point { |
113
|
91
|
|
|
91
|
1
|
33110
|
my ($line, $marker) = @_; |
114
|
91
|
|
100
|
|
|
494
|
$marker //= '^'; |
115
|
|
|
|
|
|
|
|
116
|
91
|
|
|
|
|
235
|
my $point = index($line, $marker); |
117
|
91
|
100
|
|
|
|
255
|
die "BUG: No marker '$marker' in line <$line>" unless $point >= 0; |
118
|
90
|
|
|
|
|
577
|
$line =~ s/\Q$marker\E//; |
119
|
90
|
|
|
|
|
431
|
($line, $point); |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
$SPEC{parse_cmdline} = { |
123
|
|
|
|
|
|
|
v => 1.1, |
124
|
|
|
|
|
|
|
summary => 'Parse shell command-line for processing by completion routines', |
125
|
|
|
|
|
|
|
description => <<'_', |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
This function basically converts `COMP_LINE` (str) and `COMP_POINT` (int) into |
128
|
|
|
|
|
|
|
something like (but not exactly the same as) `COMP_WORDS` (array) and |
129
|
|
|
|
|
|
|
`COMP_CWORD` (int) that bash supplies to shell functions. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
The differences with bash are (these differences are mostly for parsing |
132
|
|
|
|
|
|
|
convenience for programs that use this routine; this comparison is made against |
133
|
|
|
|
|
|
|
bash versions 4.2-4.3): |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
1) quotes and backslashes are stripped (bash's `COMP_WORDS` contains all the |
136
|
|
|
|
|
|
|
quotes and backslashes); |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
2) quoted phrase that contains spaces, or phrase that contains escaped spaces is |
139
|
|
|
|
|
|
|
parsed as a single word. For example: |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
command "First argument" Second\ argument |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
bash would split it as (represented as Perl): |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
["command", "\"First", "argument\"", "Second\\", "argument"] |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
which is not very convenient. We parse it into: |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
["command", "First argument", "Second argument"] |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
3) variables are substituted with their values from environment variables except |
152
|
|
|
|
|
|
|
for the current word (`COMP_WORDS[COMP_CWORD]`) (bash does not perform |
153
|
|
|
|
|
|
|
variable substitution for `COMP_WORDS`). However, note that special shell |
154
|
|
|
|
|
|
|
variables that are not environment variables like `$0`, `$_`, `$IFS` will not |
155
|
|
|
|
|
|
|
be replaced correctly because bash does not export those variables for us. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
4) tildes (`~`) are expanded with user's home directory except for the current |
158
|
|
|
|
|
|
|
word (bash does not perform tilde expansion for `COMP_WORDS`); |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Caveats: |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
* Like bash, we group non-whitespace word-breaking characters into its own word. |
163
|
|
|
|
|
|
|
By default `COMP_WORDBREAKS` is: |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
"'@><=;|&(: |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
So if raw command-line is: |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
command --foo=bar http://example.com:80 mail@example.org Foo::Bar |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
then the parse result will be: |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
["command", "--foo", "=", "bar", "http", ":", "//example.com", ":", "80", "Foo", "::", "Bar"] |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
which is annoying sometimes. But we follow bash here so we can more easily |
176
|
|
|
|
|
|
|
accept input from a joined `COMP_WORDS` if we write completion bash functions, |
177
|
|
|
|
|
|
|
e.g. (in the example, `foo` is a Perl script): |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
_foo () |
180
|
|
|
|
|
|
|
{ |
181
|
|
|
|
|
|
|
local words=(${COMP_CWORDS[@]}) |
182
|
|
|
|
|
|
|
# add things to words, etc |
183
|
|
|
|
|
|
|
local point=... # calculate the new point |
184
|
|
|
|
|
|
|
COMPREPLY=( `COMP_LINE="foo ${words[@]}" COMP_POINT=$point foo` ) |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
To avoid these word-breaking characters to be split/grouped, we can escape |
188
|
|
|
|
|
|
|
them with backslash or quote them, e.g.: |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
command "http://example.com:80" Foo\:\:Bar |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
which bash will parse as: |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
["command", "\"http://example.com:80\"", "Foo\\:\\:Bar"] |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
and we parse as: |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
["command", "http://example.com:80", "Foo::Bar"] |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
* Due to the way bash parses the command line (see above), the two below are |
201
|
|
|
|
|
|
|
equivalent: |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
% cmd --foo=bar |
204
|
|
|
|
|
|
|
% cmd --foo = bar |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Because they both expand to `['--foo', '=', 'bar']`. But obviously |
207
|
|
|
|
|
|
|
does not regard the two as equivalent. |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
_ |
210
|
|
|
|
|
|
|
args_as => 'array', |
211
|
|
|
|
|
|
|
args => { |
212
|
|
|
|
|
|
|
cmdline => { |
213
|
|
|
|
|
|
|
summary => 'Command-line, defaults to COMP_LINE environment', |
214
|
|
|
|
|
|
|
schema => 'str*', |
215
|
|
|
|
|
|
|
pos => 0, |
216
|
|
|
|
|
|
|
}, |
217
|
|
|
|
|
|
|
point => { |
218
|
|
|
|
|
|
|
summary => 'Point/position to complete in command-line, '. |
219
|
|
|
|
|
|
|
'defaults to COMP_POINT', |
220
|
|
|
|
|
|
|
schema => 'int*', |
221
|
|
|
|
|
|
|
pos => 1, |
222
|
|
|
|
|
|
|
}, |
223
|
|
|
|
|
|
|
opts => { |
224
|
|
|
|
|
|
|
summary => 'Options', |
225
|
|
|
|
|
|
|
schema => 'hash*', |
226
|
|
|
|
|
|
|
description => <<'_', |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
Optional. Known options: |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
* `truncate_current_word` (bool). If set to 1, will truncate current word to the |
231
|
|
|
|
|
|
|
position of cursor, for example (`^` marks the position of cursor): |
232
|
|
|
|
|
|
|
`--vers^oo` to `--vers` instead of `--versoo`. This is more convenient when |
233
|
|
|
|
|
|
|
doing tab completion. |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
_ |
236
|
|
|
|
|
|
|
schema => 'hash*', |
237
|
|
|
|
|
|
|
pos => 2, |
238
|
|
|
|
|
|
|
}, |
239
|
|
|
|
|
|
|
}, |
240
|
|
|
|
|
|
|
result => { |
241
|
|
|
|
|
|
|
schema => ['array*', len=>2], |
242
|
|
|
|
|
|
|
description => <<'_', |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
Return a 2-element array: `[$words, $cword]`. `$words` is array of str, |
245
|
|
|
|
|
|
|
equivalent to `COMP_WORDS` provided by bash to shell functions. `$cword` is an |
246
|
|
|
|
|
|
|
integer, roughly equivalent to `COMP_CWORD` provided by bash to shell functions. |
247
|
|
|
|
|
|
|
The word to be completed is at `$words->[$cword]`. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Note that COMP_LINE includes the command name. If you want the command-line |
250
|
|
|
|
|
|
|
arguments only (like in `@ARGV`), you need to strip the first element from |
251
|
|
|
|
|
|
|
`$words` and reduce `$cword` by 1. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
_ |
255
|
|
|
|
|
|
|
}, |
256
|
|
|
|
|
|
|
result_naked => 1, |
257
|
|
|
|
|
|
|
links => [ |
258
|
|
|
|
|
|
|
], |
259
|
|
|
|
|
|
|
}; |
260
|
|
|
|
|
|
|
sub parse_cmdline { |
261
|
4
|
|
|
4
|
|
34
|
no warnings 'uninitialized'; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
11428
|
|
262
|
87
|
|
|
87
|
1
|
227
|
my ($line, $point, $opts) = @_; |
263
|
|
|
|
|
|
|
|
264
|
87
|
|
33
|
|
|
187
|
$line //= $ENV{COMP_LINE}; |
265
|
87
|
|
0
|
|
|
181
|
$point //= $ENV{COMP_POINT} // 0; |
|
|
|
33
|
|
|
|
|
266
|
|
|
|
|
|
|
|
267
|
87
|
50
|
|
|
|
183
|
die "$0: COMP_LINE not set, make sure this script is run under ". |
268
|
|
|
|
|
|
|
"bash completion (e.g. through complete -C)\n" unless defined $line; |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
log_trace "[compbash] parse_cmdline(): input: line=<$line> point=<$point>" |
271
|
87
|
50
|
|
|
|
214
|
if $ENV{COMPLETE_BASH_TRACE}; |
272
|
|
|
|
|
|
|
|
273
|
87
|
|
|
|
|
153
|
my @words; |
274
|
|
|
|
|
|
|
my $cword; |
275
|
87
|
|
|
|
|
145
|
my $pos = 0; |
276
|
87
|
|
|
|
|
128
|
my $pos_min_ws = 0; |
277
|
87
|
|
|
|
|
126
|
my $after_ws = 1; # XXX what does this variable mean? |
278
|
87
|
|
|
|
|
219
|
my $chunk; |
279
|
|
|
|
|
|
|
my $add_blank; |
280
|
87
|
|
|
|
|
0
|
my $is_cur_word; |
281
|
87
|
|
|
|
|
785
|
$line =~ s!( # 1) everything |
282
|
|
|
|
|
|
|
(")((?: \\\\|\\"|[^"])*)(?:"|\z)(\s*) | # 2) open " 3) content 4) space after |
283
|
|
|
|
|
|
|
(')((?: \\\\|\\'|[^'])*)(?:'|\z)(\s*) | # 5) open ' 6) content 7) space after |
284
|
|
|
|
|
|
|
((?: \\\\|\\"|\\'|\\=|\\\s|[^"'@><=|&\(:\s])+)(\s*) | # 8) unquoted word 9) space after |
285
|
|
|
|
|
|
|
([\@><=|&\(:]+) | # 10) non-whitespace word-breaking characters |
286
|
|
|
|
|
|
|
\s+ |
287
|
|
|
|
|
|
|
)! |
288
|
192
|
|
|
|
|
473
|
$pos += length($1); |
289
|
|
|
|
|
|
|
#say "D: \$1=<$1> \$2=<$3> \$3=<$3> \$4=<$4> \$5=<$5> \$6=<$6> \$7=<$7> \$8=<$8> \$9=<$9> \$10=<$10>"; |
290
|
|
|
|
|
|
|
#say "D:<$1> pos=$pos, point=$point, cword=$cword, after_ws=$after_ws"; |
291
|
|
|
|
|
|
|
|
292
|
192
|
100
|
100
|
|
|
1017
|
if ($2 || $5 || defined($8)) { |
|
|
100
|
100
|
|
|
|
|
293
|
|
|
|
|
|
|
# double-quoted/single-quoted/unquoted chunk |
294
|
|
|
|
|
|
|
|
295
|
181
|
100
|
|
|
|
367
|
if (not(defined $cword)) { |
296
|
157
|
100
|
|
|
|
441
|
$pos_min_ws = $pos - length($2 ? $4 : $5 ? $7 : $9); |
|
|
100
|
|
|
|
|
|
297
|
|
|
|
|
|
|
#say "D:pos_min_ws=$pos_min_ws"; |
298
|
157
|
100
|
|
|
|
364
|
if ($point <= $pos_min_ws) { |
|
|
100
|
|
|
|
|
|
299
|
72
|
100
|
|
|
|
173
|
$cword = @words - ($after_ws ? 0 : 1); |
300
|
|
|
|
|
|
|
} elsif ($point < $pos) { |
301
|
2
|
50
|
|
|
|
7
|
$cword = @words + 1 - ($after_ws ? 0 : 1); |
302
|
2
|
|
|
|
|
4
|
$add_blank = 1; |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
181
|
100
|
|
|
|
310
|
if ($after_ws) { |
307
|
158
|
|
100
|
|
|
460
|
$is_cur_word = defined($cword) && $cword==@words; |
308
|
|
|
|
|
|
|
} else { |
309
|
23
|
|
100
|
|
|
73
|
$is_cur_word = defined($cword) && $cword==@words-1; |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
#say "D:is_cur_word=$is_cur_word"; |
312
|
181
|
100
|
|
|
|
584
|
$chunk = |
|
|
100
|
|
|
|
|
|
313
|
|
|
|
|
|
|
$2 ? _add_double_quoted($3, $is_cur_word) : |
314
|
|
|
|
|
|
|
$5 ? _add_single_quoted($6) : |
315
|
|
|
|
|
|
|
_add_unquoted($8, $is_cur_word, $after_ws); |
316
|
181
|
100
|
66
|
|
|
446
|
if ($opts && $opts->{truncate_current_word} && |
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
317
|
|
|
|
|
|
|
$is_cur_word && $pos > $point) { |
318
|
9
|
|
|
|
|
63
|
$chunk = substr( |
319
|
|
|
|
|
|
|
$chunk, 0, length($chunk)-($pos_min_ws-$point)); |
320
|
|
|
|
|
|
|
#say "D:truncating current word to <$chunk>"; |
321
|
|
|
|
|
|
|
} |
322
|
181
|
100
|
|
|
|
311
|
if ($after_ws) { |
323
|
158
|
|
|
|
|
314
|
push @words, $chunk; |
324
|
|
|
|
|
|
|
} else { |
325
|
23
|
|
|
|
|
45
|
$words[-1] .= $chunk; |
326
|
|
|
|
|
|
|
} |
327
|
181
|
100
|
|
|
|
339
|
if ($add_blank) { |
328
|
2
|
|
|
|
|
4
|
push @words, ''; |
329
|
2
|
|
|
|
|
4
|
$add_blank = 0; |
330
|
|
|
|
|
|
|
} |
331
|
181
|
100
|
|
|
|
1107
|
$after_ws = ($2 ? $4 : $5 ? $7 : $9) ? 1:0; |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
} elsif ($10) { |
334
|
|
|
|
|
|
|
# non-whitespace word-breaking characters |
335
|
10
|
|
|
|
|
24
|
push @words, $10; |
336
|
10
|
|
|
|
|
42
|
$after_ws = 1; |
337
|
|
|
|
|
|
|
} else { |
338
|
|
|
|
|
|
|
# whitespace |
339
|
1
|
|
|
|
|
11
|
$after_ws = 1; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
!egx; |
342
|
|
|
|
|
|
|
|
343
|
87
|
|
66
|
|
|
250
|
$cword //= @words; |
344
|
87
|
|
100
|
|
|
217
|
$words[$cword] //= ''; |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
log_trace "[compbash] parse_cmdline(): result: words=%s, cword=%s", \@words, $cword |
347
|
87
|
50
|
|
|
|
189
|
if $ENV{COMPLETE_BASH_TRACE}; |
348
|
|
|
|
|
|
|
|
349
|
87
|
|
|
|
|
735
|
[\@words, $cword]; |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
$SPEC{join_wordbreak_words} = { |
353
|
|
|
|
|
|
|
v => 1.1, |
354
|
|
|
|
|
|
|
summary => 'Post-process parse_cmdline() result by joining some words', |
355
|
|
|
|
|
|
|
description => <<'_', |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
`parse_cmdline()`, like bash, splits some characters that are considered as |
358
|
|
|
|
|
|
|
word-breaking characters: |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
"'@><=;|&(: |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
So if command-line is: |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
command --module=Data::Dump bob@example.org |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
then they will be parsed as: |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
["command", "--module", "=", "Data", "::", "Dump", "bob", '@', "example.org"] |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
Normally in Perl applications, we want `:`, `@` to be part of word. So this |
371
|
|
|
|
|
|
|
routine will convert the above into: |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
["command", "--module=Data::Dump", 'bob@example.org'] |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
_ |
376
|
|
|
|
|
|
|
}; |
377
|
|
|
|
|
|
|
sub join_wordbreak_words { |
378
|
3
|
|
|
3
|
1
|
4514
|
my ($words, $cword) = @_; |
379
|
3
|
|
|
|
|
5
|
my $new_words = []; |
380
|
3
|
|
|
|
|
20
|
my $i = -1; |
381
|
3
|
|
|
|
|
10
|
while (++$i < @$words) { |
382
|
15
|
|
|
|
|
21
|
my $w = $words->[$i]; |
383
|
15
|
100
|
|
|
|
43
|
if ($w =~ /\A[\@=:]+\z/) { |
384
|
5
|
100
|
66
|
|
|
26
|
if (@$new_words and $#$new_words != $cword) { |
385
|
3
|
|
|
|
|
6
|
$new_words->[-1] .= $w; |
386
|
3
|
50
|
66
|
|
|
12
|
$cword-- if $cword >= $i || $cword >= @$new_words; |
387
|
|
|
|
|
|
|
} else { |
388
|
2
|
|
|
|
|
5
|
push @$new_words, $w; |
389
|
|
|
|
|
|
|
} |
390
|
5
|
50
|
|
|
|
13
|
if ($i+1 < @$words) { |
391
|
5
|
|
|
|
|
8
|
$i++; |
392
|
5
|
|
|
|
|
9
|
$new_words->[-1] .= $words->[$i]; |
393
|
5
|
100
|
100
|
|
|
21
|
$cword-- if $cword >= $i || $cword >= @$new_words; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} else { |
396
|
10
|
|
|
|
|
26
|
push @$new_words, $w; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
log_trace "[compbash] join_wordbreak_words(): result: words=%s, cword=%d", $new_words, $cword |
400
|
3
|
50
|
|
|
|
8
|
if $ENV{COMPLETE_BASH_TRACE}; |
401
|
3
|
|
|
|
|
23
|
[$new_words, $cword]; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
sub _terminal_width { |
405
|
|
|
|
|
|
|
# XXX need to cache? |
406
|
15
|
50
|
|
15
|
|
26
|
if (eval { require Term::Size; 1 }) { |
|
15
|
|
|
|
|
576
|
|
|
15
|
|
|
|
|
623
|
|
407
|
15
|
|
|
|
|
148
|
my ($cols, undef) = Term::Size::chars(*STDOUT{IO}); |
408
|
15
|
|
50
|
|
|
89
|
$cols // 80; |
409
|
|
|
|
|
|
|
} else { |
410
|
0
|
|
0
|
|
|
0
|
$ENV{COLUMNS} // 80; |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
# given terminal width & number of columns, calculate column width |
415
|
|
|
|
|
|
|
sub _column_width { |
416
|
14
|
|
|
14
|
|
27
|
my ($terminal_width, $num_columns) = @_; |
417
|
14
|
50
|
33
|
|
|
48
|
if (defined $num_columns && $num_columns > 0) { |
418
|
0
|
|
|
|
|
0
|
int( ($terminal_width - ($num_columns-1)*2) / $num_columns ) - 1; |
419
|
|
|
|
|
|
|
} else { |
420
|
14
|
|
|
|
|
26
|
undef; |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
# given terminal width & column width, calculate number of columns |
425
|
|
|
|
|
|
|
sub _num_columns { |
426
|
0
|
|
|
0
|
|
0
|
my ($terminal_width, $column_width) = @_; |
427
|
0
|
|
|
|
|
0
|
my $n = int( ($terminal_width+2) / ($column_width+2) ); |
428
|
0
|
0
|
|
|
|
0
|
$n >= 1 ? $n : 1; |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
$SPEC{format_completion} = { |
432
|
|
|
|
|
|
|
v => 1.1, |
433
|
|
|
|
|
|
|
summary => 'Format completion for output (for shell)', |
434
|
|
|
|
|
|
|
description => <<'_', |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
Bash accepts completion reply in the form of one entry per line to STDOUT. Some |
437
|
|
|
|
|
|
|
characters will need to be escaped. This function helps you do the formatting, |
438
|
|
|
|
|
|
|
with some options. |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
This function accepts completion answer structure as described in the `Complete` |
441
|
|
|
|
|
|
|
POD. Aside from `words`, this function also recognizes these keys: |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
_ |
444
|
|
|
|
|
|
|
args_as => 'array', |
445
|
|
|
|
|
|
|
args => { |
446
|
|
|
|
|
|
|
completion => { |
447
|
|
|
|
|
|
|
summary => 'Completion answer structure', |
448
|
|
|
|
|
|
|
description => <<'_', |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Either an array or hash. See function description for more details. |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
_ |
453
|
|
|
|
|
|
|
schema=>['any*' => of => ['hash*', 'array*']], |
454
|
|
|
|
|
|
|
req=>1, |
455
|
|
|
|
|
|
|
pos=>0, |
456
|
|
|
|
|
|
|
}, |
457
|
|
|
|
|
|
|
opts => { |
458
|
|
|
|
|
|
|
summary => 'Specify options', |
459
|
|
|
|
|
|
|
schema=>'hash*', |
460
|
|
|
|
|
|
|
pos=>1, |
461
|
|
|
|
|
|
|
description => <<'_', |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Known options: |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
* as |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
Either `string` (the default) or `array` (to return array of lines instead of |
468
|
|
|
|
|
|
|
the lines joined together). Returning array is useful if you are doing |
469
|
|
|
|
|
|
|
completion inside `Term::ReadLine`, for example, where the library expects an |
470
|
|
|
|
|
|
|
array. |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
* esc_mode |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
Escaping mode for entries. Either `default` (most nonalphanumeric characters |
475
|
|
|
|
|
|
|
will be escaped), `shellvar` (like `default`, but dollar sign `$` will also be |
476
|
|
|
|
|
|
|
escaped, convenient when completing environment variables for example), |
477
|
|
|
|
|
|
|
`filename` (currently equals to `default`), `option` (currently equals to |
478
|
|
|
|
|
|
|
`default`), or `none` (no escaping will be done). |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
* word |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
A workaround. String. For now, see source code for more details. |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
* show_summaries |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
Whether to show item's summaries. Boolean, default is from |
487
|
|
|
|
|
|
|
COMPLETE_BASH_SHOW_SUMMARIES environment variable or 1. |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
An answer item contain summary, which is a short description about the item, |
490
|
|
|
|
|
|
|
e.g.: |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
[{word=>"-a" , summary=>"Show hidden files"}, |
493
|
|
|
|
|
|
|
{word=>"-l" , summary=>"Show details"}, |
494
|
|
|
|
|
|
|
{word=>"--sort", summary=>"Specify sort order"}], |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
When summaries are not shown, user will just be seeing something like: |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
-a |
499
|
|
|
|
|
|
|
-l |
500
|
|
|
|
|
|
|
--sort |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
But when summaries are shown, user will see: |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
-a -- Show hidden files |
505
|
|
|
|
|
|
|
-l -- Show details |
506
|
|
|
|
|
|
|
--sort -- Specify sort order |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
which is quite helpful. |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
* workaround_with_wordbreaks |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
Boolean. Default is true. See source code for more details. |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
_ |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
}, |
517
|
|
|
|
|
|
|
}, |
518
|
|
|
|
|
|
|
result => { |
519
|
|
|
|
|
|
|
summary => 'Formatted string (or array, if `as` is set to `array`)', |
520
|
|
|
|
|
|
|
schema => ['any*' => of => ['str*', 'array*']], |
521
|
|
|
|
|
|
|
}, |
522
|
|
|
|
|
|
|
result_naked => 1, |
523
|
|
|
|
|
|
|
}; |
524
|
|
|
|
|
|
|
sub format_completion { |
525
|
15
|
|
|
15
|
1
|
24145
|
my ($hcomp, $opts) = @_; |
526
|
|
|
|
|
|
|
|
527
|
15
|
|
100
|
|
|
73
|
$opts //= {}; |
528
|
|
|
|
|
|
|
|
529
|
15
|
100
|
|
|
|
58
|
$hcomp = {words=>$hcomp} unless ref($hcomp) eq 'HASH'; |
530
|
15
|
|
|
|
|
28
|
my $words = $hcomp->{words}; |
531
|
15
|
|
100
|
|
|
48
|
my $as = $opts->{as} // 'string'; |
532
|
|
|
|
|
|
|
# 'escmode' key is deprecated (Complete 0.11-) and will be removed later |
533
|
|
|
|
|
|
|
my $esc_mode = $opts->{esc_mode} // $ENV{COMPLETE_BASH_DEFAULT_ESC_MODE} // |
534
|
15
|
|
66
|
|
|
58
|
'default'; |
|
|
|
50
|
|
|
|
|
535
|
15
|
|
|
|
|
24
|
my $path_sep = $hcomp->{path_sep}; |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# we keep the original words (before formatted with summaries) when we want |
538
|
|
|
|
|
|
|
# to use fzf instead of passing to bash directly |
539
|
15
|
|
|
|
|
40
|
my @words; |
540
|
|
|
|
|
|
|
my @summaries; |
541
|
15
|
|
|
|
|
0
|
my @res; |
542
|
15
|
|
|
|
|
0
|
my $has_summary; |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
my $code_return_message = sub { |
545
|
|
|
|
|
|
|
# display a message instead of list of words. we send " " (ASCII space) |
546
|
|
|
|
|
|
|
# which bash does not display, so we can display a line of message while |
547
|
|
|
|
|
|
|
# the user does not get the message as the completion. I've also tried |
548
|
|
|
|
|
|
|
# \000 to \037 instead of space (\040) but nothing works better. |
549
|
1
|
|
|
1
|
|
2
|
my $msg = shift; |
550
|
1
|
50
|
|
|
|
4
|
if ($msg =~ /\A /) { |
551
|
0
|
|
|
|
|
0
|
$msg =~ s/\A +//; |
552
|
0
|
0
|
|
|
|
0
|
$msg = " (empty message)" unless length $msg; |
553
|
|
|
|
|
|
|
} |
554
|
1
|
|
|
|
|
3
|
return (sprintf("%-"._terminal_width()."s", $msg), " "); |
555
|
15
|
|
|
|
|
62
|
}; |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
FORMAT_MESSAGE: |
558
|
|
|
|
|
|
|
# display a message instead of list of words. we send " " (ASCII space) |
559
|
|
|
|
|
|
|
# which bash does not display, so we can display a line of message while the |
560
|
|
|
|
|
|
|
# user does not get the message as the completion. I've also tried \000 to |
561
|
|
|
|
|
|
|
# \037 instead of space (\040) but nothing works better. |
562
|
15
|
100
|
|
|
|
41
|
if (defined $hcomp->{message}) { |
563
|
1
|
|
|
|
|
5
|
@res = $code_return_message->($hcomp->{message}); |
564
|
1
|
|
|
|
|
7
|
goto RETURN_RES; |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
WORKAROUND_PREVENT_BASH_FROM_INSERTING_SPACE: |
568
|
|
|
|
|
|
|
{ |
569
|
14
|
100
|
|
|
|
20
|
last unless @$words == 1; |
|
14
|
|
|
|
|
31
|
|
570
|
9
|
100
|
|
|
|
20
|
if (defined $path_sep) { |
571
|
4
|
|
|
|
|
45
|
my $re = qr/\Q$path_sep\E\z/; |
572
|
4
|
|
|
|
|
8
|
my $word; |
573
|
4
|
100
|
|
|
|
12
|
if (ref $words->[0] eq 'HASH') { |
574
|
|
|
|
|
|
|
$words = [$words->[0], {word=>"$words->[0]{word} "}] if |
575
|
1
|
50
|
|
|
|
11
|
$words->[0]{word} =~ $re; |
576
|
|
|
|
|
|
|
} else { |
577
|
3
|
100
|
|
|
|
26
|
$words = [$words->[0], "$words->[0] "] |
578
|
|
|
|
|
|
|
if $words->[0] =~ $re; |
579
|
|
|
|
|
|
|
} |
580
|
4
|
|
|
|
|
14
|
last; |
581
|
|
|
|
|
|
|
} |
582
|
|
|
|
|
|
|
|
583
|
5
|
50
|
66
|
|
|
28
|
if ($hcomp->{is_partial} || |
|
|
|
66
|
|
|
|
|
584
|
|
|
|
|
|
|
ref $words->[0] eq 'HASH' && $words->[0]{is_partial}) { |
585
|
2
|
100
|
|
|
|
7
|
if (ref $words->[0] eq 'HASH') { |
586
|
1
|
|
|
|
|
6
|
$words = [$words->[0], {word=>"$words->[0]{word} "}]; |
587
|
|
|
|
|
|
|
} else { |
588
|
1
|
|
|
|
|
5
|
$words = [$words->[0], "$words->[0] "]; |
589
|
|
|
|
|
|
|
} |
590
|
2
|
|
|
|
|
4
|
last; |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
WORKAROUND_WITH_WORDBREAKS: |
595
|
|
|
|
|
|
|
# this is a workaround. since bash breaks words using characters in |
596
|
|
|
|
|
|
|
# $COMP_WORDBREAKS, which by default is "'@><=;|&(: this presents a problem |
597
|
|
|
|
|
|
|
# we often encounter: if we want to provide with a list of strings |
598
|
|
|
|
|
|
|
# containing say ':', most often Perl modules/packages, if user types e.g. |
599
|
|
|
|
|
|
|
# "Text::AN" and we provide completion ["Text::ANSI"] then bash will change |
600
|
|
|
|
|
|
|
# the word at cursor to become "Text::Text::ANSI" since it sees the current |
601
|
|
|
|
|
|
|
# word as "AN" and not "Text::AN". the workaround is to chop /^Text::/ from |
602
|
|
|
|
|
|
|
# completion answers. btw, we actually chop /^text::/i to handle |
603
|
|
|
|
|
|
|
# case-insensitive matching, although this does not have the ability to |
604
|
|
|
|
|
|
|
# replace the current word (e.g. if we type 'text::an' then bash can only |
605
|
|
|
|
|
|
|
# replace the current word 'an' with 'ANSI). |
606
|
|
|
|
|
|
|
{ |
607
|
14
|
50
|
50
|
|
|
20
|
last unless $opts->{workaround_with_wordbreaks} // 1; |
|
14
|
|
|
|
|
50
|
|
608
|
14
|
50
|
|
|
|
33
|
last unless defined $opts->{word}; |
609
|
|
|
|
|
|
|
|
610
|
0
|
0
|
|
|
|
0
|
if ($opts->{word} =~ s/(.+[\@><=;|&\(:])//) { |
611
|
0
|
|
|
|
|
0
|
my $prefix = $1; |
612
|
0
|
|
|
|
|
0
|
for (@$words) { |
613
|
0
|
0
|
|
|
|
0
|
if (ref($_) eq 'HASH') { |
614
|
0
|
|
|
|
|
0
|
$_->{word} =~ s/\A\Q$prefix\E//i; |
615
|
|
|
|
|
|
|
} else { |
616
|
0
|
|
|
|
|
0
|
s/\A\Q$prefix\E//i; |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
ESCAPE_WORDS: |
623
|
14
|
|
|
|
|
27
|
for my $entry (@$words) { |
624
|
26
|
100
|
|
|
|
51
|
my $word = ref($entry) eq 'HASH' ? $entry->{word} : $entry; |
625
|
26
|
100
|
50
|
|
|
77
|
my $summary = (ref($entry) eq 'HASH' ? $entry->{summary} : undef) // ''; |
626
|
26
|
100
|
|
|
|
50
|
if ($esc_mode eq 'shellvar') { |
|
|
100
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# escape $ also |
628
|
1
|
|
|
|
|
10
|
$word =~ s!([^A-Za-z0-9,+._/:~-])!\\$1!g; |
629
|
|
|
|
|
|
|
} elsif ($esc_mode eq 'none') { |
630
|
|
|
|
|
|
|
# no escaping |
631
|
|
|
|
|
|
|
} else { |
632
|
|
|
|
|
|
|
# default |
633
|
24
|
|
|
|
|
88
|
$word =~ s!([^A-Za-z0-9,+._/:\$~-])!\\$1!g; |
634
|
|
|
|
|
|
|
} |
635
|
26
|
|
|
|
|
46
|
push @words, $word; |
636
|
26
|
|
|
|
|
38
|
push @summaries, $summary; |
637
|
26
|
50
|
|
|
|
77
|
$has_summary = 1 if length $summary; |
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
|
640
|
14
|
|
50
|
|
|
47
|
my $summary_align = $ENV{COMPLETE_BASH_SUMMARY_ALIGN} // 'left'; |
641
|
14
|
|
50
|
|
|
34
|
my $max_columns = $ENV{COMPLETE_BASH_MAX_COLUMNS} // 0; |
642
|
14
|
|
|
|
|
31
|
my $terminal_width = _terminal_width(); |
643
|
14
|
|
|
|
|
32
|
my $column_width = _column_width($terminal_width, $max_columns); |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
#warn "terminal_width=$terminal_width, column_width=".($column_width // 'undef')."\n"; |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
FORMAT_SUMMARIES: { |
648
|
14
|
|
|
|
|
21
|
@res = @words; |
|
14
|
|
|
|
|
35
|
|
649
|
14
|
100
|
|
|
|
34
|
last if @words <= 1; |
650
|
10
|
50
|
|
|
|
36
|
last unless $has_summary; |
651
|
|
|
|
|
|
|
last unless $opts->{show_summaries} // |
652
|
0
|
0
|
0
|
|
|
0
|
$ENV{COMPLETE_BASH_SHOW_SUMMARIES} // 1; |
|
|
|
0
|
|
|
|
|
653
|
0
|
|
|
|
|
0
|
my $max_entry_width = 8; |
654
|
0
|
|
|
|
|
0
|
my $max_summ_width = 0; |
655
|
0
|
|
|
|
|
0
|
for (0..$#words) { |
656
|
0
|
0
|
|
|
|
0
|
$max_entry_width = length $words[$_] |
657
|
|
|
|
|
|
|
if $max_entry_width < length $words[$_]; |
658
|
0
|
0
|
|
|
|
0
|
$max_summ_width = length $summaries[$_] |
659
|
|
|
|
|
|
|
if $max_summ_width < length $summaries[$_]; |
660
|
|
|
|
|
|
|
} |
661
|
|
|
|
|
|
|
#warn "max_entry_width=$max_entry_width, max_summ_width=$max_summ_width\n"; |
662
|
0
|
0
|
|
|
|
0
|
if ($summary_align eq 'right') { |
663
|
|
|
|
|
|
|
# if we are aligning summary to the right, we want to fill column |
664
|
|
|
|
|
|
|
# width width |
665
|
0
|
0
|
|
|
|
0
|
if ($max_columns <= 0) { |
666
|
0
|
|
|
|
|
0
|
$max_columns = _num_columns( |
667
|
|
|
|
|
|
|
$terminal_width, ($max_entry_width + 2 + $max_summ_width)); |
668
|
|
|
|
|
|
|
} |
669
|
0
|
|
|
|
|
0
|
$column_width = _column_width($terminal_width, $max_columns); |
670
|
0
|
|
|
|
|
0
|
my $new_max_summ_width = $column_width - 2 - $max_entry_width; |
671
|
0
|
0
|
|
|
|
0
|
$max_summ_width = $new_max_summ_width |
672
|
|
|
|
|
|
|
if $max_summ_width < $new_max_summ_width; |
673
|
|
|
|
|
|
|
#warn "max_columns=$max_columns, column_width=$column_width, max_summ_width=$max_summ_width\n"; |
674
|
|
|
|
|
|
|
} |
675
|
|
|
|
|
|
|
|
676
|
0
|
|
|
|
|
0
|
for (0..$#words) { |
677
|
0
|
|
|
|
|
0
|
my $summary = $summaries[$_]; |
678
|
0
|
0
|
|
|
|
0
|
if (length $summary) { |
679
|
0
|
0
|
|
|
|
0
|
$res[$_] = sprintf( |
680
|
|
|
|
|
|
|
"%-${max_entry_width}s |%". |
681
|
|
|
|
|
|
|
($summary_align eq 'right' ? $max_summ_width : '')."s", |
682
|
|
|
|
|
|
|
$words[$_], $summary); |
683
|
|
|
|
|
|
|
} |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
} # FORMAT_SUMMARIES |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
MAX_COLUMNS: { |
688
|
14
|
50
|
|
|
|
19
|
last unless $max_columns > 0; |
|
14
|
|
|
|
|
29
|
|
689
|
0
|
|
|
|
|
0
|
my $max_entry_width = 0; |
690
|
0
|
|
|
|
|
0
|
for (@res) { |
691
|
0
|
0
|
|
|
|
0
|
$max_entry_width = length if $max_entry_width < length; |
692
|
|
|
|
|
|
|
} |
693
|
0
|
0
|
|
|
|
0
|
last if $max_entry_width >= $column_width; |
694
|
0
|
|
|
|
|
0
|
for (@res) { |
695
|
0
|
0
|
|
|
|
0
|
$_ .= " " x ($column_width - length) if $column_width > length; |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
PASS_TO_FZF: { |
700
|
14
|
50
|
|
|
|
20
|
last if $ENV{INSIDE_EMACS}; |
|
14
|
|
|
|
|
32
|
|
701
|
14
|
50
|
|
|
|
29
|
last unless $ENV{COMPLETE_BASH_FZF}; |
702
|
0
|
|
0
|
|
|
0
|
my $items = $ENV{COMPLETE_BASH_FZF_ITEMS} // 100; |
703
|
0
|
0
|
|
|
|
0
|
last unless @words >= $items; |
704
|
|
|
|
|
|
|
|
705
|
0
|
|
|
|
|
0
|
require File::Which; |
706
|
0
|
0
|
|
|
|
0
|
unless (File::Which::which("fzf")) { |
707
|
|
|
|
|
|
|
#@res = $code_return_message->("Cannot find fzf to filter ". |
708
|
|
|
|
|
|
|
# scalar(@words)." items"); |
709
|
0
|
|
|
|
|
0
|
goto RETURN_RES; |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
|
712
|
0
|
|
|
|
|
0
|
require IPC::Open2; |
713
|
0
|
|
|
|
|
0
|
local *CHLD_OUT; |
714
|
0
|
|
|
|
|
0
|
local *CHLD_IN; |
715
|
|
|
|
|
|
|
my $pid = IPC::Open2::open2( |
716
|
|
|
|
|
|
|
\*CHLD_OUT, \*CHLD_IN, "fzf", "-m", "-d:", "--with-nth=2..") |
717
|
0
|
0
|
|
|
|
0
|
or do { |
718
|
0
|
|
|
|
|
0
|
@res = $code_return_message->("Cannot open fzf to filter ". |
719
|
|
|
|
|
|
|
scalar(@words)." items"); |
720
|
0
|
|
|
|
|
0
|
goto RETURN_RES; |
721
|
|
|
|
|
|
|
}; |
722
|
|
|
|
|
|
|
|
723
|
0
|
|
|
|
|
0
|
print CHLD_IN map { "$_:$res[$_]\n" } 0..$#res; |
|
0
|
|
|
|
|
0
|
|
724
|
0
|
|
|
|
|
0
|
close CHLD_IN; |
725
|
|
|
|
|
|
|
|
726
|
0
|
|
|
|
|
0
|
my @res_words; |
727
|
0
|
|
|
|
|
0
|
while () { |
728
|
0
|
0
|
|
|
|
0
|
my ($index) = /\A([0-9]+)\:/ or next; |
729
|
0
|
|
|
|
|
0
|
push @res_words, $words[$index]; |
730
|
|
|
|
|
|
|
} |
731
|
0
|
0
|
|
|
|
0
|
if (@res_words) { |
732
|
0
|
|
|
|
|
0
|
@res = join(" ", @res_words); |
733
|
|
|
|
|
|
|
} else { |
734
|
0
|
|
|
|
|
0
|
@res = (); |
735
|
|
|
|
|
|
|
} |
736
|
0
|
|
|
|
|
0
|
waitpid($pid, 0); |
737
|
|
|
|
|
|
|
} |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
RETURN_RES: |
740
|
|
|
|
|
|
|
#use Data::Dump; warn Data::Dump::dump(\@res); |
741
|
15
|
100
|
|
|
|
25
|
if ($as eq 'array') { |
742
|
1
|
|
|
|
|
12
|
return \@res; |
743
|
|
|
|
|
|
|
} else { |
744
|
14
|
|
|
|
|
27
|
return join("", map {($_, "\n")} @res); |
|
26
|
|
|
|
|
224
|
|
745
|
|
|
|
|
|
|
} |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
1; |
749
|
|
|
|
|
|
|
# ABSTRACT: Completion routines for bash shell |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
__END__ |