diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2019-01-17 17:18:18 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2019-01-17 17:25:33 +0200 |
commit | 43784d4a2bf21d606e4dd55045017dd0f42d0f7c (patch) | |
tree | b463e38e53e03ec4a0b27cee4c80d80cb18e8851 | |
parent | b49c2929dfe3200ca631b744b3cfc26215b43d58 (diff) | |
download | mailutils-43784d4a2bf21d606e4dd55045017dd0f42d0f7c.tar.gz mailutils-43784d4a2bf21d606e4dd55045017dd0f42d0f7c.tar.bz2 |
Improve completion in mail
This affects the following commands: pipe, file, write, store.
* mail/mail.h (shell_compl): New function.
* mail/mailline.c (ml_command_completion): Change the meaning of the
last argument to command_completion function. All functions updated.
(msgtype_generator): Take into account '/' that can follow ':'.
Prefix returned values with :.
(header_generator): Append ':/' to the returned strings.
(compl_concat): New function.
(file_compl): Rewrite.
(msglist_closure_compl): New function.
(file_compl_matches,msglist_file_compl,exec_compl): Rewrite.
(shell_compl): New function.
* mail/table.c: Implement completion for pipe and shell commands.
-rw-r--r-- | mail/mail.h | 2 | ||||
-rw-r--r-- | mail/mailline.c | 292 | ||||
-rw-r--r-- | mail/table.c | 8 |
3 files changed, 231 insertions, 71 deletions
diff --git a/mail/mail.h b/mail/mail.h index 4d3c1db6a..a6cc3139d 100644 --- a/mail/mail.h +++ b/mail/mail.h @@ -492,6 +492,7 @@ char **command_compl (int argc, char **argv, int ws); char **alias_compl (int argc, char **argv, int ws); char **mailvar_set_compl (int argc, char **argv, int ws); char **exec_compl (int argc, char **argv, int ws); +char **shell_compl (int argc, char **argv, int ws); #else # define file_compl NULL # define no_compl NULL @@ -502,6 +503,7 @@ char **exec_compl (int argc, char **argv, int ws); # define alias_compl NULL # define var_compl NULL # define exec_compl NULL +# define shell_compl NULL # define mailvar_set_compl NULL #endif diff --git a/mail/mailline.c b/mail/mailline.c index 1bab8295b..6511e23da 100644 --- a/mail/mailline.c +++ b/mail/mailline.c @@ -50,13 +50,13 @@ sig_handler (int signo) } void -ml_clear_interrupt () +ml_clear_interrupt (void) { _interrupted = 0; } int -ml_got_interrupt () +ml_got_interrupt (void) { int rc = _interrupted; _interrupted = 0; @@ -85,7 +85,7 @@ ml_getc (FILE *stream) } void -ml_readline_init () +ml_readline_init (void) { if (!interactive) return; @@ -116,7 +116,7 @@ ml_readline_init () } char * -ml_readline_internal () +ml_readline_internal (void) { char *buf = NULL; size_t size = 0, n; @@ -158,6 +158,15 @@ ml_readline_with_intr (const char *prompt) #ifdef WITH_READLINE +/* Flags for per-command completion functions */ +enum + { + COMPL_DFL = 0x0, /* Nothing special */ + COMPL_WS = 0x1, /* Cursor pointing to a whitespace character + at the start of input or past another whitespace */ + COMPL_LASTARG = 0x2 /* Cursor pointing at or past the last argument */ + }; + static char *insert_text; static int @@ -200,7 +209,7 @@ ml_command_completion (char *cmd, int start, int end) char **ret; char *p; struct mu_wordsplit ws; - + for (p = rl_line_buffer; p < rl_line_buffer + start && mu_isblank (*p); p++) ; @@ -223,8 +232,16 @@ ml_command_completion (char *cmd, int start, int end) const struct mail_command_entry *entry = mail_find_command (ws.ws_wordv[0]); if (entry && entry->command_completion) - ret = entry->command_completion (ws.ws_wordc, ws.ws_wordv, - start == end); + { + int point = COMPL_DFL; + + if (start == end) + point |= COMPL_WS; + if (mu_str_skip_class (p + end, MU_CTYPE_SPACE)[0] == 0) + point |= COMPL_LASTARG; + + ret = entry->command_completion (ws.ws_wordc, ws.ws_wordv, point); + } else ret = NULL; } @@ -268,7 +285,7 @@ ml_set_completion_append_character (int c) } void -ml_attempted_completion_over () +ml_attempted_completion_over (void) { rl_attempted_completion_over = 1; } @@ -276,14 +293,15 @@ ml_attempted_completion_over () /* Completion functions */ char ** -no_compl (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED, int ws MU_ARG_UNUSED) +no_compl (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED, + int flags MU_ARG_UNUSED) { ml_attempted_completion_over (); return NULL; } char ** -msglist_compl (int argc, char **argv, int ws) +msglist_compl (int argc, char **argv, int point) { /* FIXME */ ml_attempted_completion_over (); @@ -291,10 +309,10 @@ msglist_compl (int argc, char **argv, int ws) } char ** -command_compl (int argc, char **argv, int ws) +command_compl (int argc, char **argv, int point) { ml_set_completion_append_character (0); - if (ws) + if (point & COMPL_WS) return NULL; return rl_completion_matches (argv[argc-1], ml_command_generator); } @@ -408,25 +426,29 @@ folder_generator (const char *text, int state) static char * msgtype_generator (const char *text, int state) { - static char types[] = "dnorTtu"; + /* Allowed message types, plus '/'. The latter can folow a colon, + meaning body lookup */ + static char types[] = "dnorTtu/"; static int i; - char *s; + char c; if (!state) { - if (text[1]) - return NULL; i = 0; } - if (!types[i]) - return NULL; - s = malloc (2); - if (s) + while ((c = types[i])) { - s[0] = types[i++]; - s[1] = 0; + i++; + if (!text[1] || text[1] == c) + { + char *s = mu_alloc (3); + s[0] = ':'; + s[1] = c; + s[2] = 0; + return s; + } } - return s; + return NULL; } static char * @@ -493,74 +515,193 @@ header_generator (const char *text, int state) { i++; if (mu_c_strncasecmp (hdr, text, len) == 0) - return mu_strdup (hdr); + return strcat (strcpy (mu_alloc (strlen (hdr) + 3), hdr), ":/"); } return NULL; } +/* Concatenate results of two completions. Both A and B are expected to + be returned by rl_completion_matches, i.e. their first entry is the + longest common prefix of the remaining entries, which are sorted + lexicographically. Array B is treated as case-insensitive. + + If either of the arrays is NULL, the other one is returned unchanged. + + Otherwise, if A[0] begins with a lowercase letter, all items from B + will be converted to lowercase. + + Both A and B (but not their elements) are freed prior to returning. + + Note: This function assumes that no string from A is present in B and + vice versa. + */ static char ** -file_compl_internal (int argc, char **argv, int ws, int msglist) +compl_concat (char **a, char **b) { - char *text; - - if (ws) + size_t i, j, k, n = 0, an, bn; + char **ret; + int lwr = 0; + + if (a) { - ml_set_completion_append_character (0); - ml_attempted_completion_over (); - return NULL; + lwr = mu_islower (a[0][0]); + for (an = 0; a[an]; an++) + ; + } + else + return b; + + if (b) + { + for (bn = 0; b[bn]; bn++) + { + if (lwr) + mu_strlower (b[bn]); + } } + else + return a; - text = argv[argc-1]; + i = an == 1 ? 0 : 1; + j = bn == 1 ? 0 : 1; + + n = (an - i) + (bn - j) + 1; + ret = mu_calloc (n + 1, sizeof (ret[0])); - if (msglist) + if (an == bn && an == 1) { - if (text[0] == ':') - { - ml_set_completion_append_character (' '); - return rl_completion_matches (text, msgtype_generator); - } - else if (mu_isalpha (text[0])) + /* Compute LCP of both entries */ + for (i = 0; a[i] && b[i] && a[i] == b[i]; i++) + ; + ret[0] = mu_alloc (i + 1); + memcpy (ret[0], a[0], i); + ret[0][i] = 0; + } + else + /* The first entry is the LCP of the rest. Select the shortest one. */ + ret[0] = mu_strdup ((strlen (a[0]) < strlen (b[0])) ? a[0] : b[0]); + + if (i) + free (a[0]); + if (j) + free (b[0]); + + k = 1; + while (k < n) + { + if (!a[i]) { - ml_set_completion_append_character (':'); - return rl_completion_matches (text, header_generator); + memcpy (ret + k, b + j, sizeof (b[0]) * (bn - j)); + break; } - else if (mu_isdigit (text[0])) + else if (!b[j]) { - ml_attempted_completion_over (); - return NULL; + memcpy (ret + k, a + i, sizeof (a[0]) * (an - i)); + break; } + else + ret[k++] = (strcmp (a[i], b[j]) < 0) ? a[i++] : b[j++]; + } + ret[n] = NULL; + free (a); + free (b); + return ret; +} + +char ** +file_compl (int argc, char **argv, int point) +{ + char *text; + + if (point & COMPL_WS) + { + ml_set_completion_append_character (0); + ml_attempted_completion_over (); + return NULL; } + text = argv[argc-1]; + switch (text[0]) { case '+': - text++; - break; + return rl_completion_matches (text + 1, folder_generator); case '%': case '#': case '&': ml_attempted_completion_over (); - return NULL; - + break; + default: - return NULL; /* Will be expanded by readline itself */ + /* Suppose it is a file name */ + return rl_completion_matches (text, rl_filename_completion_function); } + + return NULL; +} - return rl_completion_matches (text, folder_generator); +/* Internal completion generator for commands that take a message list + followed by a separate object as their arguments (e.g. write, where + the last object is a mailbox or pipe, where it is a command). + The MATCHES function generates expansions for the last argument. + CLOSURE supplies its argument. +*/ +static char ** +msglist_closure_compl (int argc, char **argv, int point, + char **(*matches) (void*), + void *closure) +{ + char *text = (point & COMPL_WS) ? "" : argv[argc-1]; + size_t len = strlen (text); + + if (text[0] == ':') + { + if (text[1] && (text[1] == '/' || text[2])) + { + ml_set_completion_append_character (0); + ml_attempted_completion_over (); + return NULL; + } + ml_set_completion_append_character (' '); + return rl_completion_matches (text, msgtype_generator); + } + + ml_set_completion_append_character (0); + if (len && text[len-1] == ':') + { + char **ret = mu_calloc(2, sizeof (ret[0])); + ret[0] = strcat (strcpy (mu_alloc (len + 2), text), "/"); + return ret; + } + + if (point & COMPL_LASTARG) + return compl_concat (matches (closure), + rl_completion_matches (text, header_generator)); + else + return rl_completion_matches (text, header_generator); } -char ** -file_compl (int argc, char **argv, int ws) +struct compl_closure +{ + int argc; + char **argv; + int point; +}; + +static char ** +file_compl_matches (void *closure) { - return file_compl_internal (argc, argv, ws, 0); + struct compl_closure *cp = closure; + return file_compl (cp->argc, cp->argv, cp->point); } char ** -msglist_file_compl (int argc, char **argv, int ws) +msglist_file_compl (int argc, char **argv, int point) { - return file_compl_internal (argc, argv, ws, 1); + struct compl_closure clos = { argc, argv, point }; + return msglist_closure_compl (argc, argv, point, file_compl_matches, &clos); } static char * @@ -616,10 +757,10 @@ dir_generator (const char *text, int state) } char ** -dir_compl (int argc, char **argv, int ws) +dir_compl (int argc, char **argv, int point) { ml_attempted_completion_over (); - if (ws) + if (point & COMPL_WS) { ml_set_completion_append_character (0); return NULL; @@ -647,10 +788,11 @@ alias_generator (const char *text, int state) } char ** -alias_compl (int argc, char **argv, int ws) +alias_compl (int argc, char **argv, int point) { ml_attempted_completion_over (); - return rl_completion_matches (ws ? "" : argv[argc-1], alias_generator); + return rl_completion_matches ((point & COMPL_WS) ? "" : argv[argc-1], + alias_generator); } static char * @@ -756,10 +898,26 @@ exec_generator (const char *text, int state) return NULL; } +static char ** +exec_matches (void *closure) +{ + return rl_completion_matches ((char *)closure, exec_generator); +} + +char ** +exec_compl (int argc, char **argv, int point) +{ + return msglist_closure_compl (argc, argv, point, + exec_matches, + (point & COMPL_WS) ? "" : argv[argc-1]); +} + char ** -exec_compl (int argc, char **argv, int ws) +shell_compl (int argc, char **argv, int point) { - return rl_completion_matches (ws ? "" : argv[argc-1], exec_generator); + if (argc == 2) + return rl_completion_matches (argv[1], exec_generator); + return no_compl (argc, argv, point); } #else @@ -779,7 +937,7 @@ static int ch_kill; static struct termios term_settings; int -set_tty () +set_tty (void) { struct termios new_settings; @@ -803,7 +961,7 @@ set_tty () } void -restore_tty () +restore_tty (void) { tcsetattr (STDOUT, TCSADRAIN, &term_settings); } @@ -814,7 +972,7 @@ restore_tty () static struct termio term_settings; int -set_tty () +set_tty (void) { struct termio new_settings; @@ -834,7 +992,7 @@ set_tty () } void -restore_tty () +restore_tty (void) { ioctl(STDOUT, TCSETA, &term_settings); } @@ -845,7 +1003,7 @@ restore_tty () static struct sgttyb term_settings; int -set_tty () +set_tty (void) { struct sgttyb new_settings; @@ -863,7 +1021,7 @@ set_tty () } void -restore_tty () +restore_tty (void) { ioctl(STDOUT, TIOCSETP, &term_settings); } diff --git a/mail/table.c b/mail/table.c index f48ca5f6c..52f70baba 100644 --- a/mail/table.c +++ b/mail/table.c @@ -95,7 +95,7 @@ static const struct mail_command_entry mail_command_table[] = { { "P", "Print", "P[rint] [msglist]", 0, mail_print, msglist_compl }, { "pi", "pipe", "pi[pe] [[msglist] command]", 0, - mail_pipe, no_compl }, /* FIXME: exec_compl */ + mail_pipe, exec_compl }, { "pre", "preserve", "pre[serve] [msglist]", 0, mail_hold, msglist_compl }, { "prev", "previous", "prev[ious] [message]", 0, @@ -129,7 +129,7 @@ static const struct mail_command_entry mail_command_table[] = { { "sete", "setenv", "sete[nv] [name[=value]]", 0, mail_setenv, no_compl }, { "sh", "shell", "sh[ell] [command]", 0, - mail_shell, no_compl }, /* FIXME: exec_compl */ + mail_shell, shell_compl }, { "si", "size", "si[ze] [msglist]", 0, mail_size, msglist_compl }, { "so", "source", "so[urce] file", 0, @@ -177,7 +177,7 @@ static const struct mail_command_entry mail_command_table[] = { { "?", "?", "? [command...]", 0, mail_help, command_compl }, { "!", "", "![command]", 0, - mail_shell, exec_compl }, + mail_shell, shell_compl }, { "=", "=", "=", 0, mail_eq, no_compl }, { "#", "#", "# comment", 0, @@ -187,7 +187,7 @@ static const struct mail_command_entry mail_command_table[] = { { "+", "+", "+ [message]", 0, mail_next, msglist_compl }, { "|", "|", "| [[msglist] command]", 0, - mail_pipe, msglist_compl }, /* FIXME: msglist_exec_compl */ + mail_pipe, exec_compl }, { "-", "-", "- [message]", 0, mail_previous, msglist_compl }, }; |