Merge #6539 'More cursor shape modes'

This commit is contained in:
Justin M. Keyes
2017-04-21 19:09:50 +02:00
17 changed files with 512 additions and 340 deletions

View File

@@ -270,13 +270,17 @@ a dictionary with these (optional) keys:
Defaults to false. Defaults to false.
Nvim will then send msgpack-rpc notifications, with the method name "redraw" Nvim will then send msgpack-rpc notifications, with the method name "redraw"
and a single argument, an array of screen updates (described below). and a single argument, an array of screen updates (described below). These
These should be processed in order. Preferably the user should only be able to should be processed in order. Preferably the user should only be able to see
see the screen state after all updates are processed (not any intermediate the screen state after all updates in the same "redraw" event are processed
state after processing only a part of the array). (not any intermediate state after processing only a part of the array).
Screen updates are arrays. The first element a string describing the kind Future versions of Nvim may add new update kinds and may append new parameters
of update. to existing update kinds. Clients must be prepared to ignore such extensions
to be forward-compatible. |api-contract|
Screen updates are tuples whose first element is the string name of the update
kind.
["resize", width, height] ["resize", width, height]
The grid is resized to `width` and `height` cells. The grid is resized to `width` and `height` cells.
@@ -387,10 +391,31 @@ of update.
["update_menu"] ["update_menu"]
The menu mappings changed. The menu mappings changed.
["mode_change", mode] ["mode_info_set", cursor_style_enabled, mode_info]
The mode changed. Currently sent when "insert", "replace", "cmdline" and `cursor_style_enabled` is a boolean indicating if the UI should set the cursor
"normal" modes are entered. A client could for instance change the cursor style. `mode_info` is a list of mode property maps. The current mode is given
shape. by the `mode_idx` field of the `mode_change` event.
Each mode property map may contain these keys:
KEY DESCRIPTION ~
`cursor_shape`: "block", "horizontal", "vertical"
`cell_percentage`: Cell % occupied by the cursor.
`blinkwait`, `blinkon`, `blinkoff`: See |cursor-blinking|.
`hl_id`: Cursor highlight group.
`hl_lm`: Cursor highlight group if 'langmap' is active.
`short_name`: Mode code name, see 'guicursor'.
`name`: Mode descriptive name.
`mouse_shape`: (To be implemented.)
Some keys are missing in some modes.
["mode_change", mode, mode_idx]
The mode changed. The first parameter `mode` is a string representing the
current mode. `mode_idx` is an index into the array received in the
`mode_info_set` event. UIs should change the cursor style according to the
properties specified in the corresponding item. The set of modes reported will
change in new versions of Nvim, for instance more submodes and temporary
states might be represented as separate modes.
["popupmenu_show", items, selected, row, col] ["popupmenu_show", items, selected, row, col]
When `popupmenu_external` is set to true, nvim will not draw the When `popupmenu_external` is set to true, nvim will not draw the

View File

