1 |
|
|
/* $OpenBSD: filecomplete.c,v 1.12 2016/04/11 20:43:33 schwarze Exp $ */ |
2 |
|
|
/* $NetBSD: filecomplete.c,v 1.22 2010/12/02 04:42:46 dholland Exp $ */ |
3 |
|
|
|
4 |
|
|
/*- |
5 |
|
|
* Copyright (c) 1997 The NetBSD Foundation, Inc. |
6 |
|
|
* All rights reserved. |
7 |
|
|
* |
8 |
|
|
* This code is derived from software contributed to The NetBSD Foundation |
9 |
|
|
* by Jaromir Dolecek. |
10 |
|
|
* |
11 |
|
|
* Redistribution and use in source and binary forms, with or without |
12 |
|
|
* modification, are permitted provided that the following conditions |
13 |
|
|
* are met: |
14 |
|
|
* 1. Redistributions of source code must retain the above copyright |
15 |
|
|
* notice, this list of conditions and the following disclaimer. |
16 |
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
17 |
|
|
* notice, this list of conditions and the following disclaimer in the |
18 |
|
|
* documentation and/or other materials provided with the distribution. |
19 |
|
|
* |
20 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
21 |
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
22 |
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
23 |
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
24 |
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
25 |
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
26 |
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
27 |
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
28 |
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
29 |
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
30 |
|
|
* POSSIBILITY OF SUCH DAMAGE. |
31 |
|
|
*/ |
32 |
|
|
|
33 |
|
|
#include "config.h" |
34 |
|
|
|
35 |
|
|
#include <sys/types.h> |
36 |
|
|
#include <sys/stat.h> |
37 |
|
|
#include <dirent.h> |
38 |
|
|
#include <errno.h> |
39 |
|
|
#include <fcntl.h> |
40 |
|
|
#include <limits.h> |
41 |
|
|
#include <pwd.h> |
42 |
|
|
#include <stdio.h> |
43 |
|
|
#include <stdlib.h> |
44 |
|
|
#include <string.h> |
45 |
|
|
#include <unistd.h> |
46 |
|
|
|
47 |
|
|
#include "el.h" |
48 |
|
|
#include "filecomplete.h" |
49 |
|
|
|
50 |
|
|
static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{("; |
51 |
|
|
|
52 |
|
|
/********************************/ |
53 |
|
|
/* completion functions */ |
54 |
|
|
|
55 |
|
|
/* |
56 |
|
|
* does tilde expansion of strings of type ``~user/foo'' |
57 |
|
|
* if ``user'' isn't valid user name or ``txt'' doesn't start |
58 |
|
|
* w/ '~', returns pointer to strdup()ed copy of ``txt'' |
59 |
|
|
* |
60 |
|
|
* it's the caller's responsibility to free() the returned string |
61 |
|
|
*/ |
62 |
|
|
char * |
63 |
|
|
fn_tilde_expand(const char *txt) |
64 |
|
|
{ |
65 |
|
|
struct passwd pwres, *pass; |
66 |
|
|
char *temp; |
67 |
|
|
size_t tempsz, len = 0; |
68 |
|
|
char pwbuf[1024]; |
69 |
|
|
|
70 |
|
|
if (txt[0] != '~') |
71 |
|
|
return strdup(txt); |
72 |
|
|
|
73 |
|
|
temp = strchr(txt + 1, '/'); |
74 |
|
|
if (temp == NULL) { |
75 |
|
|
temp = strdup(txt + 1); |
76 |
|
|
if (temp == NULL) |
77 |
|
|
return NULL; |
78 |
|
|
} else { |
79 |
|
|
len = temp - txt + 1; /* text until string after slash */ |
80 |
|
|
temp = malloc(len); |
81 |
|
|
if (temp == NULL) |
82 |
|
|
return NULL; |
83 |
|
|
(void)strncpy(temp, txt + 1, len - 2); |
84 |
|
|
temp[len - 2] = '\0'; |
85 |
|
|
} |
86 |
|
|
if (temp[0] == 0) { |
87 |
|
|
if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), &pass) != 0) |
88 |
|
|
pass = NULL; |
89 |
|
|
} else { |
90 |
|
|
if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0) |
91 |
|
|
pass = NULL; |
92 |
|
|
} |
93 |
|
|
free(temp); /* value no more needed */ |
94 |
|
|
if (pass == NULL) |
95 |
|
|
return strdup(txt); |
96 |
|
|
|
97 |
|
|
/* update pointer txt to point at string immedially following */ |
98 |
|
|
/* first slash */ |
99 |
|
|
txt += len; |
100 |
|
|
|
101 |
|
|
tempsz = strlen(pass->pw_dir) + 1 + strlen(txt) + 1; |
102 |
|
|
temp = malloc(tempsz); |
103 |
|
|
if (temp == NULL) |
104 |
|
|
return NULL; |
105 |
|
|
(void)snprintf(temp, tempsz, "%s/%s", pass->pw_dir, txt); |
106 |
|
|
|
107 |
|
|
return temp; |
108 |
|
|
} |
109 |
|
|
|
110 |
|
|
|
111 |
|
|
/* |
112 |
|
|
* return first found file name starting by the ``text'' or NULL if no |
113 |
|
|
* such file can be found |
114 |
|
|
* value of ``state'' is ignored |
115 |
|
|
* |
116 |
|
|
* it's the caller's responsibility to free the returned string |
117 |
|
|
*/ |
118 |
|
|
char * |
119 |
|
|
fn_filename_completion_function(const char *text, int state) |
120 |
|
|
{ |
121 |
|
|
static DIR *dir = NULL; |
122 |
|
|
static char *filename = NULL, *dirname = NULL, *dirpath = NULL; |
123 |
|
|
static size_t filename_len = 0; |
124 |
|
|
struct dirent *entry; |
125 |
|
|
char *temp; |
126 |
|
|
size_t tempsz, len; |
127 |
|
|
|
128 |
|
|
if (state == 0 || dir == NULL) { |
129 |
|
|
temp = strrchr(text, '/'); |
130 |
|
|
if (temp) { |
131 |
|
|
size_t sz = strlen(temp + 1) + 1; |
132 |
|
|
char *nptr; |
133 |
|
|
temp++; |
134 |
|
|
nptr = realloc(filename, sz); |
135 |
|
|
if (nptr == NULL) { |
136 |
|
|
free(filename); |
137 |
|
|
filename = NULL; |
138 |
|
|
return NULL; |
139 |
|
|
} |
140 |
|
|
filename = nptr; |
141 |
|
|
(void)strlcpy(filename, temp, sz); |
142 |
|
|
len = temp - text; /* including last slash */ |
143 |
|
|
|
144 |
|
|
nptr = realloc(dirname, len + 1); |
145 |
|
|
if (nptr == NULL) { |
146 |
|
|
free(dirname); |
147 |
|
|
dirname = NULL; |
148 |
|
|
return NULL; |
149 |
|
|
} |
150 |
|
|
dirname = nptr; |
151 |
|
|
(void)strncpy(dirname, text, len); |
152 |
|
|
dirname[len] = '\0'; |
153 |
|
|
} else { |
154 |
|
|
free(filename); |
155 |
|
|
if (*text == 0) |
156 |
|
|
filename = NULL; |
157 |
|
|
else { |
158 |
|
|
filename = strdup(text); |
159 |
|
|
if (filename == NULL) |
160 |
|
|
return NULL; |
161 |
|
|
} |
162 |
|
|
free(dirname); |
163 |
|
|
dirname = NULL; |
164 |
|
|
} |
165 |
|
|
|
166 |
|
|
if (dir != NULL) { |
167 |
|
|
(void)closedir(dir); |
168 |
|
|
dir = NULL; |
169 |
|
|
} |
170 |
|
|
|
171 |
|
|
/* support for ``~user'' syntax */ |
172 |
|
|
|
173 |
|
|
free(dirpath); |
174 |
|
|
dirpath = NULL; |
175 |
|
|
if (dirname == NULL) { |
176 |
|
|
if ((dirname = strdup("")) == NULL) |
177 |
|
|
return NULL; |
178 |
|
|
dirpath = strdup("./"); |
179 |
|
|
} else if (*dirname == '~') |
180 |
|
|
dirpath = fn_tilde_expand(dirname); |
181 |
|
|
else |
182 |
|
|
dirpath = strdup(dirname); |
183 |
|
|
|
184 |
|
|
if (dirpath == NULL) |
185 |
|
|
return NULL; |
186 |
|
|
|
187 |
|
|
dir = opendir(dirpath); |
188 |
|
|
if (!dir) |
189 |
|
|
return NULL; /* cannot open the directory */ |
190 |
|
|
|
191 |
|
|
/* will be used in cycle */ |
192 |
|
|
filename_len = filename ? strlen(filename) : 0; |
193 |
|
|
} |
194 |
|
|
|
195 |
|
|
/* find the match */ |
196 |
|
|
while ((entry = readdir(dir)) != NULL) { |
197 |
|
|
/* skip . and .. */ |
198 |
|
|
if (entry->d_name[0] == '.' && (!entry->d_name[1] |
199 |
|
|
|| (entry->d_name[1] == '.' && !entry->d_name[2]))) |
200 |
|
|
continue; |
201 |
|
|
if (filename_len == 0) |
202 |
|
|
break; |
203 |
|
|
/* otherwise, get first entry where first */ |
204 |
|
|
/* filename_len characters are equal */ |
205 |
|
|
if (entry->d_name[0] == filename[0] |
206 |
|
|
#if HAVE_STRUCT_DIRENT_D_NAMLEN |
207 |
|
|
&& entry->d_namlen >= filename_len |
208 |
|
|
#else |
209 |
|
|
&& strlen(entry->d_name) >= filename_len |
210 |
|
|
#endif |
211 |
|
|
&& strncmp(entry->d_name, filename, |
212 |
|
|
filename_len) == 0) |
213 |
|
|
break; |
214 |
|
|
} |
215 |
|
|
|
216 |
|
|
if (entry) { /* match found */ |
217 |
|
|
|
218 |
|
|
#if HAVE_STRUCT_DIRENT_D_NAMLEN |
219 |
|
|
len = entry->d_namlen; |
220 |
|
|
#else |
221 |
|
|
len = strlen(entry->d_name); |
222 |
|
|
#endif |
223 |
|
|
|
224 |
|
|
tempsz = strlen(dirname) + len + 1; |
225 |
|
|
temp = malloc(tempsz); |
226 |
|
|
if (temp == NULL) |
227 |
|
|
return NULL; |
228 |
|
|
(void)snprintf(temp, tempsz, "%s%s", dirname, entry->d_name); |
229 |
|
|
} else { |
230 |
|
|
(void)closedir(dir); |
231 |
|
|
dir = NULL; |
232 |
|
|
temp = NULL; |
233 |
|
|
} |
234 |
|
|
|
235 |
|
|
return temp; |
236 |
|
|
} |
237 |
|
|
|
238 |
|
|
|
239 |
|
|
static const char * |
240 |
|
|
append_char_function(const char *name) |
241 |
|
|
{ |
242 |
|
|
struct stat stbuf; |
243 |
|
|
char *expname = *name == '~' ? fn_tilde_expand(name) : NULL; |
244 |
|
|
const char *rs = " "; |
245 |
|
|
|
246 |
|
|
if (stat(expname ? expname : name, &stbuf) == -1) |
247 |
|
|
goto out; |
248 |
|
|
if (S_ISDIR(stbuf.st_mode)) |
249 |
|
|
rs = "/"; |
250 |
|
|
out: |
251 |
|
|
if (expname) |
252 |
|
|
free(expname); |
253 |
|
|
return rs; |
254 |
|
|
} |
255 |
|
|
/* |
256 |
|
|
* returns list of completions for text given |
257 |
|
|
* non-static for readline. |
258 |
|
|
*/ |
259 |
|
|
char ** completion_matches(const char *, char *(*)(const char *, int)); |
260 |
|
|
char ** |
261 |
|
|
completion_matches(const char *text, char *(*genfunc)(const char *, int)) |
262 |
|
|
{ |
263 |
|
|
char **match_list = NULL, *retstr, *prevstr; |
264 |
|
|
size_t match_list_len, max_equal, which, i; |
265 |
|
|
size_t matches; |
266 |
|
|
|
267 |
|
|
matches = 0; |
268 |
|
|
match_list_len = 1; |
269 |
|
|
while ((retstr = (*genfunc) (text, (int)matches)) != NULL) { |
270 |
|
|
/* allow for list terminator here */ |
271 |
|
|
if (matches + 3 >= match_list_len) { |
272 |
|
|
char **nmatch_list; |
273 |
|
|
while (matches + 3 >= match_list_len) |
274 |
|
|
match_list_len <<= 1; |
275 |
|
|
nmatch_list = reallocarray(match_list, |
276 |
|
|
match_list_len, sizeof(char *)); |
277 |
|
|
if (nmatch_list == NULL) { |
278 |
|
|
free(match_list); |
279 |
|
|
return NULL; |
280 |
|
|
} |
281 |
|
|
match_list = nmatch_list; |
282 |
|
|
|
283 |
|
|
} |
284 |
|
|
match_list[++matches] = retstr; |
285 |
|
|
} |
286 |
|
|
|
287 |
|
|
if (!match_list) |
288 |
|
|
return NULL; /* nothing found */ |
289 |
|
|
|
290 |
|
|
/* find least denominator and insert it to match_list[0] */ |
291 |
|
|
which = 2; |
292 |
|
|
prevstr = match_list[1]; |
293 |
|
|
max_equal = strlen(prevstr); |
294 |
|
|
for (; which <= matches; which++) { |
295 |
|
|
for (i = 0; i < max_equal && |
296 |
|
|
prevstr[i] == match_list[which][i]; i++) |
297 |
|
|
continue; |
298 |
|
|
max_equal = i; |
299 |
|
|
} |
300 |
|
|
|
301 |
|
|
retstr = malloc(max_equal + 1); |
302 |
|
|
if (retstr == NULL) { |
303 |
|
|
free(match_list); |
304 |
|
|
return NULL; |
305 |
|
|
} |
306 |
|
|
(void)strncpy(retstr, match_list[1], max_equal); |
307 |
|
|
retstr[max_equal] = '\0'; |
308 |
|
|
match_list[0] = retstr; |
309 |
|
|
|
310 |
|
|
/* add NULL as last pointer to the array */ |
311 |
|
|
match_list[matches + 1] = NULL; |
312 |
|
|
|
313 |
|
|
return match_list; |
314 |
|
|
} |
315 |
|
|
|
316 |
|
|
/* |
317 |
|
|
* Sort function for qsort(). Just wrapper around strcasecmp(). |
318 |
|
|
*/ |
319 |
|
|
static int |
320 |
|
|
_fn_qsort_string_compare(const void *i1, const void *i2) |
321 |
|
|
{ |
322 |
|
|
const char *s1 = ((const char * const *)i1)[0]; |
323 |
|
|
const char *s2 = ((const char * const *)i2)[0]; |
324 |
|
|
|
325 |
|
|
return strcasecmp(s1, s2); |
326 |
|
|
} |
327 |
|
|
|
328 |
|
|
/* |
329 |
|
|
* Display list of strings in columnar format on readline's output stream. |
330 |
|
|
* 'matches' is list of strings, 'num' is number of strings in 'matches', |
331 |
|
|
* 'width' is maximum length of string in 'matches'. |
332 |
|
|
* |
333 |
|
|
* matches[0] is not one of the match strings, but it is counted in |
334 |
|
|
* num, so the strings are matches[1] *through* matches[num-1]. |
335 |
|
|
*/ |
336 |
|
|
void |
337 |
|
|
fn_display_match_list (EditLine *el, char **matches, size_t num, size_t width) |
338 |
|
|
{ |
339 |
|
|
size_t line, lines, col, cols, thisguy; |
340 |
|
|
int screenwidth = el->el_terminal.t_size.h; |
341 |
|
|
|
342 |
|
|
/* Ignore matches[0]. Avoid 1-based array logic below. */ |
343 |
|
|
matches++; |
344 |
|
|
num--; |
345 |
|
|
|
346 |
|
|
/* |
347 |
|
|
* Find out how many entries can be put on one line; count |
348 |
|
|
* with one space between strings the same way it's printed. |
349 |
|
|
*/ |
350 |
|
|
cols = screenwidth / (width + 1); |
351 |
|
|
if (cols == 0) |
352 |
|
|
cols = 1; |
353 |
|
|
|
354 |
|
|
/* how many lines of output, rounded up */ |
355 |
|
|
lines = (num + cols - 1) / cols; |
356 |
|
|
|
357 |
|
|
/* Sort the items. */ |
358 |
|
|
qsort(matches, num, sizeof(char *), _fn_qsort_string_compare); |
359 |
|
|
|
360 |
|
|
/* |
361 |
|
|
* On the ith line print elements i, i+lines, i+lines*2, etc. |
362 |
|
|
*/ |
363 |
|
|
for (line = 0; line < lines; line++) { |
364 |
|
|
for (col = 0; col < cols; col++) { |
365 |
|
|
thisguy = line + col * lines; |
366 |
|
|
if (thisguy >= num) |
367 |
|
|
break; |
368 |
|
|
(void)fprintf(el->el_outfile, "%s%-*s", |
369 |
|
|
col == 0 ? "" : " ", (int)width, matches[thisguy]); |
370 |
|
|
} |
371 |
|
|
(void)fprintf(el->el_outfile, "\n"); |
372 |
|
|
} |
373 |
|
|
} |
374 |
|
|
|
375 |
|
|
/* |
376 |
|
|
* Complete the word at or before point, |
377 |
|
|
* 'what_to_do' says what to do with the completion. |
378 |
|
|
* \t means do standard completion. |
379 |
|
|
* `?' means list the possible completions. |
380 |
|
|
* `*' means insert all of the possible completions. |
381 |
|
|
* `!' means to do standard completion, and list all possible completions if |
382 |
|
|
* there is more than one. |
383 |
|
|
* |
384 |
|
|
* Note: '*' support is not implemented |
385 |
|
|
* '!' could never be invoked |
386 |
|
|
*/ |
387 |
|
|
int |
388 |
|
|
fn_complete(EditLine *el, |
389 |
|
|
char *(*complet_func)(const char *, int), |
390 |
|
|
char **(*attempted_completion_function)(const char *, int, int), |
391 |
|
|
const wchar_t *word_break, const wchar_t *special_prefixes, |
392 |
|
|
const char *(*app_func)(const char *), size_t query_items, |
393 |
|
|
int *completion_type, int *over, int *point, int *end) |
394 |
|
|
{ |
395 |
|
|
const LineInfoW *li; |
396 |
|
|
wchar_t *temp; |
397 |
|
|
char **matches; |
398 |
|
|
const wchar_t *ctemp; |
399 |
|
|
size_t len; |
400 |
|
|
int what_to_do = '\t'; |
401 |
|
|
int retval = CC_NORM; |
402 |
|
|
|
403 |
|
|
if (el->el_state.lastcmd == el->el_state.thiscmd) |
404 |
|
|
what_to_do = '?'; |
405 |
|
|
|
406 |
|
|
/* readline's rl_complete() has to be told what we did... */ |
407 |
|
|
if (completion_type != NULL) |
408 |
|
|
*completion_type = what_to_do; |
409 |
|
|
|
410 |
|
|
if (!complet_func) |
411 |
|
|
complet_func = fn_filename_completion_function; |
412 |
|
|
if (!app_func) |
413 |
|
|
app_func = append_char_function; |
414 |
|
|
|
415 |
|
|
/* We now look backwards for the start of a filename/variable word */ |
416 |
|
|
li = el_wline(el); |
417 |
|
|
ctemp = li->cursor; |
418 |
|
|
while (ctemp > li->buffer |
419 |
|
|
&& !wcschr(word_break, ctemp[-1]) |
420 |
|
|
&& (!special_prefixes || !wcschr(special_prefixes, ctemp[-1]) ) ) |
421 |
|
|
ctemp--; |
422 |
|
|
|
423 |
|
|
len = li->cursor - ctemp; |
424 |
|
|
temp = reallocarray(NULL, len + 1, sizeof(*temp)); |
425 |
|
|
(void)wcsncpy(temp, ctemp, len); |
426 |
|
|
temp[len] = '\0'; |
427 |
|
|
|
428 |
|
|
/* these can be used by function called in completion_matches() */ |
429 |
|
|
/* or (*attempted_completion_function)() */ |
430 |
|
|
if (point != NULL) |
431 |
|
|
*point = (int)(li->cursor - li->buffer); |
432 |
|
|
if (end != NULL) |
433 |
|
|
*end = (int)(li->lastchar - li->buffer); |
434 |
|
|
|
435 |
|
|
if (attempted_completion_function) { |
436 |
|
|
int cur_off = (int)(li->cursor - li->buffer); |
437 |
|
|
matches = (*attempted_completion_function) ( |
438 |
|
|
ct_encode_string(temp, &el->el_scratch), |
439 |
|
|
(int)(cur_off - len), cur_off); |
440 |
|
|
} else |
441 |
|
|
matches = NULL; |
442 |
|
|
if (!attempted_completion_function || |
443 |
|
|
(over != NULL && !*over && !matches)) |
444 |
|
|
matches = completion_matches( |
445 |
|
|
ct_encode_string(temp, &el->el_scratch), complet_func); |
446 |
|
|
|
447 |
|
|
if (over != NULL) |
448 |
|
|
*over = 0; |
449 |
|
|
|
450 |
|
|
if (matches) { |
451 |
|
|
int i; |
452 |
|
|
size_t matches_num, maxlen, match_len, match_display=1; |
453 |
|
|
|
454 |
|
|
retval = CC_REFRESH; |
455 |
|
|
/* |
456 |
|
|
* Only replace the completed string with common part of |
457 |
|
|
* possible matches if there is possible completion. |
458 |
|
|
*/ |
459 |
|
|
if (matches[0][0] != '\0') { |
460 |
|
|
el_deletestr(el, (int) len); |
461 |
|
|
el_winsertstr(el, |
462 |
|
|
ct_decode_string(matches[0], &el->el_scratch)); |
463 |
|
|
} |
464 |
|
|
|
465 |
|
|
if (what_to_do == '?') |
466 |
|
|
goto display_matches; |
467 |
|
|
|
468 |
|
|
if (matches[2] == NULL && strcmp(matches[0], matches[1]) == 0) { |
469 |
|
|
/* |
470 |
|
|
* We found exact match. Add a space after |
471 |
|
|
* it, unless we do filename completion and the |
472 |
|
|
* object is a directory. |
473 |
|
|
*/ |
474 |
|
|
el_winsertstr(el, |
475 |
|
|
ct_decode_string((*app_func)(matches[0]), |
476 |
|
|
&el->el_scratch)); |
477 |
|
|
} else if (what_to_do == '!') { |
478 |
|
|
display_matches: |
479 |
|
|
/* |
480 |
|
|
* More than one match and requested to list possible |
481 |
|
|
* matches. |
482 |
|
|
*/ |
483 |
|
|
|
484 |
|
|
for(i = 1, maxlen = 0; matches[i]; i++) { |
485 |
|
|
match_len = strlen(matches[i]); |
486 |
|
|
if (match_len > maxlen) |
487 |
|
|
maxlen = match_len; |
488 |
|
|
} |
489 |
|
|
/* matches[1] through matches[i-1] are available */ |
490 |
|
|
matches_num = i - 1; |
491 |
|
|
|
492 |
|
|
/* newline to get on next line from command line */ |
493 |
|
|
(void)fprintf(el->el_outfile, "\n"); |
494 |
|
|
|
495 |
|
|
/* |
496 |
|
|
* If there are too many items, ask user for display |
497 |
|
|
* confirmation. |
498 |
|
|
*/ |
499 |
|
|
if (matches_num > query_items) { |
500 |
|
|
(void)fprintf(el->el_outfile, |
501 |
|
|
"Display all %zu possibilities? (y or n) ", |
502 |
|
|
matches_num); |
503 |
|
|
(void)fflush(el->el_outfile); |
504 |
|
|
if (getc(stdin) != 'y') |
505 |
|
|
match_display = 0; |
506 |
|
|
(void)fprintf(el->el_outfile, "\n"); |
507 |
|
|
} |
508 |
|
|
|
509 |
|
|
if (match_display) { |
510 |
|
|
/* |
511 |
|
|
* Interface of this function requires the |
512 |
|
|
* strings be matches[1..num-1] for compat. |
513 |
|
|
* We have matches_num strings not counting |
514 |
|
|
* the prefix in matches[0], so we need to |
515 |
|
|
* add 1 to matches_num for the call. |
516 |
|
|
*/ |
517 |
|
|
fn_display_match_list(el, matches, |
518 |
|
|
matches_num+1, maxlen); |
519 |
|
|
} |
520 |
|
|
retval = CC_REDISPLAY; |
521 |
|
|
} else if (matches[0][0]) { |
522 |
|
|
/* |
523 |
|
|
* There was some common match, but the name was |
524 |
|
|
* not complete enough. Next tab will print possible |
525 |
|
|
* completions. |
526 |
|
|
*/ |
527 |
|
|
el_beep(el); |
528 |
|
|
} else { |
529 |
|
|
/* lcd is not a valid object - further specification */ |
530 |
|
|
/* is needed */ |
531 |
|
|
el_beep(el); |
532 |
|
|
retval = CC_NORM; |
533 |
|
|
} |
534 |
|
|
|
535 |
|
|
/* free elements of array and the array itself */ |
536 |
|
|
for (i = 0; matches[i]; i++) |
537 |
|
|
free(matches[i]); |
538 |
|
|
free(matches); |
539 |
|
|
matches = NULL; |
540 |
|
|
} |
541 |
|
|
free(temp); |
542 |
|
|
return retval; |
543 |
|
|
} |
544 |
|
|
|
545 |
|
|
/* |
546 |
|
|
* el-compatible wrapper around rl_complete; needed for key binding |
547 |
|
|
*/ |
548 |
|
|
/* ARGSUSED */ |
549 |
|
|
unsigned char |
550 |
|
|
_el_fn_complete(EditLine *el, int ch __attribute__((__unused__))) |
551 |
|
|
{ |
552 |
|
|
return (unsigned char)fn_complete(el, NULL, NULL, |
553 |
|
|
break_chars, NULL, NULL, 100, |
554 |
|
|
NULL, NULL, NULL, NULL); |
555 |
|
|
} |