Merge #6539 'More cursor shape modes'

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

View File

@ -270,13 +270,17 @@ a dictionary with these (optional) keys:
Defaults to false.
Nvim will then send msgpack-rpc notifications, with the method name "redraw"
and a single argument, an array of screen updates (described below).
These should be processed in order. Preferably the user should only be able to
see the screen state after all updates are processed (not any intermediate
state after processing only a part of the array).
and a single argument, an array of screen updates (described below). These
should be processed in order. Preferably the user should only be able to see
the screen state after all updates in the same "redraw" event are processed
(not any intermediate state after processing only a part of the array).
Screen updates are arrays. The first element a string describing the kind
of update.
Future versions of Nvim may add new update kinds and may append new parameters
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]
The grid is resized to `width` and `height` cells.
@ -387,10 +391,31 @@ of update.
["update_menu"]
The menu mappings changed.
["mode_change", mode]
The mode changed. Currently sent when "insert", "replace", "cmdline" and
"normal" modes are entered. A client could for instance change the cursor
shape.
["mode_info_set", cursor_style_enabled, mode_info]
`cursor_style_enabled` is a boolean indicating if the UI should set the cursor
style. `mode_info` is a list of mode property maps. The current mode is given
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]
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->eol_clear = remote_ui_eol_clear;
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->busy_start = remote_ui_busy_start;
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);
}
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;
if (mode == INSERT) {
ADD(args, STRING_OBJ(cstr_to_string("insert")));
} else if (mode == REPLACE) {
ADD(args, STRING_OBJ(cstr_to_string("replace")));
} else if (mode == CMDLINE) {
ADD(args, STRING_OBJ(cstr_to_string("cmdline")));
} else {
assert(mode == NORMAL);
ADD(args, STRING_OBJ(cstr_to_string("normal")));
}
char *full_name = shape_table[mode_idx].full_name;
ADD(args, STRING_OBJ(cstr_to_string(full_name)));
ADD(args, INTEGER_OBJ(mode_idx));
push_call(ui, "mode_change", args);
}
@ -303,12 +298,12 @@ static void remote_ui_scroll(UI *ui, int count)
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;
ADD(args, BOOLEAN_OBJ(enabled));
ADD(args, copy_object(DICTIONARY_OBJ(data)));
push_call(ui, "cursor_style_set", args);
ADD(args, BOOLEAN_OBJ(guicursor_enabled));
ADD(args, copy_object(ARRAY_OBJ(data)));
push_call(ui, "mode_info_set", args);
}
static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
@ -396,8 +391,10 @@ static void remote_ui_update_sp(UI *ui, int sp)
static void remote_ui_flush(UI *ui)
{
UIData *data = ui->data;
channel_send_event(data->channel_id, "redraw", data->buffer);
data->buffer = (Array)ARRAY_DICT_INIT;
if (data->buffer.size > 0) {
channel_send_event(data->channel_id, "redraw", data->buffer);
data->buffer = (Array)ARRAY_DICT_INIT;
}
}
static void remote_ui_suspend(UI *ui)

View File

