aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/api.txt4
-rw-r--r--runtime/doc/options.txt9
-rw-r--r--runtime/doc/ui.txt1
-rw-r--r--src/nvim/api/vim.c8
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/options.lua8
-rw-r--r--src/nvim/os/input.c6
-rw-r--r--src/nvim/popupmenu.c7
-rw-r--r--src/nvim/tui/input.c7
-rw-r--r--src/nvim/tui/tui.c27
-rw-r--r--test/functional/terminal/tui_spec.lua52
-rw-r--r--test/functional/ui/mouse_spec.lua15
-rw-r--r--test/functional/ui/options_spec.lua7
-rw-r--r--test/functional/ui/popupmenu_spec.lua111
14 files changed, 245 insertions, 18 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 82978d0b3c..063e17ded9 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1067,9 +1067,11 @@ nvim_input_mouse({button}, {action}, {modifier}, {grid}, {row}, {col})
|api-fast|
Parameters: ~
- {button} Mouse button: one of "left", "right", "middle", "wheel".
+ {button} Mouse button: one of "left", "right", "middle", "wheel",
+ "move".
{action} For ordinary buttons, one of "press", "drag", "release".
For the wheel, one of "up", "down", "left", "right".
+ Ignored for "move".
{modifier} String of modifiers each represented by a single char. The
same specifiers are used as for a key press, except that
the "-" separator is optional, so "C-A-", "c-a" and "CA"
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 9e396dd3e8..316b7ae3f1 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4231,6 +4231,15 @@ A jump table for the options with a short description can be found at |Q_op|.
The 'mousemodel' option is set by the |:behave| command.
+ *'mousemoveevent'* *'mousemev'*
+'mousemoveevent' 'mousemev' boolean (default off)
+ global
+ When on, mouse move events are delivered to the input queue and are
+ available for mapping. The default, off, avoids the mouse movement
+ overhead except when needed.
+ Warning: Setting this option can make pending mappings to be aborted
+ when the mouse is moved.
+
*'mousescroll'*
'mousescroll' string (default "ver:3,hor:6")
global
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 3fb9ed1125..955af84679 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -207,6 +207,7 @@ the editor.
'guifontwide'
'linespace'
'mousefocus'
+ 'mousemoveevent'
'pumblend'
'showtabline'
'termguicolors'
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 83d6ba8dc7..95c9919522 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -336,9 +336,9 @@ Integer nvim_input(String keys)
/// mouse input in a GUI. The deprecated pseudokey form
/// ("<LeftMouse><col,row>") of |nvim_input()| has the same limitation.
///
-/// @param button Mouse button: one of "left", "right", "middle", "wheel".
+/// @param button Mouse button: one of "left", "right", "middle", "wheel", "move".
/// @param action For ordinary buttons, one of "press", "drag", "release".
-/// For the wheel, one of "up", "down", "left", "right".
+/// For the wheel, one of "up", "down", "left", "right". Ignored for "move".
/// @param modifier String of modifiers each represented by a single char.
/// The same specifiers are used as for a key press, except
/// that the "-" separator is optional, so "C-A-", "c-a"
@@ -365,6 +365,8 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
code = KE_RIGHTMOUSE;
} else if (strequal(button.data, "wheel")) {
code = KE_MOUSEDOWN;
+ } else if (strequal(button.data, "move")) {
+ code = KE_MOUSEMOVE;
} else {
goto error;
}
@@ -381,7 +383,7 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
} else {
goto error;
}
- } else {
+ } else if (code != KE_MOUSEMOVE) {
if (strequal(action.data, "press")) {
// pass
} else if (strequal(action.data, "drag")) {
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index bfac0cff87..64b3f69df4 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -616,6 +616,7 @@ EXTERN int p_ma; ///< 'modifiable'
EXTERN int p_mod; ///< 'modified'
EXTERN char *p_mouse; // 'mouse'
EXTERN char *p_mousem; // 'mousemodel'
+EXTERN int p_mousemev; ///< 'mousemoveevent'
EXTERN int p_mousef; // 'mousefocus'
EXTERN char *p_mousescroll; // 'mousescroll'
EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT);
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 2f941f5d0c..717ca6747b 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1622,6 +1622,14 @@ return {
defaults={if_true="popup_setpos"}
},
{
+ full_name='mousemoveevent', abbreviation='mousemev',
+ short_desc=N_("deliver mouse move events to input queue"),
+ type='bool', scope={'global'},
+ redraw={'ui_option'},
+ varname='p_mousemev',
+ defaults={if_true=false}
+ },
+ {
full_name='mousescroll',
short_desc=N_("amount to scroll by when scrolling with a mouse"),
type='string', list='comma', scope={'global'},
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index bfe6d59dc6..ea9f31d776 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -293,7 +293,8 @@ static uint8_t check_multiclick(int code, int grid, int row, int col)
|| code == KE_MOUSEDOWN
|| code == KE_MOUSEUP
|| code == KE_MOUSELEFT
- || code == KE_MOUSERIGHT) {
+ || code == KE_MOUSERIGHT
+ || code == KE_MOUSEMOVE) {
return 0;
}
uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns)
@@ -347,7 +348,8 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, unsigned int bu
if (type != KS_EXTRA
|| !((mouse_code >= KE_LEFTMOUSE && mouse_code <= KE_RIGHTRELEASE)
- || (mouse_code >= KE_MOUSEDOWN && mouse_code <= KE_MOUSERIGHT))) {
+ || (mouse_code >= KE_MOUSEDOWN && mouse_code <= KE_MOUSERIGHT)
+ || mouse_code == KE_MOUSEMOVE)) {
return bufsize;
}
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 0d9080ceb7..2265334a45 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -1042,6 +1042,10 @@ void pum_show_popupmenu(vimmenu_T *menu)
pum_scrollbar = 0;
pum_height = pum_size;
pum_position_at_mouse(20);
+ if (!p_mousemev) {
+ // Pretend 'mousemoveevent' is set.
+ ui_call_option_set(STATIC_CSTR_AS_STRING("mousemoveevent"), BOOLEAN_OBJ(true));
+ }
pum_selected = -1;
pum_first = 0;
@@ -1102,6 +1106,9 @@ void pum_show_popupmenu(vimmenu_T *menu)
xfree(array);
pum_undisplay(true);
+ if (!p_mousemev) {
+ ui_call_option_set(STATIC_CSTR_AS_STRING("mousemoveevent"), BOOLEAN_OBJ(false));
+ }
}
void pum_make_popup(const char *path_name, int use_mouse_pos)
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index d269878f46..728520c20c 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -406,8 +406,8 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
}
}
- if (button == 0 || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG
- && ev != TERMKEY_MOUSE_RELEASE)) {
+ if ((button == 0 && ev != TERMKEY_MOUSE_RELEASE)
+ || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG && ev != TERMKEY_MOUSE_RELEASE)) {
return;
}
@@ -453,7 +453,8 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Drag");
break;
case TERMKEY_MOUSE_RELEASE:
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Release");
+ len += (size_t)snprintf(buf + len, sizeof(buf) - len, button ? "Release" : "MouseMove");
+ last_pressed_button = 0;
break;
case TERMKEY_MOUSE_UNKNOWN:
abort();
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 38e8c15762..471a3fb859 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -103,6 +103,7 @@ struct TUIData {
bool immediate_wrap_after_last_column;
bool bce;
bool mouse_enabled;
+ bool mouse_move_enabled;
bool busy, is_invisible, want_invisible;
bool cork, overflow;
bool cursor_color_changed;
@@ -117,6 +118,7 @@ struct TUIData {
ModeShape showing_mode;
struct {
int enable_mouse, disable_mouse;
+ int enable_mouse_move, disable_mouse_move;
int enable_bracketed_paste, disable_bracketed_paste;
int enable_lr_margin, disable_lr_margin;
int enter_strikethrough_mode;
@@ -236,6 +238,8 @@ static void terminfo_start(UI *ui)
data->showing_mode = SHAPE_IDX_N;
data->unibi_ext.enable_mouse = -1;
data->unibi_ext.disable_mouse = -1;
+ data->unibi_ext.enable_mouse_move = -1;
+ data->unibi_ext.disable_mouse_move = -1;
data->unibi_ext.set_cursor_color = -1;
data->unibi_ext.reset_cursor_color = -1;
data->unibi_ext.enable_bracketed_paste = -1;
@@ -1138,6 +1142,9 @@ static void tui_mouse_on(UI *ui)
TUIData *data = ui->data;
if (!data->mouse_enabled) {
unibi_out_ext(ui, data->unibi_ext.enable_mouse);
+ if (data->mouse_move_enabled) {
+ unibi_out_ext(ui, data->unibi_ext.enable_mouse_move);
+ }
data->mouse_enabled = true;
}
}
@@ -1146,6 +1153,9 @@ static void tui_mouse_off(UI *ui)
{
TUIData *data = ui->data;
if (data->mouse_enabled) {
+ if (data->mouse_move_enabled) {
+ unibi_out_ext(ui, data->unibi_ext.disable_mouse_move);
+ }
unibi_out_ext(ui, data->unibi_ext.disable_mouse);
data->mouse_enabled = false;
}
@@ -1457,9 +1467,18 @@ static void tui_screenshot(UI *ui, String path)
static void tui_option_set(UI *ui, String name, Object value)
{
TUIData *data = ui->data;
- if (strequal(name.data, "termguicolors")) {
+ if (strequal(name.data, "mousemoveevent")) {
+ if (data->mouse_move_enabled != value.data.boolean) {
+ if (data->mouse_enabled) {
+ tui_mouse_off(ui);
+ data->mouse_move_enabled = value.data.boolean;
+ tui_mouse_on(ui);
+ } else {
+ data->mouse_move_enabled = value.data.boolean;
+ }
+ }
+ } else if (strequal(name.data, "termguicolors")) {
ui->rgb = value.data.boolean;
-
data->print_attr_id = -1;
invalidate(ui, 0, data->grid.height, 0, data->grid.width);
} else if (strequal(name.data, "ttimeout")) {
@@ -2135,6 +2154,10 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version,
"\x1b[?1002h\x1b[?1006h");
data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse",
"\x1b[?1002l\x1b[?1006l");
+ data->unibi_ext.enable_mouse_move = (int)unibi_add_ext_str(ut, "ext.enable_mouse_move",
+ "\x1b[?1003h");
+ data->unibi_ext.disable_mouse_move = (int)unibi_add_ext_str(ut, "ext.disable_mouse_move",
+ "\x1b[?1003l");
// Extended underline.
// terminfo will have Smulx for this (but no support for colors yet).
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 4f444316c3..3c56ad5f79 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -21,6 +21,7 @@ local nvim_set = helpers.nvim_set
local ok = helpers.ok
local read_file = helpers.read_file
local funcs = helpers.funcs
+local meths = helpers.meths
if helpers.pending_win32(pending) then return end
@@ -666,6 +667,57 @@ describe('TUI', function()
]], attrs)
end)
+ it('mouse events work with right-click menu', function()
+ child_session:request('nvim_command', [[
+ call setline(1, 'popup menu test')
+ set mouse=a mousemodel=popup
+
+ aunmenu PopUp
+ menu PopUp.foo :let g:menustr = 'foo'<CR>
+ menu PopUp.bar :let g:menustr = 'bar'<CR>
+ menu PopUp.baz :let g:menustr = 'baz'<CR>
+ highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse
+ highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold
+ ]])
+ local attrs = screen:get_default_attr_ids()
+ attrs[11] = {underline = true, reverse = true}
+ attrs[12] = {underline = true, reverse = true, bold = true}
+ meths.input_mouse('right', 'press', '', 0, 0, 4)
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }{11: foo }{4: }|
+ {4:~ }{11: bar }{4: }|
+ {4:~ }{11: baz }{4: }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]], attrs)
+ meths.input_mouse('right', 'release', '', 0, 0, 4)
+ screen:expect_unchanged()
+ meths.input_mouse('move', '', '', 0, 3, 6)
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }{11: foo }{4: }|
+ {4:~ }{11: bar }{4: }|
+ {4:~ }{12: baz }{4: }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]], attrs)
+ meths.input_mouse('left', 'press', '', 0, 2, 6)
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] [+] }|
+ :let g:menustr = 'bar' |
+ {3:-- TERMINAL --} |
+ ]], attrs)
+ meths.input_mouse('left', 'release', '', 0, 2, 6)
+ screen:expect_unchanged()
+ end)
+
it('paste: Insert mode', function()
-- "bracketed paste"
feed_data('i""\027i\027[200~')
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index 9896b11218..8c5475ecce 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -1585,7 +1585,20 @@ describe('ui/mouse/input', function()
eq(0, meths.get_var('mouse_up2'))
end)
- it('feeding <MouseMove> does not use uninitialized memory #19480', function()
+ it('<MouseMove> is not translated into multiclicks and can be mapped', function()
+ meths.set_var('mouse_move', 0)
+ meths.set_var('mouse_move2', 0)
+ command('nnoremap <MouseMove> <Cmd>let g:mouse_move += 1<CR>')
+ command('nnoremap <2-MouseMove> <Cmd>let g:mouse_move2 += 1<CR>')
+ feed('<MouseMove><0,0>')
+ feed('<MouseMove><0,0>')
+ meths.input_mouse('move', '', '', 0, 0, 0)
+ meths.input_mouse('move', '', '', 0, 0, 0)
+ eq(4, meths.get_var('mouse_move'))
+ eq(0, meths.get_var('mouse_move2'))
+ end)
+
+ it('feeding <MouseMove> in Normal mode does not use uninitialized memory #19480', function()
feed('<MouseMove>')
helpers.poke_eventloop()
helpers.assert_alive()
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index 8d7c404637..bd0d2104db 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -19,6 +19,7 @@ describe('UI receives option updates', function()
linespace=0,
pumblend=0,
mousefocus=false,
+ mousemoveevent=false,
showtabline=1,
termguicolors=false,
ttimeout=true,
@@ -131,6 +132,12 @@ describe('UI receives option updates', function()
eq(expected, screen.options)
end)
+ command("set mousemoveevent")
+ expected.mousemoveevent = true
+ screen:expect(function()
+ eq(expected, screen.options)
+ end)
+
command("set nottimeout")
expected.ttimeout = false
screen:expect(function()
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index dcd4ad3d9a..3c752875f0 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -2755,7 +2755,7 @@ describe('builtin popupmenu', function()
menu PopUp.bar :let g:menustr = 'bar'<CR>
menu PopUp.baz :let g:menustr = 'baz'<CR>
]])
- meths.input_mouse('right', 'press', '', 0, 0, 4)
+ feed('<RightMouse><4,0>')
screen:expect([[
^popup menu test |
{1:~ }{n: foo }{1: }|
@@ -2792,7 +2792,7 @@ describe('builtin popupmenu', function()
:let g:menustr = 'bar' |
]])
eq('bar', meths.get_var('menustr'))
- meths.input_mouse('right', 'press', '', 0, 1, 20)
+ feed('<RightMouse><20,1>')
screen:expect([[
^popup menu test |
{1:~ }|
@@ -2801,7 +2801,7 @@ describe('builtin popupmenu', function()
{1:~ }{n: baz }{1: }|
:let g:menustr = 'bar' |
]])
- meths.input_mouse('left', 'press', '', 0, 4, 22)
+ feed('<LeftMouse><22,4>')
screen:expect([[
^popup menu test |
{1:~ }|
@@ -2811,7 +2811,7 @@ describe('builtin popupmenu', function()
:let g:menustr = 'baz' |
]])
eq('baz', meths.get_var('menustr'))
- meths.input_mouse('right', 'press', '', 0, 0, 4)
+ feed('<RightMouse><4,0>')
screen:expect([[
^popup menu test |
{1:~ }{n: foo }{1: }|
@@ -2820,7 +2820,7 @@ describe('builtin popupmenu', function()
{1:~ }|
:let g:menustr = 'baz' |
]])
- meths.input_mouse('right', 'drag', '', 0, 3, 6)
+ feed('<RightDrag><6,3>')
screen:expect([[
^popup menu test |
{1:~ }{n: foo }{1: }|
@@ -2829,7 +2829,7 @@ describe('builtin popupmenu', function()
{1:~ }|
:let g:menustr = 'baz' |
]])
- meths.input_mouse('right', 'release', '', 0, 1, 6)
+ feed('<RightRelease><6,1>')
screen:expect([[
^popup menu test |
{1:~ }|
@@ -2839,6 +2839,38 @@ describe('builtin popupmenu', function()
:let g:menustr = 'foo' |
]])
eq('foo', meths.get_var('menustr'))
+ eq(false, screen.options.mousemoveevent)
+ feed('<RightMouse><4,0>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ eq(true, screen.options.mousemoveevent)
+ feed('<MouseMove><6,3>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{s: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ eq(true, screen.options.mousemoveevent)
+ feed('<LeftMouse><6,2>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :let g:menustr = 'bar' |
+ ]])
+ eq(false, screen.options.mousemoveevent)
+ eq('bar', meths.get_var('menustr'))
end)
end)
@@ -3047,5 +3079,72 @@ describe('builtin popupmenu with ui/ext_multigrid', function()
:let g:menustr = 'foo' |
]]})
eq('foo', meths.get_var('menustr'))
+ eq(false, screen.options.mousemoveevent)
+ meths.input_mouse('right', 'press', '', 2, 0, 4)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {n: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}})
+ eq(true, screen.options.mousemoveevent)
+ meths.input_mouse('move', '', '', 2, 3, 6)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {s: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}})
+ eq(true, screen.options.mousemoveevent)
+ meths.input_mouse('left', 'press', '', 2, 2, 6)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'bar' |
+ ]]})
+ eq(false, screen.options.mousemoveevent)
+ eq('bar', meths.get_var('menustr'))
end)
end)