@@ -73,7 +73,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->clear = remote_ui_clear; ui->clear = remote_ui_clear;
ui->eol_clear = remote_ui_eol_clear; ui->eol_clear = remote_ui_eol_clear;
ui->cursor_goto = remote_ui_cursor_goto; ui->cursor_goto = remote_ui_cursor_goto;
ui->cursor_style_set = remote_ui_cursor_style_set; ui->mode_info_set = remote_ui_mode_info_set;
ui->update_menu = remote_ui_update_menu; ui->update_menu = remote_ui_update_menu;
ui->busy_start = remote_ui_busy_start; ui->busy_start = remote_ui_busy_start;
ui->busy_stop = remote_ui_busy_stop; ui->busy_stop = remote_ui_busy_stop;
@@ -269,19 +269,14 @@ static void remote_ui_mouse_off(UI *ui)
push_call(ui, "mouse_off", args); push_call(ui, "mouse_off", args);
} }
static void remote_ui_mode_change(UI *ui, int mode) static void remote_ui_mode_change(UI *ui, int mode_idx)
{ {
Array args = ARRAY_DICT_INIT; Array args = ARRAY_DICT_INIT;
if (mode == INSERT) {
ADD(args, STRING_OBJ(cstr_to_string("insert"))); char *full_name = shape_table[mode_idx].full_name;
} else if (mode == REPLACE) { ADD(args, STRING_OBJ(cstr_to_string(full_name)));
ADD(args, STRING_OBJ(cstr_to_string("replace")));
} else if (mode == CMDLINE) { ADD(args, INTEGER_OBJ(mode_idx));
ADD(args, STRING_OBJ(cstr_to_string("cmdline")));
} else {
assert(mode == NORMAL);
ADD(args, STRING_OBJ(cstr_to_string("normal")));
}
push_call(ui, "mode_change", args); push_call(ui, "mode_change", args);
} }
@@ -303,12 +298,12 @@ static void remote_ui_scroll(UI *ui, int count)
push_call(ui, "scroll", args); push_call(ui, "scroll", args);
} }
static void remote_ui_cursor_style_set(UI *ui, bool enabled, Dictionary data) static void remote_ui_mode_info_set(UI *ui, bool guicursor_enabled, Array data)
{ {
Array args = ARRAY_DICT_INIT; Array args = ARRAY_DICT_INIT;
ADD(args, BOOLEAN_OBJ(enabled)); ADD(args, BOOLEAN_OBJ(guicursor_enabled));
ADD(args, copy_object(DICTIONARY_OBJ(data))); ADD(args, copy_object(ARRAY_OBJ(data)));
push_call(ui, "cursor_style_set", args); push_call(ui, "mode_info_set", args);
} }
static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
@@ -396,9 +391,11 @@ static void remote_ui_update_sp(UI *ui, int sp)
static void remote_ui_flush(UI *ui) static void remote_ui_flush(UI *ui)
{ {
UIData *data = ui->data; UIData *data = ui->data;
if (data->buffer.size > 0) {
channel_send_event(data->channel_id, "redraw", data->buffer); channel_send_event(data->channel_id, "redraw", data->buffer);
data->buffer = (Array)ARRAY_DICT_INIT; data->buffer = (Array)ARRAY_DICT_INIT;
} }
}
static void remote_ui_suspend(UI *ui) static void remote_ui_suspend(UI *ui)
{ {

View File

@@ -14,7 +14,7 @@
#include "nvim/ui.h" #include "nvim/ui.h"
/// Handling of cursor and mouse pointer shapes in various modes. /// Handling of cursor and mouse pointer shapes in various modes.
static cursorentry_T shape_table[SHAPE_IDX_COUNT] = cursorentry_T shape_table[SHAPE_IDX_COUNT] =
{ {
// Values are set by 'guicursor' and 'mouseshape'. // Values are set by 'guicursor' and 'mouseshape'.
// Adjust the SHAPE_IDX_ defines when changing this! // Adjust the SHAPE_IDX_ defines when changing this!
@@ -37,11 +37,11 @@ static cursorentry_T shape_table[SHAPE_IDX_COUNT] =
{ "showmatch", 0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR }, { "showmatch", 0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR },
}; };
/// Converts cursor_shapes into a Dictionary of dictionaries /// Converts cursor_shapes into an Array of Dictionaries
/// @return dictionary of the form {"normal" : { "cursor_shape": ... }, ...} /// @return Array of the form {[ "cursor_shape": ... ], ...}
Dictionary cursor_shape_dict(void) Array mode_style_array(void)
{ {
Dictionary all = ARRAY_DICT_INIT; Array all = ARRAY_DICT_INIT;
for (int i = 0; i < SHAPE_IDX_COUNT; i++) { for (int i = 0; i < SHAPE_IDX_COUNT; i++) {
Dictionary dic = ARRAY_DICT_INIT; Dictionary dic = ARRAY_DICT_INIT;
@@ -65,9 +65,10 @@ Dictionary cursor_shape_dict(void)
PUT(dic, "hl_id", INTEGER_OBJ(cur->id)); PUT(dic, "hl_id", INTEGER_OBJ(cur->id));
PUT(dic, "id_lm", INTEGER_OBJ(cur->id_lm)); PUT(dic, "id_lm", INTEGER_OBJ(cur->id_lm));
} }
PUT(dic, "name", STRING_OBJ(cstr_to_string(cur->full_name)));
PUT(dic, "short_name", STRING_OBJ(cstr_to_string(cur->name))); PUT(dic, "short_name", STRING_OBJ(cstr_to_string(cur->name)));
PUT(all, cur->full_name, DICTIONARY_OBJ(dic)); ADD(all, DICTIONARY_OBJ(dic));
} }
return all; return all;
@@ -243,7 +244,7 @@ char_u *parse_shape_opt(int what)
shape_table[SHAPE_IDX_VE].id_lm = shape_table[SHAPE_IDX_V].id_lm; shape_table[SHAPE_IDX_VE].id_lm = shape_table[SHAPE_IDX_V].id_lm;
} }
} }
ui_cursor_style_set(); ui_mode_info_set();
return NULL; return NULL;
} }
@@ -263,3 +264,35 @@ int cursor_mode_str2int(const char *mode)
return -1; return -1;
} }
/// Return the index into shape_table[] for the current mode.
int cursor_get_mode_idx(void)
{
if (State == SHOWMATCH) {
return SHAPE_IDX_SM;
} else if (State & VREPLACE_FLAG) {
return SHAPE_IDX_R;
} else if (State & REPLACE_FLAG) {
return SHAPE_IDX_R;
} else if (State & INSERT) {
return SHAPE_IDX_I;
} else if (State & CMDLINE) {
if (cmdline_at_end()) {
return SHAPE_IDX_C;
} else if (cmdline_overstrike()) {
return SHAPE_IDX_CR;
} else {
return SHAPE_IDX_CI;
}
} else if (finish_op) {
return SHAPE_IDX_O;
} else if (VIsual_active) {
if (*p_sel == 'e') {
return SHAPE_IDX_VE;
} else {
return SHAPE_IDX_V;
}
} else {
return SHAPE_IDX_N;
}
}

View File

