Changeset View
Standalone View
bin/sh/histedit.c
Show All 33 Lines | |||||||||||||||||||
#if 0 | #if 0 | ||||||||||||||||||
static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; | static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; | ||||||||||||||||||
#endif | #endif | ||||||||||||||||||
#endif /* not lint */ | #endif /* not lint */ | ||||||||||||||||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||||||||||||||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||||||||||||||||
#include <sys/param.h> | #include <sys/param.h> | ||||||||||||||||||
#include <sys/stat.h> | |||||||||||||||||||
#include <dirent.h> | |||||||||||||||||||
#include <limits.h> | #include <limits.h> | ||||||||||||||||||
#include <paths.h> | #include <paths.h> | ||||||||||||||||||
#include <stdio.h> | #include <stdio.h> | ||||||||||||||||||
#include <stdlib.h> | #include <stdlib.h> | ||||||||||||||||||
#include <unistd.h> | #include <unistd.h> | ||||||||||||||||||
/* | /* | ||||||||||||||||||
* Editline and history functions (and glue). | * Editline and history functions (and glue). | ||||||||||||||||||
*/ | */ | ||||||||||||||||||
Show All 13 Lines | |||||||||||||||||||
#define MAXHISTLOOPS 4 /* max recursions through fc */ | #define MAXHISTLOOPS 4 /* max recursions through fc */ | ||||||||||||||||||
#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ | #define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ | ||||||||||||||||||
History *hist; /* history cookie */ | History *hist; /* history cookie */ | ||||||||||||||||||
EditLine *el; /* editline cookie */ | EditLine *el; /* editline cookie */ | ||||||||||||||||||
int displayhist; | int displayhist; | ||||||||||||||||||
static FILE *el_in, *el_out; | static FILE *el_in, *el_out; | ||||||||||||||||||
static int curpos; | |||||||||||||||||||
arichardson: I don't think this needs to be a global variable (see comments below). | |||||||||||||||||||
static char *fc_replace(const char *, char *, char *); | static char *fc_replace(const char *, char *, char *); | ||||||||||||||||||
static int not_fcnumber(const char *); | static int not_fcnumber(const char *); | ||||||||||||||||||
static int str_to_event(const char *, int); | static int str_to_event(const char *, int); | ||||||||||||||||||
Done Inline ActionsThe attribute is ignored in the declaration, it's only needed in the definition. arichardson: The attribute is ignored in the declaration, it's only needed in the definition. | |||||||||||||||||||
static int comparator(const void *, const void *); | |||||||||||||||||||
static char **sh_matches(const char *, int, int); | |||||||||||||||||||
static unsigned char sh_complete(EditLine *, int); | |||||||||||||||||||
/* | /* | ||||||||||||||||||
* Set history and editing status. Called whenever the status may | * Set history and editing status. Called whenever the status may | ||||||||||||||||||
* have changed (figures out what to do). | * have changed (figures out what to do). | ||||||||||||||||||
*/ | */ | ||||||||||||||||||
void | void | ||||||||||||||||||
histedit(void) | histedit(void) | ||||||||||||||||||
{ | { | ||||||||||||||||||
Show All 34 Lines | if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ | ||||||||||||||||||
unsetenv("TERM"); | unsetenv("TERM"); | ||||||||||||||||||
el = el_init(arg0, el_in, el_out, el_out); | el = el_init(arg0, el_in, el_out, el_out); | ||||||||||||||||||
if (el != NULL) { | if (el != NULL) { | ||||||||||||||||||
if (hist) | if (hist) | ||||||||||||||||||
el_set(el, EL_HIST, history, hist); | el_set(el, EL_HIST, history, hist); | ||||||||||||||||||
el_set(el, EL_PROMPT, getprompt); | el_set(el, EL_PROMPT, getprompt); | ||||||||||||||||||
el_set(el, EL_ADDFN, "sh-complete", | el_set(el, EL_ADDFN, "sh-complete", | ||||||||||||||||||
"Filename completion", | "Filename completion", | ||||||||||||||||||
_el_fn_complete); | sh_complete); | ||||||||||||||||||
} else { | } else { | ||||||||||||||||||
bad: | bad: | ||||||||||||||||||
out2fmt_flush("sh: can't initialize editing\n"); | out2fmt_flush("sh: can't initialize editing\n"); | ||||||||||||||||||
} | } | ||||||||||||||||||
INTON; | INTON; | ||||||||||||||||||
} else if (!editing && el) { | } else if (!editing && el) { | ||||||||||||||||||
INTOFF; | INTOFF; | ||||||||||||||||||
el_end(el); | el_end(el); | ||||||||||||||||||
▲ Show 20 Lines • Show All 378 Lines • ▼ Show 20 Lines | |||||||||||||||||||
} | } | ||||||||||||||||||
int | int | ||||||||||||||||||
bindcmd(int argc __unused, char **argv __unused) | bindcmd(int argc __unused, char **argv __unused) | ||||||||||||||||||
{ | { | ||||||||||||||||||
error("not compiled with line editing support"); | error("not compiled with line editing support"); | ||||||||||||||||||
return (0); | return (0); | ||||||||||||||||||
} | } | ||||||||||||||||||
Not Done Inline ActionsDoes this function need a special case if text starts with /? arichardson: Does this function need a special case if `text` starts with `/`? | |||||||||||||||||||
#endif | #endif | ||||||||||||||||||
/* | |||||||||||||||||||
* Comparator function for qsort(). The use of curpos here is to skip | |||||||||||||||||||
* characters that we already know to compare equal (common prefix). | |||||||||||||||||||
*/ | |||||||||||||||||||
static int | |||||||||||||||||||
comparator(const void *a, const void *b) | |||||||||||||||||||
Not Done Inline Actions
arichardson: | |||||||||||||||||||
{ | |||||||||||||||||||
return (strcmp(*(char *const *)a + curpos, | |||||||||||||||||||
*(char *const *)b + curpos)); | |||||||||||||||||||
} | |||||||||||||||||||
Not Done Inline Actions
arichardson: | |||||||||||||||||||
/* | |||||||||||||||||||
* This function is passed to libedit's fn_complete(). The library will | |||||||||||||||||||
Done Inline ActionsCould just declare char execpath[PATH_MAX] and avoid this malloc? arichardson: Could just declare `char execpath[PATH_MAX]` and avoid this malloc? | |||||||||||||||||||
* use it instead of its standard function to find matches, which | |||||||||||||||||||
* searches for files in current directory. If we're at the start of the | |||||||||||||||||||
* line, we want to look for available commands from all paths in $PATH. | |||||||||||||||||||
*/ | |||||||||||||||||||
static char | |||||||||||||||||||
**sh_matches(const char *text, int start, int end) | |||||||||||||||||||
Not Done Inline ActionsCode to retrieve all commands in PATH does not exist yet in sh, so it is appropriate to write it here. Note that this does not include builtins or functions. jilles: Code to retrieve all commands in `PATH` does not exist yet in `sh`, so it is appropriate to… | |||||||||||||||||||
Done Inline ActionsDo we want to autocomplete builtins and functions? If so, maybe in a later patch? :) pstef: Do we want to autocomplete builtins and functions? If so, maybe in a later patch? :) | |||||||||||||||||||
{ | |||||||||||||||||||
char *free_path = NULL, *dirname, *path; | |||||||||||||||||||
Not Done Inline Actionsusr.bin/which.c has the following, so maybe we need to handle empty elements explicitly: if (strchr(filename, '/') != NULL) return (is_there(filename) ? 0 : -1); found = 0; while ((d = strsep(&path, ":")) != NULL) { if (*d == '\0') d = "."; if (snprintf(candidate, sizeof(candidate), "%s/%s", d, filename) >= (int)sizeof(candidate)) continue; if (is_there(candidate)) { found = 1; if (!allpaths) break; } } arichardson: usr.bin/which.c has the following, so maybe we need to handle empty elements explicitly:
```… | |||||||||||||||||||
Not Done Inline ActionsHandling empty elements is definitely required. jilles: Handling empty elements is definitely required. | |||||||||||||||||||
Done Inline ActionsDo you mean the if (*d == '\0') d = "."; part? So that it will search for matching commands in $PWD? pstef: Do you mean the `if (*d == '\0') d = ".";` part? So that it will search for matching commands… | |||||||||||||||||||
char **matches = NULL; | |||||||||||||||||||
size_t i = 0, size = 16; | |||||||||||||||||||
if (start > 0) | |||||||||||||||||||
return (NULL); | |||||||||||||||||||
curpos = end - start; | |||||||||||||||||||
if ((free_path = path = strdup(pathval())) == NULL) | |||||||||||||||||||
goto out; | |||||||||||||||||||
if ((matches = malloc(size * sizeof(matches[0]))) == NULL) | |||||||||||||||||||
goto out; | |||||||||||||||||||
while ((dirname = strsep(&path, ":")) != NULL) { | |||||||||||||||||||
Done Inline ActionsUsing fstatat() will allow avoiding the string manipulation. jilles: Using `fstatat()` will allow avoiding the string manipulation. | |||||||||||||||||||
Done Inline Actionsd_type could be used to reduce stat calls: if DT_REG the file can be accepted without stat call, if DT_UNKNOWN or DT_LNK a stat call is required, otherwise the file can be rejected without stat call. jilles: `d_type` could be used to reduce stat calls: if `DT_REG` the file can be accepted without stat… | |||||||||||||||||||
struct dirent *entry; | |||||||||||||||||||
DIR *dir; | |||||||||||||||||||
int dfd; | |||||||||||||||||||
if ((dir = opendir(dirname)) == NULL) | |||||||||||||||||||
Done Inline ActionsThe paranoid will want some overflow checks here. jilles: The paranoid will want some overflow checks here. | |||||||||||||||||||
continue; | |||||||||||||||||||
if ((dfd = dirfd(dir)) == -1) | |||||||||||||||||||
continue; | |||||||||||||||||||
while ((entry = readdir(dir)) != NULL) { | |||||||||||||||||||
struct stat statb; | |||||||||||||||||||
Done Inline Actionsmissing closedir(dir)? arichardson: missing closedir(dir)? | |||||||||||||||||||
Not Done Inline ActionsThe code in contrib/libedit/filecomplete.c will sort the list, but will not remove duplicates. Do we care? jilles: The code in contrib/libedit/filecomplete.c will sort the list, but will not remove duplicates. | |||||||||||||||||||
Done Inline ActionsCurrently I don't see a good reason to sort the list, but I may change my mind. pstef: Currently I don't see a good reason to sort the list, but I may change my mind. | |||||||||||||||||||
if (strncmp(entry->d_name, text, curpos) != 0) | |||||||||||||||||||
continue; | |||||||||||||||||||
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) { | |||||||||||||||||||
if (fstatat(dfd, entry->d_name, &statb, 0) == -1) | |||||||||||||||||||
continue; | |||||||||||||||||||
if (!S_ISREG(statb.st_mode)) | |||||||||||||||||||
continue; | |||||||||||||||||||
} else if (entry->d_type != DT_REG) | |||||||||||||||||||
continue; | |||||||||||||||||||
Done Inline ActionsThis will have to be escaped since libedit will refuse to do it seeing that the completion function is non-standard. pstef: This will have to be escaped since libedit will refuse to do it seeing that the completion… | |||||||||||||||||||
Not Done Inline ActionsTo be precise, all names will have to be escaped. Otherwise, the single_match check in contrib/libedit/filecomplete.c may see a difference. jilles: To be precise, all names will have to be escaped. Otherwise, the `single_match` check in… | |||||||||||||||||||
Done Inline ActionsIt may be debatable. If only one command was found (so matches[2] is NULL) and we decide to escape only matches[0] and matches[1] then all the conditions for single_match == TRUE are met. Then libedit should replace user input with an escaped equivalent and skip showing matches. For the multiple matches case we could escape all of them, so we would have the behavior described above and also every item on the list of suggestions would be escaped, like I've been seeing in some Linux distributions for a while (except that seems to be limited to enclosing the file name with single or double quotes; no backslashes). pstef: It may be debatable. If only one command was found (so matches[2] is NULL) and we decide to… | |||||||||||||||||||
if (++i >= size - 1) { | |||||||||||||||||||
Done Inline ActionsIs is possible that this overflows the allocated memory by one pointer? jilles: Is is possible that this overflows the allocated memory by one pointer? | |||||||||||||||||||
char **ramatches; | |||||||||||||||||||
size *= 2; | |||||||||||||||||||
ramatches = reallocarray(matches, size, sizeof(char *)); | |||||||||||||||||||
if (ramatches == NULL) { | |||||||||||||||||||
Done Inline Actionscdefs.h has a macro for this attribute, so it can be arichardson: cdefs.h has a macro for this attribute, so it can be
`sh_complete(EditLine *sel, int ch… | |||||||||||||||||||
closedir(dir); | |||||||||||||||||||
goto out; | |||||||||||||||||||
} | |||||||||||||||||||
matches = ramatches; | |||||||||||||||||||
} | |||||||||||||||||||
matches[i] = strdup(entry->d_name); | |||||||||||||||||||
} | |||||||||||||||||||
closedir(dir); | |||||||||||||||||||
} | |||||||||||||||||||
out: | |||||||||||||||||||
free(free_path); | |||||||||||||||||||
if (i == 0) { | |||||||||||||||||||
free(matches); | |||||||||||||||||||
return (NULL); | |||||||||||||||||||
} | |||||||||||||||||||
if (i == 1) { | |||||||||||||||||||
matches[0] = strdup(matches[1]); | |||||||||||||||||||
matches[i + 1] = NULL; | |||||||||||||||||||
} else { | |||||||||||||||||||
size_t j, k; | |||||||||||||||||||
qsort(matches + 1, i, sizeof(matches[0]), comparator); | |||||||||||||||||||
for (j = 1, k = 2; k <= i; k++) | |||||||||||||||||||
if (strcmp(matches[j] + curpos, matches[k] + curpos) == 0) | |||||||||||||||||||
free(matches[k]); | |||||||||||||||||||
else | |||||||||||||||||||
matches[++j] = matches[k]; | |||||||||||||||||||
matches[0] = strdup(text); | |||||||||||||||||||
matches[j + 1] = NULL; | |||||||||||||||||||
} | |||||||||||||||||||
return (matches); | |||||||||||||||||||
} | |||||||||||||||||||
/* | |||||||||||||||||||
* This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to | |||||||||||||||||||
* bind a key (tab by default) to execute the function. | |||||||||||||||||||
*/ | |||||||||||||||||||
unsigned char | |||||||||||||||||||
sh_complete(EditLine *sel, int ch __unused) | |||||||||||||||||||
Not Done Inline Actions
If you use qsort_s here and pass (void *)(intptr_t)curpos as the extra argument you can avoid the global variable and make curpos local to sh_matches. arichardson: If you use `qsort_s` here and pass `(void *)(intptr_t)curpos` as the extra argument you can… | |||||||||||||||||||
{ | |||||||||||||||||||
return (unsigned char)fn_complete(sel, NULL, sh_matches, | |||||||||||||||||||
L" \t\n\"\\'`@$><=;|&{(", NULL, NULL, (size_t)100, | |||||||||||||||||||
NULL, &((int) {0}), NULL, NULL); | |||||||||||||||||||
} |
I don't think this needs to be a global variable (see comments below).