rc

[fork] interactive rc shell
Log | Files | Refs | README | LICENSE

commit f58969944e1c582c75240c0ceed8806387bc9841
parent 8ab057499c351655fc4d93d5e997b1fe816416eb
Author: Bert Münnich <ber.t@posteo.de>
Date:   Mon, 30 Sep 2019 22:37:13 +0200

Supplant readline's quoting and word separation mechanisms

With this we can finally complete an open quoted word with an escaped quote
(e.g. 'it''s alive^I) without any issues!

It is really f!*#ing hard to bend readline to rc's simple quoting rules. Tons
of variables control a huge pile of code but every code path assumes just
enough of bash's quoting rules to render all these mechanisms unusable for rc.

Abusing *rl_completion_word_break_hook* is our only hope. Let us pray that this
keeps working.

Diffstat:
Medit-readline.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 55 insertions(+), 22 deletions(-)

diff --git a/edit-readline.c b/edit-readline.c @@ -28,10 +28,10 @@ static char *dir_join(const char *a, const char *b) { return mprint("%s%s%s", a, l && a[l-1] != '/' ? "/" : "", b); } -char *maybe_quote(char *p) { +char *quote(char *p, int open) { if (strpbrk(p, quote_chars)) { char *r = mprint("%#S", p); - if (rl_completion_suppress_quote && rl_completion_type != '*') + if (open) r[strlen(r)-1] = '\0'; efree(p); return r; @@ -39,6 +39,19 @@ char *maybe_quote(char *p) { return p; } +static char *unquote(const char *text) { + int quoted = 0; + char *p, *r; + p = r = ealloc(strlen(text) + 1); + while ((*p = *text++)) { + if (*p == '\'' && (!quoted || *text != '\'')) + quoted = !quoted; + else + p++; + } + return r; +} + /* Decide if this directory entry is a completion candidate, either executable * or a directory. "dname" is the absolute path of the directory, "name" is the * current entry. "subdirs" is the name being completed up to and including the @@ -153,17 +166,8 @@ static char *compl_filename(const char *text, int state) { return name; } -static rl_compentry_func_t *compl_func(const char *text, int start, int end) { - int quote = FALSE; - char last = ';', *s, *t; - - for (s = &rl_line_buffer[0], t = &rl_line_buffer[start]; s < t; s++) { - if (*s == '\'') - quote = !quote; - if (!quote && *s != ' ' && *s != '\t') - last = *s; - } - switch (last) { +static rl_compentry_func_t *compl_func(char prefix) { + switch (prefix) { case '`': case '@': case '|': case '&': case '(': case ')': case '{': case ';': return compl_command; @@ -173,10 +177,40 @@ static rl_compentry_func_t *compl_func(const char *text, int start, int end) { return compl_filename; } +static char compl_prefix(int index) { + while (index-- > 0) { + char c = rl_line_buffer[index]; + if (c != ' ' && c != '\t') + return c; + } + return ';'; +} + +/* Find the start of the word to complete. This function is the only way to + * make readline's code fully support rc's quoting rules. It is called in + * *_rl_find_completion_word* as the *rl_completion_word_break_hook* and + * exploits the fact that readline stores the start of the word in *rl_point*. + * We put the correct postion there first and prevent readline from overwriting + * it by keeping *rl_completer_quote_characters* empty! + */ +static char *compl_start() { + int i, quoted = 0, start = 0; + for (i = 0; i < rl_point; i++) { + char c = rl_line_buffer[i]; + if (c == '\'') + quoted = !quoted; + if (!quoted && strchr(rl_basic_word_break_characters, c)) + start = i; + } + rl_point = start; + return NULL; +} + static rl_compentry_func_t *compentry_func; static char **rc_completion(const char *text, int start, int end) { size_t i; + char *t = unquote(text); char **matches = NULL; rl_compentry_func_t *func; @@ -184,17 +218,16 @@ static char **rc_completion(const char *text, int start, int end) { func = compentry_func; compentry_func = NULL; } else - func = compl_func(text, start, end); - matches = rl_completion_matches(text, func); + func = compl_func(compl_prefix(start)); + matches = rl_completion_matches(t, func); if (matches) { - if (matches[1]) - rl_completion_suppress_quote = 1; if (rl_completion_type != '?') - matches[0] = maybe_quote(matches[0]); + matches[0] = quote(matches[0], matches[1] != NULL); if (rl_completion_type == '*') for (i = 1; matches[i]; i++) - matches[i] = maybe_quote(matches[i]); + matches[i] = quote(matches[i], 0); } + efree(t); rl_attempted_completion_over = 1; return matches; } @@ -222,15 +255,15 @@ void *edit_begin(int fd) { List *hist; struct cookie *c; - rl_initialize(); - rl_attempted_completion_function = rc_completion; rl_basic_quote_characters = ""; rl_basic_word_break_characters = " \t\n`@$><=;|&{("; rl_catch_signals = 0; - rl_completer_quote_characters = "'"; + rl_completion_word_break_hook = compl_start; rl_readline_name = "rc"; + rl_initialize(); + rl_add_funmap_entry("rc-complete-command", rc_complete_command); rl_add_funmap_entry("rc-complete-filename", rc_complete_filename); rl_add_funmap_entry("rc-complete-variable", rc_complete_variable);