neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 93c8fe77cbfd3934c7599196e573136f75f6e9af
parent 9ced05413474a7c8b8a8b2f36a27db29a37dfaf6
Author: Jay <jaysandhu1993@gmail.com>
Date:   Wed,  6 Jul 2022 12:34:24 +0100

feat: add 'mousescroll' option (#12355)

Add 'mousescroll' option to control how many lines to scroll by when a
mouse wheel keycode is received. The mousescroll option controls both
horizontal and vertical scrolling. The option is a string in the format:

    set mousescroll=direction:count,direction:count

Where direction is either "ver" or "hor", and count is a non negative
integer. If a direction is omitted, a default value is used. The default
values remain unchanged, that is 3 for vertical scrolling, and 6 for
horizontal scrolling. As such, the mousescroll default is "ver:3,hor:6".

Add mousescroll documentation
 - Add option documentation in options.txt
 - Add brief summary in quickref.txt

Update :help scroll-mouse-wheel
 - Mention mousescroll option as a means of controlling scrolling.
 - Remove obsolete suggestion to map scroll wheel keys to <C-U> to
   scroll by a single line -- users should prefer the mousescroll option.
 - Add some information about the consequences of remapping scroll wheel
   keys (they lose their magic ability to affect inactive windows).

Update :help vim-differences
 - Add brief mousescroll summary under Options

Add mousescroll tests
 - Test option validation
 - Test default mousescroll value and behavior
 - Test fallback to default values
 - Test mouse vertical and horizontal scrolling in normal mode
 - Test mouse vertical and horizontal scrolling in insert mode
Diffstat:
Mruntime/doc/options.txt | 20++++++++++++++++++++
Mruntime/doc/quickref.txt | 1+
Mruntime/doc/scroll.txt | 18+++++++++++-------
Mruntime/doc/vim_diff.txt | 1+
Msrc/nvim/edit.c | 8+++-----
Msrc/nvim/mouse.c | 2+-
Msrc/nvim/normal.c | 4++--
Msrc/nvim/option.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/nvim/option_defs.h | 8++++++++
Msrc/nvim/options.lua | 8++++++++
Atest/functional/options/mousescroll_spec.lua | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 271 insertions(+), 15 deletions(-)

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt @@ -4210,6 +4210,26 @@ 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. + *mousescroll* +'mousescroll' string (default "ver:3,hor:6") + global + This option controls the number of lines / columns to scroll by when + scrolling with a mouse. The option is a comma separated list of parts. + Each part consists of a direction and a count as follows: + direction:count,direction:count + Direction is one of either "hor" or "ver", "hor" controls horizontal + scrolling and "ver" controls vertical scrolling. Count sets the amount + to scroll by for the given direction, it should be a non negative + integer. Each direction should be set at most once. If a direction + is omitted, a default value is used (6 for horizontal scrolling and 3 + for vertical scrolling). You can disable mouse scrolling by using + a count of 0. + + Example: > + :set mousescroll=ver:5,hor:2 +< Will make Nvim scroll 5 lines at a time when scrolling vertically, and + scroll 2 columns at a time when scrolling horizontally. + *'mouseshape'* *'mouses'* *E547* 'mouseshape' 'mouses' string (default "i:beam,r:beam,s:updown,sd:cross, m:no,ml:up-arrow,v:rightup-arrow") diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt @@ -785,6 +785,7 @@ Short explanation of each option: *option-list* 'mousefocus' 'mousef' keyboard focus follows the mouse 'mousehide' 'mh' hide mouse pointer while typing 'mousemodel' 'mousem' changes meaning of mouse buttons +'mousescroll' amount to scroll by when scrolling with a mouse 'mouseshape' 'mouses' shape of the mouse pointer in different modes 'mousetime' 'mouset' max time between mouse double-click 'nrformats' 'nf' number formats recognized for CTRL-A command diff --git a/runtime/doc/scroll.txt b/runtime/doc/scroll.txt @@ -239,12 +239,16 @@ the "h" flag in 'guioptions' is set, the cursor moves to the longest visible line if the cursor line is about to be scrolled off the screen (similarly to how the horizontal scrollbar works). -You can modify the default behavior by mapping the keys. For example, to make -the scroll wheel move one line or half a page in Normal mode: > - :map <ScrollWheelUp> <C-Y> - :map <S-ScrollWheelUp> <C-U> - :map <ScrollWheelDown> <C-E> - :map <S-ScrollWheelDown> <C-D> -You can also use Alt and Ctrl modifiers. +You can control the number of lines / columns to scroll by using the +'mousescroll' option. You can also modify the default behavior by mapping +the keys. For example, to scroll a page at a time in normal mode: > + :map <ScrollWheelUp> <C-B> + :map <ScrollWheelDown> <C-F> +Scroll keys can also be combined with modifiers such as Shift, Ctrl, and Alt. + +When scrolling with a mouse, the window currently under the cursor is +scrolled. This allows you to scroll inactive windows. Note that when scroll +keys are remapped to keyboard keys, the active window is affected regardless +of the current cursor position. vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt @@ -238,6 +238,7 @@ Options: 'inccommand' shows interactive results for |:substitute|-like commands and |:command-preview| commands 'laststatus' global statusline support + 'mousescroll' amount to scroll by when scrolling with a mouse 'pumblend' pseudo-transparent popupmenu 'scrollback' 'signcolumn' supports up to 9 dynamic/fixed columns diff --git a/src/nvim/edit.c b/src/nvim/edit.c @@ -8557,14 +8557,12 @@ static void ins_mousescroll(int dir) } // Don't scroll the window in which completion is being done. - if (!pum_visible() - || curwin != old_curwin) { + if (!pum_visible() || curwin != old_curwin) { if (dir == MSCR_DOWN || dir == MSCR_UP) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { - scroll_redraw(dir, - (curwin->w_botline - curwin->w_topline)); + scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline)); } else { - scroll_redraw(dir, 3L); + scroll_redraw(dir, p_mousescroll_vert); } } else { mouse_scroll_horiz(dir); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c @@ -688,7 +688,7 @@ bool mouse_scroll_horiz(int dir) return false; } - int step = 6; + int step = (int)p_mousescroll_hor; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { step = curwin->w_width_inner; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c @@ -3392,8 +3392,8 @@ static void nv_mousescroll(cmdarg_T *cap) if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L); } else { - cap->count1 = 3; - cap->count0 = 3; + cap->count1 = p_mousescroll_vert; + cap->count0 = p_mousescroll_vert; nv_scroll_line(cap); } } else { diff --git a/src/nvim/option.c b/src/nvim/option.c @@ -2366,6 +2366,69 @@ static bool valid_spellfile(const char_u *val) return true; } +/// Handle setting 'mousescroll'. +/// @return error message, NULL if it's OK. +static char *check_mousescroll(char *string) +{ + long vertical = -1; + long horizontal = -1; + + for (;;) { + char *end = vim_strchr(string, ','); + size_t length = end ? (size_t)(end - string) : STRLEN(string); + + // Both "ver:" and "hor:" are 4 bytes long. + // They should be followed by at least one digit. + if (length <= 4) { + return e_invarg; + } + + long *direction; + + if (memcmp(string, "ver:", 4) == 0) { + direction = &vertical; + } else if (memcmp(string, "hor:", 4) == 0) { + direction = &horizontal; + } else { + return e_invarg; + } + + // If the direction has already been set, this is a duplicate. + if (*direction != -1) { + return e_invarg; + } + + // Verify that only digits follow the colon. + for (size_t i = 4; i < length; i++) { + if (!ascii_isdigit(string[i])) { + return N_("E548: digit expected"); + } + } + + string += 4; + *direction = getdigits_int(&string, false, -1); + + // Num options are generally kept within the signed int range. + // We know this number won't be negative because we've already checked for + // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. + if (*direction == -1) { + return e_invarg; + } + + if (!end) { + break; + } + + string = end + 1; + } + + // If a direction wasn't set, fallback to the default value. + p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; + p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; + + return NULL; +} + /// Handle string options that need some action to perform when changed. /// Returns NULL for success, or an error message for an error. /// @@ -2859,6 +2922,8 @@ ambw_end: if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { errmsg = e_invarg; } + } else if (varp == &p_mousescroll) { // 'mousescroll' + errmsg = check_mousescroll((char *)p_mousescroll); } else if (varp == &p_swb) { // 'switchbuf' if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { errmsg = e_invarg; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h @@ -153,6 +153,11 @@ #define MOUSE_NONE ' ' // don't use Visual selection #define MOUSE_NONEF 'x' // forced modeless selection +// default vertical and horizontal mouse scroll values. +// Note: This should be in sync with the default mousescroll option. +#define MOUSESCROLL_VERT_DFLT 3 +#define MOUSESCROLL_HOR_DFLT 6 + #define COCU_ALL "nvic" // flags for 'concealcursor' /// characters for p_shm option: @@ -528,6 +533,9 @@ EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' EXTERN int p_mousef; // 'mousefocus' +EXTERN char_u *p_mousescroll; // 'mousescroll' +EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT); +EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT); EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char_u *p_opfunc; // 'operatorfunc' diff --git a/src/nvim/options.lua b/src/nvim/options.lua @@ -1622,6 +1622,14 @@ return { defaults={if_true="extend"} }, { + full_name='mousescroll', + short_desc=N_("amount to scroll by when scrolling with a mouse"), + type='string', list='comma', scope={'global'}, + vi_def=true, + varname='p_mousescroll', + defaults={if_true="ver:3,hor:6"} + }, + { full_name='mouseshape', abbreviation='mouses', short_desc=N_("shape of the mouse pointer in different modes"), type='string', list='onecomma', scope={'global'}, diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua @@ -0,0 +1,151 @@ +local helpers = require('test.functional.helpers')(after_each) +local command = helpers.command +local clear = helpers.clear +local eval = helpers.eval +local eq = helpers.eq +local exc_exec = helpers.exc_exec +local feed = helpers.feed + +local scroll = function(direction) + return helpers.request('nvim_input_mouse', 'wheel', direction, '', 0, 2, 2) +end + +local screenrow = function() + return helpers.call('screenrow') +end + +local screencol = function() + return helpers.call('screencol') +end + +describe("'mousescroll'", function() + local invalid_arg = 'Vim(set):E474: Invalid argument: mousescroll=' + local digit_expected = 'Vim(set):E548: digit expected: mousescroll=' + + local function should_fail(val, errorstr) + eq(errorstr..val, exc_exec('set mousescroll='..val)) + end + + local function should_succeed(val) + eq(0, exc_exec('set mousescroll='..val)) + end + + before_each(function() + clear() + command('set nowrap lines=20 columns=20 virtualedit=all') + feed('100o<Esc>50G10|') + end) + + it('handles invalid values', function() + should_fail('', invalid_arg) -- empty string + should_fail('foo:123', invalid_arg) -- unknown direction + should_fail('hor:1,hor:2', invalid_arg) -- duplicate direction + should_fail('ver:99999999999999999999', invalid_arg) -- integer overflow + should_fail('ver:bar', digit_expected) -- expected digit + should_fail('ver:-1', digit_expected) -- negative count + end) + + it('handles valid values', function() + should_succeed('hor:1,ver:1') -- both directions set + should_succeed('hor:1') -- only horizontal + should_succeed('ver:1') -- only vertical + should_succeed('hor:0,ver:0') -- zero + should_succeed('hor:2147483647') -- large count + end) + + it('default set correctly', function() + eq('ver:3,hor:6', eval('&mousescroll')) + + eq(10, screenrow()) + scroll('up') + eq(13, screenrow()) + scroll('down') + eq(10, screenrow()) + + eq(10, screencol()) + scroll('right') + eq(4, screencol()) + scroll('left') + eq(10, screencol()) + end) + + it('vertical scrolling falls back to default value', function() + command('set mousescroll=hor:1') + eq(10, screenrow()) + scroll('up') + eq(13, screenrow()) + end) + + it('horizontal scrolling falls back to default value', function() + command('set mousescroll=ver:1') + eq(10, screencol()) + scroll('right') + eq(4, screencol()) + end) + + it('count of zero disables mouse scrolling', function() + command('set mousescroll=hor:0,ver:0') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) + + eq(10, screencol()) + scroll('right') + eq(10, screencol()) + scroll('left') + eq(10, screencol()) + end) + + local test_vertical_scrolling = function() + eq(10, screenrow()) + + command('set mousescroll=ver:1') + scroll('up') + eq(11, screenrow()) + + command('set mousescroll=ver:2') + scroll('down') + eq(9, screenrow()) + + command('set mousescroll=ver:5') + scroll('up') + eq(14, screenrow()) + end + + it('controls vertical scrolling in normal mode', function() + test_vertical_scrolling() + end) + + it('controls vertical scrolling in insert mode', function() + feed('i') + test_vertical_scrolling() + end) + + local test_horizontal_scrolling = function() + eq(10, screencol()) + + command('set mousescroll=hor:1') + scroll('right') + eq(9, screencol()) + + command('set mousescroll=hor:3') + scroll('right') + eq(6, screencol()) + + command('set mousescroll=hor:2') + scroll('left') + eq(8, screencol()) + end + + it('controls horizontal scrolling in normal mode', function() + test_horizontal_scrolling() + end) + + it('controls horizontal scrolling in insert mode', function() + feed('i') + test_horizontal_scrolling() + end) +end)