@@ -25,7 +25,7 @@ SHAPE_IDX_MORE = 14, ///< Hit-return or More
SHAPE_IDX_MOREL = 15, ///< Hit-return or More in last line SHAPE_IDX_MOREL = 15, ///< Hit-return or More in last line
SHAPE_IDX_SM = 16, ///< showing matching paren SHAPE_IDX_SM = 16, ///< showing matching paren
SHAPE_IDX_COUNT = 17 SHAPE_IDX_COUNT = 17
} MouseMode; } ModeShape;
typedef enum { typedef enum {
SHAPE_BLOCK = 0, ///< block cursor SHAPE_BLOCK = 0, ///< block cursor
@@ -53,6 +53,7 @@ typedef struct cursor_entry {
char used_for; ///< SHAPE_MOUSE and/or SHAPE_CURSOR char used_for; ///< SHAPE_MOUSE and/or SHAPE_CURSOR
} cursorentry_T; } cursorentry_T;
extern cursorentry_T shape_table[SHAPE_IDX_COUNT];
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor_shape.h.generated.h" # include "cursor_shape.h.generated.h"

View File

@@ -354,6 +354,7 @@ static int command_line_check(VimState *state)
quit_more = false; // reset after CTRL-D which had a more-prompt quit_more = false; // reset after CTRL-D which had a more-prompt
cursorcmd(); // set the cursor on the right spot cursorcmd(); // set the cursor on the right spot
ui_cursor_shape();
return 1; return 1;
} }
@@ -2095,6 +2096,18 @@ redraw:
return (char_u *)line_ga.ga_data; return (char_u *)line_ga.ga_data;
} }
bool cmdline_overstrike(void)
{
return ccline.overstrike;
}
/// Return true if the cursor is at the end of the cmdline.
bool cmdline_at_end(void)
{
return (ccline.cmdpos >= ccline.cmdlen);
}
/* /*
* Allocate a new command line buffer. * Allocate a new command line buffer.
* Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen.
@@ -2265,6 +2278,7 @@ void putcmdline(int c, int shift)
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
msg_no_more = FALSE; msg_no_more = FALSE;
cursorcmd(); cursorcmd();
ui_cursor_shape();
} }
/* /*
@@ -2284,6 +2298,7 @@ void unputcmdline(void)
draw_cmdline(ccline.cmdpos, 1); draw_cmdline(ccline.cmdpos, 1);
msg_no_more = FALSE; msg_no_more = FALSE;
cursorcmd(); cursorcmd();
ui_cursor_shape();
} }
/* /*
@@ -2601,6 +2616,7 @@ void redrawcmdline(void)
compute_cmdrow(); compute_cmdrow();
redrawcmd(); redrawcmd();
cursorcmd(); cursorcmd();
ui_cursor_shape();
} }
static void redrawcmdprompt(void) static void redrawcmdprompt(void)

View File

@@ -459,6 +459,7 @@ void setmouse(void)
{ {
int checkfor; int checkfor;
ui_cursor_shape();
/* be quick when mouse is off */ /* be quick when mouse is off */
if (*p_mouse == NUL) if (*p_mouse == NUL)

View File

