0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 8.2.4713: plugins cannot track text scrolling

Problem:    Plugins cannot track text scrolling.
Solution:   Add the WinScrolled event. (closes #10102)
This commit is contained in:
LemonBoy 2022-04-08 15:18:45 +01:00 committed by Bram Moolenaar
parent 18ee0f603e
commit 0937182d49
12 changed files with 153 additions and 5 deletions

View 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: >

View 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

View 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()

View File

@ -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

View 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);

View 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);

View 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);

View 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.

View 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.

View File

@ -746,6 +746,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
4713,
/**/
4712,
/**/

View File

@ -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
};

View 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,