summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLemonBoy <thatlemon@gmail.com>2022-04-08 15:18:45 +0100
committerBram Moolenaar <Bram@vim.org>2022-04-08 15:18:45 +0100
commit0937182d49fa8db50cec42785f22f1031760a0bd (patch)
treea41ab36fcbeb5b2f0bc91ce36b2d056af2ec2491
parent18ee0f603ebd3c091f6d2ab88e652fda32821048 (diff)
downloadvim-git-8.2.4713.tar.gz
patch 8.2.4713: plugins cannot track text scrollingv8.2.4713
Problem: Plugins cannot track text scrolling. Solution: Add the WinScrolled event. (closes #10102)
-rw-r--r--runtime/doc/autocmd.txt25
-rw-r--r--src/autocmd.c22
-rw-r--r--src/edit.c3
-rw-r--r--src/gui.c3
-rw-r--r--src/main.c8
-rw-r--r--src/proto/autocmd.pro1
-rw-r--r--src/proto/window.pro3
-rw-r--r--src/structs.h6
-rw-r--r--src/testdir/test_autocmd.vim55
-rw-r--r--src/version.c2
-rw-r--r--src/vim.h1
-rw-r--r--src/window.c29
12 files changed, 153 insertions, 5 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 16bbbf580..beb1f2e3a 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -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: >
diff --git a/src/autocmd.c b/src/autocmd.c
index 7eb08c85b..a0065decb 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -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)
{
@@ -1783,6 +1793,15 @@ trigger_cursorhold(void)
}
/*
+ * 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 @@ 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
diff --git a/src/edit.c b/src/edit.c
index f30edd5c7..1585f8518 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -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()
diff --git a/src/gui.c b/src/gui.c
index 68ac9d804..23694d195 100644
--- 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
diff --git a/src/main.c b/src/main.c
index 2a3d310ab..fe3571b92 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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);
diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro
index d8145dfe7..366d7aa4d 100644
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -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);
diff --git a/src/proto/window.pro b/src/proto/window.pro
index 1954dfd0b..589dd0931 100644
--- a/src/proto/window.pro
+++ b/src/proto/window.pro
@@ -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);
diff --git a/src/structs.h b/src/structs.h
index b8648a545..738c59231 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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.
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index eb1fa046c..3ff9d0b34 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -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.
diff --git a/src/version.c b/src/version.c
index cebb0d018..15d02e141 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
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 4174fa0d7..f448ad010 100644
--- 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
};
diff --git a/src/window.c b/src/window.c
index f76347589..1eab3dcce 100644
--- a/src/window.c
+++ b/src/window.c
@@ -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,