patch 8.2.4902: mouse wheel scrolling is inconsistent

Problem:    Mouse wheel scrolling is inconsistent.
Solution:   Use the MS-Winows system setting. (closes #10368)
diff --git a/src/gui_w32.c b/src/gui_w32.c
index c6a8b6d..7bca9e7 100644
--- a/src/gui_w32.c
+++ b/src/gui_w32.c
@@ -230,6 +230,10 @@
 # define SPI_GETWHEELSCROLLCHARS	0x006C
 #endif
 
+#ifndef SPI_SETWHEELSCROLLCHARS
+# define SPI_SETWHEELSCROLLCHARS	0x006D
+#endif
+
 #ifdef PROTO
 /*
  * Define a few things for generating prototypes.  This is just to avoid
@@ -4117,19 +4121,33 @@
 /*
  * Setup for the Intellimouse
  */
+    static long
+mouse_vertical_scroll_step(void)
+{
+    UINT val;
+    if (SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &val, 0))
+	return (val != WHEEL_PAGESCROLL) ? (long)val : -1;
+    return 3; // Safe default;
+}
+
+    static long
+mouse_horizontal_scroll_step(void)
+{
+    UINT val;
+    if (SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &val, 0))
+	return (long)val;
+    return 3; // Safe default;
+}
+
     static void
 init_mouse_wheel(void)
 {
-    // Reasonable default values.
-    mouse_scroll_lines = 3;
-    mouse_scroll_chars = 3;
-
-    // if NT 4.0+ (or Win98) get scroll lines directly from system
-    SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &mouse_scroll_lines, 0);
-    SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &mouse_scroll_chars, 0);
+    // Get the default values for the horizontal and vertical scroll steps from
+    // the system.
+    mouse_set_vert_scroll_step(mouse_vertical_scroll_step());
+    mouse_set_hor_scroll_step(mouse_horizontal_scroll_step());
 }
 
-
 /*
  * Intellimouse wheel handler.
  * Treat a mouse wheel event as if it were a scroll request.
@@ -4137,16 +4155,10 @@
     static void
 _OnMouseWheel(HWND hwnd, short zDelta, LPARAM param, int horizontal)
 {
-    int		i;
-    int		amount;
     int		button;
     win_T	*wp;
     int		modifiers, kbd_modifiers;
 
-    // Initializes mouse_scroll_chars too.
-    if (mouse_scroll_lines == 0)
-	init_mouse_wheel();
-
     wp = gui_mouse_window(FIND_POPUP);
 
 #ifdef FEAT_PROP_POPUP
@@ -4185,23 +4197,9 @@
     // Translate the scroll event into an event that Vim can process so that
     // the user has a chance to map the scrollwheel buttons.
     if (horizontal)
-    {
 	button = zDelta >= 0 ? MOUSE_6 : MOUSE_7;
-	if (mouse_scroll_chars > 0
-			       && mouse_scroll_chars < MAX(wp->w_width - 2, 1))
-	    amount = mouse_scroll_chars;
-	else
-	    amount = MAX(wp->w_width - 2, 1);
-    }
     else
-    {
 	button = zDelta >= 0 ? MOUSE_4 : MOUSE_5;
-	if (mouse_scroll_lines > 0
-			      && mouse_scroll_lines < MAX(wp->w_height - 2, 1))
-	    amount = mouse_scroll_lines;
-	else
-	    amount = MAX(wp->w_height - 2, 1);
-    }
 
     kbd_modifiers = get_active_modifiers();
 
@@ -4213,8 +4211,7 @@
 	modifiers |= MOUSE_ALT;
 
     mch_disable_flush();
-    for (i = amount; i > 0; --i)
-	gui_send_mouse_event(button, GET_X_LPARAM(param), GET_Y_LPARAM(param),
+    gui_send_mouse_event(button, GET_X_LPARAM(param), GET_Y_LPARAM(param),
 		FALSE, kbd_modifiers);
     mch_enable_flush();
     gui_may_flush();
@@ -4296,12 +4293,10 @@
     switch (param)
     {
 	case SPI_SETWHEELSCROLLLINES:
-	    SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0,
-		    &mouse_scroll_lines, 0);
+	    mouse_set_vert_scroll_step(mouse_vertical_scroll_step());
 	    break;
-	case SPI_GETWHEELSCROLLCHARS:
-	    SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0,
-		    &mouse_scroll_chars, 0);
+	case SPI_SETWHEELSCROLLCHARS:
+	    mouse_set_hor_scroll_step(mouse_horizontal_scroll_step());
 	    break;
 	case SPI_SETNONCLIENTMETRICS:
 	    set_tabline_font();
diff --git a/src/mouse.c b/src/mouse.c
index a0c5156..57b0f14 100644
--- a/src/mouse.c
+++ b/src/mouse.c
@@ -13,6 +13,25 @@
 
 #include "vim.h"
 
+/*
+ * Horiziontal and vertical steps used when scrolling.
+ * When negative scroll by a whole page.
+ */
+static long mouse_hor_step = 6;
+static long mouse_vert_step = 3;
+
+    void
+mouse_set_vert_scroll_step(long step)
+{
+    mouse_vert_step = step;
+}
+
+    void
+mouse_set_hor_scroll_step(long step)
+{
+    mouse_hor_step = step;
+}
+
 #ifdef CHECK_DOUBLE_CLICK
 /*
  * Return the duration from t1 to t2 in milliseconds.
@@ -1101,13 +1120,16 @@
     // Don't scroll the window in which completion is being done.
     if (!pum_visible() || curwin != old_curwin)
     {
+	long step;
+
 	if (dir == MSCR_DOWN || dir == MSCR_UP)
 	{
-	    if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
-		scroll_redraw(dir,
-			(long)(curwin->w_botline - curwin->w_topline));
+	    if (mouse_vert_step < 0
+		    || mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
+		step = (long)(curwin->w_botline - curwin->w_topline);
 	    else
-		scroll_redraw(dir, 3L);
+		step = mouse_vert_step;
+	    scroll_redraw(dir, step);
 # ifdef FEAT_PROP_POPUP
 	if (WIN_IS_POPUP(curwin))
 	    popup_set_firstline(curwin);
@@ -1116,10 +1138,13 @@
 #ifdef FEAT_GUI
 	else
 	{
-	    int val, step = 6;
+	    int val;
 
-	    if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
+	    if (mouse_hor_step < 0
+		    || mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
 		step = curwin->w_width;
+	    else
+		step = mouse_hor_step;
 	    val = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : step);
 	    if (val < 0)
 		val = 0;
@@ -2005,8 +2030,9 @@
 }
 
 /*
- * Mouse scroll wheel: Default action is to scroll three lines, or one page
- * when Shift or Ctrl is used.
+ * Mouse scroll wheel: Default action is to scroll mouse_vert_step lines (or
+ * mouse_hor_step, depending on the scroll direction), or one page when Shift or
+ * Ctrl is used.
  * K_MOUSEUP (cap->arg == 1) or K_MOUSEDOWN (cap->arg == 0) or
  * K_MOUSELEFT (cap->arg == -1) or K_MOUSERIGHT (cap->arg == -2)
  */
