diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index d3f4b09d96..596dd2d4af 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -609,9 +609,10 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) pos_T end_visual = { 0 }; pos_T start_visual = { 0 }; + bool mouse_can_visual = ui_mouse_has(MOUSE_VISUAL); if ((State & (MODE_NORMAL | MODE_INSERT)) && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - if (which_button == MOUSE_LEFT) { + if (which_button == MOUSE_LEFT && mouse_can_visual) { if (is_click) { // stop Visual mode for a left click in a window, but not when on a status line if (VIsual_active) { @@ -620,7 +621,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) } else { jump_flags |= MOUSE_MAY_VIS; } - } else if (which_button == MOUSE_RIGHT) { + } else if (which_button == MOUSE_RIGHT && mouse_can_visual) { if (is_click && VIsual_active) { // Remember the start and end of visual before moving the cursor. if (lt(curwin->w_cursor, VIsual)) { @@ -631,8 +632,10 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) end_visual = curwin->w_cursor; } } - jump_flags |= MOUSE_FOCUS; jump_flags |= MOUSE_MAY_VIS; + jump_flags |= MOUSE_FOCUS; + } else if (which_button == MOUSE_RIGHT) { + jump_flags |= MOUSE_FOCUS; } } @@ -650,7 +653,12 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) // JUMP! int old_active = VIsual_active; pos_T save_cursor = curwin->w_cursor; - jump_flags = jump_to_mouse(jump_flags, oap == NULL ? NULL : &(oap->inclusive), which_button); + + // Even though we gate *_VIS flags above, we want to make sure the cursor doesn't move + // in visual mode unless it is set as a mouse option + if (!VIsual_active || mouse_can_visual) { + jump_flags = jump_to_mouse(jump_flags, oap == NULL ? NULL : &(oap->inclusive), which_button); + } bool moved = (jump_flags & CURSOR_MOVED); bool in_winbar = (jump_flags & MOUSE_WINBAR); @@ -891,7 +899,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) } else if (in_status_line || in_sep_line) { // Do nothing if on status line or vertical separator // Handle double clicks otherwise - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT)) + && mouse_can_visual) { if (is_click || !VIsual_active) { if (VIsual_active) { orig_cursor = VIsual; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index f63cb715b0..3973fa6550 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -630,32 +630,44 @@ void ui_check_mouse(void) checkfor = ' '; // don't use mouse for ":!cmd" } - // mouse should be active if at least one of the following is true: - // - "c" is in 'mouse', or - // - 'a' is in 'mouse' and "c" is in MOUSE_A, or - // - the current buffer is a help file and 'h' is in 'mouse' and we are in a - // normal editing mode (not at hit-return message). + if (ui_mouse_has(checkfor)) { + has_mouse = true; + } +} + +// check if 'mouse' is active for the given mode +// +// mouse should be active if at least one of the following is true: +// - "mode" is in 'mouse', or +// - 'a' is in 'mouse' and "mode" is in MOUSE_A, or +// - the current buffer is a help file and 'h' is in 'mouse' and we are in a +// normal editing mode (not at hit-return message). +bool ui_mouse_has(int mode) +{ for (char *p = p_mouse; *p; p++) { switch (*p) { case 'a': - if (vim_strchr(MOUSE_A, checkfor) != NULL) { - has_mouse = true; - return; + if (vim_strchr(MOUSE_A, mode) != NULL) { + return true; } + break; case MOUSE_HELP: - if (checkfor != MOUSE_RETURN && curbuf->b_help) { - has_mouse = true; - return; + if (mode != MOUSE_RETURN && curbuf->b_help) { + return true; } + break; default: - if (checkfor == *p) { - has_mouse = true; - return; + if (mode == *p) { + return true; } + + break; } } + + return false; } /// Check if current mode has changed. diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 514bc7a179..6c00995f64 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -111,6 +111,63 @@ describe('ui/mouse/input', function() }) end) + it('double left click stays in normal mode if mouse does not contain v', function() + api.nvim_set_option_value('mouse', 'n', {}) + + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + screen:expect({ + any = { + '%^testing', + 'mouse', + 'support and selection', + }, + mode = 'normal', + }) + end) + + it('triple left click stays in normal mode if mouse does not contain v', function() + api.nvim_set_option_value('mouse', 'n', {}) + + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + screen:expect({ + any = { + '%^testing', + 'mouse', + 'support and selection', + }, + mode = 'normal', + }) + end) + + it('quadruple left click stays in normal mode if mouse does not contain v', function() + api.nvim_set_option_value('mouse', 'n', {}) + + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + feed('<0,0>') + screen:expect({ + any = { + '%^testing', + 'mouse', + 'support and selection', + }, + mode = 'normal', + }) + end) + describe('tab drag', function() it('in tabline on filler space moves tab to the end', function() feed_command('%delete') @@ -579,6 +636,104 @@ describe('ui/mouse/input', function() }) end) + it('left drag moves cursor if mouse does not contain v', function() + api.nvim_set_option_value('mouse', 'n', {}) + + -- drag events must be preceded by a click + feed('<2,1>') + screen:expect({ + any = { + 'testing', + 'mo%^use', + 'support and selection', + }, + mode = 'normal', + }) + feed('<4,1>') + screen:expect({ + any = { + 'testing', + 'mous%^e', + 'support and selection', + }, + mode = 'normal', + }) + feed('<2,2>') + screen:expect({ + any = { + 'testing', + 'mouse', + 'su%^pport and selection', + }, + mode = 'normal', + }) + feed('<0,0>') + screen:expect({ + any = { + '%^testing', + 'mouse', + 'support and selection', + }, + mode = 'normal', + }) + end) + + it('left drag does not adjust existing visual selection if mouse does not contain v', function() + api.nvim_set_option_value('mouse', 'n', {}) + + feed('gg^vlj') + screen:expect({ + any = { + '{17:testing}', + '{17:m}%^ouse', + 'support and selection', + 'VISUAL', + }, + }) + + -- drag events must be preceded by a click + feed('<2,1>') + screen:expect({ + any = { + '{17:testing}', + '{17:m}%^ouse', + 'support and selection', + 'VISUAL', + }, + unchanged = true, + }) + feed('<4,1>') + screen:expect({ + any = { + '{17:testing}', + '{17:m}%^ouse', + 'support and selection', + 'VISUAL', + }, + unchanged = true, + }) + feed('<2,2>') + screen:expect({ + any = { + '{17:testing}', + '{17:m}%^ouse', + 'support and selection', + 'VISUAL', + }, + unchanged = true, + }) + feed('') + screen:expect({ + any = { + '{17:testing}', + '{17:m}%^ouse', + 'support and selection', + 'VISUAL', + }, + unchanged = true, + }) + end) + it('left drag changes visual selection after tab click', function() feed_command('silent file foo | tabnew | file bar') insert('this is bar')