@@ -76,7 +76,7 @@ typedef struct {
bool busy; bool busy;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs print_attrs; HlAttrs print_attrs;
int showing_mode; ModeShape showing_mode;
struct { struct {
int enable_mouse, disable_mouse; int enable_mouse, disable_mouse;
int enable_bracketed_paste, disable_bracketed_paste; int enable_bracketed_paste, disable_bracketed_paste;
@@ -104,7 +104,7 @@ UI *tui_start(void)
ui->clear = tui_clear; ui->clear = tui_clear;
ui->eol_clear = tui_eol_clear; ui->eol_clear = tui_eol_clear;
ui->cursor_goto = tui_cursor_goto; ui->cursor_goto = tui_cursor_goto;
ui->cursor_style_set = tui_cursor_style_set; ui->mode_info_set = tui_mode_info_set;
ui->update_menu = tui_update_menu; ui->update_menu = tui_update_menu;
ui->busy_start = tui_busy_start; ui->busy_start = tui_busy_start;
ui->busy_stop = tui_busy_stop; ui->busy_stop = tui_busy_stop;
@@ -134,7 +134,7 @@ static void terminfo_start(UI *ui)
data->can_use_terminal_scroll = true; data->can_use_terminal_scroll = true;
data->bufpos = 0; data->bufpos = 0;
data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE; data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE;
data->showing_mode = 0; data->showing_mode = SHAPE_IDX_N;
data->unibi_ext.enable_mouse = -1; data->unibi_ext.enable_mouse = -1;
data->unibi_ext.disable_mouse = -1; data->unibi_ext.disable_mouse = -1;
data->unibi_ext.set_cursor_color = -1; data->unibi_ext.set_cursor_color = -1;
@@ -176,7 +176,7 @@ static void terminfo_stop(UI *ui)
{ {
TUIData *data = ui->data; TUIData *data = ui->data;
// Destroy output stuff // Destroy output stuff
tui_mode_change(ui, NORMAL); tui_mode_change(ui, SHAPE_IDX_N);
tui_mouse_off(ui); tui_mouse_off(ui);
unibi_out(ui, unibi_exit_attribute_mode); unibi_out(ui, unibi_exit_attribute_mode);
// cursor should be set to normal before exiting alternate screen // cursor should be set to normal before exiting alternate screen
@@ -475,27 +475,24 @@ static cursorentry_T decode_cursor_entry(Dictionary args)
return r; return r;
} }
static void tui_cursor_style_set(UI *ui, bool enabled, Dictionary args) static void tui_mode_info_set(UI *ui, bool guicursor_enabled, Array args)
{ {
cursor_style_enabled = enabled; cursor_style_enabled = guicursor_enabled;
if (!enabled) { if (!guicursor_enabled) {
return; // Do not send cursor style control codes. return; // Do not send cursor style control codes.
} }
TUIData *data = ui->data; TUIData *data = ui->data;
assert(args.size); assert(args.size);
// Keys: as defined by `shape_table`.
// cursor style entries as defined by `shape_table`.
for (size_t i = 0; i < args.size; i++) { for (size_t i = 0; i < args.size; i++) {
char *mode_name = args.items[i].key.data; assert(args.items[i].type == kObjectTypeDictionary);
const int mode_id = cursor_mode_str2int(mode_name); cursorentry_T r = decode_cursor_entry(args.items[i].data.dictionary);
assert(mode_id >= 0); data->cursor_shapes[i] = r;
cursorentry_T r = decode_cursor_entry(args.items[i].value.data.dictionary);
r.full_name = mode_name;
data->cursor_shapes[mode_id] = r;
} }
MouseMode cursor_mode = tui_mode2cursor(data->showing_mode); tui_set_mode(ui, data->showing_mode);
tui_set_cursor(ui, cursor_mode);
} }
static void tui_update_menu(UI *ui) static void tui_update_menu(UI *ui)
@@ -532,7 +529,7 @@ static void tui_mouse_off(UI *ui)
} }
/// @param mode one of SHAPE_XXX /// @param mode one of SHAPE_XXX
static void tui_set_cursor(UI *ui, MouseMode mode) static void tui_set_mode(UI *ui, ModeShape mode)
{ {
if (!cursor_style_enabled) { if (!cursor_style_enabled) {
return; return;
@@ -587,42 +584,12 @@ static void tui_set_cursor(UI *ui, MouseMode mode)
} }
} }
/// Returns cursor mode from edit mode
static MouseMode tui_mode2cursor(int mode)
{
switch (mode) {
case INSERT: return SHAPE_IDX_I;
case CMDLINE: return SHAPE_IDX_C;
case REPLACE: return SHAPE_IDX_R;
case NORMAL:
default: return SHAPE_IDX_N;
}
}
/// @param mode editor mode /// @param mode editor mode
static void tui_mode_change(UI *ui, int mode) static void tui_mode_change(UI *ui, int mode_idx)
{ {
TUIData *data = ui->data; TUIData *data = ui->data;
tui_set_mode(ui, (ModeShape)mode_idx);
if (mode == INSERT) { data->showing_mode = (ModeShape)mode_idx;
if (data->showing_mode != INSERT) {
tui_set_cursor(ui, SHAPE_IDX_I);
}
} else if (mode == CMDLINE) {
if (data->showing_mode != CMDLINE) {
tui_set_cursor(ui, SHAPE_IDX_C);
}
} else if (mode == REPLACE) {
if (data->showing_mode != REPLACE) {
tui_set_cursor(ui, SHAPE_IDX_R);
}
} else {
assert(mode == NORMAL);
if (data->showing_mode != NORMAL) {
tui_set_cursor(ui, SHAPE_IDX_N);
}
}
data->showing_mode = mode;
} }
static void tui_set_scroll_region(UI *ui, int top, int bot, int left, static void tui_set_scroll_region(UI *ui, int top, int bot, int left,

View File

@@ -56,6 +56,7 @@ static int current_attr_code = 0;
static bool pending_cursor_update = false; static bool pending_cursor_update = false;
static int busy = 0; static int busy = 0;
static int height, width; static int height, width;
static int old_mode_idx = -1;
// UI_CALL invokes a function on all registered UI instances. The functions can // UI_CALL invokes a function on all registered UI instances. The functions can
// have 0-5 arguments (configurable by SELECT_NTH). // have 0-5 arguments (configurable by SELECT_NTH).
@@ -153,12 +154,6 @@ void ui_event(char *name, Array args)
} }
} }
// May update the shape of the cursor.
void ui_cursor_shape(void)
{
ui_mode_change();
}
void ui_refresh(void) void ui_refresh(void)
{ {
if (!ui_active()) { if (!ui_active()) {
@@ -183,7 +178,9 @@ void ui_refresh(void)
row = col = 0; row = col = 0;
screen_resize(width, height); screen_resize(width, height);
pum_set_external(pum_external); pum_set_external(pum_external);
ui_cursor_style_set(); ui_mode_info_set();
old_mode_idx = -1;
ui_cursor_shape();
} }
static void ui_refresh_event(void **argv) static void ui_refresh_event(void **argv)
@@ -381,12 +378,12 @@ void ui_cursor_goto(int new_row, int new_col)
pending_cursor_update = true; pending_cursor_update = true;
} }
void ui_cursor_style_set(void) void ui_mode_info_set(void)
{ {
Dictionary style = cursor_shape_dict(); Array style = mode_style_array();
bool enabled = (*p_guicursor != NUL); bool enabled = (*p_guicursor != NUL);
UI_CALL(cursor_style_set, enabled, style); UI_CALL(mode_info_set, enabled, style);
api_free_dictionary(style); api_free_array(style);
} }
void ui_update_menu(void) void ui_update_menu(void)
@@ -544,25 +541,19 @@ static void flush_cursor_update(void)
} }
} }
// Notify that the current mode has changed. Can be used to change cursor /// Check if current mode has changed.
// shape, for example. /// May update the shape of the cursor.
static void ui_mode_change(void) void ui_cursor_shape(void)
{ {
int mode;
if (!full_screen) { if (!full_screen) {
return; return;
} }
// Get a simple UI mode out of State. int mode_idx = cursor_get_mode_idx();
if ((State & REPLACE) == REPLACE) {
mode = REPLACE; if (old_mode_idx != mode_idx) {
} else if (State & INSERT) { old_mode_idx = mode_idx;
mode = INSERT; UI_CALL(mode_change, mode_idx);
} else if (State & CMDLINE) {
mode = CMDLINE;
} else {
mode = NORMAL;
} }
UI_CALL(mode_change, mode);
conceal_check_cursur_line(); conceal_check_cursur_line();
} }

