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.
This commit is contained in:
Luuk van Baal 2024-12-11 02:13:14 +01:00
parent 0dd933265f
commit 378edbbd34
24 changed files with 217 additions and 297 deletions

View File

@ -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.

View File

@ -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

View File

@ -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.
---

View File

@ -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.

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -123,7 +123,7 @@ typedef struct {
int indent;
int c;
bool gotesc; // true when <ESC> 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,9 +2745,12 @@ static void abandon_cmdline(void)
if (msg_scrolled == 0) {
compute_cmdrow();
}
// 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);

View File

@ -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
};

View File

@ -7,6 +7,7 @@
#include <string.h>
#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 <C-c> 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 (prompt == NULL) {
if (mouse_used != NULL) {
*mouse_used = false;
prompt = _("Type number and <Enter> or click with the mouse (q or empty cancels):");
} else {
prompt = _("Type number and <Enter> (q or empty cancels):");
}
}
// 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;
}
}
no_mapping--;
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--;
if (resp) {
ret = one_key ? (int)(*resp) : atoi(resp);
xfree(resp);
}
/// 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 <CR> was entered.
if (mouse_used != NULL) {
msg_puts(_("Type number and <Enter> or click with the mouse "
"(q or empty cancels): "));
} else {
msg_puts(_("Type number and <Enter> (q or empty cancels): "));
if (kmsg != NULL) {
set_keep_msg(kmsg, keep_msg_hl_id);
xfree(kmsg);
}
// 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;
}
State = save_State;
// May need to restore mouse shape.
setmouse();
return i;
return ret;
}

View File

@ -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;
}

View File

@ -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
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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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, {

View File

@ -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('<c-c>')
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]', '' },

View File

@ -268,43 +268,6 @@ describe('vim.ui_attach', function()
},
},
})
-- No fast context for prompt message kinds
feed(':%s/Function/Replacement/c<cr>')
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('<esc>:call inputlist(["Select:", "One", "Two"])<cr>')
screen:expect({
grid = [[
E122: {10:Function} Foo already exists, add !|
to replace it |
Type number and <Enter> or click with th|
e mouse (q or empty cancels): |
{1:^~ }|
]],
messages = {
{
content = { { 'Select:\nOne\nTwo\n' } },
kind = 'list_cmd',
},
{
content = { { 'Type number and <Enter> or click with the mouse (q or empty cancels): ' } },
kind = 'number_prompt',
},
},
})
end)
end)

View File

@ -1456,13 +1456,12 @@ describe('cmdheight=0', function()
}
feed(':%s/foo/bar/gc<CR>')
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 {

View File

@ -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}^ |
]])

View File

@ -42,18 +42,25 @@ describe('ui/ext_messages', function()
it('msg_clear follows msg_show kind of confirm', function()
feed('iline 1<esc>')
feed(':call confirm("test")<cr>')
screen:expect {
screen:expect({
grid = [[
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('<cr>')
screen:expect {
@ -67,22 +74,29 @@ describe('ui/ext_messages', function()
it('msg_show kinds', function()
feed('iline 1\nline 2<esc>')
-- kind=confirm
-- confirm is now cmdline prompt
feed(':echo confirm("test")<cr>')
screen:expect {
screen:expect({
grid = [[
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('<cr><cr>')
})
feed('<cr>')
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('<cr><cr>')
feed('<cr>')
-- kind=confirm_sub
-- :substitute confirm is now cmdline prompt
feed(':%s/i/X/gc<cr>')
screen:expect {
screen:expect({
grid = [[
l{2:i}ne 1 |
l{10:i}ne ^2 |
l{2:^i}ne 1 |
l{10:i}ne 2 |
{1:~ }|*3
]],
messages = {
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 <Enter> 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 <Enter> 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 <Enter> 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 <Enter> or click with the mouse (q or empty cancels): ' } },
kind = 'number_prompt',
},
{
content = { { '1' } },
kind = '',
},
},
})

View File

@ -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 <Enter> or click with the mouse (q or empty cancels): ',
'',
{ 0, 0 }
)
null_expr_test(

View File

@ -471,7 +471,9 @@ func Test_spellsuggest_option_number()
\ .. "Change \"baord\" to:\n"
\ .. " 1 \"board\"\n"
\ .. " 2 \"bard\"\n"
\ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
"\ Nvim: Prompt message is sent to cmdline prompt.
"\ .. "Type number and <Enter> 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 <Enter> or click with the mouse (q or empty cancels): ", a)
"\ Nvim: Prompt message is sent to cmdline prompt.
"\ .. "Type number and <Enter> 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 <Enter> or click with the mouse (q or empty cancels): ", a)
"\ Nvim: Prompt message is sent to cmdline prompt.
"\ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
\ , a)
set spell& spellsuggest& verbose&
bwipe!

View File

@ -1231,8 +1231,10 @@ func Test_tselect_listing()
2 FS v first Xfoo
typeref:typename:char
2
Type number and <Enter> (q or empty cancels):
[DATA]
" Type number and <Enter> (q or empty cancels):
" Nvim: Prompt message is sent to cmdline prompt.
call assert_equal(expected, l)
call delete('Xtags')