Compare commits

...

5 Commits

Author SHA1 Message Date
luukvbaal
f6239b5278
Merge 378edbbd34 into 02bc40c194 2024-12-18 22:35:14 -04:00
zeertzjq
02bc40c194
vim-patch:9.1.0945: ComplMatchIns highlight doesn't end after inserted text (#31628)
Problem:  ComplMatchIns highlight doesn't end after inserted text.
Solution: Handle ComplMatchIns highlight more like search highlight.
          Fix off-by-one error. Handle deleting text properly.
          (zeertzjq)

closes: vim/vim#16244

f25d8f9312
2024-12-18 23:59:03 +00:00
luukvbaal
160cbd0ef4
test(cursor_spec): global highlight definitions (#31613) 2024-12-18 19:06:16 +00:00
Gregory Anders
3db3947b0e
fix(terminal): restore cursor from 'guicursor' on TermLeave (#31620)
Fixes: https://github.com/neovim/neovim/issues/31612
2024-12-18 11:41:05 -06:00
Luuk van Baal
378edbbd34 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.
2024-12-17 15:55:17 +01:00
34 changed files with 467 additions and 371 deletions

View File

@ -60,7 +60,7 @@ hi('PmenuMatch', { link = 'Pmenu' })
hi('PmenuMatchSel', { link = 'PmenuSel' }) hi('PmenuMatchSel', { link = 'PmenuSel' })
hi('PmenuExtra', { link = 'Pmenu' }) hi('PmenuExtra', { link = 'Pmenu' })
hi('PmenuExtraSel', { link = 'PmenuSel' }) hi('PmenuExtraSel', { link = 'PmenuSel' })
hi('ComplMatchIns', { link = 'Normal' }) hi('ComplMatchIns', {})
hi('Substitute', { link = 'Search' }) hi('Substitute', { link = 'Search' })
hi('Whitespace', { link = 'NonText' }) hi('Whitespace', { link = 'NonText' })
hi('MsgSeparator', { link = 'StatusLine' }) hi('MsgSeparator', { link = 'StatusLine' })

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 |ui-popupmenu| and the sections below for event format for respective
events. events.
Callbacks for `msg_show` events are executed in |api-fast| context unless Callbacks for `msg_show` events are executed in |api-fast| context;
Nvim will wait for input, in which case messages should be shown showing the message should be scheduled.
immediately.
Excessive errors inside the callback will result in forced detachment. Excessive errors inside the callback will result in forced detachment.

View File

@ -791,7 +791,6 @@ must handle.
"" (empty) Unknown (consider a |feature-request|) "" (empty) Unknown (consider a |feature-request|)
"bufwrite" |:write| message "bufwrite" |:write| message
"confirm" |confirm()| or |:confirm| dialog "confirm" |confirm()| or |:confirm| dialog
"confirm_sub" |:substitute| confirm dialog |:s_c|
"emsg" Error (|errors|, internal error, |:throw|, …) "emsg" Error (|errors|, internal error, |:throw|, …)
"echo" |:echo| message "echo" |:echo| message
"echomsg" |:echomsg| message "echomsg" |:echomsg| message
@ -800,7 +799,6 @@ must handle.
"lua_error" Error in |:lua| code "lua_error" Error in |:lua| code
"lua_print" |print()| from |:lua| code "lua_print" |print()| from |:lua| code
"rpc_error" Error response from |rpcrequest()| "rpc_error" Error response from |rpcrequest()|
"number_prompt" Number input prompt (|inputlist()|, |z=|, …)
"return_prompt" |press-enter| prompt after a multiple messages "return_prompt" |press-enter| prompt after a multiple messages
"quickfix" Quickfix navigation message "quickfix" Quickfix navigation message
"search_cmd" Entered search command "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| --- {callback} receives event name plus additional parameters. See |ui-popupmenu|
--- and the sections below for event format for respective events. --- and the sections below for event format for respective events.
--- ---
--- Callbacks for `msg_show` events are executed in |api-fast| context unless --- Callbacks for `msg_show` events are executed in |api-fast| context; showing
--- Nvim will wait for input, in which case messages should be shown --- the message should be scheduled.
--- immediately.
--- ---
--- Excessive errors inside the callback will result in forced detachment. --- 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. msg_silent = 0; // Must give this prompt.
// Don't use emsg() here, don't want to flush the buffers. // Don't use emsg() here, don't want to flush the buffers.
msg(_("WARNING: The file has been changed since reading it!!!"), HLF_E); 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; return FAIL;
} }
msg_scroll = false; // Always overwrite the file message now. msg_scroll = false; // Always overwrite the file message now.

View File

