patch 8.2.4713: plugins cannot track text scrolling

Problem:    Plugins cannot track text scrolling.
Solution:   Add the WinScrolled event. (closes #10102)
diff --git a/src/autocmd.c b/src/autocmd.c
index 7eb08c8..a0065de 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -190,6 +190,7 @@
     {"WinClosed",	EVENT_WINCLOSED},
     {"WinEnter",	EVENT_WINENTER},
     {"WinLeave",	EVENT_WINLEAVE},
+    {"WinScrolled",	EVENT_WINSCROLLED},
     {"VimResized",	EVENT_VIMRESIZED},
     {"TextYankPost",	EVENT_TEXTYANKPOST},
     {"VimSuspend",	EVENT_VIMSUSPEND},
@@ -1251,6 +1252,15 @@
 		    vim_free(rettv.vval.v_string);
 		}
 #endif
+		// Initialize the fields checked by the WinScrolled trigger to
+		// stop it from firing right after the first autocmd is defined.
+		if (event == EVENT_WINSCROLLED && !has_winscrolled())
+		{
+		    curwin->w_last_topline = curwin->w_topline;
+		    curwin->w_last_leftcol = curwin->w_leftcol;
+		    curwin->w_last_width = curwin->w_width;
+		    curwin->w_last_height = curwin->w_height;
+		}
 
 		if (is_buflocal)
 		{
@@ -1783,6 +1793,15 @@
 }
 
 /*
+ * Return TRUE when there is a WinScrolled autocommand defined.
+ */
+    int
+has_winscrolled(void)
+{
+    return (first_autopat[(int)EVENT_WINSCROLLED] != NULL);
+}
+
+/*
  * Return TRUE when there is a CursorMoved autocommand defined.
  */
     int
@@ -2078,7 +2097,8 @@
 		|| event == EVENT_DIRCHANGEDPRE
 		|| event == EVENT_MODECHANGED
 		|| event == EVENT_USER
-		|| event == EVENT_WINCLOSED)
+		|| event == EVENT_WINCLOSED
+		|| event == EVENT_WINSCROLLED)
 	{
 	    fname = vim_strsave(fname);
 	    autocmd_fname_full = TRUE; // don't expand it later
diff --git a/src/edit.c b/src/edit.c
index f30edd5..1585f85 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -1527,6 +1527,9 @@
 					(linenr_T)(curwin->w_cursor.lnum + 1));
     }
 
+    if (ready)
+	may_trigger_winscrolled(curwin);
+
     // Trigger SafeState if nothing is pending.
     may_trigger_safestate(ready
 	    && !ins_compl_active()
diff --git a/src/gui.c b/src/gui.c
index 68ac9d8..23694d1 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -5237,6 +5237,9 @@
 	last_cursormoved = curwin->w_cursor;
     }
 
+    if (!finish_op)
+	may_trigger_winscrolled(curwin);
+
 # ifdef FEAT_CONCEAL
     if (conceal_update_lines
 	    && (conceal_old_cursor_line != conceal_new_cursor_line
diff --git a/src/main.c b/src/main.c
index 2a3d310..fe3571b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1336,6 +1336,14 @@
 		curbuf->b_last_changedtick = CHANGEDTICK(curbuf);
 	    }
 
+	    // Ensure curwin->w_topline and curwin->w_leftcol are up to date
+	    // before triggering a WinScrolled autocommand.
+	    update_topline();
+	    validate_cursor();
+
+	    if (!finish_op)
+		may_trigger_winscrolled(curwin);
+
 	    // If nothing is pending and we are going to wait for the user to
 	    // type a character, trigger SafeState.
 	    may_trigger_safestate(!op_pending() && restart_edit == 0);
diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro
index d8145df..366d7aa 100644
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -26,6 +26,7 @@
 int has_textyankpost(void);
 int has_completechanged(void);
 int has_modechanged(void);
+int has_winscrolled(void);
 void block_autocmds(void);
 void unblock_autocmds(void);
 int is_autocmd_blocked(void);
diff --git a/src/proto/window.pro b/src/proto/window.pro
index 1954dfd..589dd09 100644
--- a/src/proto/window.pro
+++ b/src/proto/window.pro
@@ -13,14 +13,15 @@
 void win_move_after(win_T *win1, win_T *win2);
 void win_equal(win_T *next_curwin, int current, int dir);
 void entering_window(win_T *win);
+void curwin_init(void);
 void close_windows(buf_T *buf, int keep_curwin);
 int one_window(void);
 int win_close(win_T *win, int free_buf);
+void may_trigger_winscrolled(win_T *wp);
 void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp);
 void win_free_all(void);
 win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp);
 void close_others(int message, int forceit);
-void curwin_init(void);
 int win_alloc_first(void);
 win_T *win_alloc_popup_win(void);
 void win_init_popup_win(win_T *wp, buf_T *buf);
diff --git a/src/structs.h b/src/structs.h
index b8648a5..738c592 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -3510,6 +3510,12 @@
 				    // window
 #endif
 
+    // four fields that are only used when there is a WinScrolled autocommand
+    linenr_T	w_last_topline;	    // last known value for w_topline
+    colnr_T	w_last_leftcol;	    // last known value for w_leftcol
+    int		w_last_width;	    // last known value for w_width
+    int		w_last_height;	    // last known value for w_height
+
     /*
      * Layout of the window in the screen.
      * May need to add "msg_scrolled" to "w_winrow" in rare situations.
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index eb1fa04..3ff9d0b 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
Binary files differ
diff --git a/src/version.c b/src/version.c
index cebb0d0..15d02e1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4713,
+/**/
     4712,
 /**/
     4711,
diff --git a/src/vim.h b/src/vim.h
index 4174fa0..f448ad0 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1386,6 +1386,7 @@
     EVENT_WINCLOSED,		// after closing a window
     EVENT_VIMSUSPEND,		// before Vim is suspended
     EVENT_VIMRESUME,		// after Vim is resumed
+    EVENT_WINSCROLLED,		// after Vim window was scrolled
 
     NUM_EVENTS			// MUST be the last one
 };
diff --git a/src/window.c b/src/window.c
index f763475..1eab3dc 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2779,11 +2779,38 @@
     if (recursive)
 	return;
     recursive = TRUE;
-    vim_snprintf((char *)winid, sizeof(winid), "%i", win->w_id);
+    vim_snprintf((char *)winid, sizeof(winid), "%d", win->w_id);
     apply_autocmds(EVENT_WINCLOSED, winid, winid, FALSE, win->w_buffer);
     recursive = FALSE;
 }
 
+    void
+may_trigger_winscrolled(win_T *wp)
+{
+    static int	    recursive = FALSE;
+    char_u	    winid[NUMBUFLEN];
+
+    if (recursive || !has_winscrolled())
+	return;
+
+    if (wp->w_last_topline != wp->w_topline
+	    || wp->w_last_leftcol != wp->w_leftcol
+	    || wp->w_last_width != wp->w_width
+	    || wp->w_last_height != wp->w_height)
+    {
+	vim_snprintf((char *)winid, sizeof(winid), "%d", wp->w_id);
+
+	recursive = TRUE;
+	apply_autocmds(EVENT_WINSCROLLED, winid, winid, FALSE, wp->w_buffer);
+	recursive = FALSE;
+
+	wp->w_last_topline = wp->w_topline;
+	wp->w_last_leftcol = wp->w_leftcol;
+	wp->w_last_width = wp->w_width;
+	wp->w_last_height = wp->w_height;
+    }
+}
+
 /*
  * Close window "win" in tab page "tp", which is not the current tab page.
  * This may be the last window in that tab page and result in closing the tab,