From 378edbbd34980654067d39605b39b50efd909826 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Wed, 11 Dec 2024 02:13:14 +0100 Subject: [PATCH] feat(ui): use commandline for prompt "messages" Problem: Prompts are emitted as messages events, where cmdline events are more appropriate. The user input is also emitted as message events in fast context, so cannot be displayed with vim.ui_attach(). Solution: Ask for user input through cmdline prompts. --- runtime/doc/lua.txt | 5 +- runtime/doc/ui.txt | 2 - runtime/lua/vim/_meta/builtin.lua | 5 +- src/nvim/bufwrite.c | 2 +- src/nvim/debugger.c | 3 +- src/nvim/eval/funcs.c | 6 +- src/nvim/ex_cmds.c | 29 ++-- src/nvim/ex_docmd.c | 2 +- src/nvim/ex_getln.c | 49 ++++-- src/nvim/ex_getln_defs.h | 2 + src/nvim/input.c | 148 +++++------------- src/nvim/memline.c | 3 +- src/nvim/message.c | 56 +++---- src/nvim/spellsuggest.c | 12 +- src/nvim/tag.c | 2 +- src/nvim/ui.c | 6 +- .../swapfile_preserve_recover_spec.lua | 5 +- test/functional/lua/ui_event_spec.lua | 37 ----- test/functional/ui/cmdline_spec.lua | 13 +- test/functional/ui/input_spec.lua | 4 +- test/functional/ui/messages_spec.lua | 105 +++++++------ test/functional/vimscript/null_spec.lua | 2 +- test/old/testdir/test_spell.vim | 12 +- test/old/testdir/test_tagjump.vim | 4 +- 24 files changed, 217 insertions(+), 297 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index dad3d92238..a9e8ab5e93 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1086,9 +1086,8 @@ vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()* |ui-popupmenu| and the sections below for event format for respective events. - Callbacks for `msg_show` events are executed in |api-fast| context unless - Nvim will wait for input, in which case messages should be shown - immediately. + Callbacks for `msg_show` events are executed in |api-fast| context; + showing the message should be scheduled. Excessive errors inside the callback will result in forced detachment. diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 6fb000b285..8493cf6d00 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -791,7 +791,6 @@ must handle. "" (empty) Unknown (consider a |feature-request|) "bufwrite" |:write| message "confirm" |confirm()| or |:confirm| dialog - "confirm_sub" |:substitute| confirm dialog |:s_c| "emsg" Error (|errors|, internal error, |:throw|, …) "echo" |:echo| message "echomsg" |:echomsg| message @@ -800,7 +799,6 @@ must handle. "lua_error" Error in |:lua| code "lua_print" |print()| from |:lua| code "rpc_error" Error response from |rpcrequest()| - "number_prompt" Number input prompt (|inputlist()|, |z=|, …) "return_prompt" |press-enter| prompt after a multiple messages "quickfix" Quickfix navigation message "search_cmd" Entered search command diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index b8779b66fe..9fa2e242c4 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -233,9 +233,8 @@ function vim.wait(time, callback, interval, fast_only) end --- {callback} receives event name plus additional parameters. See |ui-popupmenu| --- and the sections below for event format for respective events. --- ---- Callbacks for `msg_show` events are executed in |api-fast| context unless ---- Nvim will wait for input, in which case messages should be shown ---- immediately. +--- Callbacks for `msg_show` events are executed in |api-fast| context; showing +--- the message should be scheduled. --- --- Excessive errors inside the callback will result in forced detachment. --- diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 2cf02403da..feb07109f2 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -351,7 +351,7 @@ static int check_mtime(buf_T *buf, FileInfo *file_info) msg_silent = 0; // Must give this prompt. // Don't use emsg() here, don't want to flush the buffers. msg(_("WARNING: The file has been changed since reading it!!!"), HLF_E); - if (ask_yesno(_("Do you really want to write to it"), true) == 'n') { + if (ask_yesno(_("Do you really want to write to it")) == 'n') { return FAIL; } msg_scroll = false; // Always overwrite the file message now. diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index b71ff23f57..f3e4ef0698 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -153,8 +153,7 @@ void do_debug(char *cmd) debug_break_level = -1; xfree(cmdline); - cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, - CALLBACK_NONE); + cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE, false, NULL); debug_break_level = n; if (typeahead_saved) { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 768d7664f7..77f197341a 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3554,10 +3554,10 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) }); // Ask for choice. - bool mouse_used; - int selected = prompt_for_number(&mouse_used); + bool mouse_used = false; + int selected = prompt_for_input(NULL, 0, false, &mouse_used); if (mouse_used) { - selected -= lines_left; + selected = tv_list_len(argvars[0].vval.v_list) - (cmdline_row - mouse_row); } rettv->vval.v_number = selected; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8cccf08e11..350bf42523 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3708,12 +3708,9 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n // Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed. while (subflags.do_ask) { if (exmode_active) { - char *prompt; - char *resp; - colnr_T sc, ec; - print_line_no_prefix(lnum, subflags.do_number, subflags.do_list); + colnr_T sc, ec; getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL); curwin->w_cursor.col = MAX(regmatch.endpos[0].col - 1, 0); @@ -3725,10 +3722,11 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n ec += numw; } - prompt = xmallocz((size_t)ec + 1); + char *prompt = xmallocz((size_t)ec + 1); memset(prompt, ' ', (size_t)sc); memset(prompt + sc, '^', (size_t)(ec - sc) + 1); - resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); + char *resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, + CALLBACK_NONE, false, NULL); msg_putchar('\n'); xfree(prompt); if (resp != NULL) { @@ -3803,27 +3801,20 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n msg_scroll = 0; // truncate msg when // needed msg_no_more = true; - msg_ext_set_kind("confirm_sub"); - // Same highlight as wait_return(). - smsg(HLF_R, _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); msg_no_more = false; msg_scroll = i; - if (!ui_has(kUIMessages)) { - ui_cursor_goto(msg_row, msg_col); - } - RedrawingDisabled = temp; - no_mapping++; // don't map this key - allow_keys++; // allow special keys - typed = plain_vgetc(); - no_mapping--; - allow_keys--; + snprintf(IObuff, IOSIZE, _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); + char *prompt = xstrdup(IObuff); + typed = prompt_for_input(prompt, HLF_R, true, NULL); + xfree(prompt); // clear the question msg_didout = false; // don't scroll up msg_col = 0; gotocmdline(true); p_lz = save_p_lz; + RedrawingDisabled = temp; // restore the line if (orig_line != NULL) { @@ -4809,7 +4800,7 @@ void ex_oldfiles(exarg_T *eap) // File selection prompt on ":browse oldfiles" if (cmdmod.cmod_flags & CMOD_BROWSE) { quit_more = false; - nr = prompt_for_number(false); + nr = prompt_for_input(NULL, 0, false, NULL); msg_starthere(); if (nr > 0 && nr <= tv_list_len(l)) { const char *const p = tv_list_find_str(l, nr - 1); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 052bf3b9f7..b6599988d0 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2202,7 +2202,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter errormsg = _("E493: Backwards range given"); goto doend; } - if (ask_yesno(_("Backwards range given, OK to swap"), false) != 'y') { + if (ask_yesno(_("Backwards range given, OK to swap")) != 'y') { goto doend; } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 2c1653006c..af1bab9ffe 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -123,7 +123,7 @@ typedef struct { int indent; int c; bool gotesc; // true when just typed - int do_abbr; // when true check for abbr. + bool do_abbr; // when true check for abbr. char *lookfor; // string to match int lookforlen; int hiscnt; // current history line in use @@ -131,17 +131,17 @@ typedef struct { // to jump to next match int histype; // history type to be used incsearch_state_T is_state; - int did_wild_list; // did wild_list() recently + bool did_wild_list; // did wild_list() recently int wim_index; // index in wim_flags[] int save_msg_scroll; int save_State; // remember State when called int prev_cmdpos; char *save_p_icm; - int some_key_typed; // one of the keys was typed + bool some_key_typed; // one of the keys was typed // mouse drag and release events are ignored, unless they are // preceded with a mouse down event - int ignore_drag_release; - int break_ctrl_c; + bool ignore_drag_release; + bool break_ctrl_c; expand_T xpc; OptInt *b_im_ptr; buf_T *b_im_ptr_buf; ///< buffer where b_im_ptr is valid @@ -1851,6 +1851,12 @@ static int command_line_browse_history(CommandLineState *s) static int command_line_handle_key(CommandLineState *s) { + // For one key prompt, avoid putting ESC and Ctrl_C onto cmdline. + // For all other keys, just put onto cmdline and exit. + if (ccline.one_key && s->c != ESC && s->c != Ctrl_C) { + goto end; + } + // Big switch for a typed command line character. switch (s->c) { case K_BS: @@ -2001,6 +2007,12 @@ static int command_line_handle_key(CommandLineState *s) } FALLTHROUGH; case K_LEFTMOUSE: + // Return on left click above number prompt + if (ccline.mouse_used && mouse_row < cmdline_row) { + *ccline.mouse_used = true; + return 0; + } + FALLTHROUGH; case K_RIGHTMOUSE: command_line_left_right_mouse(s); return command_line_not_changed(s); @@ -2158,6 +2170,14 @@ static int command_line_handle_key(CommandLineState *s) } return command_line_not_changed(s); + case 'q': + // Number prompts use the mouse and return on 'q' press + if (ccline.mouse_used) { + *ccline.cmdbuff = NUL; + return 0; + } + FALLTHROUGH; + default: // Normal character with no special meaning. Just set mod_mask // to 0x0 so that typing Shift-Space in the GUI doesn't enter @@ -2178,6 +2198,7 @@ static int command_line_handle_key(CommandLineState *s) return command_line_changed(s); } +end: // put the character in the command line if (IS_SPECIAL(s->c) || mod_mask != 0) { put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true); @@ -2186,7 +2207,7 @@ static int command_line_handle_key(CommandLineState *s) IObuff[j] = NUL; // exclude composing chars put_on_cmdline(IObuff, j, true); } - return command_line_changed(s); + return ccline.one_key ? 0 : command_line_changed(s); } static int command_line_not_changed(CommandLineState *s) @@ -2724,8 +2745,11 @@ static void abandon_cmdline(void) if (msg_scrolled == 0) { compute_cmdrow(); } - msg("", 0); - redraw_cmdline = true; + // Avoid overwriting key prompt + if (!ccline.one_key) { + msg("", 0); + redraw_cmdline = true; + } } /// getcmdline() - accept a command line starting with firstc. @@ -2764,11 +2788,13 @@ char *getcmdline(int firstc, int count, int indent, bool do_concat FUNC_ATTR_UNU /// @param[in] xp_context Type of expansion. /// @param[in] xp_arg User-defined expansion argument. /// @param[in] highlight_callback Callback used for highlighting user input. +/// @param[in] one_key Return after one key press for button prompt. +/// @param[in] mouse_used Set to true when returning after right mouse click. /// /// @return [allocated] Command line or NULL. char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl_id, const int xp_context, const char *const xp_arg, - const Callback highlight_callback) + const Callback highlight_callback, bool one_key, bool *mouse_used) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { const int msg_col_save = msg_col; @@ -2789,11 +2815,14 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl ccline.xp_arg = (char *)xp_arg; ccline.input_fn = (firstc == '@'); ccline.highlight_callback = highlight_callback; + ccline.one_key = one_key; + ccline.mouse_used = mouse_used; int msg_silent_saved = msg_silent; msg_silent = 0; char *const ret = (char *)command_line_enter(firstc, 1, 0, false); + ccline.redraw_state = kCmdRedrawNone; if (did_save_ccline) { restore_cmdline(&save_ccline); @@ -4790,7 +4819,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const const int save_ex_normal_busy = ex_normal_busy; ex_normal_busy = 0; rettv->vval.v_string = getcmdline_prompt(secret ? NUL : '@', p, get_echo_hl_id(), - xp_type, xp_arg, input_callback); + xp_type, xp_arg, input_callback, false, NULL); ex_normal_busy = save_ex_normal_busy; callback_free(&input_callback); diff --git a/src/nvim/ex_getln_defs.h b/src/nvim/ex_getln_defs.h index 584c360450..e05d8f27db 100644 --- a/src/nvim/ex_getln_defs.h +++ b/src/nvim/ex_getln_defs.h @@ -65,4 +65,6 @@ struct cmdline_info { char special_char; ///< last putcmdline char (used for redraws) bool special_shift; ///< shift of last putcmdline char CmdRedraw redraw_state; ///< needed redraw for external cmdline + bool one_key; ///< return after one key press for button prompt + bool *mouse_used; ///< mouse clicked in prompt }; diff --git a/src/nvim/input.c b/src/nvim/input.c index 0c1a8af45f..a0436de270 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -7,6 +7,7 @@ #include #include "nvim/ascii_defs.h" +#include "nvim/ex_getln.h" #include "nvim/getchar.h" #include "nvim/gettext_defs.h" #include "nvim/globals.h" @@ -34,44 +35,36 @@ /// No other characters are accepted, the message is repeated until a valid /// reply is entered or is hit. /// -/// @param[in] str Prompt: question to ask user. Is always followed by -/// " (y/n)?". -/// @param[in] direct Determines what function to use to get user input. If -/// true then input_get() will be used, otherwise vgetc(). -/// I.e. when direct is true then characters are obtained -/// directly from the user without buffers involved. +/// @param[in] str Prompt: question to ask user. Is always followed by " (y/n)?". /// /// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. -int ask_yesno(const char *const str, const bool direct) +int ask_yesno(const char *const str) { const int save_State = State; no_wait_return++; State = MODE_CONFIRM; // Mouse behaves like with :confirm. setmouse(); // Disable mouse in xterm. - no_mapping++; - allow_keys++; // no mapping here, but recognize keys + snprintf(IObuff, IOSIZE, _("%s (y/n)?"), str); + char *prompt = xstrdup(IObuff); int r = ' '; while (r != 'y' && r != 'n') { // same highlighting as for wait_return() - smsg(HLF_R, "%s (y/n)?", str); - if (direct) { - r = get_keystroke(NULL); - } else { - r = plain_vgetc(); - } + r = prompt_for_input(prompt, HLF_R, true, NULL); if (r == Ctrl_C || r == ESC) { r = 'n'; + if (!ui_has(kUIMessages)) { + msg_putchar(r); + } } - msg_putchar(r); // Show what you typed. - ui_flush(); } + + need_wait_return = msg_scrolled; no_wait_return--; State = save_State; setmouse(); - no_mapping--; - allow_keys--; + xfree(prompt); return r; } @@ -157,105 +150,42 @@ int get_keystroke(MultiQueue *events) return n; } -/// Get a number from the user. -/// When "mouse_used" is not NULL allow using the mouse. +/// Ask the user for input through a cmdline prompt. /// -/// @param colon allow colon to abort -int get_number(int colon, bool *mouse_used) +/// @param one_key Return from cmdline after one key press. +/// @param mouse_used When not NULL, allow using the mouse to press a number. +int prompt_for_input(char *prompt, int hl_id, bool one_key, bool *mouse_used) { - int n = 0; - int typed = 0; + int ret = one_key ? ESC : 0; + char *kmsg = keep_msg ? xstrdup(keep_msg) : NULL; - if (mouse_used != NULL) { - *mouse_used = false; - } - - // When not printing messages, the user won't know what to type, return a - // zero (as if CR was hit). - if (msg_silent != 0) { - return 0; - } - - no_mapping++; - allow_keys++; // no mapping here, but recognize keys - while (true) { - ui_cursor_goto(msg_row, msg_col); - int c = safe_vgetc(); - if (ascii_isdigit(c)) { - if (vim_append_digit_int(&n, c - '0') == FAIL) { - return 0; - } - msg_putchar(c); - typed++; - } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) { - if (typed > 0) { - msg_puts("\b \b"); - typed--; - } - n /= 10; - } else if (mouse_used != NULL && c == K_LEFTMOUSE) { - *mouse_used = true; - n = mouse_row + 1; - break; - } else if (n == 0 && c == ':' && colon) { - stuffcharReadbuff(':'); - if (!exmode_active) { - cmdline_row = msg_row; - } - skip_redraw = true; // skip redraw once - do_redraw = false; - break; - } else if (c == Ctrl_C || c == ESC || c == 'q') { - n = 0; - break; - } else if (c == CAR || c == NL) { - break; + if (prompt == NULL) { + if (mouse_used != NULL) { + prompt = _("Type number and or click with the mouse (q or empty cancels):"); + } else { + prompt = _("Type number and (q or empty cancels):"); } } - no_mapping--; + + cmdline_row = msg_row; + ui_flush(); + + no_mapping++; // don't map prompt input + allow_keys++; // allow special keys + char *resp = getcmdline_prompt(-1, prompt, hl_id, EXPAND_NOTHING, NULL, + CALLBACK_NONE, one_key, mouse_used); allow_keys--; - return n; -} + no_mapping--; -/// Ask the user to enter a number. -/// -/// When "mouse_used" is not NULL allow using the mouse and in that case return -/// the line number. -int prompt_for_number(bool *mouse_used) -{ - msg_ext_set_kind("number_prompt"); - // When using ":silent" assume that was entered. - if (mouse_used != NULL) { - msg_puts(_("Type number and or click with the mouse " - "(q or empty cancels): ")); - } else { - msg_puts(_("Type number and (q or empty cancels): ")); + if (resp) { + ret = one_key ? (int)(*resp) : atoi(resp); + xfree(resp); } - // Set the state such that text can be selected/copied/pasted and we still - // get mouse events. - int save_cmdline_row = cmdline_row; - cmdline_row = 0; - int save_State = State; - State = MODE_ASKMORE; // prevents a screen update when using a timer - // May show different mouse shape. - setmouse(); - - int i = get_number(true, mouse_used); - if (KeyTyped) { - // don't call wait_return() now - if (msg_row > 0) { - cmdline_row = msg_row - 1; - } - need_wait_return = false; - msg_didany = false; - msg_didout = false; - } else { - cmdline_row = save_cmdline_row; + if (kmsg != NULL) { + set_keep_msg(kmsg, keep_msg_hl_id); + xfree(kmsg); } - State = save_State; - // May need to restore mouse shape. - setmouse(); - return i; + return ret; } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index bfe90bb680..996bfb0c7a 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -805,8 +805,7 @@ void ml_recover(bool checkext) // list the names of the swapfiles recover_names(fname, true, NULL, 0, NULL); msg_putchar('\n'); - msg_puts(_("Enter number of swap file to use (0 to quit): ")); - i = get_number(false, NULL); + i = prompt_for_input(_("Enter number of swap file to use (0 to quit): "), 0, false, NULL); if (i < 1 || i > len) { goto theend; } diff --git a/src/nvim/message.c b/src/nvim/message.c index edb332a786..f42ee8d476 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -90,7 +90,7 @@ static int confirm_msg_used = false; // displaying confirm_msg # include "message.c.generated.h" #endif static char *confirm_msg = NULL; // ":confirm" message -static char *confirm_msg_tail; // tail of confirm_msg +static char *confirm_buttons; // ":confirm" buttons sent to cmdline as prompt MessageHistoryEntry *first_msg_hist = NULL; MessageHistoryEntry *last_msg_hist = NULL; @@ -2296,7 +2296,7 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse if (p_more && lines_left == 0 && State != MODE_HITRETURN && !msg_no_more && !exmode_active) { if (do_more_prompt(NUL)) { - s = confirm_msg_tail; + s = confirm_buttons; } if (quit_more) { return; @@ -2788,7 +2788,7 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) /// When at hit-enter prompt "typed_char" is the already typed character, /// otherwise it's NUL. /// -/// @return true when jumping ahead to "confirm_msg_tail". +/// @return true when jumping ahead to "confirm_buttons". static bool do_more_prompt(int typed_char) { static bool entered = false; @@ -3511,10 +3511,10 @@ int do_dialog(int type, const char *title, const char *message, const char *butt } // Get a typed character directly from the user. - int c = get_keystroke(NULL); + int c = prompt_for_input(confirm_buttons, HLF_M, true, NULL); switch (c) { case CAR: // User accepts default option - case NL: + case NUL: retval = dfltbutton; break; case Ctrl_C: // User aborts/cancels @@ -3523,6 +3523,7 @@ int do_dialog(int type, const char *title, const char *message, const char *butt break; default: // Could be a hotkey? if (c < 0) { // special keys are ignored here + msg_didout = msg_didany = false; continue; } if (c == ':' && ex_cmd) { @@ -3545,6 +3546,7 @@ int do_dialog(int type, const char *title, const char *message, const char *butt break; } // No hotkey match, so keep waiting + msg_didout = msg_didany = false; continue; } break; @@ -3598,19 +3600,20 @@ static char *console_dialog_alloc(const char *message, const char *buttons, bool has_hotkey[0] = false; // Compute the size of memory to allocate. - int len = 0; + int msg_len = 0; + int button_len = 0; int idx = 0; const char *r = buttons; while (*r) { if (*r == DLG_BUTTON_SEP) { - len += 3; // '\n' -> ', '; 'x' -> '(x)' + button_len += 3; // '\n' -> ', '; 'x' -> '(x)' lenhotkey += HOTK_LEN; // each button needs a hotkey if (idx < HAS_HOTKEY_LEN - 1) { has_hotkey[++idx] = false; } } else if (*r == DLG_HOTKEY_CHAR) { r++; - len++; // '&a' -> '[a]' + button_len++; // '&a' -> '[a]' if (idx < HAS_HOTKEY_LEN - 1) { has_hotkey[idx] = true; } @@ -3620,21 +3623,22 @@ static char *console_dialog_alloc(const char *message, const char *buttons, bool MB_PTR_ADV(r); } - len += (int)(strlen(message) - + 2 // for the NL's - + strlen(buttons) - + 3); // for the ": " and NUL - lenhotkey++; // for the NUL + msg_len += (int)strlen(message) + 3; // for the NL's and NUL + button_len += (int)strlen(buttons) + 3; // for the ": " and NUL + lenhotkey++; // for the NUL // If no hotkey is specified, first char is used. if (!has_hotkey[0]) { - len += 2; // "x" -> "[x]" + button_len += 2; // "x" -> "[x]" } // Now allocate space for the strings xfree(confirm_msg); - confirm_msg = xmalloc((size_t)len); - *confirm_msg = NUL; + confirm_msg = xmalloc((size_t)msg_len); + snprintf(confirm_msg, (size_t)msg_len, "\n%s\n", message); + + xfree(confirm_buttons); + confirm_buttons = xmalloc((size_t)button_len); return xmalloc((size_t)lenhotkey); } @@ -3652,42 +3656,34 @@ static char *msg_show_console_dialog(const char *message, const char *buttons, i bool has_hotkey[HAS_HOTKEY_LEN] = { false }; char *hotk = console_dialog_alloc(message, buttons, has_hotkey); - copy_hotkeys_and_msg(message, buttons, dfltbutton, has_hotkey, hotk); + copy_confirm_hotkeys(buttons, dfltbutton, has_hotkey, hotk); display_confirm_msg(); return hotk; } -/// Copies hotkeys & dialog message into the memory allocated for it +/// Copies hotkeys into the memory allocated for it /// -/// @param message Message which will be part of the confirm_msg /// @param buttons String containing button names /// @param default_button_idx Number of default button /// @param has_hotkey An element in this array is true if corresponding button /// has a hotkey /// @param[out] hotkeys_ptr Pointer to the memory location where hotkeys will be copied -static void copy_hotkeys_and_msg(const char *message, const char *buttons, int default_button_idx, +static void copy_confirm_hotkeys(const char *buttons, int default_button_idx, const bool has_hotkey[], char *hotkeys_ptr) { - *confirm_msg = '\n'; - STRCPY(confirm_msg + 1, message); - - char *msgp = confirm_msg + 1 + strlen(message); - // Define first default hotkey. Keep the hotkey string NUL // terminated to avoid reading past the end. hotkeys_ptr[copy_char(buttons, hotkeys_ptr, true)] = NUL; - // Remember where the choices start, displaying starts here when - // "hotkeys_ptr" typed at the more prompt. - confirm_msg_tail = msgp; - *msgp++ = '\n'; - bool first_hotkey = false; // Is the first char of button a hotkey if (!has_hotkey[0]) { first_hotkey = true; // If no hotkey is specified, first char is used } + // Remember where the choices start, sent as prompt to cmdline. + char *msgp = confirm_buttons; + int idx = 0; const char *r = buttons; while (*r) { diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 3a985ab004..21bfa367bf 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -444,7 +444,7 @@ void spell_suggest(int count) char wcopy[MAXWLEN + 2]; suginfo_T sug; suggest_T *stp; - bool mouse_used; + bool mouse_used = false; int selected = count; int badlen = 0; int msg_scroll_save = msg_scroll; @@ -594,15 +594,11 @@ void spell_suggest(int count) cmdmsg_rl = false; msg_col = 0; // Ask for choice. - selected = prompt_for_number(&mouse_used); - - if (ui_has(kUIMessages)) { - ui_call_msg_clear(); - } - + selected = prompt_for_input(NULL, 0, false, &mouse_used); if (mouse_used) { - selected -= lines_left; + selected = sug.su_ga.ga_len + 1 - (cmdline_row - mouse_row); } + lines_left = Rows; // avoid more prompt // don't delay for 'smd' in normal_cmd() msg_scroll = msg_scroll_save; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 5844d8d3f2..51928a6a72 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -669,7 +669,7 @@ void do_tag(char *tag, int type, int count, int forceit, bool verbose) if (ask_for_selection) { // Ask to select a tag from the list. - int i = prompt_for_number(NULL); + int i = prompt_for_input(NULL, 0, false, NULL); if (i <= 0 || i > num_matches || got_int) { // no valid choice: don't change anything if (use_tagstack) { diff --git a/src/nvim/ui.c b/src/nvim/ui.c index f7b5f28cad..993c6c7770 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -718,10 +718,10 @@ void ui_call_event(char *name, bool fast, Array args) bool handled = false; UIEventCallback *event_cb; - // Prompt messages should be shown immediately so must be safe + // Return prompt is still a non-fast event, other prompt messages are + // followed by a "cmdline_show" event. if (strcmp(name, "msg_show") == 0) { - char *kind = args.items[0].data.string.data; - fast = !kind || ((strncmp(kind, "confirm", 7) != 0 && strstr(kind, "_prompt") == NULL)); + fast = !strequal(args.items[0].data.string.data, "return_prompt"); } map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, { diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 2820cc9663..49256d314f 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -219,6 +219,7 @@ describe('swapfile detection', function() .. [[%.swp"]], } feed('e') -- Chose "Edit" at the swap dialog. + screen2:expect({ any = pesc('E5555: API call: Vim(edit):E325: ATTENTION') }) feed('') screen2:expect(expected_no_dialog) @@ -531,10 +532,6 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() ) end) api.nvim_chan_send(chan, 'q') - retry(nil, nil, function() - eq('Press ENTER or type command to continue', eval("getline('$')->trim(' ', 2)")) - end) - api.nvim_chan_send(chan, '\r') retry(nil, nil, function() eq( { '', '[Process exited 1]', '' }, diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 25cc46e260..da955228c7 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -268,43 +268,6 @@ describe('vim.ui_attach', function() }, }, }) - -- No fast context for prompt message kinds - feed(':%s/Function/Replacement/c') - screen:expect({ - grid = [[ - ^E122: {10:Function} Foo already exists, add !| - to replace it | - replace with Replacement (y/n/a/q/l/^E/^| - Y)? | - {1:~ }| - ]], - messages = { - { - content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 18 } }, - kind = 'confirm_sub', - }, - }, - }) - feed(':call inputlist(["Select:", "One", "Two"])') - screen:expect({ - grid = [[ - E122: {10:Function} Foo already exists, add !| - to replace it | - Type number and or click with th| - e mouse (q or empty cancels): | - {1:^~ }| - ]], - messages = { - { - content = { { 'Select:\nOne\nTwo\n' } }, - kind = 'list_cmd', - }, - { - content = { { 'Type number and or click with the mouse (q or empty cancels): ' } }, - kind = 'number_prompt', - }, - }, - }) end) end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 0221c1e0b0..75fb551f1d 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1456,13 +1456,12 @@ describe('cmdheight=0', function() } feed(':%s/foo/bar/gc') - screen:expect { - grid = [[ - {2:foo} | - {1:~ }|*3 - {6:replace wi...q/l/^E/^Y)?}^ | - ]], - } + screen:expect([[ + {3: }| + |*2 + {6:replace with bar (y/n/a/q}| + {6:/l/^E/^Y)?}^ | + ]]) feed('y') screen:expect { diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 90e0b3e380..98312c42c9 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -368,7 +368,7 @@ describe('input non-printable chars', function() "Xtest-overwrite" | {9:WARNING: The file has been changed since reading it!!!} | {6:Do you really want to write to it (y/n)?}u | - {6:Do you really want to write to it (y/n)?} | + {6:Do you really want to write to it (y/n)?}{18:^E} | {6:Do you really want to write to it (y/n)?}^ | ]]) @@ -379,7 +379,7 @@ describe('input non-printable chars', function() "Xtest-overwrite" | {9:WARNING: The file has been changed since reading it!!!} | {6:Do you really want to write to it (y/n)?}u | - {6:Do you really want to write to it (y/n)?} | + {6:Do you really want to write to it (y/n)?}{18:^E} | {6:Do you really want to write to it (y/n)?}n | {6:Press ENTER or type command to continue}^ | ]]) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 287db81a12..37026827ed 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -42,18 +42,25 @@ describe('ui/ext_messages', function() it('msg_clear follows msg_show kind of confirm', function() feed('iline 1') feed(':call confirm("test")') - screen:expect { + screen:expect({ grid = [[ - line ^1 | - {1:~ }|*4 - ]], + line ^1 | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + pos = 0, + prompt = '[O]k: ', + }, + }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 10 } }, + content = { { '\ntest\n', 6, 10 } }, kind = 'confirm', }, }, - } + }) feed('') screen:expect { @@ -67,22 +74,29 @@ describe('ui/ext_messages', function() it('msg_show kinds', function() feed('iline 1\nline 2') - -- kind=confirm + -- confirm is now cmdline prompt feed(':echo confirm("test")') - screen:expect { + screen:expect({ grid = [[ - line 1 | - line ^2 | - {1:~ }|*3 - ]], + line 1 | + line ^2 | + {1:~ }|*3 + ]], + cmdline = { + { + content = { { '' } }, + pos = 0, + prompt = '[O]k: ', + }, + }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 10 } }, + content = { { '\ntest\n', 6, 10 } }, kind = 'confirm', }, }, - } - feed('') + }) + feed('') screen:expect { grid = [[ line 1 | @@ -91,7 +105,7 @@ describe('ui/ext_messages', function() ]], messages = { { - content = { { '\ntest\n[O]k: ', 6, 10 } }, + content = { { '\ntest\n', 6, 10 } }, kind = 'confirm', }, { @@ -104,23 +118,24 @@ describe('ui/ext_messages', function() }, }, } - feed('') + feed('') - -- kind=confirm_sub + -- :substitute confirm is now cmdline prompt feed(':%s/i/X/gc') - screen:expect { + screen:expect({ grid = [[ - l{2:i}ne 1 | - l{10:i}ne ^2 | - {1:~ }|*3 - ]], - messages = { + l{2:^i}ne 1 | + l{10:i}ne 2 | + {1:~ }|*3 + ]], + cmdline = { { - content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 18 } }, - kind = 'confirm_sub', + content = { { '' } }, + pos = 0, + prompt = 'replace with X (y/n/a/q/l/^E/^Y)?', }, }, - } + }) feed('nq') -- kind=wmsg (editing readonly file) @@ -1031,42 +1046,42 @@ stack traceback: feed('z=') screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| + {100:^helllo} | + {1:~ }|*4 ]], + cmdline = { + { + content = { { '' } }, + pos = 0, + prompt = 'Type number and or click with the mouse (q or empty cancels):', + }, + }, messages = { { content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, kind = 'list_cmd', }, - { - content = { { 'Type number and or click with the mouse (q or empty cancels): ' } }, - kind = 'number_prompt', - }, }, }) feed('1') screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| + {100:^helllo} | + {1:~ }|*4 ]], + cmdline = { + { + content = { { '1' } }, + pos = 1, + prompt = 'Type number and or click with the mouse (q or empty cancels):', + }, + }, messages = { { content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, kind = 'list_cmd', }, - { - content = { { 'Type number and or click with the mouse (q or empty cancels): ' } }, - kind = 'number_prompt', - }, - { - content = { { '1' } }, - kind = '', - }, }, }) diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index 9a27239a6d..afd50f7cf9 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -116,7 +116,7 @@ describe('NULL', function() null_expr_test( 'is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', - 'Type number and or click with the mouse (q or empty cancels): ', + '', { 0, 0 } ) null_expr_test( diff --git a/test/old/testdir/test_spell.vim b/test/old/testdir/test_spell.vim index bdd8a673fd..a5ae653369 100644 --- a/test/old/testdir/test_spell.vim +++ b/test/old/testdir/test_spell.vim @@ -471,7 +471,9 @@ func Test_spellsuggest_option_number() \ .. "Change \"baord\" to:\n" \ .. " 1 \"board\"\n" \ .. " 2 \"bard\"\n" - \ .. "Type number and or click with the mouse (q or empty cancels): ", a) + "\ Nvim: Prompt message is sent to cmdline prompt. + "\ .. "Type number and or click with the mouse (q or empty cancels): ", a) + \ , a) set spell spellsuggest=0 call assert_equal("\nSorry, no suggestions", execute('norm $z=')) @@ -509,7 +511,9 @@ func Test_spellsuggest_option_expr() \ .. " 1 \"BARD\"\n" \ .. " 2 \"BOARD\"\n" \ .. " 3 \"BROAD\"\n" - \ .. "Type number and or click with the mouse (q or empty cancels): ", a) + "\ Nvim: Prompt message is sent to cmdline prompt. + "\ .. "Type number and or click with the mouse (q or empty cancels): ", a) + \ , a) " With verbose, z= should show the score i.e. word length with " our SpellSuggest() function. @@ -521,7 +525,9 @@ func Test_spellsuggest_option_expr() \ .. " 1 \"BARD\" (4 - 0)\n" \ .. " 2 \"BOARD\" (5 - 0)\n" \ .. " 3 \"BROAD\" (5 - 0)\n" - \ .. "Type number and or click with the mouse (q or empty cancels): ", a) + "\ Nvim: Prompt message is sent to cmdline prompt. + "\ .. "Type number and or click with the mouse (q or empty cancels): ", a) + \ , a) set spell& spellsuggest& verbose& bwipe! diff --git a/test/old/testdir/test_tagjump.vim b/test/old/testdir/test_tagjump.vim index 470c5c43b4..efc5e4cebe 100644 --- a/test/old/testdir/test_tagjump.vim +++ b/test/old/testdir/test_tagjump.vim @@ -1231,8 +1231,10 @@ func Test_tselect_listing() 2 FS v first Xfoo typeref:typename:char 2 -Type number and (q or empty cancels): [DATA] +" Type number and (q or empty cancels): +" Nvim: Prompt message is sent to cmdline prompt. + call assert_equal(expected, l) call delete('Xtags')