@ -14,7 +14,7 @@
#include "nvim/ui.h"
/// 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'.
// 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 },
};
/// Converts cursor_shapes into a Dictionary of dictionaries
/// @return dictionary of the form {"normal" : { "cursor_shape": ... }, ...}
Dictionary cursor_shape_dict(void)
/// Converts cursor_shapes into an Array of Dictionaries
/// @return Array of the form {[ "cursor_shape": ... ], ...}
Array mode_style_array(void)
{
Dictionary all = ARRAY_DICT_INIT;
Array all = ARRAY_DICT_INIT;
for (int i = 0; i < SHAPE_IDX_COUNT; i++) {
Dictionary dic = ARRAY_DICT_INIT;
@ -65,9 +65,10 @@ Dictionary cursor_shape_dict(void)
PUT(dic, "hl_id", INTEGER_OBJ(cur->id));
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(all, cur->full_name, DICTIONARY_OBJ(dic));
ADD(all, DICTIONARY_OBJ(dic));
}
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;
}
}
ui_cursor_style_set();
ui_mode_info_set();
return NULL;
}
@ -263,3 +264,35 @@ int cursor_mode_str2int(const char *mode)
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_SM = 16, ///< showing matching paren
SHAPE_IDX_COUNT = 17
} MouseMode;
} ModeShape;
typedef enum {
SHAPE_BLOCK = 0, ///< block cursor
@ -53,6 +53,7 @@ typedef struct cursor_entry {
char used_for; ///< SHAPE_MOUSE and/or SHAPE_CURSOR
} cursorentry_T;
extern cursorentry_T shape_table[SHAPE_IDX_COUNT];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# 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
cursorcmd(); // set the cursor on the right spot
ui_cursor_shape();
return 1;
}
@ -2095,6 +2096,18 @@ redraw:
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.
* 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);
msg_no_more = FALSE;
cursorcmd();
ui_cursor_shape();
}
/*
@ -2284,6 +2298,7 @@ void unputcmdline(void)
draw_cmdline(ccline.cmdpos, 1);
msg_no_more = FALSE;
cursorcmd();
ui_cursor_shape();
}
/*
@ -2601,6 +2616,7 @@ void redrawcmdline(void)
compute_cmdrow();
redrawcmd();
cursorcmd();
ui_cursor_shape();
}
static void redrawcmdprompt(void)

View File

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

View File

@ -76,7 +76,7 @@ typedef struct {
bool busy;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs print_attrs;
int showing_mode;
ModeShape showing_mode;
struct {
int enable_mouse, disable_mouse;
int enable_bracketed_paste, disable_bracketed_paste;
@ -104,7 +104,7 @@ UI *tui_start(void)
ui->clear = tui_clear;
ui->eol_clear = tui_eol_clear;
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->busy_start = tui_busy_start;
ui->busy_stop = tui_busy_stop;
@ -134,7 +134,7 @@ static void terminfo_start(UI *ui)
data->can_use_terminal_scroll = true;
data->bufpos = 0;
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.disable_mouse = -1;
data->unibi_ext.set_cursor_color = -1;
@ -176,7 +176,7 @@ static void terminfo_stop(UI *ui)
{
TUIData *data = ui->data;
// Destroy output stuff
tui_mode_change(ui, NORMAL);
tui_mode_change(ui, SHAPE_IDX_N);
tui_mouse_off(ui);
unibi_out(ui, unibi_exit_attribute_mode);
// cursor should be set to normal before exiting alternate screen
@ -475,27 +475,24 @@ static cursorentry_T decode_cursor_entry(Dictionary args)
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;
if (!enabled) {
cursor_style_enabled = guicursor_enabled;
if (!guicursor_enabled) {
return; // Do not send cursor style control codes.
}
TUIData *data = ui->data;
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++) {
char *mode_name = args.items[i].key.data;
const int mode_id = cursor_mode_str2int(mode_name);
assert(mode_id >= 0);
cursorentry_T r = decode_cursor_entry(args.items[i].value.data.dictionary);
r.full_name = mode_name;
data->cursor_shapes[mode_id] = r;
assert(args.items[i].type == kObjectTypeDictionary);
cursorentry_T r = decode_cursor_entry(args.items[i].data.dictionary);
data->cursor_shapes[i] = r;
}
MouseMode cursor_mode = tui_mode2cursor(data->showing_mode);
tui_set_cursor(ui, cursor_mode);
tui_set_mode(ui, data->showing_mode);
}
static void tui_update_menu(UI *ui)
@ -532,7 +529,7 @@ static void tui_mouse_off(UI *ui)
}
/// @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) {
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
static void tui_mode_change(UI *ui, int mode)
static void tui_mode_change(UI *ui, int mode_idx)
{
TUIData *data = ui->data;
if (mode == INSERT) {
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;
tui_set_mode(ui, (ModeShape)mode_idx);
data->showing_mode = (ModeShape)mode_idx;
}
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 int busy = 0;
static int height, width;
static int old_mode_idx = -1;
// UI_CALL invokes a function on all registered UI instances. The functions can
// 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)
{
if (!ui_active()) {
@ -183,7 +178,9 @@ void ui_refresh(void)
row = col = 0;
screen_resize(width, height);
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)
@ -381,12 +378,12 @@ void ui_cursor_goto(int new_row, int new_col)
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);
UI_CALL(cursor_style_set, enabled, style);
api_free_dictionary(style);
UI_CALL(mode_info_set, enabled, style);
api_free_array(style);
}
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
// shape, for example.
static void ui_mode_change(void)
/// Check if current mode has changed.
/// May update the shape of the cursor.
void ui_cursor_shape(void)
{
int mode;
if (!full_screen) {
return;
}
// Get a simple UI mode out of State.
if ((State & REPLACE) == REPLACE) {
mode = REPLACE;
} else if (State & INSERT) {
mode = INSERT;
} else if (State & CMDLINE) {
mode = CMDLINE;
} else {
mode = NORMAL;
int mode_idx = cursor_get_mode_idx();
if (old_mode_idx != mode_idx) {
old_mode_idx = mode_idx;
UI_CALL(mode_change, mode_idx);
}
UI_CALL(mode_change, mode);
conceal_check_cursur_line();
}

View File

@ -22,13 +22,13 @@ struct ui_t {
void (*clear)(UI *ui);
void (*eol_clear)(UI *ui);
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 (*busy_start)(UI *ui);
void (*busy_stop)(UI *ui);
void (*mouse_on)(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 (*scroll)(UI *ui, int count);
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.eol_clear = ui_bridge_eol_clear;
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.busy_start = ui_bridge_busy_start;
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]));
}
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));
Object *stylesp = xmalloc(sizeof(*stylesp));
Object *modesp = xmalloc(sizeof(*modesp));
*enabledp = enabled;
*stylesp = copy_object(DICTIONARY_OBJ(styles));
UI_CALL(b, cursor_style_set, 3, b, enabledp, stylesp);
*modesp = copy_object(ARRAY_OBJ(modes));
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]);
bool *enabled = argv[1];
Object *styles = argv[2];
ui->cursor_style_set(ui, *enabled, styles->data.dictionary);
Object *modes = argv[2];
ui->mode_info_set(ui, *enabled, modes->data.array);
xfree(enabled);
api_free_object(*styles);
xfree(styles);
api_free_object(*modes);
xfree(modes);
}
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);
}
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)
{

View File

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

View File

@ -392,7 +392,14 @@ end
-- sleeps the test runner (_not_ the nvim instance)
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
local function curbuf_contents()

View File

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

View File

@ -18,138 +18,155 @@ describe('ui/cursor', function()
end)
it("'guicursor' is published as a UI event", function()
local expected_cursor_style = {
cmdline_hover = {
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 = {
local expected_mode_info = {
[1] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 0,
cursor_shape = 'block',
name = 'normal',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'c' },
cmdline_replace = {
short_name = 'n' },
[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,
blinkon = 400,
blinkwait = 700,
cell_percentage = 20,
cursor_shape = 'horizontal',
name = 'replace',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'cr' },
insert = {
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 = {
short_name = 'r' },
[5] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 0,
cursor_shape = 'block',
name = 'cmdline_normal',
hl_id = 46,
id_lm = 47,
mouse_shape = 0,
short_name = 'n' },
operator = {
short_name = 'c' },
[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,
blinkon = 400,
blinkwait = 700,
cell_percentage = 50,
cursor_shape = 'horizontal',
name = 'operator',
hl_id = 46,
id_lm = 46,
mouse_shape = 0,
short_name = 'o' },
replace = {
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 = {
[9] = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 35,
cursor_shape = 'vertical',
name = 'visual_select',
hl_id = 46,
id_lm = 46,
mouse_shape = 0,
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,
short_name = 'vd' },
vsep_hover = {
[15] = {
name = 'more',
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()
-- 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('normal', screen.mode)
end)
-- Event is published ONLY if the cursor style changed.
screen._cursor_style = nil
screen._mode_info = nil
command("echo 'test'")
screen:expect([[
^ |
@ -158,20 +175,24 @@ describe('ui/cursor', function()
~ |
test |
]], nil, nil, function()
eq(nil, screen._cursor_style)
eq(nil, screen._mode_info)
end)
-- 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')
screen:expect(function()
eq('vertical', screen._cursor_style.normal.cursor_shape)
eq('horizontal', screen._cursor_style.visual_select.cursor_shape)
eq('vertical', screen._cursor_style.operator.cursor_shape)
eq('block', screen._cursor_style.insert.cursor_shape)
eq('vertical', screen._cursor_style.showmatch.cursor_shape)
eq(171, screen._cursor_style.normal.blinkwait)
eq(172, screen._cursor_style.normal.blinkoff)
eq(173, screen._cursor_style.normal.blinkon)
local named = {}
for _, m in ipairs(screen._mode_info) do
named[m.name] = m
end
eq('vertical', named.normal.cursor_shape)
eq('horizontal', named.visual_select.cursor_shape)
eq('vertical', named.operator.cursor_shape)
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)
@ -180,11 +201,11 @@ describe('ui/cursor', function()
screen:expect(function()
-- Empty 'guicursor' sets enabled=false.
eq(false, screen._cursor_style_enabled)
for _, m in ipairs({ 'cmdline_insert', 'cmdline_normal', 'cmdline_replace', 'insert',
'showmatch', 'normal', 'replace', 'visual',
'visual_select', }) do
eq('block', screen._cursor_style[m].cursor_shape)
eq(0, screen._cursor_style[m].blinkon)
for _, m in ipairs(screen._mode_info) do
if m['cursor_shape'] ~= nil then
eq('block', m.cursor_shape)
eq(0, m.blinkon)
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
function Screen:_handle_cursor_style_set(enabled, style)
self._cursor_style_enabled = enabled
self._cursor_style = style
function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
self._cursor_style_enabled = cursor_style_enabled
self._mode_info = mode_info
end
function Screen:_handle_clear()
@ -384,9 +384,8 @@ function Screen:_handle_mouse_off()
self._mouse_enabled = false
end
function Screen:_handle_mode_change(mode)
assert(mode == 'insert' or mode == 'replace'
or mode == 'normal' or mode == 'cmdline')
function Screen:_handle_mode_change(mode, idx)
assert(mode == self._mode_info[idx+1].name)
self.mode = mode
end

View File

@ -566,119 +566,6 @@ describe('Screen', function()
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()
screen:detach()
screen = Screen.new(999, 999)