aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/normal.c28
-rw-r--r--src/nvim/os/input.c93
-rw-r--r--test/functional/ui/mouse_spec.lua157
-rw-r--r--test/functional/ui/screen.lua6
4 files changed, 264 insertions, 20 deletions
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 1d4b47414c..3b4d4047db 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1856,21 +1856,21 @@ do_mouse (
save_cursor = curwin->w_cursor;
- /*
- * When GUI is active, always recognize mouse events, otherwise:
- * - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
- * - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
- * - For command line and insert mode 'mouse' is checked before calling
- * do_mouse().
- */
- if (do_always)
- do_always = false;
- else {
- if (VIsual_active) {
- if (!mouse_has(MOUSE_VISUAL))
+ // When "abstract_ui" is active, always recognize mouse events, otherwise:
+ // - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
+ // - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
+ // - For command line and insert mode 'mouse' is checked before calling
+ // do_mouse().
+ if (!abstract_ui) {
+ if (do_always)
+ do_always = false;
+ else {
+ if (VIsual_active) {
+ if (!mouse_has(MOUSE_VISUAL))
+ return false;
+ } else if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
return false;
- } else if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
- return false;
+ }
}
for (;; ) {
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 246ebf123c..cddc28fac9 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -183,17 +183,19 @@ size_t input_enqueue(String keys)
char *ptr = keys.data, *end = ptr + keys.size;
while (rbuffer_available(input_buffer) >= 6 && ptr < end) {
- int new_size = trans_special((char_u **)&ptr,
- (char_u *)rbuffer_write_ptr(input_buffer),
- false);
+ uint8_t buf[6] = {0};
+ int new_size = trans_special((uint8_t **)&ptr, buf, false);
+
if (!new_size) {
// copy the character unmodified
- *rbuffer_write_ptr(input_buffer) = *ptr++;
+ *buf = (uint8_t)*ptr++;
new_size = 1;
}
+
+ new_size = handle_mouse_event(&ptr, buf, new_size);
// TODO(tarruda): Don't produce past unclosed '<' characters, except if
// there's a lot of characters after the '<'
- rbuffer_produced(input_buffer, (size_t)new_size);
+ rbuffer_write(input_buffer, (char *)buf, (size_t)new_size);
}
size_t rv = (size_t)(ptr - keys.data);
@@ -201,6 +203,87 @@ size_t input_enqueue(String keys)
return rv;
}
+// Mouse event handling code(Extract row/col if available and detect multiple
+// clicks)
+static int handle_mouse_event(char **ptr, uint8_t *buf, int bufsize)
+{
+ int mouse_code = 0;
+
+ if (bufsize == 3) {
+ mouse_code = buf[2];
+ } else if (bufsize == 6) {
+ // prefixed with K_SPECIAL KS_MODIFIER mod
+ mouse_code = buf[5];
+ }
+
+ if (mouse_code < KE_LEFTMOUSE || mouse_code > KE_RIGHTRELEASE) {
+ return bufsize;
+ }
+
+ // a <[COL],[ROW]> sequence can follow and will set the mouse_row/mouse_col
+ // global variables. This is ugly but its how the rest of the code expects to
+ // find mouse coordinates, and it would be too expensive to refactor this
+ // now.
+ int col, row, advance;
+ if (sscanf(*ptr, "<%d,%d>%n", &col, &row, &advance)) {
+ if (col >= 0 && row >= 0) {
+ mouse_row = row;
+ mouse_col = col;
+ }
+ *ptr += advance;
+ }
+
+ static int orig_num_clicks = 0;
+ static int orig_mouse_code = 0;
+ static int orig_mouse_col = 0;
+ static int orig_mouse_row = 0;
+ static uint64_t orig_mouse_time = 0; // time of previous mouse click
+ uint64_t mouse_time = os_hrtime(); // time of current mouse click
+
+ // compute the time elapsed since the previous mouse click and
+ // convert p_mouse from ms to ns
+ uint64_t timediff = mouse_time - orig_mouse_time;
+ uint64_t mouset = (uint64_t)p_mouset * 1000000;
+ if (mouse_code == orig_mouse_code
+ && timediff < mouset
+ && orig_num_clicks != 4
+ && orig_mouse_col == mouse_col
+ && orig_mouse_row == mouse_row) {
+ orig_num_clicks++;
+ } else {
+ orig_num_clicks = 1;
+ }
+ orig_mouse_code = mouse_code;
+ orig_mouse_col = mouse_col;
+ orig_mouse_row = mouse_row;
+ orig_mouse_time = mouse_time;
+
+ int modifiers = 0;
+ if (orig_num_clicks == 2) {
+ modifiers |= MOD_MASK_2CLICK;
+ } else if (orig_num_clicks == 3) {
+ modifiers |= MOD_MASK_3CLICK;
+ } else if (orig_num_clicks == 4) {
+ modifiers |= MOD_MASK_4CLICK;
+ }
+
+ if (modifiers) {
+ if (buf[1] != KS_MODIFIER) {
+ // no modifiers in the buffer yet, shift the bytes 3 positions
+ memcpy(buf + 3, buf, 3);
+ // add the modifier sequence
+ buf[0] = K_SPECIAL;
+ buf[1] = KS_MODIFIER;
+ buf[2] = (uint8_t)modifiers;
+ bufsize += 3;
+ } else {
+ buf[2] |= (uint8_t)modifiers;
+ }
+ }
+
+ return bufsize;
+}
+
static bool input_poll(int ms)
{
if (do_profiling == PROF_YES && ms) {
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
new file mode 100644
index 0000000000..507b5aacae
--- /dev/null
+++ b/test/functional/ui/mouse_spec.lua
@@ -0,0 +1,157 @@
+local helpers = require('test.functional.helpers')
+local Screen = require('test.functional.ui.screen')
+local clear, feed, nvim = helpers.clear, helpers.feed, helpers.nvim
+
+describe('Mouse input', function()
+ local screen, hlgroup_colors
+
+ setup(function()
+ hlgroup_colors = {
+ Visual = nvim('name_to_color', 'LightGrey'),
+ }
+ end)
+
+ before_each(function()
+ clear()
+ nvim('set_option', 'mouse', 'a')
+ -- set mouset to very high value to ensure that even in valgrind/travis,
+ -- nvim will still pick multiple clicks
+ nvim('set_option', 'mouset', 5000)
+ screen = Screen.new(25, 5)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {background = hlgroup_colors.Visual}
+ })
+ feed('itesting<cr>mouse<cr>support and selection<esc>')
+ screen:expect([[
+ testing |
+ mouse |
+ support and selectio^ |
+ ~ |
+ |
+ ]])
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ it('left click moves cursor', function()
+ feed('<LeftMouse><2,1>')
+ screen:expect([[
+ testing |
+ mo^se |
+ support and selection |
+ ~ |
+ |
+ ]])
+ feed('<LeftMouse><0,0>')
+ screen:expect([[
+ ^esting |
+ mouse |
+ support and selection |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('left drag changes visual selection', function()
+ -- drag events must be preceded by a click
+ feed('<LeftMouse><2,1>')
+ screen:expect([[
+ testing |
+ mo^se |
+ support and selection |
+ ~ |
+ |
+ ]])
+ feed('<LeftDrag><4,1>')
+ screen:expect([[
+ testing |
+ mo{1:us}^ |
+ support and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ feed('<LeftDrag><2,2>')
+ screen:expect([[
+ testing |
+ mo{1:use } |
+ {1:su}^port and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ feed('<LeftDrag><0,0>')
+ screen:expect([[
+ ^{1:esting } |
+ {1:mou}se |
+ support and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ end)
+
+ it('two clicks will select the word and enter VISUAL', function()
+ feed('<LeftMouse><2,2><LeftMouse><2,2>')
+ screen:expect([[
+ testing |
+ mouse |
+ {1:suppor}^ and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ end)
+
+ it('three clicks will select the line and enter VISUAL LINE', function()
+ feed('<LeftMouse><2,2><LeftMouse><2,2><LeftMouse><2,2>')
+ screen:expect([[
+ testing |
+ mouse |
+ {1:su}^{1:port and selection } |
+ ~ |
+ -- VISUAL LINE -- |
+ ]])
+ end)
+
+ it('four clicks will enter VISUAL BLOCK', function()
+ feed('<LeftMouse><2,2><LeftMouse><2,2><LeftMouse><2,2><LeftMouse><2,2>')
+ screen:expect([[
+ testing |
+ mouse |
+ su^port and selection |
+ ~ |
+ -- VISUAL BLOCK -- |
+ ]])
+ end)
+
+ it('right click extends visual selection to the clicked location', function()
+ feed('<LeftMouse><0,0>')
+ screen:expect([[
+ ^esting |
+ mouse |
+ support and selection |
+ ~ |
+ |
+ ]])
+ feed('<RightMouse><2,2>')
+ screen:expect([[
+ {1:testing } |
+ {1:mouse } |
+ {1:su}^port and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ end)
+
+ it('ctrl + left click will search for a tag', function()
+ feed('<C-LeftMouse><0,0>')
+ screen:expect([[
+ E433: No tags file |
+ E426: tag not found: test|
+ ing |
+ Press ENTER or type comma|
+ nd to continue^ |
+ ]])
+ feed('<cr>')
+ end)
+end)
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index ff22321e4e..8e7d1ed798 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -147,17 +147,21 @@ function Screen:expect(expected, attr_ids)
end
function Screen:_wait(check, timeout)
- local err
+ local err, checked = false
local function notification_cb(method, args)
assert(method == 'redraw')
self:_redraw(args)
err = check()
+ checked = true
if not err then
stop()
end
return true
end
run(nil, notification_cb, nil, timeout or 5000)
+ if not checked then
+ err = check()
+ end
if err then
error(err)
end