@ -153,8 +153,7 @@ void do_debug(char *cmd)
debug_break_level = -1; debug_break_level = -1;
xfree(cmdline); xfree(cmdline);
cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE, false, NULL);
CALLBACK_NONE);
debug_break_level = n; debug_break_level = n;
if (typeahead_saved) { if (typeahead_saved) {

View File

@ -955,7 +955,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
bool area_highlighting = false; // Visual or incsearch highlighting in this line bool area_highlighting = false; // Visual or incsearch highlighting in this line
int vi_attr = 0; // attributes for Visual and incsearch highlighting int vi_attr = 0; // attributes for Visual and incsearch highlighting
int area_attr = 0; // attributes desired by highlighting int area_attr = 0; // attributes desired by highlighting
int search_attr = 0; // attributes desired by 'hlsearch' int search_attr = 0; // attributes desired by 'hlsearch' or ComplMatchIns
int vcol_save_attr = 0; // saved attr for 'cursorcolumn' int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
int decor_attr = 0; // attributes desired by syntax and extmarks int decor_attr = 0; // attributes desired by syntax and extmarks
bool has_syntax = false; // this buffer has syntax highl. bool has_syntax = false; // this buffer has syntax highl.
@ -969,7 +969,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
int spell_attr = 0; // attributes desired by spelling int spell_attr = 0; // attributes desired by spelling
int word_end = 0; // last byte with same spell_attr int word_end = 0; // last byte with same spell_attr
int cur_checked_col = 0; // checked column for current line int cur_checked_col = 0; // checked column for current line
bool extra_check = 0; // has syntax or linebreak bool extra_check = false; // has extra highlighting
int multi_attr = 0; // attributes desired by multibyte int multi_attr = 0; // attributes desired by multibyte
int mb_l = 1; // multi-byte byte length int mb_l = 1; // multi-byte byte length
int mb_c = 0; // decoded multi-byte character int mb_c = 0; // decoded multi-byte character
@ -1495,6 +1495,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
ptr = line + v; // "line" may have been updated ptr = line + v; // "line" may have been updated
} }
if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
area_highlighting = true;
}
win_line_start(wp, &wlv); win_line_start(wp, &wlv);
bool draw_cols = true; bool draw_cols = true;
int leftcols_width = 0; int leftcols_width = 0;
@ -1740,6 +1744,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
if (*ptr == NUL) { if (*ptr == NUL) {
has_match_conc = 0; has_match_conc = 0;
} }
// Check if ComplMatchIns highlight is needed.
if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
if (ins_match_attr > 0) {
search_attr = hl_combine_attr(search_attr, ins_match_attr);
}
}
} }
if (wlv.diff_hlf != (hlf_T)0) { if (wlv.diff_hlf != (hlf_T)0) {
@ -1787,16 +1799,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri); wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
} }
// Apply ComplMatchIns highlight if needed.
if (wlv.filler_todo <= 0
&& (State & MODE_INSERT) && in_curline && ins_compl_active()) {
int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
if (ins_match_attr > 0) {
char_attr_pri = hl_combine_attr(char_attr_pri, ins_match_attr);
wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
}
}
if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) { if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) {
const int v = (int)(ptr - line); const int v = (int)(ptr - line);
linenr_T lnume = lnum + foldinfo.fi_lines - 1; linenr_T lnume = lnum + foldinfo.fi_lines - 1;

View File

@ -3554,10 +3554,10 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}); });
// Ask for choice. // Ask for choice.
bool mouse_used; bool mouse_used = false;
int selected = prompt_for_number(&mouse_used); int selected = prompt_for_input(NULL, 0, false, &mouse_used);
if (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; 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. // Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed.
while (subflags.do_ask) { while (subflags.do_ask) {
if (exmode_active) { if (exmode_active) {
char *prompt;
char *resp;
colnr_T sc, ec;
print_line_no_prefix(lnum, subflags.do_number, subflags.do_list); print_line_no_prefix(lnum, subflags.do_number, subflags.do_list);
colnr_T sc, ec;
getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL); getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL);
curwin->w_cursor.col = MAX(regmatch.endpos[0].col - 1, 0); 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; ec += numw;
} }
prompt = xmallocz((size_t)ec + 1); char *prompt = xmallocz((size_t)ec + 1);
memset(prompt, ' ', (size_t)sc); memset(prompt, ' ', (size_t)sc);
memset(prompt + sc, '^', (size_t)(ec - sc) + 1); 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'); msg_putchar('\n');
xfree(prompt); xfree(prompt);
if (resp != NULL) { 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 msg_scroll = 0; // truncate msg when
// needed // needed
msg_no_more = true; 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_no_more = false;
msg_scroll = i; msg_scroll = i;
if (!ui_has(kUIMessages)) {
ui_cursor_goto(msg_row, msg_col);
}
RedrawingDisabled = temp;
no_mapping++; // don't map this key snprintf(IObuff, IOSIZE, _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
allow_keys++; // allow special keys char *prompt = xstrdup(IObuff);
typed = plain_vgetc(); typed = prompt_for_input(prompt, HLF_R, true, NULL);
no_mapping--; xfree(prompt);
allow_keys--;
// clear the question // clear the question
msg_didout = false; // don't scroll up msg_didout = false; // don't scroll up
msg_col = 0; msg_col = 0;
gotocmdline(true); gotocmdline(true);
p_lz = save_p_lz; p_lz = save_p_lz;
RedrawingDisabled = temp;
// restore the line // restore the line
if (orig_line != NULL) { if (orig_line != NULL) {
@ -4809,7 +4800,7 @@ void ex_oldfiles(exarg_T *eap)
// File selection prompt on ":browse oldfiles" // File selection prompt on ":browse oldfiles"
if (cmdmod.cmod_flags & CMOD_BROWSE) { if (cmdmod.cmod_flags & CMOD_BROWSE) {
quit_more = false; quit_more = false;
nr = prompt_for_number(false); nr = prompt_for_input(NULL, 0, false, NULL);
msg_starthere(); msg_starthere();
if (nr > 0 && nr <= tv_list_len(l)) { if (nr > 0 && nr <= tv_list_len(l)) {
const char *const p = tv_list_find_str(l, nr - 1); 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"); errormsg = _("E493: Backwards range given");
goto doend; 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; goto doend;
} }
} }

View File

@ -123,7 +123,7 @@ typedef struct {
int indent; int indent;
int c; int c;
bool gotesc; // true when <ESC> just typed 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 char *lookfor; // string to match
int lookforlen; int lookforlen;
int hiscnt; // current history line in use int hiscnt; // current history line in use
@ -131,17 +131,17 @@ typedef struct {
// to jump to next match // to jump to next match
int histype; // history type to be used int histype; // history type to be used
incsearch_state_T is_state; 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 wim_index; // index in wim_flags[]
int save_msg_scroll; int save_msg_scroll;
int save_State; // remember State when called int save_State; // remember State when called
int prev_cmdpos; int prev_cmdpos;
char *save_p_icm; 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 // mouse drag and release events are ignored, unless they are
// preceded with a mouse down event // preceded with a mouse down event
int ignore_drag_release; bool ignore_drag_release;
int break_ctrl_c; bool break_ctrl_c;
expand_T xpc; expand_T xpc;
OptInt *b_im_ptr; OptInt *b_im_ptr;
buf_T *b_im_ptr_buf; ///< buffer where b_im_ptr is valid 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) 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. // Big switch for a typed command line character.
switch (s->c) { switch (s->c) {
case K_BS: case K_BS:
@ -2001,6 +2007,12 @@ static int command_line_handle_key(CommandLineState *s)
} }
FALLTHROUGH; FALLTHROUGH;
case K_LEFTMOUSE: 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: case K_RIGHTMOUSE:
command_line_left_right_mouse(s); command_line_left_right_mouse(s);
return command_line_not_changed(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); 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: default:
// Normal character with no special meaning. Just set mod_mask // Normal character with no special meaning. Just set mod_mask
// to 0x0 so that typing Shift-Space in the GUI doesn't enter // 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); return command_line_changed(s);
} }
end:
// put the character in the command line // put the character in the command line
if (IS_SPECIAL(s->c) || mod_mask != 0) { if (IS_SPECIAL(s->c) || mod_mask != 0) {
put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true); 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 IObuff[j] = NUL; // exclude composing chars
put_on_cmdline(IObuff, j, true); 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) static int command_line_not_changed(CommandLineState *s)
@ -2724,8 +2745,11 @@ static void abandon_cmdline(void)
if (msg_scrolled == 0) { if (msg_scrolled == 0) {
compute_cmdrow(); compute_cmdrow();
} }
msg("", 0); // Avoid overwriting key prompt
redraw_cmdline = true; if (!ccline.one_key) {
msg("", 0);
redraw_cmdline = true;
}
} }
/// getcmdline() - accept a command line starting with firstc. /// 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_context Type of expansion.
/// @param[in] xp_arg User-defined expansion argument. /// @param[in] xp_arg User-defined expansion argument.
/// @param[in] highlight_callback Callback used for highlighting user input. /// @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. /// @return [allocated] Command line or NULL.
char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl_id, char *getcmdline_prompt(const int firstc, const char *const prompt, const int hl_id,
const int xp_context, const char *const xp_arg, 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 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{ {
const int msg_col_save = msg_col; 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.xp_arg = (char *)xp_arg;
ccline.input_fn = (firstc == '@'); ccline.input_fn = (firstc == '@');
ccline.highlight_callback = highlight_callback; ccline.highlight_callback = highlight_callback;
ccline.one_key = one_key;
ccline.mouse_used = mouse_used;
int msg_silent_saved = msg_silent; int msg_silent_saved = msg_silent;
msg_silent = 0; msg_silent = 0;
char *const ret = (char *)command_line_enter(firstc, 1, 0, false); char *const ret = (char *)command_line_enter(firstc, 1, 0, false);
ccline.redraw_state = kCmdRedrawNone;
if (did_save_ccline) { if (did_save_ccline) {
restore_cmdline(&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; const int save_ex_normal_busy = ex_normal_busy;
ex_normal_busy = 0; ex_normal_busy = 0;
rettv->vval.v_string = getcmdline_prompt(secret ? NUL : '@', p, get_echo_hl_id(), 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; ex_normal_busy = save_ex_normal_busy;
callback_free(&input_callback); callback_free(&input_callback);

View File

@ -65,4 +65,6 @@ struct cmdline_info {
char special_char; ///< last putcmdline char (used for redraws) char special_char; ///< last putcmdline char (used for redraws)
bool special_shift; ///< shift of last putcmdline char bool special_shift; ///< shift of last putcmdline char
CmdRedraw redraw_state; ///< needed redraw for external cmdline 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

@ -173,7 +173,7 @@ static const char *highlight_init_both[] = {
"default link PmenuKind Pmenu", "default link PmenuKind Pmenu",
"default link PmenuKindSel PmenuSel", "default link PmenuKindSel PmenuSel",
"default link PmenuSbar Pmenu", "default link PmenuSbar Pmenu",
"default link ComplMatchIns Normal", "default link ComplMatchIns NONE",
"default link Substitute Search", "default link Substitute Search",
"default link StatusLineTerm StatusLine", "default link StatusLineTerm StatusLine",
"default link StatusLineTermNC StatusLineNC", "default link StatusLineTermNC StatusLineNC",

View File

@ -7,6 +7,7 @@
#include <string.h> #include <string.h>
#include "nvim/ascii_defs.h" #include "nvim/ascii_defs.h"
#include "nvim/ex_getln.h"
#include "nvim/getchar.h" #include "nvim/getchar.h"
#include "nvim/gettext_defs.h" #include "nvim/gettext_defs.h"
#include "nvim/globals.h" #include "nvim/globals.h"
@ -34,44 +35,36 @@
/// No other characters are accepted, the message is repeated until a valid /// No other characters are accepted, the message is repeated until a valid
/// reply is entered or <C-c> is hit. /// reply is entered or <C-c> is hit.
/// ///
/// @param[in] str Prompt: question to ask user. Is always followed by /// @param[in] str Prompt: question to ask user. Is always followed by " (y/n)?".
/// " (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.
/// ///
/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. /// @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; const int save_State = State;
no_wait_return++; no_wait_return++;
State = MODE_CONFIRM; // Mouse behaves like with :confirm. State = MODE_CONFIRM; // Mouse behaves like with :confirm.
setmouse(); // Disable mouse in xterm. setmouse(); // Disable mouse in xterm.
no_mapping++; snprintf(IObuff, IOSIZE, _("%s (y/n)?"), str);
allow_keys++; // no mapping here, but recognize keys char *prompt = xstrdup(IObuff);
int r = ' '; int r = ' ';
while (r != 'y' && r != 'n') { while (r != 'y' && r != 'n') {
// same highlighting as for wait_return() // same highlighting as for wait_return()
smsg(HLF_R, "%s (y/n)?", str); r = prompt_for_input(prompt, HLF_R, true, NULL);
if (direct) {
r = get_keystroke(NULL);
} else {
r = plain_vgetc();
}
if (r == Ctrl_C || r == ESC) { if (r == Ctrl_C || r == ESC) {
r = 'n'; 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--; no_wait_return--;
State = save_State; State = save_State;
setmouse(); setmouse();
no_mapping--; xfree(prompt);
allow_keys--;
return r; return r;
} }
@ -157,105 +150,42 @@ int get_keystroke(MultiQueue *events)
return n; return n;
} }
/// Get a number from the user. /// Ask the user for input through a cmdline prompt.
/// When "mouse_used" is not NULL allow using the mouse.
/// ///
/// @param colon allow colon to abort /// @param one_key Return from cmdline after one key press.
int get_number(int colon, bool *mouse_used) /// @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 ret = one_key ? ESC : 0;
int typed = 0; char *kmsg = keep_msg ? xstrdup(keep_msg) : NULL;
if (mouse_used != NULL) { if (prompt == NULL) {
*mouse_used = false; if (mouse_used != NULL) {
} prompt = _("Type number and <Enter> or click with the mouse (q or empty cancels):");
} else {
// When not printing messages, the user won't know what to type, return a prompt = _("Type number and <Enter> (q or empty cancels):");
// 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--;
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--; allow_keys--;
return n; no_mapping--;
}
/// Ask the user to enter a number. if (resp) {
/// ret = one_key ? (int)(*resp) : atoi(resp);
/// When "mouse_used" is not NULL allow using the mouse and in that case return xfree(resp);
/// 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): "));
} }
// Set the state such that text can be selected/copied/pasted and we still if (kmsg != NULL) {
// get mouse events. set_keep_msg(kmsg, keep_msg_hl_id);
int save_cmdline_row = cmdline_row; xfree(kmsg);
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

@ -958,7 +958,7 @@ static void ins_compl_insert_bytes(char *p, int len)
} }
assert(len >= 0); assert(len >= 0);
ins_bytes_len(p, (size_t)len); ins_bytes_len(p, (size_t)len);
compl_ins_end_col = curwin->w_cursor.col - 1; compl_ins_end_col = curwin->w_cursor.col;
} }
/// Checks if the column is within the currently inserted completion text /// Checks if the column is within the currently inserted completion text
@ -2147,6 +2147,8 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval)
&& pum_visible()) { && pum_visible()) {
word = xstrdup(compl_shown_match->cp_str); word = xstrdup(compl_shown_match->cp_str);
retval = true; retval = true;
// May need to remove ComplMatchIns highlight.
redrawWinline(curwin, curwin->w_cursor.lnum);
} }
// CTRL-E means completion is Ended, go back to the typed text. // CTRL-E means completion is Ended, go back to the typed text.
@ -3648,6 +3650,7 @@ void ins_compl_delete(bool new_leader)
return; return;
} }
backspace_until_column(col); backspace_until_column(col);
compl_ins_end_col = curwin->w_cursor.col;
} }
// TODO(vim): is this sufficient for redrawing? Redrawing everything // TODO(vim): is this sufficient for redrawing? Redrawing everything

