Merge pull request #39429 from ollien/mouse-n-visual

fix(mouse): mouse=n should not adjust visual selection
This commit is contained in:
bfredl
2026-05-19 12:36:26 +02:00
committed by GitHub
3 changed files with 195 additions and 19 deletions

View File

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

View File

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

View File

@@ -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('<LeftMouse><0,0>')
feed('<LeftRelease><0,0>')
feed('<LeftMouse><0,0>')
feed('<LeftRelease><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('<LeftMouse><0,0>')
feed('<LeftRelease><0,0>')
feed('<LeftMouse><0,0>')
feed('<LeftRelease><0,0>')
feed('<LeftMouse><0,0>')
feed('<LeftRelease><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('<LeftMouse><0,0>')
feed('<LeftRelease><0,0>')
feed('<LeftMouse><0,0>')
feed('<LeftRelease><0,0>')
feed('<LeftMouse><0,0>')
feed('<LeftRelease><0,0>')
feed('<LeftMouse><0,0>')
feed('<LeftRelease><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('<LeftMouse><2,1>')
screen:expect({
any = {
'testing',
'mo%^use',
'support and selection',
},
mode = 'normal',
})
feed('<LeftDrag><4,1>')
screen:expect({
any = {
'testing',
'mous%^e',
'support and selection',
},
mode = 'normal',
})
feed('<LeftDrag><2,2>')
screen:expect({
any = {
'testing',
'mouse',
'su%^pport and selection',
},
mode = 'normal',
})
feed('<LeftDrag><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('<LeftMouse><2,1>')
screen:expect({
any = {
'{17:testing}',
'{17:m}%^ouse',
'support and selection',
'VISUAL',
},
unchanged = true,
})
feed('<LeftDrag><4,1>')
screen:expect({
any = {
'{17:testing}',
'{17:m}%^ouse',
'support and selection',
'VISUAL',
},
unchanged = true,
})
feed('<LeftDrag><2,2>')
screen:expect({
any = {
'{17:testing}',
'{17:m}%^ouse',
'support and selection',
'VISUAL',
},
unchanged = true,
})
feed('<LeftRelease>')
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')