@@ -2033,7 +2059,6 @@
 	curwin = wp;
 	curbuf = curwin->w_buffer;
     }
-
     if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN)
     {
 # ifdef FEAT_TERMINAL
@@ -2043,21 +2068,21 @@
 	    send_keys_to_term(curbuf->b_term, cap->cmdchar, mod_mask, FALSE);
 	else
 # endif
-	if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
+	if (mouse_vert_step < 0 || mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
 	{
 	    (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L);
 	}
 	else
 	{
 	    // Don't scroll more than half the window height.
-	    if (curwin->w_height < 6)
+	    if (curwin->w_height < mouse_vert_step * 2)
 	    {
 		cap->count1 = curwin->w_height / 2;
 		if (cap->count1 == 0)
 		    cap->count1 = 1;
 	    }
 	    else
-		cap->count1 = 3;
+		cap->count1 = mouse_vert_step;
 	    cap->count0 = cap->count1;
 	    nv_scroll_line(cap);
 	}
@@ -2072,10 +2097,13 @@
 	// Horizontal scroll - only allowed when 'wrap' is disabled
 	if (!curwin->w_p_wrap)
 	{
-	    int val, step = 6;
+	    int val, step;
 
-	    if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
+	    if (mouse_hor_step < 0
+		    || mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
 		step = curwin->w_width;
+	    else
+		step = mouse_hor_step;
 	    val = curwin->w_leftcol + (cap->arg == MSCR_RIGHT ? -step : +step);
 	    if (val < 0)
 		val = 0;
diff --git a/src/proto/mouse.pro b/src/proto/mouse.pro
index 37a2f07..249e7f2 100644
--- a/src/proto/mouse.pro
+++ b/src/proto/mouse.pro
@@ -1,4 +1,6 @@
 /* mouse.c */
+void mouse_set_vert_scroll_step(long step);
+void mouse_set_hor_scroll_step(long step);
 int do_mouse(oparg_T *oap, int c, int dir, long count, int fixindent);
 void ins_mouse(int c);
 void ins_mousescroll(int dir);
diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim
index 993954b..bc3c698 100644
--- a/src/testdir/test_gui.vim
+++ b/src/testdir/test_gui.vim
@@ -997,6 +997,7 @@
   call assert_equal(['one two abc three', 'four five posix'], getline(1, '$'))
 
   %d _
+  set scrolloff=0
   call setline(1, range(1, 100))
   " scroll up
   let args = #{button: 0x200, row: 2, col: 1, multiclick: 0, modifiers: 0}
@@ -1012,6 +1013,7 @@
   call test_gui_event('mouse', args)
   call feedkeys("H", 'Lx!')
   call assert_equal(4, line('.'))
+  set scrolloff&
 
   %d _
   set nowrap
diff --git a/src/testing.c b/src/testing.c
index 3340123..572dcdc 100644
--- a/src/testing.c
+++ b/src/testing.c
@@ -1393,6 +1393,11 @@
 	repeated_click = (int)dict_get_number(args, (char_u *)"multiclick");
 	mods = (int)dict_get_number(args, (char_u *)"modifiers");
 
+	// Reset the scroll values to known values.
+	// XXX: Remove this when/if the scroll step is made configurable.
+	mouse_set_hor_scroll_step(6);
+	mouse_set_vert_scroll_step(3);
+
 	gui_send_mouse_event(button, TEXT_X(col - 1), TEXT_Y(row - 1),
 							repeated_click, mods);
     }
diff --git a/src/version.c b/src/version.c
index 647011a..5ee96b6 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4902,
+/**/
     4901,
 /**/
     4900,