View File

@ -805,8 +805,7 @@ void ml_recover(bool checkext)
// list the names of the swapfiles // list the names of the swapfiles
recover_names(fname, true, NULL, 0, NULL); recover_names(fname, true, NULL, 0, NULL);
msg_putchar('\n'); msg_putchar('\n');
msg_puts(_("Enter number of swap file to use (0 to quit): ")); i = prompt_for_input(_("Enter number of swap file to use (0 to quit): "), 0, false, NULL);
i = get_number(false, NULL);
if (i < 1 || i > len) { if (i < 1 || i > len) {
goto theend; goto theend;
} }

View File

@ -90,7 +90,7 @@ static int confirm_msg_used = false; // displaying confirm_msg
# include "message.c.generated.h" # include "message.c.generated.h"
#endif #endif
static char *confirm_msg = NULL; // ":confirm" message 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 *first_msg_hist = NULL;
MessageHistoryEntry *last_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 if (p_more && lines_left == 0 && State != MODE_HITRETURN
&& !msg_no_more && !exmode_active) { && !msg_no_more && !exmode_active) {
if (do_more_prompt(NUL)) { if (do_more_prompt(NUL)) {
s = confirm_msg_tail; s = confirm_buttons;
} }
if (quit_more) { if (quit_more) {
return; 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, /// When at hit-enter prompt "typed_char" is the already typed character,
/// otherwise it's NUL. /// 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 do_more_prompt(int typed_char)
{ {
static bool entered = false; 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. // 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) { switch (c) {
case CAR: // User accepts default option case CAR: // User accepts default option
case NL: case NUL:
retval = dfltbutton; retval = dfltbutton;
break; break;
case Ctrl_C: // User aborts/cancels 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; break;
default: // Could be a hotkey? default: // Could be a hotkey?
if (c < 0) { // special keys are ignored here if (c < 0) { // special keys are ignored here
msg_didout = msg_didany = false;
continue; continue;
} }
if (c == ':' && ex_cmd) { if (c == ':' && ex_cmd) {
@ -3545,6 +3546,7 @@ int do_dialog(int type, const char *title, const char *message, const char *butt
break; break;
} }
// No hotkey match, so keep waiting // No hotkey match, so keep waiting
msg_didout = msg_didany = false;
continue; continue;
} }
break; break;
@ -3598,19 +3600,20 @@ static char *console_dialog_alloc(const char *message, const char *buttons, bool
has_hotkey[0] = false; has_hotkey[0] = false;
// Compute the size of memory to allocate. // Compute the size of memory to allocate.
int len = 0; int msg_len = 0;
int button_len = 0;
int idx = 0; int idx = 0;
const char *r = buttons; const char *r = buttons;
while (*r) { while (*r) {
if (*r == DLG_BUTTON_SEP) { if (*r == DLG_BUTTON_SEP) {
len += 3; // '\n' -> ', '; 'x' -> '(x)' button_len += 3; // '\n' -> ', '; 'x' -> '(x)'
lenhotkey += HOTK_LEN; // each button needs a hotkey lenhotkey += HOTK_LEN; // each button needs a hotkey
if (idx < HAS_HOTKEY_LEN - 1) { if (idx < HAS_HOTKEY_LEN - 1) {
has_hotkey[++idx] = false; has_hotkey[++idx] = false;
} }
} else if (*r == DLG_HOTKEY_CHAR) { } else if (*r == DLG_HOTKEY_CHAR) {
r++; r++;
len++; // '&a' -> '[a]' button_len++; // '&a' -> '[a]'
if (idx < HAS_HOTKEY_LEN - 1) { if (idx < HAS_HOTKEY_LEN - 1) {
has_hotkey[idx] = true; has_hotkey[idx] = true;
} }
@ -3620,21 +3623,22 @@ static char *console_dialog_alloc(const char *message, const char *buttons, bool
MB_PTR_ADV(r); MB_PTR_ADV(r);
} }
len += (int)(strlen(message) msg_len += (int)strlen(message) + 3; // for the NL's and NUL
+ 2 // for the NL's button_len += (int)strlen(buttons) + 3; // for the ": " and NUL
+ strlen(buttons) lenhotkey++; // for the NUL
+ 3); // for the ": " and NUL
lenhotkey++; // for the NUL
// If no hotkey is specified, first char is used. // If no hotkey is specified, first char is used.
if (!has_hotkey[0]) { if (!has_hotkey[0]) {
len += 2; // "x" -> "[x]" button_len += 2; // "x" -> "[x]"
} }
// Now allocate space for the strings // Now allocate space for the strings
xfree(confirm_msg); xfree(confirm_msg);
confirm_msg = xmalloc((size_t)len); confirm_msg = xmalloc((size_t)msg_len);
*confirm_msg = NUL; 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); 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 }; bool has_hotkey[HAS_HOTKEY_LEN] = { false };
char *hotk = console_dialog_alloc(message, buttons, has_hotkey); 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(); display_confirm_msg();
return hotk; 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 buttons String containing button names
/// @param default_button_idx Number of default button /// @param default_button_idx Number of default button
/// @param has_hotkey An element in this array is true if corresponding button /// @param has_hotkey An element in this array is true if corresponding button
/// has a hotkey /// has a hotkey
/// @param[out] hotkeys_ptr Pointer to the memory location where hotkeys will be copied /// @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) 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 // Define first default hotkey. Keep the hotkey string NUL
// terminated to avoid reading past the end. // terminated to avoid reading past the end.
hotkeys_ptr[copy_char(buttons, hotkeys_ptr, true)] = NUL; 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 bool first_hotkey = false; // Is the first char of button a hotkey
if (!has_hotkey[0]) { if (!has_hotkey[0]) {
first_hotkey = true; // If no hotkey is specified, first char is used 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; int idx = 0;
const char *r = buttons; const char *r = buttons;
while (*r) { while (*r) {

View File

@ -444,7 +444,7 @@ void spell_suggest(int count)
char wcopy[MAXWLEN + 2]; char wcopy[MAXWLEN + 2];
suginfo_T sug; suginfo_T sug;
suggest_T *stp; suggest_T *stp;
bool mouse_used; bool mouse_used = false;
int selected = count; int selected = count;
int badlen = 0; int badlen = 0;
int msg_scroll_save = msg_scroll; int msg_scroll_save = msg_scroll;
@ -594,15 +594,11 @@ void spell_suggest(int count)
cmdmsg_rl = false; cmdmsg_rl = false;
msg_col = 0; msg_col = 0;
// Ask for choice. // Ask for choice.
selected = prompt_for_number(&mouse_used); selected = prompt_for_input(NULL, 0, false, &mouse_used);
if (ui_has(kUIMessages)) {
ui_call_msg_clear();
}
if (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 lines_left = Rows; // avoid more prompt
// don't delay for 'smd' in normal_cmd() // don't delay for 'smd' in normal_cmd()
msg_scroll = msg_scroll_save; 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) { if (ask_for_selection) {
// Ask to select a tag from the list. // 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) { if (i <= 0 || i > num_matches || got_int) {
// no valid choice: don't change anything // no valid choice: don't change anything
if (use_tagstack) { if (use_tagstack) {

View File

@ -641,9 +641,6 @@ bool terminal_enter(void)
curwin->w_p_so = 0; curwin->w_p_so = 0;
curwin->w_p_siso = 0; curwin->w_p_siso = 0;
// Save the existing cursor entry since it may be modified by the application
cursorentry_T save_cursorentry = shape_table[SHAPE_IDX_TERM];
// Update the cursor shape table and flush changes to the UI // Update the cursor shape table and flush changes to the UI
s->term->pending.cursor = true; s->term->pending.cursor = true;
refresh_cursor(s->term); refresh_cursor(s->term);
@ -674,8 +671,8 @@ bool terminal_enter(void)
RedrawingDisabled = s->save_rd; RedrawingDisabled = s->save_rd;
apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf);
shape_table[SHAPE_IDX_TERM] = save_cursorentry; // Restore the terminal cursor to what is set in 'guicursor'
ui_mode_info_set(); (void)parse_shape_opt(SHAPE_CURSOR);
if (save_curwin == curwin->handle) { // Else: window was closed. if (save_curwin == curwin->handle) { // Else: window was closed.
curwin->w_p_cul = save_w_p_cul; curwin->w_p_cul = save_w_p_cul;

View File

@ -718,10 +718,10 @@ void ui_call_event(char *name, bool fast, Array args)
bool handled = false; bool handled = false;
UIEventCallback *event_cb; 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) { if (strcmp(name, "msg_show") == 0) {
char *kind = args.items[0].data.string.data; fast = !strequal(args.items[0].data.string.data, "return_prompt");
fast = !kind || ((strncmp(kind, "confirm", 7) != 0 && strstr(kind, "_prompt") == NULL));
} }
map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, { map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, {

View File

@ -219,6 +219,7 @@ describe('swapfile detection', function()
.. [[%.swp"]], .. [[%.swp"]],
} }
feed('e') -- Chose "Edit" at the swap dialog. feed('e') -- Chose "Edit" at the swap dialog.
screen2:expect({ any = pesc('E5555: API call: Vim(edit):E325: ATTENTION') })
feed('<c-c>') feed('<c-c>')
screen2:expect(expected_no_dialog) screen2:expect(expected_no_dialog)
@ -531,10 +532,6 @@ describe('quitting swapfile dialog on startup stops TUI properly', function()
) )
end) end)
api.nvim_chan_send(chan, 'q') 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() retry(nil, nil, function()
eq( eq(
{ '', '[Process exited 1]', '' }, { '', '[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)
end) end)

View File

@ -15,9 +15,20 @@ local skip = t.skip
describe(':terminal cursor', function() describe(':terminal cursor', function()
local screen local screen
local terminal_mode_idx ---@type number
before_each(function() before_each(function()
clear() clear()
screen = tt.setup_screen() screen = tt.setup_screen()
if terminal_mode_idx == nil then
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
terminal_mode_idx = i
end
end
assert(terminal_mode_idx)
end
end) end)
it('moves the screen cursor when focused', function() it('moves the screen cursor when focused', function()
@ -143,13 +154,6 @@ describe(':terminal cursor', function()
it('can be modified by application #3681', function() it('can be modified by application #3681', function()
skip(is_os('win'), '#31587') skip(is_os('win'), '#31587')
local idx ---@type number
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
idx = i
end
end
assert(idx)
local states = { local states = {
[1] = { blink = true, shape = 'block' }, [1] = { blink = true, shape = 'block' },
@ -171,13 +175,13 @@ describe(':terminal cursor', function()
]], ]],
condition = function() condition = function()
if v.blink then if v.blink then
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
else else
eq(0, screen._mode_info[idx].blinkon) eq(0, screen._mode_info[terminal_mode_idx].blinkon)
eq(0, screen._mode_info[idx].blinkoff) eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
end end
eq(v.shape, screen._mode_info[idx].cursor_shape) eq(v.shape, screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
end end
@ -191,20 +195,13 @@ describe(':terminal cursor', function()
]]) ]])
-- Cursor returns to default on TermLeave -- Cursor returns to default on TermLeave
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('block', screen._mode_info[idx].cursor_shape) eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
end) end)
it('can be modified per terminal', function() it('can be modified per terminal', function()
skip(is_os('win'), '#31587') skip(is_os('win'), '#31587')
local idx ---@type number
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
idx = i
end
end
assert(idx)
-- Set cursor to vertical bar with blink -- Set cursor to vertical bar with blink
tt.feed_csi('5 q') tt.feed_csi('5 q')
@ -216,9 +213,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape) eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -231,9 +228,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape) eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -256,9 +253,9 @@ describe(':terminal cursor', function()
]], ]],
condition = function() condition = function()
-- New terminal, cursor resets to defaults -- New terminal, cursor resets to defaults
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('block', screen._mode_info[idx].cursor_shape) eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -275,9 +272,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(0, screen._mode_info[idx].blinkon) eq(0, screen._mode_info[terminal_mode_idx].blinkon)
eq(0, screen._mode_info[idx].blinkoff) eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
eq('horizontal', screen._mode_info[idx].cursor_shape) eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -294,9 +291,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape) eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
end) end)
@ -326,6 +323,32 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]])
end) end)
it('preserves guicursor value on TermLeave #31612', function()
eq(3, screen._mode_info[terminal_mode_idx].hl_id)
-- Change 'guicursor' while terminal mode is active
command('set guicursor+=t:Error')
local error_hl_id = call('hlID', 'Error')
screen:expect({
condition = function()
eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
end,
})
-- Exit terminal mode
feed([[<C-\><C-N>]])
screen:expect([[
tty ready |
^ |
|*5
]])
eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
end)
end) end)
describe('buffer cursor position is correct in terminal without number column', function() describe('buffer cursor position is correct in terminal without number column', function()
@ -350,12 +373,6 @@ describe('buffer cursor position is correct in terminal without number column',
}, { }, {
cols = 70, cols = 70,
}) })
screen:set_default_attr_ids({
[1] = { foreground = 253, background = 11 },
[2] = { reverse = true },
[3] = { bold = true },
[4] = { background = 11 },
})
-- Also check for real cursor position, as it is used for stuff like input methods -- Also check for real cursor position, as it is used for stuff like input methods
screen._handle_busy_start = function() end screen._handle_busy_start = function() end
screen._handle_busy_stop = function() end screen._handle_busy_stop = function() end
@ -667,13 +684,6 @@ describe('buffer cursor position is correct in terminal with number column', fun
}, { }, {
cols = 70, cols = 70,
}) })
screen:set_default_attr_ids({
[1] = { foreground = 253, background = 11 },
[2] = { reverse = true },
[3] = { bold = true },
[4] = { background = 11 },
[7] = { foreground = 130 },
})
-- Also check for real cursor position, as it is used for stuff like input methods -- Also check for real cursor position, as it is used for stuff like input methods
screen._handle_busy_start = function() end screen._handle_busy_start = function() end
screen._handle_busy_stop = function() end screen._handle_busy_stop = function() end

