Changeset View
Standalone View
bin/sh/histedit.c
Show All 39 Lines | |||||
#include <sys/param.h> | #include <sys/param.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 <paths.h> | #include <paths.h> | ||||
#include <stdbool.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). | ||||
*/ | */ | ||||
#include "shell.h" | #include "shell.h" | ||||
#include "parser.h" | #include "parser.h" | ||||
Show All 12 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 int savehist; | static int savehist; | ||||
static FILE *el_in, *el_out; | static FILE *el_in, *el_out; | ||||
static bool in_command_completion; | |||||
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); | ||||
static int comparator(const void *, const void *, void *); | static int comparator(const void *, const void *, void *); | ||||
static char **sh_matches(const char *, int, int); | static char **sh_matches(const char *, int, int); | ||||
static const char *append_char_function(const char *); | |||||
static unsigned char sh_complete(EditLine *, int); | static unsigned char sh_complete(EditLine *, int); | ||||
static const char * | static const char * | ||||
get_histfile(void) | get_histfile(void) | ||||
{ | { | ||||
const char *histfile; | const char *histfile; | ||||
/* don't try to save if the history size is 0 */ | /* don't try to save if the history size is 0 */ | ||||
▲ Show 20 Lines • Show All 507 Lines • ▼ Show 20 Lines | |||||
**sh_matches(const char *text, int start, int end) | **sh_matches(const char *text, int start, int end) | ||||
{ | { | ||||
char *free_path = NULL, *path; | char *free_path = NULL, *path; | ||||
const char *dirname; | const char *dirname; | ||||
char **matches = NULL; | char **matches = NULL; | ||||
size_t i = 0, size = 16, uniq; | size_t i = 0, size = 16, uniq; | ||||
size_t curpos = end - start, lcstring = -1; | size_t curpos = end - start, lcstring = -1; | ||||
in_command_completion = false; | |||||
if (start > 0 || memchr("/.~", text[0], 3) != NULL) | if (start > 0 || memchr("/.~", text[0], 3) != NULL) | ||||
return (NULL); | return (NULL); | ||||
in_command_completion = true; | |||||
if ((free_path = path = strdup(pathval())) == NULL) | if ((free_path = path = strdup(pathval())) == NULL) | ||||
goto out; | goto out; | ||||
if ((matches = malloc(size * sizeof(matches[0]))) == NULL) | if ((matches = malloc(size * sizeof(matches[0]))) == NULL) | ||||
goto out; | goto out; | ||||
while ((dirname = strsep(&path, ":")) != NULL) { | while ((dirname = strsep(&path, ":")) != NULL) { | ||||
struct dirent *entry; | struct dirent *entry; | ||||
DIR *dir; | DIR *dir; | ||||
int dfd; | int dfd; | ||||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | for (size_t k = 1; k <= uniq; k++) | ||||
free(matches[k]); | free(matches[k]); | ||||
free(matches); | free(matches); | ||||
return (NULL); | return (NULL); | ||||
} | } | ||||
return (matches); | return (matches); | ||||
} | } | ||||
/* | /* | ||||
* If we don't specify this function as app_func in the call to fn_complete2, | |||||
* libedit will use the default one, which adds a " " to plain files and | |||||
* a "/" to directories regardless of whether it's a command name or a plain | |||||
* path (relative or absolute). We never want to add "/" to commands. | |||||
* | |||||
* For example, after I did "mkdir rmdir", "rmdi" would be autocompleted to | |||||
* "rmdir/" instead of "rmdir ". | |||||
*/ | |||||
static const char * | |||||
append_char_function(const char *name) | |||||
{ | |||||
struct stat stbuf; | |||||
char *expname = name[0] == '~' ? fn_tilde_expand(name) : NULL; | |||||
const char *rs; | |||||
jilles: Since both `tilde_expand` and the parser use `STARTSTACKSTR` and the like to build strings… | |||||
Done Inline ActionsCan you help me understand under what circumstances this interactive-mode-only code could interfere with the parser performance (if we added grabstackstr()) or behavior (STARTSTACKSTR use)? Do you mean those could happen during script execution? pstef: Can you help me understand under what circumstances this interactive-mode-only code could… | |||||
Not Done Inline ActionsScenario: start sh, type <<X some text ~/ then press Tab. As it is now, readtoken1() in parser.c builds up the text of the here-document in the stack string, using a local variable (out) to point to the location for the next byte. If the line editing code (called from there via pgetc() and variants) also uses the stack string, it will overwrite this data with the tilde-expanded completion word. Some other code in this situation uses grabstackstr() and ungrabstackstr() to "allocate" the space, so the tilde expansion will not overwrite it. However, doing that here in the straightforward way would involve a grabstackstr()/ungrabstackstr() pair for most bytes parsed by the shell, greatly slowing down the parser (I have not benchmarked this). Making out a global variable is also likely to lead to a measurable slowdown. An approach that may work is to pierce the abstraction and only grabstackstr()/ungrabstackstr() when pgetc_macro() cannot obtain a byte from the buffer. Another approach may be to move the problem entirely to the completion code, and have that save and restore the state of the stack string so it does not interfere with the parser. Since it is not known how much space readtoken1() is using in the current block (tracked in the local variable out), the completion code cannot use the block at all. I have not thought out the details of this, but it seems most promising. jilles: Scenario: start sh, type
```
<<X
some text
~/
```
then press Tab.
As it is now, `readtoken1… | |||||
Done Inline Actionsthe ,0 is a left over? bapt: the ,0 is a left over? | |||||
if (!in_command_completion && | |||||
stat(expname ? expname : name, &stbuf) == 0 && | |||||
S_ISDIR(stbuf.st_mode)) | |||||
rs = "/"; | |||||
else | |||||
rs = " "; | |||||
free(expname); | |||||
return (rs); | |||||
} | |||||
/* | |||||
* This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to | * 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. | * bind a key (tab by default) to execute the function. | ||||
*/ | */ | ||||
unsigned char | unsigned char | ||||
sh_complete(EditLine *sel, int ch __unused) | sh_complete(EditLine *sel, int ch __unused) | ||||
{ | { | ||||
return (unsigned char)fn_complete2(sel, NULL, sh_matches, | return (unsigned char)fn_complete2(sel, NULL, sh_matches, | ||||
L" \t\n\"\\'`@$><=;|&{(", NULL, NULL, (size_t)100, | L" \t\n\"\\'`@$><=;|&{(", NULL, append_char_function, | ||||
NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH); | (size_t)100, NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH); | ||||
} | } | ||||
#else | #else | ||||
#include "error.h" | #include "error.h" | ||||
int | int | ||||
histcmd(int argc __unused, char **argv __unused) | histcmd(int argc __unused, char **argv __unused) | ||||
{ | { | ||||
Show All 14 Lines |
Since both tilde_expand and the parser use STARTSTACKSTR and the like to build strings, this needs some extra code to prevent them from interfering. Otherwise, things could go wrong if completion is done while reading a multi-line string or a here-document.
The grabstackstr()/ungrabstackstr() pair suggested by comments in memalloc.c is not attractive here, since it would slow down all parsing (including of scripts) for an interactive feature. Perhaps it is better to make it such that the code called here will call malloc() for a new stack block.