View File

@@ -22,13 +22,13 @@ struct ui_t {
void (*clear)(UI *ui); void (*clear)(UI *ui);
void (*eol_clear)(UI *ui); void (*eol_clear)(UI *ui);
void (*cursor_goto)(UI *ui, int row, int col); void (*cursor_goto)(UI *ui, int row, int col);
void (*cursor_style_set)(UI *ui, bool enabled, Dictionary cursor_styles); void (*mode_info_set)(UI *ui, bool enabled, Array cursor_styles);
void (*update_menu)(UI *ui); void (*update_menu)(UI *ui);
void (*busy_start)(UI *ui); void (*busy_start)(UI *ui);
void (*busy_stop)(UI *ui); void (*busy_stop)(UI *ui);
void (*mouse_on)(UI *ui); void (*mouse_on)(UI *ui);
void (*mouse_off)(UI *ui); void (*mouse_off)(UI *ui);
void (*mode_change)(UI *ui, int mode); void (*mode_change)(UI *ui, int mode_idx);
void (*set_scroll_region)(UI *ui, int top, int bot, int left, int right); void (*set_scroll_region)(UI *ui, int top, int bot, int left, int right);
void (*scroll)(UI *ui, int count); void (*scroll)(UI *ui, int count);
void (*highlight_set)(UI *ui, HlAttrs attrs); void (*highlight_set)(UI *ui, HlAttrs attrs);

View File

@@ -63,7 +63,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.clear = ui_bridge_clear; rv->bridge.clear = ui_bridge_clear;
rv->bridge.eol_clear = ui_bridge_eol_clear; rv->bridge.eol_clear = ui_bridge_eol_clear;
rv->bridge.cursor_goto = ui_bridge_cursor_goto; rv->bridge.cursor_goto = ui_bridge_cursor_goto;
rv->bridge.cursor_style_set = ui_bridge_cursor_style_set; rv->bridge.mode_info_set = ui_bridge_mode_info_set;
rv->bridge.update_menu = ui_bridge_update_menu; rv->bridge.update_menu = ui_bridge_update_menu;
rv->bridge.busy_start = ui_bridge_busy_start; rv->bridge.busy_start = ui_bridge_busy_start;
rv->bridge.busy_stop = ui_bridge_busy_stop; rv->bridge.busy_stop = ui_bridge_busy_stop;
@@ -183,23 +183,23 @@ static void ui_bridge_cursor_goto_event(void **argv)
ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2])); ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
} }
static void ui_bridge_cursor_style_set(UI *b, bool enabled, Dictionary styles) static void ui_bridge_mode_info_set(UI *b, bool enabled, Array modes)
{ {
bool *enabledp = xmalloc(sizeof(*enabledp)); bool *enabledp = xmalloc(sizeof(*enabledp));
Object *stylesp = xmalloc(sizeof(*stylesp)); Object *modesp = xmalloc(sizeof(*modesp));
*enabledp = enabled; *enabledp = enabled;
*stylesp = copy_object(DICTIONARY_OBJ(styles)); *modesp = copy_object(ARRAY_OBJ(modes));
UI_CALL(b, cursor_style_set, 3, b, enabledp, stylesp); UI_CALL(b, mode_info_set, 3, b, enabledp, modesp);
} }
static void ui_bridge_cursor_style_set_event(void **argv) static void ui_bridge_mode_info_set_event(void **argv)
{ {
UI *ui = UI(argv[0]); UI *ui = UI(argv[0]);
bool *enabled = argv[1]; bool *enabled = argv[1];
Object *styles = argv[2]; Object *modes = argv[2];
ui->cursor_style_set(ui, *enabled, styles->data.dictionary); ui->mode_info_set(ui, *enabled, modes->data.array);
xfree(enabled); xfree(enabled);
api_free_object(*styles); api_free_object(*modes);
xfree(styles); xfree(modes);
} }
static void ui_bridge_update_menu(UI *b) static void ui_bridge_update_menu(UI *b)
@@ -252,9 +252,9 @@ static void ui_bridge_mouse_off_event(void **argv)
ui->mouse_off(ui); ui->mouse_off(ui);
} }
static void ui_bridge_mode_change(UI *b, int mode) static void ui_bridge_mode_change(UI *b, int mode_idx)
{ {
UI_CALL(b, mode_change, 2, b, INT2PTR(mode)); UI_CALL(b, mode_change, 2, b, INT2PTR(mode_idx));
} }
static void ui_bridge_mode_change_event(void **argv) static void ui_bridge_mode_change_event(void **argv)
{ {

View File

@@ -41,7 +41,7 @@ describe("CTRL-C (mapped)", function()
local function test_ctrl_c(ms) local function test_ctrl_c(ms)
feed(":global/^/p<CR>") feed(":global/^/p<CR>")
helpers.sleep(ms) screen:sleep(ms)
feed("<C-C>") feed("<C-C>")
screen:expect([[Interrupt]], nil, nil, nil, true) screen:expect([[Interrupt]], nil, nil, nil, true)
end end

View File

@@ -392,7 +392,14 @@ end
-- sleeps the test runner (_not_ the nvim instance) -- sleeps the test runner (_not_ the nvim instance)
local function sleep(ms) local function sleep(ms)
run(nil, nil, nil, ms) local function notification_cb(method, _)
if method == "redraw" then
error("Screen is attached; use screen:sleep() instead.")
end
return true
end
run(nil, notification_cb, nil, ms)
end end
local function curbuf_contents() local function curbuf_contents()

View File

@@ -26,7 +26,7 @@ describe(':terminal', function()
feed_command([[terminal while true; do echo X; done]]) feed_command([[terminal while true; do echo X; done]])
helpers.feed([[<C-\><C-N>]]) helpers.feed([[<C-\><C-N>]])
wait() wait()
helpers.sleep(10) -- Let some terminal activity happen. screen:sleep(10) -- Let some terminal activity happen.
feed_command("messages") feed_command("messages")
screen:expect([[ screen:expect([[
msg1 | msg1 |

View File

@@ -18,138 +18,155 @@ describe('ui/cursor', function()
end) end)
it("'guicursor' is published as a UI event", function() it("'guicursor' is published as a UI event", function()
local expected_cursor_style = { local expected_mode_info = {
cmdline_hover = { [1] = {
mouse_shape = 0,
short_name = 'e' },
cmdline_insert = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 25,
cursor_shape = 'vertical',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'ci' },
cmdline_normal = {
blinkoff = 250, blinkoff = 250,
blinkon = 400, blinkon = 400,
blinkwait = 700, blinkwait = 700,
cell_percentage = 0, cell_percentage = 0,
cursor_shape = 'block', cursor_shape = 'block',
name = 'normal',
hl_id = 46, hl_id = 46,
id_lm = 47, id_lm = 47,
mouse_shape = 0, mouse_shape = 0,
short_name = 'c' }, short_name = 'n' },
cmdline_replace = { [2] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 0,
cursor_shape = 'block',
name = 'visual',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'v' },
[3] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 25,
cursor_shape = 'vertical',
name = 'insert',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'i' },
[4] = {
blinkoff = 250, blinkoff = 250,
blinkon = 400, blinkon = 400,
blinkwait = 700, blinkwait = 700,
cell_percentage = 20, cell_percentage = 20,
cursor_shape = 'horizontal', cursor_shape = 'horizontal',
name = 'replace',
hl_id = 46, hl_id = 46,
id_lm = 47, id_lm = 47,
mouse_shape = 0, mouse_shape = 0,
short_name = 'cr' }, short_name = 'r' },
insert = { [5] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 25,
cursor_shape = 'vertical',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'i' },
more = {
mouse_shape = 0,
short_name = 'm' },
more_lastline = {
mouse_shape = 0,
short_name = 'ml' },
normal = {
blinkoff = 250, blinkoff = 250,
blinkon = 400, blinkon = 400,
blinkwait = 700, blinkwait = 700,
cell_percentage = 0, cell_percentage = 0,
cursor_shape = 'block', cursor_shape = 'block',
name = 'cmdline_normal',
hl_id = 46, hl_id = 46,
id_lm = 47, id_lm = 47,
mouse_shape = 0, mouse_shape = 0,
short_name = 'n' }, short_name = 'c' },
operator = { [6] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 25,
cursor_shape = 'vertical',
name = 'cmdline_insert',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'ci' },
[7] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 20,
cursor_shape = 'horizontal',
name = 'cmdline_replace',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'cr' },
[8] = {
blinkoff = 250, blinkoff = 250,
blinkon = 400, blinkon = 400,
blinkwait = 700, blinkwait = 700,
cell_percentage = 50, cell_percentage = 50,
cursor_shape = 'horizontal', cursor_shape = 'horizontal',
name = 'operator',
hl_id = 46, hl_id = 46,
id_lm = 46, id_lm = 46,
mouse_shape = 0, mouse_shape = 0,
short_name = 'o' }, short_name = 'o' },
replace = { [9] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 20,
cursor_shape = 'horizontal',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'r' },
showmatch = {
blinkoff = 150,
blinkon = 175,
blinkwait = 175,
cell_percentage = 0,
cursor_shape = 'block',
hl_id = 46,
id_lm = 46,
short_name = 'sm' },
statusline_drag = {
mouse_shape = 0,
short_name = 'sd' },
statusline_hover = {
mouse_shape = 0,
short_name = 's' },
visual = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 0,
cursor_shape = 'block',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'v' },
visual_select = {
blinkoff = 250, blinkoff = 250,
blinkon = 400, blinkon = 400,
blinkwait = 700, blinkwait = 700,
cell_percentage = 35, cell_percentage = 35,
cursor_shape = 'vertical', cursor_shape = 'vertical',
name = 'visual_select',
hl_id = 46, hl_id = 46,
id_lm = 46, id_lm = 46,
mouse_shape = 0, mouse_shape = 0,
short_name = 've' }, short_name = 've' },
vsep_drag = { [10] = {
name = 'cmdline_hover',
mouse_shape = 0,
short_name = 'e' },
[11] = {
name = 'statusline_hover',
mouse_shape = 0,
short_name = 's' },
[12] = {
name = 'statusline_drag',
mouse_shape = 0,
short_name = 'sd' },
[13] = {
name = 'vsep_hover',
mouse_shape = 0,
short_name = 'vs' },
[14] = {
name = 'vsep_drag',
mouse_shape = 0, mouse_shape = 0,
short_name = 'vd' }, short_name = 'vd' },
vsep_hover = { [15] = {
name = 'more',
mouse_shape = 0, mouse_shape = 0,
short_name = 'vs' } short_name = 'm' },
[16] = {
name = 'more_lastline',
mouse_shape = 0,
short_name = 'ml' },
[17] = {
blinkoff = 150,
blinkon = 175,
blinkwait = 175,
cell_percentage = 0,
cursor_shape = 'block',
name = 'showmatch',
hl_id = 46,
id_lm = 46,
short_name = 'sm' },
} }
screen:expect(function() screen:expect(function()
-- Default 'guicursor' published on startup. -- Default 'guicursor' published on startup.
eq(expected_cursor_style, screen._cursor_style) eq(expected_mode_info, screen._mode_info)
eq(true, screen._cursor_style_enabled) eq(true, screen._cursor_style_enabled)
eq('normal', screen.mode) eq('normal', screen.mode)
end) end)
-- Event is published ONLY if the cursor style changed. -- Event is published ONLY if the cursor style changed.
screen._cursor_style = nil screen._mode_info = nil
command("echo 'test'") command("echo 'test'")
screen:expect([[ screen:expect([[
^ | ^ |
@@ -158,20 +175,24 @@ describe('ui/cursor', function()
~ | ~ |
test | test |
]], nil, nil, function() ]], nil, nil, function()
eq(nil, screen._cursor_style) eq(nil, screen._mode_info)
end) end)
-- Change the cursor style. -- Change the cursor style.
meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173,ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42') meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173,ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42')
screen:expect(function() screen:expect(function()
eq('vertical', screen._cursor_style.normal.cursor_shape) local named = {}
eq('horizontal', screen._cursor_style.visual_select.cursor_shape) for _, m in ipairs(screen._mode_info) do
eq('vertical', screen._cursor_style.operator.cursor_shape) named[m.name] = m
eq('block', screen._cursor_style.insert.cursor_shape) end
eq('vertical', screen._cursor_style.showmatch.cursor_shape) eq('vertical', named.normal.cursor_shape)
eq(171, screen._cursor_style.normal.blinkwait) eq('horizontal', named.visual_select.cursor_shape)
eq(172, screen._cursor_style.normal.blinkoff) eq('vertical', named.operator.cursor_shape)
eq(173, screen._cursor_style.normal.blinkon) eq('block', named.insert.cursor_shape)
eq('vertical', named.showmatch.cursor_shape)
eq(171, named.normal.blinkwait)
eq(172, named.normal.blinkoff)
eq(173, named.normal.blinkon)
end) end)
end) end)
@@ -180,11 +201,11 @@ describe('ui/cursor', function()
screen:expect(function() screen:expect(function()
-- Empty 'guicursor' sets enabled=false. -- Empty 'guicursor' sets enabled=false.
eq(false, screen._cursor_style_enabled) eq(false, screen._cursor_style_enabled)
for _, m in ipairs({ 'cmdline_insert', 'cmdline_normal', 'cmdline_replace', 'insert', for _, m in ipairs(screen._mode_info) do
'showmatch', 'normal', 'replace', 'visual', if m['cursor_shape'] ~= nil then
'visual_select', }) do eq('block', m.cursor_shape)
eq('block', screen._cursor_style[m].cursor_shape) eq(0, m.blinkon)
eq(0, screen._cursor_style[m].blinkon) end
end end
end) end)
end) end)