View File

@ -1456,13 +1456,12 @@ describe('cmdheight=0', function()
} }
feed(':%s/foo/bar/gc<CR>') feed(':%s/foo/bar/gc<CR>')
screen:expect { screen:expect([[
grid = [[ {3: }|
{2:foo} | |*2
{1:~ }|*3 {6:replace with bar (y/n/a/q}|
{6:replace wi...q/l/^E/^Y)?}^ | {6:/l/^E/^Y)?}^ |
]], ]])
}
feed('y') feed('y')
screen:expect { screen:expect {

View File

@ -368,7 +368,7 @@ describe('input non-printable chars', function()
"Xtest-overwrite" | "Xtest-overwrite" |
{9:WARNING: The file has been changed since reading it!!!} | {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)?}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)?}^ | {6:Do you really want to write to it (y/n)?}^ |
]]) ]])
@ -379,7 +379,7 @@ describe('input non-printable chars', function()
"Xtest-overwrite" | "Xtest-overwrite" |
{9:WARNING: The file has been changed since reading it!!!} | {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)?}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:Do you really want to write to it (y/n)?}n |
{6:Press ENTER or type command to continue}^ | {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() it('msg_clear follows msg_show kind of confirm', function()
feed('iline 1<esc>') feed('iline 1<esc>')
feed(':call confirm("test")<cr>') feed(':call confirm("test")<cr>')
screen:expect { screen:expect({
grid = [[ grid = [[
line ^1 | line ^1 |
{1:~ }|*4 {1:~ }|*4
]], ]],
cmdline = {
{
content = { { '' } },
pos = 0,
prompt = '[O]k: ',
},
},
messages = { messages = {
{ {
content = { { '\ntest\n[O]k: ', 6, 10 } }, content = { { '\ntest\n', 6, 10 } },
kind = 'confirm', kind = 'confirm',
}, },
}, },
} })
feed('<cr>') feed('<cr>')
screen:expect { screen:expect {
@ -67,22 +74,29 @@ describe('ui/ext_messages', function()
it('msg_show kinds', function() it('msg_show kinds', function()
feed('iline 1\nline 2<esc>') feed('iline 1\nline 2<esc>')
-- kind=confirm -- confirm is now cmdline prompt
feed(':echo confirm("test")<cr>') feed(':echo confirm("test")<cr>')
screen:expect { screen:expect({
grid = [[ grid = [[
line 1 | line 1 |
line ^2 | line ^2 |
{1:~ }|*3 {1:~ }|*3
]], ]],
cmdline = {
{
content = { { '' } },
pos = 0,
prompt = '[O]k: ',
},
},
messages = { messages = {
{ {
content = { { '\ntest\n[O]k: ', 6, 10 } }, content = { { '\ntest\n', 6, 10 } },
kind = 'confirm', kind = 'confirm',
}, },
}, },
} })
feed('<cr><cr>') feed('<cr>')
screen:expect { screen:expect {
grid = [[ grid = [[
line 1 | line 1 |
@ -91,7 +105,7 @@ describe('ui/ext_messages', function()
]], ]],
messages = { messages = {
{ {
content = { { '\ntest\n[O]k: ', 6, 10 } }, content = { { '\ntest\n', 6, 10 } },
kind = 'confirm', 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>') feed(':%s/i/X/gc<cr>')
screen:expect { screen:expect({
grid = [[ grid = [[
l{2:i}ne 1 | l{2:^i}ne 1 |
l{10:i}ne ^2 | l{10:i}ne 2 |
{1:~ }|*3 {1:~ }|*3
]], ]],
messages = { cmdline = {
{ {
content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 18 } }, content = { { '' } },
kind = 'confirm_sub', pos = 0,
prompt = 'replace with X (y/n/a/q/l/^E/^Y)?',
}, },
}, },
} })
feed('nq') feed('nq')
-- kind=wmsg (editing readonly file) -- kind=wmsg (editing readonly file)
@ -1031,42 +1046,42 @@ stack traceback:
feed('z=') feed('z=')
screen:expect({ screen:expect({
grid = [[ grid = [[
{100:helllo} | {100:^helllo} |
{1:~ }|*3 {1:~ }|*4
{1:^~ }|
]], ]],
cmdline = {
{
content = { { '' } },
pos = 0,
prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels):',
},
},
messages = { messages = {
{ {
content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } },
kind = 'list_cmd', kind = 'list_cmd',
}, },
{
content = { { 'Type number and <Enter> or click with the mouse (q or empty cancels): ' } },
kind = 'number_prompt',
},
}, },
}) })
feed('1') feed('1')
screen:expect({ screen:expect({
grid = [[ grid = [[
{100:helllo} | {100:^helllo} |
{1:~ }|*3 {1:~ }|*4
{1:^~ }|
]], ]],
cmdline = {
{
content = { { '1' } },
pos = 1,
prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels):',
},
},
messages = { messages = {
{ {
content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } },
kind = 'list_cmd', 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

@ -94,7 +94,7 @@ describe('ui mode_change event', function()
} }
end) end)
-- oldtest: Test_indent_norm_with_gq() -- oldtest: Test_mouse_shape_indent_norm_with_gq()
it('is restored to Normal mode after "gq" indents using :normal #12309', function() it('is restored to Normal mode after "gq" indents using :normal #12309', function()
screen:try_resize(60, 6) screen:try_resize(60, 6)
n.exec([[ n.exec([[

View File

@ -1162,6 +1162,8 @@ describe('builtin popupmenu', function()
[6] = { foreground = Screen.colors.White, background = Screen.colors.Red }, [6] = { foreground = Screen.colors.White, background = Screen.colors.Red },
[7] = { background = Screen.colors.Yellow }, -- Search [7] = { background = Screen.colors.Yellow }, -- Search
[8] = { foreground = Screen.colors.Red }, [8] = { foreground = Screen.colors.Red },
[9] = { foreground = Screen.colors.Yellow, background = Screen.colors.Green },
[10] = { foreground = Screen.colors.White, background = Screen.colors.Green },
ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey }, ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey },
kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 }, kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 },
xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey }, xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey },
@ -5625,6 +5627,114 @@ describe('builtin popupmenu', function()
{2:-- INSERT --} | {2:-- INSERT --} |
]]) ]])
feed('<Esc>') feed('<Esc>')
-- text after the inserted text shouldn't be highlighted
feed('0ea <C-X><C-O>')
screen:expect([[
αβγ {8:foo}^ foo |
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 1 of 3} |
]])
feed('<C-P>')
screen:expect([[
αβγ ^ foo |
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{8:Back at original} |
]])
feed('<C-P>')
screen:expect([[
αβγ {8:}^ foo |
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{s: }{1: }|
{1:~ }|*15
{2:-- }{5:match 3 of 3} |
]])
feed('<C-Y>')
screen:expect([[
αβγ ^ foo |
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<Esc>')
end)
-- oldtest: Test_pum_matchins_highlight_combine()
it('with ComplMatchIns, Normal and CursorLine highlights', function()
exec([[
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
endfunc
set omnifunc=Omni_test
hi Normal guibg=blue
hi CursorLine guibg=green guifg=white
set cursorline
call setline(1, 'aaa bbb')
]])
-- when ComplMatchIns is not set, CursorLine applies normally
feed('0ea <C-X><C-O>')
screen:expect([[
{10:aaa foo^ bbb }|
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E>')
screen:expect([[
{10:aaa ^ bbb }|
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<BS><Esc>')
-- when ComplMatchIns is set, it is applied over CursorLine
command('hi ComplMatchIns guifg=Yellow')
feed('0ea <C-X><C-O>')
screen:expect([[
{10:aaa }{9:foo}{10:^ bbb }|
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 1 of 3} |
]])
feed('<C-P>')
screen:expect([[
{10:aaa ^ bbb }|
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{8:Back at original} |
]])
feed('<C-P>')
screen:expect([[
{10:aaa }{9:}{10:^ bbb }|
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{s: }{1: }|
{1:~ }|*15
{2:-- }{5:match 3 of 3} |
]])
feed('<C-E>')
screen:expect([[
{10:aaa ^ bbb }|
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<Esc>')
end) end)
end end
end end

View File

@ -116,7 +116,7 @@ describe('NULL', function()
null_expr_test( null_expr_test(
'is accepted as an empty list by inputlist()', 'is accepted as an empty list by inputlist()',
'[feedkeys("\\n"), inputlist(L)]', '[feedkeys("\\n"), inputlist(L)]',
'Type number and <Enter> or click with the mouse (q or empty cancels): ', '',
{ 0, 0 } { 0, 0 }
) )
null_expr_test( null_expr_test(

View File

@ -308,7 +308,7 @@ endfunc
" Test that mouse shape is restored to Normal mode after using "gq" when " Test that mouse shape is restored to Normal mode after using "gq" when
" 'indentexpr' executes :normal. " 'indentexpr' executes :normal.
func Test_indent_norm_with_gq() func Test_mouse_shape_indent_norm_with_gq()
CheckFeature mouseshape CheckFeature mouseshape
CheckCanRunGui CheckCanRunGui

View File

@ -1747,13 +1747,67 @@ func Test_pum_matchins_highlight()
call TermWait(buf) call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-Y>") call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-Y>")
call VerifyScreenDump(buf, 'Test_pum_matchins_04', {}) call VerifyScreenDump(buf, 'Test_pum_matchins_04', {})
call term_sendkeys(buf, "\<C-E>\<Esc>") call term_sendkeys(buf, "\<Esc>")
" restore after cancel completion " restore after cancel completion
call TermWait(buf) call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<Space>") call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<Space>")
call VerifyScreenDump(buf, 'Test_pum_matchins_05', {}) call VerifyScreenDump(buf, 'Test_pum_matchins_05', {})
call term_sendkeys(buf, "\<C-E>\<Esc>") call term_sendkeys(buf, "\<Esc>")
" text after the inserted text shouldn't be highlighted
call TermWait(buf)
call term_sendkeys(buf, "0ea \<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_matchins_07', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_08', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_09', {})
call term_sendkeys(buf, "\<C-Y>")
call VerifyScreenDump(buf, 'Test_pum_matchins_10', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
endfunc
func Test_pum_matchins_highlight_combine()
CheckScreendump
let lines =<< trim END
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
endfunc
set omnifunc=Omni_test
hi Normal ctermbg=blue
hi CursorLine cterm=underline ctermbg=green
set cursorline
call setline(1, 'aaa bbb')
END
call writefile(lines, 'Xscript', 'D')
let buf = RunVimInTerminal('-S Xscript', {})
" when ComplMatchIns is not set, CursorLine applies normally
call term_sendkeys(buf, "0ea \<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_01', {})
call term_sendkeys(buf, "\<C-E>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_02', {})
call term_sendkeys(buf, "\<BS>\<Esc>")
" when ComplMatchIns is set, it is applied over CursorLine
call TermWait(buf)
call term_sendkeys(buf, ":hi ComplMatchIns ctermbg=red ctermfg=yellow\<CR>")
call TermWait(buf)
call term_sendkeys(buf, "0ea \<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_03', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_04', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_05', {})
call term_sendkeys(buf, "\<C-E>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_06', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
endfunc endfunc

View File

@ -471,7 +471,9 @@ func Test_spellsuggest_option_number()
\ .. "Change \"baord\" to:\n" \ .. "Change \"baord\" to:\n"
\ .. " 1 \"board\"\n" \ .. " 1 \"board\"\n"
\ .. " 2 \"bard\"\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 set spell spellsuggest=0
call assert_equal("\nSorry, no suggestions", execute('norm $z=')) call assert_equal("\nSorry, no suggestions", execute('norm $z='))
@ -509,7 +511,9 @@ func Test_spellsuggest_option_expr()
\ .. " 1 \"BARD\"\n" \ .. " 1 \"BARD\"\n"
\ .. " 2 \"BOARD\"\n" \ .. " 2 \"BOARD\"\n"
\ .. " 3 \"BROAD\"\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 " With verbose, z= should show the score i.e. word length with
" our SpellSuggest() function. " our SpellSuggest() function.
@ -521,7 +525,9 @@ func Test_spellsuggest_option_expr()
\ .. " 1 \"BARD\" (4 - 0)\n" \ .. " 1 \"BARD\" (4 - 0)\n"
\ .. " 2 \"BOARD\" (5 - 0)\n" \ .. " 2 \"BOARD\" (5 - 0)\n"
\ .. " 3 \"BROAD\" (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& set spell& spellsuggest& verbose&
bwipe! bwipe!

View File

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