From e876a739eeb1c0c73bf633ff823a3313f3ea405d Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Mon, 9 Jun 2025 18:57:28 +0200 Subject: [PATCH] fix(messages): recognize cmdline one_key/number prompt State (#34206) Problem: Since 48e2a736, prompt messages are handled by an actual active cmdline, resulting in `State` no longer being equal to `MODE_CONFIRM` which is used in some places. E.g. to specify the current `mode()` or to re-emit a confirm message. Solution: Replace `MODE_CONFIRM` with a new `MODE_CMDLINE` sub-mode when `ccline.one_key/mouse_used` is set. Use it to avoid clearing mouse_used prompt messages, and to re-emit one_key messages (when ext_messages is inactive, for which this is unnecessary). --- src/nvim/drawscreen.c | 23 +++++++++++++++++------ src/nvim/ex_cmds.c | 5 ----- src/nvim/getchar.c | 1 - src/nvim/input.c | 2 -- src/nvim/message.c | 14 +++++++++----- src/nvim/state.c | 6 +++--- src/nvim/state_defs.h | 1 - src/nvim/ui.c | 4 ++-- test/functional/api/vim_spec.lua | 5 +++++ test/functional/ui/cmdline_spec.lua | 27 ++++++++++++++++++++++++++- 10 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 71ab93fcb0..cc890489bf 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -266,12 +266,19 @@ void screenclear(void) } } +/// Unlike cmdline "one_key" prompts, the message part of the prompt is not stored +/// to be re-emitted: avoid clearing the prompt from the message grid. +static bool cmdline_number_prompt(void) +{ + return !ui_has(kUIMessages) && State == MODE_CMDLINE && get_cmdline_info()->mouse_used != NULL; +} + /// Set dimensions of the Nvim application "screen". void screen_resize(int width, int height) { // Avoid recursiveness, can happen when setting the window size causes // another window-changed signal. - if (updating_screen || resizing_screen) { + if (updating_screen || resizing_screen || cmdline_number_prompt()) { return; } @@ -348,7 +355,7 @@ void screen_resize(int width, int height) resizing_autocmd = false; redraw_all_later(UPD_CLEAR); - if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { + if (State != MODE_ASKMORE && State != MODE_EXTERNCMD) { screenclear(); } @@ -364,8 +371,11 @@ void screen_resize(int width, int height) // - While editing the command line, only redraw that. TODO: lies // - in Ex mode, don't redraw anything. // - Otherwise, redraw right now, and position the cursor. - if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM - || exmode_active) { + if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || exmode_active + || (State == MODE_CMDLINE && get_cmdline_info()->one_key)) { + if (State == MODE_CMDLINE) { + update_screen(); + } if (msg_grid.chars) { msg_grid_validate(); } @@ -451,7 +461,7 @@ int update_screen(void) // Postpone the redrawing when it's not needed and when being called // recursively. - if (!redrawing() || updating_screen) { + if (!redrawing() || updating_screen || cmdline_number_prompt()) { return FAIL; } @@ -490,7 +500,7 @@ int update_screen(void) } // if the screen was scrolled up when displaying a message, scroll it down - if (msg_scrolled || msg_grid_invalid) { + if ((msg_scrolled || msg_grid_invalid) && !cmdline_number_prompt()) { clear_cmdline = true; int valid = MAX(Rows - msg_scrollsize(), 0); if (msg_grid.chars) { @@ -697,6 +707,7 @@ int update_screen(void) if (still_may_intro) { intro_message(false); } + repeat_message(); decor_providers_invoke_end(); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index e5f6747eb6..5ee4608868 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3716,12 +3716,7 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n if (subflags.do_ask && cmdpreview_ns <= 0) { int typed = 0; - - // change State to MODE_CONFIRM, so that the mouse works - // properly int save_State = State; - State = MODE_CONFIRM; - setmouse(); // disable mouse in xterm curwin->w_cursor.col = regmatch.startpos[0].col; if (curwin->w_p_crb) { diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index aa539c8793..3dd2e34996 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2197,7 +2197,6 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth) && !(p_paste && (State & (MODE_INSERT | MODE_CMDLINE))) && !(State == MODE_HITRETURN && (tb_c1 == CAR || tb_c1 == ' ')) && State != MODE_ASKMORE - && State != MODE_CONFIRM && !at_ins_compl_key()) { int mlen; int nolmaplen; diff --git a/src/nvim/input.c b/src/nvim/input.c index fb89d86a75..06a5f1b4f3 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -41,8 +41,6 @@ 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. snprintf(IObuff, IOSIZE, _("%s (y/n)?"), str); char *prompt = xstrdup(IObuff); diff --git a/src/nvim/message.c b/src/nvim/message.c index 1f54bb37b1..c776c39c64 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -29,6 +29,7 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/garray_defs.h" @@ -3072,13 +3073,17 @@ void msg_moremsg(bool full) } /// Repeat the message for the current mode: MODE_ASKMORE, MODE_EXTERNCMD, -/// MODE_CONFIRM or exmode_active. +/// confirm() prompt or exmode_active. void repeat_message(void) { + if (ui_has(kUIMessages)) { + return; + } + if (State == MODE_ASKMORE) { msg_moremsg(true); // display --more-- message again msg_row = Rows - 1; - } else if (State == MODE_CONFIRM) { + } else if (State == MODE_CMDLINE && confirm_msg != NULL) { display_confirm_msg(); // display ":confirm" message again msg_row = Rows - 1; } else if (State == MODE_EXTERNCMD) { @@ -3550,8 +3555,6 @@ int do_dialog(int type, const char *title, const char *message, const char *butt int oldState = State; msg_silent = 0; // If dialog prompts for input, user needs to see it! #8788 - State = MODE_CONFIRM; - setmouse(); // Since we wait for a keypress, don't make the // user press RETURN as well afterwards. @@ -3608,6 +3611,8 @@ int do_dialog(int type, const char *title, const char *message, const char *butt } xfree(hotkeys); + xfree(confirm_msg); + confirm_msg = NULL; msg_silent = save_msg_silent; State = oldState; @@ -3688,7 +3693,6 @@ static char *console_dialog_alloc(const char *message, const char *buttons, bool } // Now allocate space for the strings - xfree(confirm_msg); confirm_msg = xmalloc((size_t)msg_len); snprintf(confirm_msg, (size_t)msg_len, "\n%s\n", message); diff --git a/src/nvim/state.c b/src/nvim/state.c index c4041dda07..776672884f 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -184,12 +184,12 @@ void get_mode(char *buf) { int i = 0; - if (State == MODE_HITRETURN || State == MODE_ASKMORE - || State == MODE_SETWSIZE || State == MODE_CONFIRM) { + if (State == MODE_HITRETURN || State == MODE_ASKMORE || State == MODE_SETWSIZE + || (State == MODE_CMDLINE && get_cmdline_info()->one_key)) { buf[i++] = 'r'; if (State == MODE_ASKMORE) { buf[i++] = 'm'; - } else if (State == MODE_CONFIRM) { + } else if (State == MODE_CMDLINE) { buf[i++] = '?'; } } else if (State == MODE_EXTERNCMD) { diff --git a/src/nvim/state_defs.h b/src/nvim/state_defs.h index 0b32412f5a..fe70e3f71c 100644 --- a/src/nvim/state_defs.h +++ b/src/nvim/state_defs.h @@ -41,5 +41,4 @@ enum { MODE_SETWSIZE = 0x4000, ///< window size has changed MODE_EXTERNCMD = 0x5000, ///< executing an external command MODE_SHOWMATCH = 0x6000 | MODE_INSERT, ///< show matching paren - MODE_CONFIRM = 0x7000, ///< ":confirm" prompt }; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index d1cc9731d1..bb7f1e91c8 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -613,8 +613,8 @@ void ui_check_mouse(void) checkfor = MOUSE_INSERT; } else if (State & MODE_CMDLINE) { checkfor = MOUSE_COMMAND; - } else if (State == MODE_CONFIRM || State == MODE_EXTERNCMD) { - checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd" + } else if (State == MODE_EXTERNCMD) { + checkfor = ' '; // don't use mouse for ":!cmd" } // mouse should be active if at least one of the following is true: diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 8f5b5aa2e1..bddea7e4f5 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -2157,6 +2157,11 @@ describe('API', function() feed('') eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) end) + + it('returns "c" during number prompt', function() + feed('ifooz=') + eq({ mode = 'c', blocking = false }, api.nvim_get_mode()) + end) end) describe('RPC (K_EVENT)', function() diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index c2b53ea2e1..9c96709803 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1106,7 +1106,7 @@ describe('cmdline redraw', function() } end) - it('silent prompt', function() + it('prompt with silent mapping and screen update', function() command([[nmap T :call confirm("Save changes?", "&Yes\n&No\n&Cancel")]]) feed('T') screen:expect([[ @@ -1116,6 +1116,31 @@ describe('cmdline redraw', function() {6:Save changes?} | {6:[Y]es, (N)o, (C)ancel: }^ | ]]) + command('call setline(1, "foo") | redraw') + screen:expect([[ + foo | + {3: }| + | + {6:Save changes?} | + {6:[Y]es, (N)o, (C)ancel: }^ | + ]]) + feed('Y') + screen:expect([[ + ^foo | + {1:~ }|*3 + | + ]]) + screen:try_resize(75, screen._height) + feed(':call inputlist(["foo", "bar"])') + screen:expect([[ + foo | + {3: }| + foo | + bar | + Type number and or click with the mouse (q or empty cancels): ^ | + ]]) + command('redraw') + screen:expect_unchanged() end) it('substitute confirm prompt does not scroll', function()