From 511fef0f5c402483ca32f6b9b6a94ebdb7e5d2d5 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Mon, 6 Nov 2023 09:44:01 -0500 Subject: [PATCH] new bindable readline command `execute-named-command', bound to M-x in emacs mode --- CWRU/CWRU.chlog | 27 +++ doc/bashref.texi | 4 +- general.h | 4 +- lib/readline/doc/rluser.texi | 7 + lib/readline/doc/version.texi | 6 +- lib/readline/emacs_keymap.c | 2 +- lib/readline/funmap.c | 1 + lib/readline/readline.h | 2 + lib/readline/rlprivate.h | 33 +++ lib/readline/text.c | 418 ++++++++++++++++++++++++++++++++++ 10 files changed, 497 insertions(+), 7 deletions(-) diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index d1e6fe86..808a061c 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -7946,3 +7946,30 @@ subst.c single-element vector with a copy of the original word so we can add it to the result list unchanged. Inspired by https://savannah.gnu.org/support/?110948 + + 11/3 + ---- +lib/readline/text.c + - readstr: set of functions to read a string from the keyboard, using + rl_line_buffer for temporary storage, with minimal editing and an + optional caller-supplied completion function. Doesn't use the + callback framework yet since none of the functions are public + - rl_execute_named_command: new bindable function to read a bindable + command name (from the funmap_names array) and execute it, with + command name completion on SPACE and TAB + +lib/readline/readline.h + - rl_execute_named_command: new extern declaration + +lib/readline/rlprivate.h + - new extern declarations for the readstr function framework + +lib/readline/funmap.c + - execute-named-command: new bindable function name + +lib/readline/emacs_keymap.c + - rl_execute_named_function: bound to M-x by default + +lib/readline/doc/rluser.texi + - execute-named-command: document as bindable function name with its + default binding to M-x in emacs mode diff --git a/doc/bashref.texi b/doc/bashref.texi index f79ad8d2..41db2ed5 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -6135,8 +6135,8 @@ The Bash @sc{posix} mode is described in @ref{Bash POSIX Mode}. These are the @sc{posix} special builtins: @example -@w{break : . continue eval exec exit export readonly return set} -@w{shift trap unset} +@w{break : . source continue eval exec exit export readonly return set} +@w{shift times trap unset} @end example @node Shell Variables diff --git a/general.h b/general.h index 2320c67a..ecbc8877 100644 --- a/general.h +++ b/general.h @@ -159,7 +159,9 @@ STREQ(const char *a, const char *b) static inline int STREQN(const char *a, const char *b, size_t n) { - return ((n == 0) || ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)); + return ((n == 0) || + (n == 1 && a[0] == b[0]) || + ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)); } /* More convenience definitions that possibly save system or libc calls. */ diff --git a/lib/readline/doc/rluser.texi b/lib/readline/doc/rluser.texi index a0cf21c8..72d951aa 100644 --- a/lib/readline/doc/rluser.texi +++ b/lib/readline/doc/rluser.texi @@ -1912,6 +1912,13 @@ editing mode. @end ifclear +@item execute-named-command (M-x) +Read a bindable readline command name from the input and execute the +function to which it's bound, as if the key sequence to which it was +bound appeared in the input. +If this function is supplied with a numeric argument, it passes that +argument to the function it executes. + @end ftable @node Readline vi Mode diff --git a/lib/readline/doc/version.texi b/lib/readline/doc/version.texi index f1b82d41..aa585cde 100644 --- a/lib/readline/doc/version.texi +++ b/lib/readline/doc/version.texi @@ -5,7 +5,7 @@ Copyright (C) 1988-2023 Free Software Foundation, Inc. @set EDITION 8.3 @set VERSION 8.3 -@set UPDATED 4 October 2023 -@set UPDATED-MONTH October 2023 +@set UPDATED 3 November 2023 +@set UPDATED-MONTH November 2023 -@set LASTCHANGE Wed Oct 4 10:57:26 EDT 2023 +@set LASTCHANGE Fri Nov 3 12:04:26 EDT 2023 diff --git a/lib/readline/emacs_keymap.c b/lib/readline/emacs_keymap.c index 02597dad..cf2adcc1 100644 --- a/lib/readline/emacs_keymap.c +++ b/lib/readline/emacs_keymap.c @@ -448,7 +448,7 @@ KEYMAP_ENTRY_ARRAY emacs_meta_keymap = { { ISFUNC, rl_upcase_word }, /* Meta-u */ { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-v */ { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-w */ - { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-x */ + { ISFUNC, rl_execute_named_command }, /* Meta-x */ { ISFUNC, rl_yank_pop }, /* Meta-y */ { ISFUNC, (rl_command_func_t *)0x0 }, /* Meta-z */ diff --git a/lib/readline/funmap.c b/lib/readline/funmap.c index affa0fdc..548cfb57 100644 --- a/lib/readline/funmap.c +++ b/lib/readline/funmap.c @@ -89,6 +89,7 @@ static const FUNMAP default_funmap[] = { { "end-of-history", rl_end_of_history }, { "end-of-line", rl_end_of_line }, { "exchange-point-and-mark", rl_exchange_point_and_mark }, + { "execute-named-command", rl_execute_named_command }, { "fetch-history", rl_fetch_history }, { "forward-backward-delete-char", rl_rubout_or_delete }, { "forward-byte", rl_forward_byte }, diff --git a/lib/readline/readline.h b/lib/readline/readline.h index 82b22343..d300e6a0 100644 --- a/lib/readline/readline.h +++ b/lib/readline/readline.h @@ -209,6 +209,8 @@ extern int rl_stop_output (int, int); extern int rl_abort (int, int); extern int rl_tty_status (int, int); +extern int rl_execute_named_command (int, int); + /* Bindable commands for incremental and non-incremental history searching. */ extern int rl_history_search_forward (int, int); extern int rl_history_search_backward (int, int); diff --git a/lib/readline/rlprivate.h b/lib/readline/rlprivate.h index c3e43e39..461617c4 100644 --- a/lib/readline/rlprivate.h +++ b/lib/readline/rlprivate.h @@ -110,6 +110,27 @@ typedef struct __rl_search_context char *search_terminators; } _rl_search_cxt; +/* readstr flags */ +#define RL_READSTR_NOSPACE 0x01 /* don't insert space, use for completion */ + +typedef struct __rl_readstr_context +{ + int flags; + + int prevc; + int lastc; +#if defined (HANDLE_MULTIBYTE) + char mb[MB_LEN_MAX]; + char pmb[MB_LEN_MAX]; +#endif + + int save_point; + int save_mark; + int save_line; + + int (*compfunc) (struct __rl_readstr_context *, int); +} _rl_readstr_cxt; + struct _rl_cmd { Keymap map; int count; @@ -461,6 +482,16 @@ extern int _rl_char_search_internal (int, int, int); #endif extern int _rl_set_mark_at_pos (int); +extern _rl_readstr_cxt *_rl_rscxt_alloc (int); +extern void _rl_rscxt_dispose (_rl_readstr_cxt *, int); +extern void _rl_free_saved_readstr_line (void); +extern void _rl_unsave_saved_readstr_line (void); +extern _rl_readstr_cxt *_rl_readstr_init (int, int); +extern int _rl_readstr_cleanup (_rl_readstr_cxt *, int); +extern void _rl_readstr_restore (_rl_readstr_cxt *); +extern int _rl_readstr_getchar (_rl_readstr_cxt *); +extern int _rl_readstr_dispatch (_rl_readstr_cxt *, int); + /* undo.c */ extern UNDO_LIST *_rl_copy_undo_entry (UNDO_LIST *); extern UNDO_LIST *_rl_copy_undo_list (UNDO_LIST *); @@ -635,6 +666,8 @@ extern int _rl_term_autowrap; extern int _rl_optimize_typeahead; extern int _rl_keep_mark_active; +extern _rl_readstr_cxt *_rl_rscxt; + /* undo.c */ extern int _rl_doing_an_undo; extern int _rl_undo_group_level; diff --git a/lib/readline/text.c b/lib/readline/text.c index e3e5bb9e..933536ad 100644 --- a/lib/readline/text.c +++ b/lib/readline/text.c @@ -1928,3 +1928,421 @@ rl_mark_active_p (void) { return (mark_active); } + +/* **************************************************************** */ +/* */ +/* Reading a string entered from the keyboard */ +/* */ +/* **************************************************************** */ + +/* A very simple set of functions to read a string from the keyboard using + the line buffer as temporary storage. The caller can set a completion + function to perform completion on TAB and SPACE. */ + +/* XXX - this is all very similar to the search stuff but with a different + CXT. */ + +static HIST_ENTRY *_rl_saved_line_for_readstr; +_rl_readstr_cxt *_rl_rscxt; + +_rl_readstr_cxt * +_rl_rscxt_alloc (int flags) +{ + _rl_readstr_cxt *cxt; + + cxt = (_rl_readstr_cxt *)xmalloc (sizeof (_rl_readstr_cxt)); + + cxt->flags = flags; + + cxt->save_point = rl_point; + cxt->save_mark = rl_mark; + cxt->save_line = where_history (); + + cxt->prevc = cxt->lastc = 0; + + cxt->compfunc = NULL; + + return cxt; +} + +void +_rl_rscxt_dispose (_rl_readstr_cxt *cxt, int flags) +{ + xfree (cxt); +} + +void +_rl_free_saved_readstr_line () +{ + if (_rl_saved_line_for_readstr) + _rl_free_saved_line (_rl_saved_line_for_readstr); + _rl_saved_line_for_readstr = (HIST_ENTRY *)NULL; +} + +void +_rl_unsave_saved_readstr_line () +{ + if (_rl_saved_line_for_readstr) + _rl_unsave_line (_rl_saved_line_for_readstr); + _rl_saved_line_for_readstr = (HIST_ENTRY *)NULL; +} + +_rl_readstr_cxt * +_rl_readstr_init (int pchar, int flags) +{ + _rl_readstr_cxt *cxt; + char *p; + + cxt = _rl_rscxt_alloc (flags); + + rl_maybe_replace_line (); + _rl_saved_line_for_readstr = _rl_alloc_saved_line (); + + rl_undo_list = 0; + + rl_line_buffer[0] = 0; + rl_end = rl_point = 0; + + p = _rl_make_prompt_for_search (pchar ? pchar : '@'); + rl_message ("%s", p); + xfree (p); + + _rl_rscxt = cxt; + + return cxt; +} + +int +_rl_readstr_cleanup (_rl_readstr_cxt *cxt, int r) +{ + _rl_rscxt_dispose (cxt, 0); + _rl_rscxt = 0; + + return (r != 1); +} + +void +_rl_readstr_restore (_rl_readstr_cxt *cxt) +{ + _rl_unsave_saved_readstr_line (); /* restores rl_undo_list */ + rl_point = cxt->save_point; + rl_mark = cxt->save_mark; + rl_restore_prompt (); /* _rl_make_prompt_for_search saved it */ + rl_clear_message (); + _rl_fix_point (1); +} + +int +_rl_readstr_getchar (_rl_readstr_cxt *cxt) +{ + int c; + + cxt->prevc = cxt->lastc; + + /* Read a key and decide how to proceed. */ + RL_SETSTATE(RL_STATE_MOREINPUT); + c = cxt->lastc = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + +#if defined (HANDLE_MULTIBYTE) + /* This ends up with C (and LASTC) being set to the last byte of the + multibyte character. In most cases c == lastc == mb[0] */ + if (c >= 0 && MB_CUR_MAX > 1 && rl_byte_oriented == 0) + c = cxt->lastc = _rl_read_mbstring (cxt->lastc, cxt->mb, MB_LEN_MAX); +#endif + + RL_CHECK_SIGNALS (); + return c; +} + +/* Process just-read character C according to readstr context CXT. Return -1 + if the caller should abort the read, 0 if we should break out of the + loop, and 1 if we should continue to read characters. This can perform + completion on the string read so far (stored in rl_line_buffer) if the + caller has set up a completion function. The completion function can + return -1 to indicate that we should abort the read. If we return -1 + we will call _rl_readstr_restore to clean up the state, leaving the caller + to free the context. */ +int +_rl_readstr_dispatch (_rl_readstr_cxt *cxt, int c) +{ + int n; + + if (c < 0) + c = CTRL ('C'); + + switch (c) + { + case CTRL('W'): + rl_unix_word_rubout (1, c); + break; + + case CTRL('U'): + rl_unix_line_discard (1, c); + break; + + case CTRL('Q'): + case CTRL('V'): + n = rl_quoted_insert (1, c); + if (n < 0) + { + _rl_readstr_restore (cxt); + return -1; + } + cxt->lastc = rl_line_buffer[rl_point - 1]; /* preserve prevc */ + break; + + case RETURN: + case NEWLINE: + return 0; + + case CTRL('H'): + case RUBOUT: + if (rl_point == 0) + { + _rl_readstr_restore (cxt); + return -1; + } + _rl_rubout_char (1, c); + break; + + case CTRL('C'): + case CTRL('G'): + rl_ding (); + _rl_readstr_restore (cxt); + return -1; + + case ESC: + /* Allow users to bracketed-paste text into the string. + Similar code is in search.c:_rl_nsearch_dispatch(). */ + if (_rl_enable_bracketed_paste && ((n = _rl_nchars_available ()) >= (BRACK_PASTE_SLEN-1))) + { + if (_rl_read_bracketed_paste_prefix (c) == 1) + rl_bracketed_paste_begin (1, c); + else + { + c = rl_read_key (); /* get the ESC that got pushed back */ + _rl_insert_char (1, c); + } + } + else + _rl_insert_char (1, c); + break; + + case ' ': + if ((cxt->flags & RL_READSTR_NOSPACE) == 0) + { + _rl_insert_char (1, c); + break; + } + /* FALLTHROUGH */ + case TAB: + /* Perform completion if the caller has set a completion function. */ + n = (cxt->compfunc) ? (*cxt->compfunc) (cxt, c) : _rl_insert_char (1, c); + if (n < 0) + { + _rl_readstr_restore (cxt); + return -1; + } + break; + + default: +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_insert_text (cxt->mb); + else +#endif + _rl_insert_char (1, c); + break; + } + + (*rl_redisplay_function) (); + rl_deactivate_mark (); + return 1; +} + +/* **************************************************************** */ +/* */ +/* Reading and Executing named commands */ +/* */ +/* **************************************************************** */ + +/* A completion generator for bindable readline command names. */ +static char * +readcmd_completion_function (const char *text, int state) +{ + static const char **cmdlist = NULL; + static size_t lind, nlen; + const char *cmdname; + + if (state == 0) + { + if (cmdlist) + free (cmdlist); + + cmdlist = rl_funmap_names (); + lind = 0; + nlen = RL_STRLEN (text); + } + if (cmdlist == 0 || cmdlist[lind] == 0) + return (char *)NULL; + + while (cmdlist[lind]) + { + cmdname = cmdlist[lind++]; + if (STREQN (text, cmdname, nlen)) + return (savestring (cmdname)); + } + return ((char *)NULL); +} + +static void +_rl_display_cmdname_matches (char **matches) +{ + size_t len, max, i; + int old; + + old = rl_filename_completion_desired; + rl_filename_completion_desired = 0; + + /* There is more than one match. Find out how many there are, + and find the maximum printed length of a single entry. */ + for (max = 0, i = 1; matches[i]; i++) + { + len = strlen (matches[i]); + + if (len > max) + max = len; + } + len = i - 1; + + rl_display_match_list (matches, len, max); + rl_filename_completion_desired = old; + + rl_forced_update_display (); + rl_display_fixed = 1; +} + +static int +_rl_readcmd_complete (_rl_readstr_cxt *cxt, int c) +{ + char **matches; + char *prefix; + size_t plen; + + matches = rl_completion_matches (rl_line_buffer, readcmd_completion_function); + + if (RL_SIG_RECEIVED()) + { + _rl_free_match_list (matches); + matches = 0; + RL_CHECK_SIGNALS (); + return -1; + } + else if (matches == 0) + rl_ding (); + + /* Whether or not there are multiple matches, we just want to append the + new characters in matches[0]. We display possible matches if we didn't + append anything. */ + if (matches) + { + prefix = matches[0]; + plen = strlen (prefix); + + if (plen > rl_end) + { + size_t n; + for (n = rl_end; n < plen && prefix[n]; n++) + _rl_insert_char (1, prefix[n]); + } + else if (matches[1]) + _rl_display_cmdname_matches (matches); + _rl_free_match_list (matches); + } + + return 0; +} + +/* Use the readstr functions to read a bindable command name using the + line buffer, with completion. */ +static char * +_rl_read_command_name () +{ + _rl_readstr_cxt *cxt; + char *ret; + int c, r; + + cxt = _rl_readstr_init ('!', RL_READSTR_NOSPACE); + cxt->compfunc = _rl_readcmd_complete; + + /* skip callback stuff for now */ + r = 0; + while (1) + { + c = _rl_readstr_getchar (cxt); + + if (c < 0) + { + _rl_readstr_restore (cxt); + _rl_readstr_cleanup (cxt, r); + return NULL; + } + + if (c == 0) + break; + + r = _rl_readstr_dispatch (cxt, c); + if (r < 0) + { + _rl_readstr_cleanup (cxt, r); + return NULL; /* dispatch function cleans up */ + } + else if (r == 0) + break; + } + + ret = savestring (rl_line_buffer); + + /* Now restore the original line and perform one final redisplay. */ + _rl_readstr_restore (cxt); + (*rl_redisplay_function) (); + + /* And free up the context. */ + _rl_readstr_cleanup (cxt, r); + return ret; +} + +/* Read a command name from the keyboard and execute it as if the bound key + sequence had been entered. */ +int +rl_execute_named_command (int count, int key) +{ + char *command; + rl_command_func_t *func; + int r; + + command = _rl_read_command_name (); + if (command == 0 || *command == '\0') + return 1; + if (func = rl_named_function (command)) + { + int prev, ostate; + + prev = rl_dispatching; + ostate = RL_ISSTATE (RL_STATE_DISPATCHING); + rl_dispatching = 1; + RL_SETSTATE (RL_STATE_DISPATCHING); /* make sure it's set */ + r = (*func) (count, key); + if (ostate == 0) + RL_UNSETSTATE (RL_STATE_DISPATCHING); /* unset it if it wasn't set */ + rl_dispatching = prev; + } + else + { + rl_ding (); + r = 1; + } + + return r; +}