Changeset View
Changeset View
Standalone View
Standalone View
lib/libedit/filecomplete.c
/* $NetBSD: filecomplete.c,v 1.40 2016/02/17 19:47:49 christos Exp $ */ | /* $NetBSD: filecomplete.c,v 1.57 2019/07/28 09:27:29 christos Exp $ */ | ||||
/*- | /*- | ||||
* Copyright (c) 1997 The NetBSD Foundation, Inc. | * Copyright (c) 1997 The NetBSD Foundation, Inc. | ||||
* All rights reserved. | * All rights reserved. | ||||
* | * | ||||
* This code is derived from software contributed to The NetBSD Foundation | * This code is derived from software contributed to The NetBSD Foundation | ||||
* by Jaromir Dolecek. | * by Jaromir Dolecek. | ||||
* | * | ||||
Show All 16 Lines | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||||
* POSSIBILITY OF SUCH DAMAGE. | * POSSIBILITY OF SUCH DAMAGE. | ||||
*/ | */ | ||||
#include "config.h" | #include "config.h" | ||||
#if !defined(lint) && !defined(SCCSID) | #if !defined(lint) && !defined(SCCSID) | ||||
__RCSID("$NetBSD: filecomplete.c,v 1.40 2016/02/17 19:47:49 christos Exp $"); | __RCSID("$NetBSD: filecomplete.c,v 1.57 2019/07/28 09:27:29 christos Exp $"); | ||||
#endif /* not lint && not SCCSID */ | #endif /* not lint && not SCCSID */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include <sys/types.h> | #include <sys/types.h> | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <dirent.h> | #include <dirent.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <fcntl.h> | #include <fcntl.h> | ||||
#include <limits.h> | #include <limits.h> | ||||
#include <pwd.h> | #include <pwd.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
#include "el.h" | #include "el.h" | ||||
#include "filecomplete.h" | #include "filecomplete.h" | ||||
static const Char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`', '@', | static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{("; | ||||
'$', '>', '<', '=', ';', '|', '&', '{', '(', '\0' }; | |||||
/* Tilde is deliberately omitted here, we treat it specially. */ | |||||
static const Char extra_quote_chars[] = { ')', '}', '*', '?', '[', '$', '\0' }; | |||||
/********************************/ | /********************************/ | ||||
/* completion functions */ | /* completion functions */ | ||||
/* | /* | ||||
* does tilde expansion of strings of type ``~user/foo'' | * does tilde expansion of strings of type ``~user/foo'' | ||||
* if ``user'' isn't valid user name or ``txt'' doesn't start | * if ``user'' isn't valid user name or ``txt'' doesn't start | ||||
* w/ '~', returns pointer to strdup()ed copy of ``txt'' | * w/ '~', returns pointer to strdup()ed copy of ``txt'' | ||||
* | * | ||||
Show All 16 Lines | #endif | ||||
temp = strchr(txt + 1, '/'); | temp = strchr(txt + 1, '/'); | ||||
if (temp == NULL) { | if (temp == NULL) { | ||||
temp = strdup(txt + 1); | temp = strdup(txt + 1); | ||||
if (temp == NULL) | if (temp == NULL) | ||||
return NULL; | return NULL; | ||||
} else { | } else { | ||||
/* text until string after slash */ | /* text until string after slash */ | ||||
len = (size_t)(temp - txt + 1); | len = (size_t)(temp - txt + 1); | ||||
temp = el_malloc(len * sizeof(*temp)); | temp = el_calloc(len, sizeof(*temp)); | ||||
if (temp == NULL) | if (temp == NULL) | ||||
return NULL; | return NULL; | ||||
(void)strncpy(temp, txt + 1, len - 2); | (void)strncpy(temp, txt + 1, len - 2); | ||||
temp[len - 2] = '\0'; | temp[len - 2] = '\0'; | ||||
} | } | ||||
if (temp[0] == 0) { | if (temp[0] == 0) { | ||||
#ifdef HAVE_GETPW_R_POSIX | #ifdef HAVE_GETPW_R_POSIX | ||||
if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), | if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), | ||||
Show All 18 Lines | #endif | ||||
if (pass == NULL) | if (pass == NULL) | ||||
return strdup(txt); | return strdup(txt); | ||||
/* update pointer txt to point at string immedially following */ | /* update pointer txt to point at string immedially following */ | ||||
/* first slash */ | /* first slash */ | ||||
txt += len; | txt += len; | ||||
len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1; | len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1; | ||||
temp = el_malloc(len * sizeof(*temp)); | temp = el_calloc(len, sizeof(*temp)); | ||||
if (temp == NULL) | if (temp == NULL) | ||||
return NULL; | return NULL; | ||||
(void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt); | (void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt); | ||||
return temp; | return temp; | ||||
} | } | ||||
static int | |||||
needs_escaping(char c) | |||||
{ | |||||
switch (c) { | |||||
case '\'': | |||||
case '"': | |||||
case '(': | |||||
case ')': | |||||
case '\\': | |||||
case '<': | |||||
case '>': | |||||
case '$': | |||||
case '#': | |||||
case ' ': | |||||
case '\n': | |||||
case '\t': | |||||
case '?': | |||||
case ';': | |||||
case '`': | |||||
case '@': | |||||
case '=': | |||||
case '|': | |||||
case '{': | |||||
case '}': | |||||
case '&': | |||||
case '*': | |||||
case '[': | |||||
return 1; | |||||
default: | |||||
return 0; | |||||
} | |||||
} | |||||
static int | |||||
needs_dquote_escaping(char c) | |||||
{ | |||||
switch (c) { | |||||
case '"': | |||||
case '\\': | |||||
case '`': | |||||
case '$': | |||||
return 1; | |||||
default: | |||||
return 0; | |||||
} | |||||
} | |||||
static wchar_t * | |||||
unescape_string(const wchar_t *string, size_t length) | |||||
{ | |||||
size_t i; | |||||
size_t j = 0; | |||||
wchar_t *unescaped = el_calloc(length + 1, sizeof(*string)); | |||||
if (unescaped == NULL) | |||||
return NULL; | |||||
for (i = 0; i < length ; i++) { | |||||
if (string[i] == '\\') | |||||
continue; | |||||
unescaped[j++] = string[i]; | |||||
} | |||||
unescaped[j] = 0; | |||||
return unescaped; | |||||
} | |||||
static char * | |||||
escape_filename(EditLine * el, const char *filename) | |||||
{ | |||||
size_t original_len = 0; | |||||
size_t escaped_character_count = 0; | |||||
size_t offset = 0; | |||||
size_t newlen; | |||||
const char *s; | |||||
char c; | |||||
size_t s_quoted = 0; /* does the input contain a single quote */ | |||||
size_t d_quoted = 0; /* does the input contain a double quote */ | |||||
char *escaped_str; | |||||
wchar_t *temp = el->el_line.buffer; | |||||
if (filename == NULL) | |||||
return NULL; | |||||
while (temp != el->el_line.cursor) { | |||||
/* | /* | ||||
* If we see a single quote but have not seen a double quote | |||||
* so far set/unset s_quote | |||||
*/ | |||||
if (temp[0] == '\'' && !d_quoted) | |||||
s_quoted = !s_quoted; | |||||
/* | |||||
* vice versa to the above condition | |||||
*/ | |||||
else if (temp[0] == '"' && !s_quoted) | |||||
d_quoted = !d_quoted; | |||||
temp++; | |||||
} | |||||
/* Count number of special characters so that we can calculate | |||||
* number of extra bytes needed in the new string | |||||
*/ | |||||
for (s = filename; *s; s++, original_len++) { | |||||
c = *s; | |||||
/* Inside a single quote only single quotes need escaping */ | |||||
if (s_quoted && c == '\'') { | |||||
escaped_character_count += 3; | |||||
continue; | |||||
} | |||||
/* Inside double quotes only ", \, ` and $ need escaping */ | |||||
if (d_quoted && needs_dquote_escaping(c)) { | |||||
escaped_character_count++; | |||||
continue; | |||||
} | |||||
if (!s_quoted && !d_quoted && needs_escaping(c)) | |||||
escaped_character_count++; | |||||
} | |||||
newlen = original_len + escaped_character_count + 1; | |||||
if (s_quoted || d_quoted) | |||||
newlen++; | |||||
if ((escaped_str = el_malloc(newlen)) == NULL) | |||||
return NULL; | |||||
for (s = filename; *s; s++) { | |||||
c = *s; | |||||
if (!needs_escaping(c)) { | |||||
/* no escaping is required continue as usual */ | |||||
escaped_str[offset++] = c; | |||||
continue; | |||||
} | |||||
/* single quotes inside single quotes require special handling */ | |||||
if (c == '\'' && s_quoted) { | |||||
escaped_str[offset++] = '\''; | |||||
escaped_str[offset++] = '\\'; | |||||
escaped_str[offset++] = '\''; | |||||
escaped_str[offset++] = '\''; | |||||
continue; | |||||
} | |||||
/* Otherwise no escaping needed inside single quotes */ | |||||
if (s_quoted) { | |||||
escaped_str[offset++] = c; | |||||
continue; | |||||
} | |||||
/* No escaping needed inside a double quoted string either | |||||
* unless we see a '$', '\', '`', or '"' (itself) | |||||
*/ | |||||
if (d_quoted && !needs_dquote_escaping(c)) { | |||||
escaped_str[offset++] = c; | |||||
continue; | |||||
} | |||||
/* If we reach here that means escaping is actually needed */ | |||||
escaped_str[offset++] = '\\'; | |||||
escaped_str[offset++] = c; | |||||
} | |||||
/* close the quotes */ | |||||
if (s_quoted) | |||||
escaped_str[offset++] = '\''; | |||||
else if (d_quoted) | |||||
escaped_str[offset++] = '"'; | |||||
escaped_str[offset] = 0; | |||||
return escaped_str; | |||||
} | |||||
/* | |||||
* return first found file name starting by the ``text'' or NULL if no | * return first found file name starting by the ``text'' or NULL if no | ||||
* such file can be found | * such file can be found | ||||
* value of ``state'' is ignored | * value of ``state'' is ignored | ||||
* | * | ||||
* it's the caller's responsibility to free the returned string | * it's the caller's responsibility to free the returned string | ||||
*/ | */ | ||||
char * | char * | ||||
fn_filename_completion_function(const char *text, int state) | fn_filename_completion_function(const char *text, int state) | ||||
▲ Show 20 Lines • Show All 98 Lines • ▼ Show 20 Lines | |||||
#if HAVE_STRUCT_DIRENT_D_NAMLEN | #if HAVE_STRUCT_DIRENT_D_NAMLEN | ||||
len = entry->d_namlen; | len = entry->d_namlen; | ||||
#else | #else | ||||
len = strlen(entry->d_name); | len = strlen(entry->d_name); | ||||
#endif | #endif | ||||
len = strlen(dirname) + len + 1; | len = strlen(dirname) + len + 1; | ||||
temp = el_malloc(len * sizeof(*temp)); | temp = el_calloc(len, sizeof(*temp)); | ||||
if (temp == NULL) | if (temp == NULL) | ||||
return NULL; | return NULL; | ||||
(void)snprintf(temp, len, "%s%s", dirname, entry->d_name); | (void)snprintf(temp, len, "%s%s", dirname, entry->d_name); | ||||
} else { | } else { | ||||
(void)closedir(dir); | (void)closedir(dir); | ||||
dir = NULL; | dir = NULL; | ||||
temp = NULL; | temp = NULL; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | completion_matches(const char *text, char *(*genfunc)(const char *, int)) | ||||
max_equal = strlen(prevstr); | max_equal = strlen(prevstr); | ||||
for (; which <= matches; which++) { | for (; which <= matches; which++) { | ||||
for (i = 0; i < max_equal && | for (i = 0; i < max_equal && | ||||
prevstr[i] == match_list[which][i]; i++) | prevstr[i] == match_list[which][i]; i++) | ||||
continue; | continue; | ||||
max_equal = i; | max_equal = i; | ||||
} | } | ||||
retstr = el_malloc((max_equal + 1) * sizeof(*retstr)); | retstr = el_calloc(max_equal + 1, sizeof(*retstr)); | ||||
if (retstr == NULL) { | if (retstr == NULL) { | ||||
el_free(match_list); | el_free(match_list); | ||||
return NULL; | return NULL; | ||||
} | } | ||||
(void)strncpy(retstr, match_list[1], max_equal); | (void)strncpy(retstr, match_list[1], max_equal); | ||||
retstr[max_equal] = '\0'; | retstr[max_equal] = '\0'; | ||||
match_list[0] = retstr; | match_list[0] = retstr; | ||||
Show All 19 Lines | |||||
* Display list of strings in columnar format on readline's output stream. | * Display list of strings in columnar format on readline's output stream. | ||||
* 'matches' is list of strings, 'num' is number of strings in 'matches', | * 'matches' is list of strings, 'num' is number of strings in 'matches', | ||||
* 'width' is maximum length of string in 'matches'. | * 'width' is maximum length of string in 'matches'. | ||||
* | * | ||||
* matches[0] is not one of the match strings, but it is counted in | * matches[0] is not one of the match strings, but it is counted in | ||||
* num, so the strings are matches[1] *through* matches[num-1]. | * num, so the strings are matches[1] *through* matches[num-1]. | ||||
*/ | */ | ||||
void | void | ||||
fn_display_match_list (EditLine *el, char **matches, size_t num, size_t width) | fn_display_match_list(EditLine * el, char **matches, size_t num, size_t width, | ||||
const char *(*app_func) (const char *)) | |||||
{ | { | ||||
size_t line, lines, col, cols, thisguy; | size_t line, lines, col, cols, thisguy; | ||||
int screenwidth = el->el_terminal.t_size.h; | int screenwidth = el->el_terminal.t_size.h; | ||||
if (app_func == NULL) | |||||
app_func = append_char_function; | |||||
/* Ignore matches[0]. Avoid 1-based array logic below. */ | /* Ignore matches[0]. Avoid 1-based array logic below. */ | ||||
matches++; | matches++; | ||||
num--; | num--; | ||||
/* | /* | ||||
* Find out how many entries can be put on one line; count | * Find out how many entries can be put on one line; count | ||||
* with one space between strings the same way it's printed. | * with one space between strings the same way it's printed. | ||||
*/ | */ | ||||
cols = (size_t)screenwidth / (width + 1); | cols = (size_t)screenwidth / (width + 2); | ||||
if (cols == 0) | if (cols == 0) | ||||
cols = 1; | cols = 1; | ||||
/* how many lines of output, rounded up */ | /* how many lines of output, rounded up */ | ||||
lines = (num + cols - 1) / cols; | lines = (num + cols - 1) / cols; | ||||
/* Sort the items. */ | /* Sort the items. */ | ||||
qsort(matches, num, sizeof(char *), _fn_qsort_string_compare); | qsort(matches, num, sizeof(char *), _fn_qsort_string_compare); | ||||
/* | /* | ||||
* On the ith line print elements i, i+lines, i+lines*2, etc. | * On the ith line print elements i, i+lines, i+lines*2, etc. | ||||
*/ | */ | ||||
for (line = 0; line < lines; line++) { | for (line = 0; line < lines; line++) { | ||||
for (col = 0; col < cols; col++) { | for (col = 0; col < cols; col++) { | ||||
thisguy = line + col * lines; | thisguy = line + col * lines; | ||||
if (thisguy >= num) | if (thisguy >= num) | ||||
break; | break; | ||||
(void)fprintf(el->el_outfile, "%s%-*s", | (void)fprintf(el->el_outfile, "%s%s%s", | ||||
col == 0 ? "" : " ", (int)width, matches[thisguy]); | col == 0 ? "" : " ", matches[thisguy], | ||||
(*app_func)(matches[thisguy])); | |||||
(void)fprintf(el->el_outfile, "%-*s", | |||||
(int) (width - strlen(matches[thisguy])), ""); | |||||
} | } | ||||
(void)fprintf(el->el_outfile, "\n"); | (void)fprintf(el->el_outfile, "\n"); | ||||
} | } | ||||
} | } | ||||
static wchar_t * | |||||
find_word_to_complete(const wchar_t * cursor, const wchar_t * buffer, | |||||
const wchar_t * word_break, const wchar_t * special_prefixes, size_t * length) | |||||
{ | |||||
/* We now look backwards for the start of a filename/variable word */ | |||||
const wchar_t *ctemp = cursor; | |||||
size_t len; | |||||
/* if the cursor is placed at a slash or a quote, we need to find the | |||||
* word before it | |||||
*/ | |||||
if (ctemp > buffer) { | |||||
switch (ctemp[-1]) { | |||||
case '\\': | |||||
case '\'': | |||||
case '"': | |||||
ctemp--; | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
for (;;) { | |||||
if (ctemp <= buffer) | |||||
break; | |||||
if (wcschr(word_break, ctemp[-1])) { | |||||
if (ctemp - buffer >= 2 && ctemp[-2] == '\\') { | |||||
ctemp -= 2; | |||||
continue; | |||||
} else | |||||
break; | |||||
} | |||||
if (special_prefixes && wcschr(special_prefixes, ctemp[-1])) | |||||
break; | |||||
ctemp--; | |||||
} | |||||
len = (size_t) (cursor - ctemp); | |||||
*length = len; | |||||
wchar_t *unescaped_word = unescape_string(ctemp, len); | |||||
if (unescaped_word == NULL) | |||||
return NULL; | |||||
return unescaped_word; | |||||
} | |||||
/* | /* | ||||
* Complete the word at or before point, | * Complete the word at or before point, | ||||
* 'what_to_do' says what to do with the completion. | * 'what_to_do' says what to do with the completion. | ||||
* \t means do standard completion. | * \t means do standard completion. | ||||
* `?' means list the possible completions. | * `?' means list the possible completions. | ||||
* `*' means insert all of the possible completions. | * `*' means insert all of the possible completions. | ||||
* `!' means to do standard completion, and list all possible completions if | * `!' means to do standard completion, and list all possible completions if | ||||
* there is more than one. | * there is more than one. | ||||
* | * | ||||
* Note: '*' support is not implemented | * Note: '*' support is not implemented | ||||
* '!' could never be invoked | * '!' could never be invoked | ||||
*/ | */ | ||||
int | int | ||||
fn_complete(EditLine *el, | fn_complete(EditLine *el, | ||||
char *(*complet_func)(const char *, int), | char *(*complet_func)(const char *, int), | ||||
char **(*attempted_completion_function)(const char *, int, int), | char **(*attempted_completion_function)(const char *, int, int), | ||||
const Char *word_break, const Char *special_prefixes, | const wchar_t *word_break, const wchar_t *special_prefixes, | ||||
const char *(*app_func)(const char *), size_t query_items, | const char *(*app_func)(const char *), size_t query_items, | ||||
int *completion_type, int *over, int *point, int *end, | int *completion_type, int *over, int *point, int *end) | ||||
const Char *(*find_word_start_func)(const Char *, const Char *), | |||||
Char *(*dequoting_func)(const Char *), | |||||
char *(*quoting_func)(const char *)) | |||||
{ | { | ||||
const TYPE(LineInfo) *li; | const LineInfoW *li; | ||||
Char *temp; | wchar_t *temp; | ||||
Char *dequoted_temp; | |||||
char **matches; | char **matches; | ||||
const Char *ctemp; | char *completion; | ||||
size_t len; | size_t len; | ||||
int what_to_do = '\t'; | int what_to_do = '\t'; | ||||
int retval = CC_NORM; | int retval = CC_NORM; | ||||
if (el->el_state.lastcmd == el->el_state.thiscmd) | if (el->el_state.lastcmd == el->el_state.thiscmd) | ||||
what_to_do = '?'; | what_to_do = '?'; | ||||
/* readline's rl_complete() has to be told what we did... */ | /* readline's rl_complete() has to be told what we did... */ | ||||
if (completion_type != NULL) | if (completion_type != NULL) | ||||
*completion_type = what_to_do; | *completion_type = what_to_do; | ||||
if (!complet_func) | if (!complet_func) | ||||
complet_func = fn_filename_completion_function; | complet_func = fn_filename_completion_function; | ||||
if (!app_func) | if (!app_func) | ||||
app_func = append_char_function; | app_func = append_char_function; | ||||
/* We now look backwards for the start of a filename/variable word */ | li = el_wline(el); | ||||
li = FUN(el,line)(el); | temp = find_word_to_complete(li->cursor, | ||||
if (find_word_start_func) | li->buffer, word_break, special_prefixes, &len); | ||||
ctemp = find_word_start_func(li->buffer, li->cursor); | if (temp == NULL) | ||||
else { | goto out; | ||||
ctemp = li->cursor; | |||||
while (ctemp > li->buffer | |||||
&& !Strchr(word_break, ctemp[-1]) | |||||
&& (!special_prefixes || !Strchr(special_prefixes, ctemp[-1]) ) ) | |||||
ctemp--; | |||||
} | |||||
len = (size_t)(li->cursor - ctemp); | |||||
temp = el_malloc((len + 1) * sizeof(*temp)); | |||||
(void)Strncpy(temp, ctemp, len); | |||||
temp[len] = '\0'; | |||||
if (dequoting_func) { | |||||
dequoted_temp = dequoting_func(temp); | |||||
if (dequoted_temp == NULL) | |||||
return retval; | |||||
} else | |||||
dequoted_temp = NULL; | |||||
/* these can be used by function called in completion_matches() */ | /* these can be used by function called in completion_matches() */ | ||||
/* or (*attempted_completion_function)() */ | /* or (*attempted_completion_function)() */ | ||||
if (point != NULL) | if (point != NULL) | ||||
*point = (int)(li->cursor - li->buffer); | *point = (int)(li->cursor - li->buffer); | ||||
if (end != NULL) | if (end != NULL) | ||||
*end = (int)(li->lastchar - li->buffer); | *end = (int)(li->lastchar - li->buffer); | ||||
if (attempted_completion_function) { | if (attempted_completion_function) { | ||||
int cur_off = (int)(li->cursor - li->buffer); | int cur_off = (int)(li->cursor - li->buffer); | ||||
matches = (*attempted_completion_function)( | matches = (*attempted_completion_function)( | ||||
ct_encode_string(dequoted_temp ? dequoted_temp : temp, | ct_encode_string(temp, &el->el_scratch), | ||||
&el->el_scratch), | |||||
cur_off - (int)len, cur_off); | cur_off - (int)len, cur_off); | ||||
} else | } else | ||||
matches = NULL; | matches = NULL; | ||||
if (!attempted_completion_function || | if (!attempted_completion_function || | ||||
(over != NULL && !*over && !matches)) | (over != NULL && !*over && !matches)) | ||||
matches = completion_matches( | matches = completion_matches( | ||||
ct_encode_string(dequoted_temp ? dequoted_temp : temp, | ct_encode_string(temp, &el->el_scratch), complet_func); | ||||
&el->el_scratch), complet_func); | |||||
if (over != NULL) | if (over != NULL) | ||||
*over = 0; | *over = 0; | ||||
if (matches) { | if (matches) { | ||||
int i; | int i; | ||||
size_t matches_num, maxlen, match_len, match_display=1; | size_t matches_num, maxlen, match_len, match_display=1; | ||||
int single_match = matches[2] == NULL && | |||||
(matches[1] == NULL || strcmp(matches[0], matches[1]) == 0); | |||||
retval = CC_REFRESH; | retval = CC_REFRESH; | ||||
/* | |||||
* Only replace the completed string with common part of | |||||
* possible matches if there is possible completion. | |||||
*/ | |||||
if (matches[0][0] != '\0') { | if (matches[0][0] != '\0') { | ||||
char *quoted_match; | |||||
if (quoting_func) { | |||||
quoted_match = quoting_func(matches[0]); | |||||
if (quoted_match == NULL) | |||||
goto free_matches; | |||||
} else | |||||
quoted_match = NULL; | |||||
el_deletestr(el, (int) len); | el_deletestr(el, (int) len); | ||||
FUN(el,insertstr)(el, | if (!attempted_completion_function) | ||||
ct_decode_string(quoted_match ? quoted_match : | completion = escape_filename(el, matches[0]); | ||||
matches[0] , &el->el_scratch)); | else | ||||
} | completion = strdup(matches[0]); | ||||
if (completion == NULL) | |||||
if (what_to_do == '?') | goto out; | ||||
goto display_matches; | if (single_match) { | ||||
if (matches[2] == NULL && | |||||
(matches[1] == NULL || strcmp(matches[0], matches[1]) == 0)) { | |||||
/* | /* | ||||
* We found exact match. Add a space after | * We found exact match. Add a space after | ||||
* it, unless we do filename completion and the | * it, unless we do filename completion and the | ||||
* object is a directory. | * object is a directory. Also do necessary escape quoting | ||||
*/ | */ | ||||
FUN(el,insertstr)(el, | el_winsertstr(el, | ||||
ct_decode_string((*app_func)(matches[0]), | ct_decode_string(completion, &el->el_scratch)); | ||||
el_winsertstr(el, | |||||
ct_decode_string((*app_func)(completion), | |||||
&el->el_scratch)); | &el->el_scratch)); | ||||
} else if (what_to_do == '!') { | } else { | ||||
display_matches: | |||||
/* | /* | ||||
* Only replace the completed string with common part of | |||||
* possible matches if there is possible completion. | |||||
*/ | |||||
el_winsertstr(el, | |||||
ct_decode_string(completion, &el->el_scratch)); | |||||
} | |||||
free(completion); | |||||
} | |||||
if (!single_match && (what_to_do == '!' || what_to_do == '?')) { | |||||
/* | |||||
* More than one match and requested to list possible | * More than one match and requested to list possible | ||||
* matches. | * matches. | ||||
*/ | */ | ||||
for(i = 1, maxlen = 0; matches[i]; i++) { | for(i = 1, maxlen = 0; matches[i]; i++) { | ||||
match_len = strlen(matches[i]); | match_len = strlen(matches[i]); | ||||
if (match_len > maxlen) | if (match_len > maxlen) | ||||
maxlen = match_len; | maxlen = match_len; | ||||
Show All 22 Lines | if (!single_match && (what_to_do == '!' || what_to_do == '?')) { | ||||
/* | /* | ||||
* Interface of this function requires the | * Interface of this function requires the | ||||
* strings be matches[1..num-1] for compat. | * strings be matches[1..num-1] for compat. | ||||
* We have matches_num strings not counting | * We have matches_num strings not counting | ||||
* the prefix in matches[0], so we need to | * the prefix in matches[0], so we need to | ||||
* add 1 to matches_num for the call. | * add 1 to matches_num for the call. | ||||
*/ | */ | ||||
fn_display_match_list(el, matches, | fn_display_match_list(el, matches, | ||||
matches_num+1, maxlen); | matches_num+1, maxlen, app_func); | ||||
} | } | ||||
retval = CC_REDISPLAY; | retval = CC_REDISPLAY; | ||||
} else if (matches[0][0]) { | } else if (matches[0][0]) { | ||||
/* | /* | ||||
* There was some common match, but the name was | * There was some common match, but the name was | ||||
* not complete enough. Next tab will print possible | * not complete enough. Next tab will print possible | ||||
* completions. | * completions. | ||||
*/ | */ | ||||
el_beep(el); | el_beep(el); | ||||
} else { | } else { | ||||
/* lcd is not a valid object - further specification */ | /* lcd is not a valid object - further specification */ | ||||
/* is needed */ | /* is needed */ | ||||
el_beep(el); | el_beep(el); | ||||
retval = CC_NORM; | retval = CC_NORM; | ||||
} | } | ||||
free_matches: | |||||
/* free elements of array and the array itself */ | /* free elements of array and the array itself */ | ||||
for (i = 0; matches[i]; i++) | for (i = 0; matches[i]; i++) | ||||
el_free(matches[i]); | el_free(matches[i]); | ||||
el_free(matches); | el_free(matches); | ||||
matches = NULL; | matches = NULL; | ||||
} | } | ||||
free(dequoted_temp); | |||||
out: | |||||
el_free(temp); | el_free(temp); | ||||
return retval; | return retval; | ||||
} | } | ||||
/* | /* | ||||
* el-compatible wrapper around rl_complete; needed for key binding | * el-compatible wrapper around rl_complete; needed for key binding | ||||
*/ | */ | ||||
/* ARGSUSED */ | /* ARGSUSED */ | ||||
unsigned char | unsigned char | ||||
_el_fn_complete(EditLine *el, int ch __attribute__((__unused__))) | _el_fn_complete(EditLine *el, int ch __attribute__((__unused__))) | ||||
{ | { | ||||
return (unsigned char)fn_complete(el, NULL, NULL, | return (unsigned char)fn_complete(el, NULL, NULL, | ||||
break_chars, NULL, NULL, (size_t)100, | break_chars, NULL, NULL, (size_t)100, | ||||
NULL, NULL, NULL, NULL, | NULL, NULL, NULL, NULL); | ||||
NULL, NULL, NULL); | |||||
} | |||||
static const Char * | |||||
sh_find_word_start(const Char *buffer, const Char *cursor) | |||||
{ | |||||
const Char *word_start = buffer; | |||||
while (buffer < cursor) { | |||||
if (*buffer == '\\') | |||||
buffer++; | |||||
else if (Strchr(break_chars, *buffer)) | |||||
word_start = buffer + 1; | |||||
buffer++; | |||||
} | |||||
return word_start; | |||||
} | |||||
static char * | |||||
sh_quote(const char *str) | |||||
{ | |||||
const char *src; | |||||
int extra_len = 0; | |||||
char *quoted_str, *dst; | |||||
for (src = str; *src != '\0'; src++) | |||||
if (Strchr(break_chars, *src) || | |||||
Strchr(extra_quote_chars, *src)) | |||||
extra_len++; | |||||
quoted_str = malloc(sizeof(*quoted_str) * | |||||
(strlen(str) + extra_len + 1)); | |||||
if (quoted_str == NULL) | |||||
return NULL; | |||||
dst = quoted_str; | |||||
for (src = str; *src != '\0'; src++) { | |||||
if (Strchr(break_chars, *src) || | |||||
Strchr(extra_quote_chars, *src)) | |||||
*dst++ = '\\'; | |||||
*dst++ = *src; | |||||
} | |||||
*dst = '\0'; | |||||
return quoted_str; | |||||
} | |||||
static Char * | |||||
sh_dequote(const Char *str) | |||||
{ | |||||
Char *dequoted_str, *dst; | |||||
/* save extra space to replace \~ with ./~ */ | |||||
dequoted_str = malloc(sizeof(*dequoted_str) * (Strlen(str) + 1 + 1)); | |||||
if (dequoted_str == NULL) | |||||
return NULL; | |||||
dst = dequoted_str; | |||||
/* dequote \~ at start as ./~ */ | |||||
if (*str == '\\' && str[1] == '~') { | |||||
str++; | |||||
*dst++ = '.'; | |||||
*dst++ = '/'; | |||||
} | |||||
while (*str) { | |||||
if (*str == '\\') | |||||
str++; | |||||
if (*str) | |||||
*dst++ = *str++; | |||||
} | |||||
*dst = '\0'; | |||||
return dequoted_str; | |||||
} | |||||
/* | |||||
* completion function using sh quoting rules; for key binding | |||||
*/ | |||||
/* ARGSUSED */ | |||||
unsigned char | |||||
_el_fn_sh_complete(EditLine *el, int ch __attribute__((__unused__))) | |||||
{ | |||||
return (unsigned char)fn_complete(el, NULL, NULL, | |||||
break_chars, NULL, NULL, 100, | |||||
NULL, NULL, NULL, NULL, | |||||
sh_find_word_start, sh_dequote, sh_quote); | |||||
} | } |