diff options
-rw-r--r-- | src/nvim/normal.c | 28 | ||||
-rw-r--r-- | src/nvim/os/input.c | 93 | ||||
-rw-r--r-- | test/functional/ui/mouse_spec.lua | 157 | ||||
-rw-r--r-- | test/functional/ui/screen.lua | 6 |
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 |