]> granicus.if.org Git - vim/commitdiff
patch 8.2.4713: plugins cannot track text scrolling v8.2.4713
authorLemonBoy <thatlemon@gmail.com>
Fri, 8 Apr 2022 14:18:45 +0000 (15:18 +0100)
committerBram Moolenaar <Bram@vim.org>
Fri, 8 Apr 2022 14:18:45 +0000 (15:18 +0100)
Problem:    Plugins cannot track text scrolling.
Solution:   Add the WinScrolled event. (closes #10102)

12 files changed:
runtime/doc/autocmd.txt
src/autocmd.c
src/edit.c
src/gui.c
src/main.c
src/proto/autocmd.pro
src/proto/window.pro
src/structs.h
src/testdir/test_autocmd.vim
src/version.c
src/vim.h
src/window.c

index 16bbbf5807bba6c540660c3c8516c65542f6e7f5..beb1f2e3ad745c415bc3a4632a178af13d6a224d 100644 (file)
@@ -402,6 +402,8 @@ Name                        triggered by ~
 |User|                 to be used in combination with ":doautocmd"
 |SigUSR1|              after the SIGUSR1 signal has been detected
 
+|WinScrolled|          after scrolling or resizing a window
+
 
 The alphabetical list of autocommand events:           *autocmd-events-abc*
 
@@ -1228,7 +1230,13 @@ User                             Never executed automatically.  To be used for
                                Note that when `:doautocmd User MyEvent` is
                                used while there are no matching autocommands,
                                you will get an error.  If you don't want
-                               that, define a dummy autocommand yourself.
+                               that, either check whether an autocommand is
+                               defined using `exists('#User#MyEvent')` or
+                               define a dummy autocommand yourself.
+                               Example: >
+                                   if exists('#User#MyEvent')
+                                       doautocmd User MyEvent
+                                   endif
 
                                                        *SigUSR1*
 SigUSR1                                After the SIGUSR1 signal has been detected.
@@ -1317,10 +1325,23 @@ WinNew                          When a new window was created.  Not done for
                                the first window, when Vim has just started.
                                Before a WinEnter event.
 
+                                                       *WinScrolled*
+WinScrolled                    After scrolling the content of a window or
+                               resizing a window.
+                               The pattern is matched against the
+                               |window-ID|.  Both <amatch> and <afile> are
+                               set to the |window-ID|.
+                               Non-recursive (the event cannot trigger
+                               itself).  However, if the command causes the
+                               window to scroll or change size another
+                               WinScrolled event will be triggered later.
+                               Does not trigger when the command is added,
+                               only after the first scroll or resize.
+
 ==============================================================================
 6. Patterns                                    *autocmd-patterns* *{aupat}*
 
-The {aupat} argument of `:autocmd` can be a comma separated list.  This works as
+The {aupat} argument of `:autocmd` can be a comma-separated list.  This works as
 if the command was given with each pattern separately.  Thus this command: >
        :autocmd BufRead *.txt,*.info set et
 Is equivalent to: >
index 7eb08c85b953b3fac3d735e9fc22fd3c4365db6b..a0065decba8da79a096daa7054ba69e261b4d194 100644 (file)
@@ -190,6 +190,7 @@ static struct event_name
     {"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 @@ do_autocmd_event(
                    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)
                {
@@ -1782,6 +1792,15 @@ trigger_cursorhold(void)
     return FALSE;
 }
 
+/*
+ * 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.
  */
@@ -2078,7 +2097,8 @@ apply_autocmds_group(
                || 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
index f30edd5c790cb63170e6ebe29caa5dea0d6ca48b..1585f851843247a435165908ead49f5a1a9ee6e0 100644 (file)
@@ -1527,6 +1527,9 @@ ins_redraw(int ready)         // not busy with something
                                        (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()
index 68ac9d804a4cb255ae94f909523afd5b5155b51e..23694d1956b5907522ba708f0474dad8392cacd5 100644 (file)
--- a/src/gui.c
+++ b/src/gui.c
@@ -5237,6 +5237,9 @@ gui_update_screen(void)
        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
index 2a3d310ab6bb77f4f05b919dfce45f909664ec72..fe3571b9266c796a925dea08784b86c3f1f16816 100644 (file)
@@ -1336,6 +1336,14 @@ main_loop(
                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);
index d8145dfe79f513bd177e640a5d28ba3c624af851..366d7aa4dc04458c3a28a77376e097f46d8fab3e 100644 (file)
@@ -26,6 +26,7 @@ int has_cmdundefined(void);
 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);
index 1954dfd0ba9f910dc6fa50b64ec94513fa0f14e4..589dd0931e317714cb94f26d401dadb65035e800 100644 (file)
@@ -13,14 +13,15 @@ int make_windows(int count, int vertical);
 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);
index b8648a5455e9b3f86b7ea1e9f762fa053bd505b4..738c592314f9297df43b9458d8f84ca973bb1e67 100644 (file)
@@ -3510,6 +3510,12 @@ struct window_S
                                    // 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.
index eb1fa046c59575b5130d4d7f037e13a72a073810..3ff9d0b340fa85b8fd228345750b9b6aaec34582 100644 (file)
@@ -3,6 +3,7 @@
 source shared.vim
 source check.vim
 source term_util.vim
+source screendump.vim
 import './vim9.vim' as v9
 
 func s:cleanup_buffers() abort
@@ -309,6 +310,60 @@ func Test_win_tab_autocmd()
   unlet g:record
 endfunc
 
+func Test_WinScrolled()
+  CheckRunVimInTerminal
+
+  let lines =<< trim END
+       set nowrap scrolloff=0
+        for ii in range(1, 18)
+          call setline(ii, repeat(nr2char(96 + ii), ii * 2))
+        endfor
+        let win_id = win_getid()
+        let g:matched = v:false
+        execute 'au WinScrolled' win_id 'let g:matched = v:true'
+        let g:scrolled = 0
+        au WinScrolled * let g:scrolled += 1
+        au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
+        au WinScrolled * let g:afile = str2nr(expand('<afile>'))
+  END
+  call writefile(lines, 'Xtest_winscrolled')
+  let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6})
+
+  call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+  call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000)
+
+  " Scroll left/right in Normal mode.
+  call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>")
+  call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
+
+  " Scroll up/down in Normal mode.
+  call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>")
+  call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000)
+
+  " Scroll up/down in Insert mode.
+  call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>")
+  call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+  call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000)
+
+  " Scroll the window horizontally to focus the last letter of the third line
+  " containing only six characters. Moving to the previous and shorter lines
+  " should trigger another autocommand as Vim has to make them visible.
+  call term_sendkeys(buf, "5zl2k")
+  call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+  call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000)
+
+  " Ensure the command was triggered for the specified window ID.
+  call term_sendkeys(buf, ":echo g:matched\<CR>")
+  call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
+
+  " Ensure the expansion of <amatch> and <afile> matches the window ID.
+  call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>")
+  call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
+
+  call StopVimInTerminal(buf)
+  call delete('Xtest_winscrolled')
+endfunc
+
 func Test_WinClosed()
   " Test that the pattern is matched against the closed window's ID, and both
   " <amatch> and <afile> are set to it.
index cebb0d0189b4e6c60ab798ffc88ec74383b5d5e9..15d02e1413a3c68b9df7686c966a49443e56e6ef 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4713,
 /**/
     4712,
 /**/
index 4174fa0d7e2ef3cbd478c46ef258b7329d8315ad..f448ad010cd205557f339268624281ba6a0b5298 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -1386,6 +1386,7 @@ enum auto_event
     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
 };
index f7634758924bfad6acdd488c181a011b7be89ac3..1eab3dcced462414cce8d880aac8ee11842f1281 100644 (file)
@@ -2779,11 +2779,38 @@ trigger_winclosed(win_T *win)
     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,