View File

@@ -0,0 +1,227 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local command, eval = helpers.command, helpers.eval
local eq = helpers.eq
describe('ui mode_change event', function()
local screen
before_each(function()
clear()
screen = Screen.new(25, 4)
screen:attach({rgb= true})
screen:set_default_attr_ids( {
[0] = {bold=true, foreground=255},
[1] = {bold=true, reverse=true},
[2] = {bold=true},
[3] = {reverse=true},
})
end)
it('works in normal mode', function()
screen:expect([[
^ |
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("normal", screen.mode)
end)
feed('d')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("operator", screen.mode)
end)
feed('<esc>')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("normal", screen.mode)
end)
end)
it('works in insert mode', function()
feed('i')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
{2:-- INSERT --} |
]],nil,nil,function ()
eq("insert", screen.mode)
end)
feed('word<esc>')
screen:expect([[
wor^d |
{0:~ }|
{0:~ }|
|
]], nil, nil, function ()
eq("normal", screen.mode)
end)
command("set showmatch")
eq(eval('&matchtime'), 5) -- tenths of seconds
feed('a(stuff')
screen:expect([[
word(stuff^ |
{0:~ }|
{0:~ }|
{2:-- INSERT --} |
]], nil, nil, function ()
eq("insert", screen.mode)
end)
feed(')')
screen:expect([[
word^(stuff) |
{0:~ }|
{0:~ }|
{2:-- INSERT --} |
]], nil, nil, function ()
eq("showmatch", screen.mode)
end)
screen:sleep(400)
screen:expect([[
word(stuff)^ |
{0:~ }|
{0:~ }|
{2:-- INSERT --} |
]], nil, nil, function ()
eq("insert", screen.mode)
end)
end)
it('works in replace mode', function()
feed('R')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
{2:-- REPLACE --} |
]], nil, nil, function ()
eq("replace", screen.mode)
end)
feed('word<esc>')
screen:expect([[
wor^d |
{0:~ }|
{0:~ }|
|
]], nil, nil, function ()
eq("normal", screen.mode)
end)
end)
it('works in cmdline mode', function()
feed(':')
screen:expect([[
|
{0:~ }|
{0:~ }|
:^ |
]],nil,nil,function ()
eq("cmdline_normal", screen.mode)
end)
feed('x<left>')
screen:expect([[
|
{0:~ }|
{0:~ }|
:^x |
]],nil,nil,function ()
eq("cmdline_insert", screen.mode)
end)
feed('<insert>')
screen:expect([[
|
{0:~ }|
{0:~ }|
:^x |
]],nil,nil,function ()
eq("cmdline_replace", screen.mode)
end)
feed('<right>')
screen:expect([[
|
{0:~ }|
{0:~ }|
:x^ |
]],nil,nil,function ()
eq("cmdline_normal", screen.mode)
end)
feed('<esc>')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("normal", screen.mode)
end)
end)
it('works in visal mode', function()
insert("text")
feed('v')
screen:expect([[
tex^t |
{0:~ }|
{0:~ }|
{2:-- VISUAL --} |
]],nil,nil,function ()
eq("visual", screen.mode)
end)
feed('<esc>')
screen:expect([[
tex^t |
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("normal", screen.mode)
end)
command('set selection=exclusive')
feed('v')
screen:expect([[
tex^t |
{0:~ }|
{0:~ }|
{2:-- VISUAL --} |
]],nil,nil,function ()
eq("visual_select", screen.mode)
end)
feed('<esc>')
screen:expect([[
tex^t |
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("normal", screen.mode)
end)
end)
end)

View File

@@ -348,9 +348,9 @@ function Screen:_handle_resize(width, height)
} }
end end
function Screen:_handle_cursor_style_set(enabled, style) function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
self._cursor_style_enabled = enabled self._cursor_style_enabled = cursor_style_enabled
self._cursor_style = style self._mode_info = mode_info
end end
function Screen:_handle_clear() function Screen:_handle_clear()
@@ -384,9 +384,8 @@ function Screen:_handle_mouse_off()
self._mouse_enabled = false self._mouse_enabled = false
end end
function Screen:_handle_mode_change(mode) function Screen:_handle_mode_change(mode, idx)
assert(mode == 'insert' or mode == 'replace' assert(mode == self._mode_info[idx+1].name)
or mode == 'normal' or mode == 'cmdline')
self.mode = mode self.mode = mode
end end

View File

@@ -566,119 +566,6 @@ describe('Screen', function()
end) end)
end) end)
describe('mode change', function()
before_each(function()
screen:try_resize(25, 5)
end)
it('works in normal mode', function()
screen:expect([[
^ |
{0:~ }|
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("normal", screen.mode)
end)
end)
it('works in insert mode', function()
feed('i')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
{0:~ }|
{2:-- INSERT --} |
]],nil,nil,function ()
eq("insert", screen.mode)
end)
feed('word<esc>')
screen:expect([[
wor^d |
{0:~ }|
{0:~ }|
{0:~ }|
|
]], nil, nil, function ()
eq("normal", screen.mode)
end)
end)
it('works in replace mode', function()
feed('R')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
{0:~ }|
{2:-- REPLACE --} |
]], nil, nil, function ()
eq("replace", screen.mode)
end)
feed('word<esc>')
screen:expect([[
wor^d |
{0:~ }|
{0:~ }|
{0:~ }|
|
]], nil, nil, function ()
eq("normal", screen.mode)
end)
end)
it('works in cmdline mode', function()
feed(':')
screen:expect([[
|
{0:~ }|
{0:~ }|
{0:~ }|
:^ |
]],nil,nil,function ()
eq("cmdline", screen.mode)
end)
feed('<esc>/')
screen:expect([[
|
{0:~ }|
{0:~ }|
{0:~ }|
/^ |
]],nil,nil,function ()
eq("cmdline", screen.mode)
end)
feed('<esc>?')
screen:expect([[
|
{0:~ }|
{0:~ }|
{0:~ }|
?^ |
]],nil,nil,function ()
eq("cmdline", screen.mode)
end)
feed('<esc>')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
{0:~ }|
|
]],nil,nil,function ()
eq("normal", screen.mode)
end)
end)
end)
it('nvim_ui_attach() handles very large width/height #2180', function() it('nvim_ui_attach() handles very large width/height #2180', function()
screen:detach() screen:detach()
screen = Screen.new(999, 999) screen = Screen.new(999, 999)