Merge pull request #21136 from zeertzjq/vim-9.0.0913

vim-patch:9.0.{partial:0913,0915}: only change in current window triggers the WinScrolled event
This commit is contained in:
zeertzjq
2022-11-20 22:00:13 +08:00
committed by GitHub
6 changed files with 261 additions and 58 deletions

View File

@@ -1096,22 +1096,35 @@ WinNew When a new window was created. Not done for
*WinScrolled* *WinScrolled*
WinScrolled After scrolling the content of a window or WinScrolled After scrolling the content of a window or
resizing a window. resizing a window in the current tab page.
The pattern is matched against the
|window-ID|. Both <amatch> and <afile> are When more than one window scrolled or resized
set to the |window-ID|. only one WinScrolled event is triggered. You
Non-recursive (the event cannot trigger can use the `winlayout()` and `getwininfo()`
itself). However, if the command causes the functions to see what changed.
window to scroll or change size another
The pattern is matched against the |window-ID|
of the first window that scrolled or resized.
Both <amatch> and <afile> are set to the
|window-ID|.
Only starts triggering after startup finished
and the first screen redraw was done.
Non-recursive: the event will not trigger
while executing commands for the WinScrolled
event. However, if the command causes a
window to scroll or change size, then another
WinScrolled event will be triggered later. WinScrolled event will be triggered later.
Does not trigger when the command is added, Does not trigger when the command is added,
only after the first scroll or resize. only after the first scroll or resize.
============================================================================== ==============================================================================
6. Patterns *autocmd-pattern* *{aupat}* 6. Patterns *autocmd-pattern* *{aupat}*
The {aupat} argument of `:autocmd` can be a comma-separated list. This works The {aupat} argument of `:autocmd` can be a comma-separated list. This works as
as if the command was given with each pattern separately. Thus this command: > if the command was given with each pattern separately. Thus this command: >
:autocmd BufRead *.txt,*.info set et :autocmd BufRead *.txt,*.info set et
Is equivalent to: > Is equivalent to: >
:autocmd BufRead *.txt set et :autocmd BufRead *.txt set et

View File

@@ -1142,13 +1142,17 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
} }
// Initialize the fields checked by the WinScrolled trigger to // Initialize the fields checked by the WinScrolled trigger to
// stop it from firing right after the first autocmd is defined. // prevent it from firing right after the first autocmd is
// defined.
if (event == EVENT_WINSCROLLED && !has_event(EVENT_WINSCROLLED)) { if (event == EVENT_WINSCROLLED && !has_event(EVENT_WINSCROLLED)) {
curwin->w_last_topline = curwin->w_topline; tabpage_T *save_curtab = curtab;
curwin->w_last_leftcol = curwin->w_leftcol; FOR_ALL_TABS(tp) {
curwin->w_last_skipcol = curwin->w_skipcol; unuse_tabpage(curtab);
curwin->w_last_width = curwin->w_width; use_tabpage(tp);
curwin->w_last_height = curwin->w_height; snapshot_windows_scroll_size();
}
unuse_tabpage(curtab);
use_tabpage(save_curtab);
} }
ap->cmds = NULL; ap->cmds = NULL;

View File

@@ -1397,6 +1397,9 @@ static int normal_check(VimState *state)
fclose(time_fd); fclose(time_fd);
time_fd = NULL; time_fd = NULL;
} }
// After the first screen update may start triggering WinScrolled
// autocmd events. Store all the scroll positions and sizes now.
may_make_initial_scroll_size_snapshot();
} }
// May perform garbage collection when waiting for a character, but // May perform garbage collection when waiting for a character, but

View File

@@ -311,7 +311,7 @@ func Test_WinScrolled()
au WinScrolled * let g:amatch = str2nr(expand('<amatch>')) au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
au WinScrolled * let g:afile = str2nr(expand('<afile>')) au WinScrolled * let g:afile = str2nr(expand('<afile>'))
END END
call writefile(lines, 'Xtest_winscrolled') call writefile(lines, 'Xtest_winscrolled', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6}) let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6})
call term_sendkeys(buf, ":echo g:scrolled\<CR>") call term_sendkeys(buf, ":echo g:scrolled\<CR>")
@@ -346,7 +346,36 @@ func Test_WinScrolled()
call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
call delete('Xtest_winscrolled') endfunc
func Test_WinScrolled_mouse()
CheckRunVimInTerminal
let lines =<< trim END
set nowrap scrolloff=0
set mouse=a term=xterm ttymouse=sgr mousetime=200 clipboard=
call setline(1, ['foo']->repeat(32))
split
let g:scrolled = 0
au WinScrolled * let g:scrolled += 1
END
call writefile(lines, 'Xtest_winscrolled_mouse', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled_mouse', {'rows': 10})
" With the upper split focused, send a scroll-down event to the unfocused one.
call test_setmouse(7, 1)
call term_sendkeys(buf, "\<ScrollWheelDown>")
call TermWait(buf)
call term_sendkeys(buf, ":echo g:scrolled\<CR>")
call WaitForAssert({-> assert_match('^1', term_getline(buf, 10))}, 1000)
" Again, but this time while we're in insert mode.
call term_sendkeys(buf, "i\<ScrollWheelDown>\<Esc>")
call TermWait(buf)
call term_sendkeys(buf, ":echo g:scrolled\<CR>")
call WaitForAssert({-> assert_match('^2', term_getline(buf, 10))}, 1000)
call StopVimInTerminal(buf)
endfunc endfunc
func Test_WinScrolled_close_curwin() func Test_WinScrolled_close_curwin()
@@ -359,7 +388,7 @@ func Test_WinScrolled_close_curwin()
au WinScrolled * close au WinScrolled * close
au VimLeave * call writefile(['123456'], 'Xtestout') au VimLeave * call writefile(['123456'], 'Xtestout')
END END
call writefile(lines, 'Xtest_winscrolled_close_curwin') call writefile(lines, 'Xtest_winscrolled_close_curwin', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6}) let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6})
" This was using freed memory " This was using freed memory
@@ -367,12 +396,64 @@ func Test_WinScrolled_close_curwin()
call TermWait(buf) call TermWait(buf)
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
" check the startup script finished to the end
call assert_equal(['123456'], readfile('Xtestout')) call assert_equal(['123456'], readfile('Xtestout'))
call delete('Xtest_winscrolled_close_curwin')
call delete('Xtestout') call delete('Xtestout')
endfunc endfunc
func Test_WinScrolled_once_only()
CheckRunVimInTerminal
let lines =<< trim END
set cmdheight=2
call setline(1, ['aaa', 'bbb'])
let trigger_count = 0
func ShowInfo(id)
echo g:trigger_count g:winid winlayout()
endfunc
vsplit
split
" use a timer to show the info after a redraw
au WinScrolled * let trigger_count += 1 | let winid = expand('<amatch>') | call timer_start(100, 'ShowInfo')
wincmd j
wincmd l
END
call writefile(lines, 'Xtest_winscrolled_once', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled_once', #{rows: 10, cols: 60, statusoff: 2})
call term_sendkeys(buf, "\<C-E>")
call VerifyScreenDump(buf, 'Test_winscrolled_once_only_1', {})
call StopVimInTerminal(buf)
endfunc
" Check that WinScrolled is not triggered immediately when defined and there
" are split windows.
func Test_WinScrolled_not_when_defined()
CheckRunVimInTerminal
let lines =<< trim END
call setline(1, ['aaa', 'bbb'])
echo 'nothing happened'
func ShowTriggered(id)
echo 'triggered'
endfunc
END
call writefile(lines, 'Xtest_winscrolled_not', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled_not', #{rows: 10, cols: 60, statusoff: 2})
call term_sendkeys(buf, ":split\<CR>")
call TermWait(buf)
" use a timer to show the message after redrawing
call term_sendkeys(buf, ":au WinScrolled * call timer_start(100, 'ShowTriggered')\<CR>")
call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_1', {})
call term_sendkeys(buf, "\<C-E>")
call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_2', {})
call StopVimInTerminal(buf)
endfunc
func Test_WinScrolled_long_wrapped() func Test_WinScrolled_long_wrapped()
CheckRunVimInTerminal CheckRunVimInTerminal
@@ -385,7 +466,7 @@ func Test_WinScrolled_long_wrapped()
call setline(1, repeat('foo', height * width)) call setline(1, repeat('foo', height * width))
call cursor(1, height * width) call cursor(1, height * width)
END END
call writefile(lines, 'Xtest_winscrolled_long_wrapped') call writefile(lines, 'Xtest_winscrolled_long_wrapped', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6}) let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6})
call term_sendkeys(buf, ":echo g:scrolled\<CR>") call term_sendkeys(buf, ":echo g:scrolled\<CR>")
@@ -402,8 +483,6 @@ func Test_WinScrolled_long_wrapped()
call term_sendkeys(buf, '$') call term_sendkeys(buf, '$')
call term_sendkeys(buf, ":echo g:scrolled\<CR>") call term_sendkeys(buf, ":echo g:scrolled\<CR>")
call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000) call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000)
call delete('Xtest_winscrolled_long_wrapped')
endfunc endfunc
func Test_WinClosed() func Test_WinClosed()
@@ -2788,6 +2867,7 @@ func Test_SpellFileMissing_bwipe()
call assert_fails('set spell spelllang=0', 'E937:') call assert_fails('set spell spelllang=0', 'E937:')
au! SpellFileMissing au! SpellFileMissing
set nospell spelllang=en
bwipe bwipe
endfunc endfunc

View File

@@ -3868,6 +3868,27 @@ void close_others(int message, int forceit)
} }
} }
/// Store the relevant window pointers for tab page "tp". To be used before
/// use_tabpage().
void unuse_tabpage(tabpage_T *tp)
{
tp->tp_topframe = topframe;
tp->tp_firstwin = firstwin;
tp->tp_lastwin = lastwin;
tp->tp_curwin = curwin;
}
/// Set the relevant pointers to use tab page "tp". May want to call
/// unuse_tabpage() first.
void use_tabpage(tabpage_T *tp)
{
curtab = tp;
topframe = curtab->tp_topframe;
firstwin = curtab->tp_firstwin;
lastwin = curtab->tp_lastwin;
curwin = curtab->tp_curwin;
}
// Allocate the first window and put an empty buffer in it. // Allocate the first window and put an empty buffer in it.
// Only called from main(). // Only called from main().
void win_alloc_first(void) void win_alloc_first(void)
@@ -3878,11 +3899,8 @@ void win_alloc_first(void)
} }
first_tabpage = alloc_tabpage(); first_tabpage = alloc_tabpage();
first_tabpage->tp_topframe = topframe;
curtab = first_tabpage; curtab = first_tabpage;
curtab->tp_firstwin = firstwin; unuse_tabpage(first_tabpage);
curtab->tp_lastwin = lastwin;
curtab->tp_curwin = curwin;
} }
// Init `aucmd_win`. This can only be done after the first window // Init `aucmd_win`. This can only be done after the first window
@@ -4253,10 +4271,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a
win_T *next_prevwin = tp->tp_prevwin; win_T *next_prevwin = tp->tp_prevwin;
tabpage_T *old_curtab = curtab; tabpage_T *old_curtab = curtab;
curtab = tp; use_tabpage(tp);
firstwin = tp->tp_firstwin;
lastwin = tp->tp_lastwin;
topframe = tp->tp_topframe;
if (old_curtab != curtab) { if (old_curtab != curtab) {
tabpage_check_windows(old_curtab); tabpage_check_windows(old_curtab);
@@ -5263,35 +5278,60 @@ void win_new_screen_cols(void)
win_reconfig_floats(); // The size of floats might change win_reconfig_floats(); // The size of floats might change
} }
/// Trigger WinScrolled for "curwin" if needed. /// Make a snapshot of all the window scroll positions and sizes of the current
/// tab page.
void snapshot_windows_scroll_size(void)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
wp->w_last_topline = wp->w_topline;
wp->w_last_leftcol = wp->w_leftcol;
wp->w_last_skipcol = wp->w_skipcol;
wp->w_last_width = wp->w_width;
wp->w_last_height = wp->w_height;
}
}
static bool did_initial_scroll_size_snapshot = false;
void may_make_initial_scroll_size_snapshot(void)
{
if (!did_initial_scroll_size_snapshot) {
did_initial_scroll_size_snapshot = true;
snapshot_windows_scroll_size();
}
}
/// Trigger WinScrolled if any window scrolled or changed size.
void may_trigger_winscrolled(void) void may_trigger_winscrolled(void)
{ {
static bool recursive = false; static bool recursive = false;
if (recursive || !has_event(EVENT_WINSCROLLED)) { if (recursive
|| !has_event(EVENT_WINSCROLLED)
|| !did_initial_scroll_size_snapshot) {
return; return;
} }
win_T *wp = curwin; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_last_topline != wp->w_topline if (wp->w_last_topline != wp->w_topline
|| wp->w_last_leftcol != wp->w_leftcol || wp->w_last_leftcol != wp->w_leftcol
|| wp->w_last_skipcol != wp->w_skipcol || wp->w_last_skipcol != wp->w_skipcol
|| wp->w_last_width != wp->w_width || wp->w_last_width != wp->w_width
|| wp->w_last_height != wp->w_height) { || wp->w_last_height != wp->w_height) {
char winid[NUMBUFLEN]; // WinScrolled is triggered only once, even when multiple windows
vim_snprintf(winid, sizeof(winid), "%d", wp->handle); // scrolled or changed size. Store the current values before
// triggering the event, if a scroll or resize happens as a side
// effect then WinScrolled is triggered again later.
snapshot_windows_scroll_size();
recursive = true; char winid[NUMBUFLEN];
apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer); vim_snprintf(winid, sizeof(winid), "%d", wp->handle);
recursive = false;
// an autocmd may close the window, "wp" may be invalid now recursive = true;
if (win_valid_any_tab(wp)) { apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer);
wp->w_last_topline = wp->w_topline; recursive = false;
wp->w_last_leftcol = wp->w_leftcol;
wp->w_last_skipcol = wp->w_skipcol; break;
wp->w_last_width = wp->w_width;
wp->w_last_height = wp->w_height;
} }
} }
} }

View File

@@ -1,8 +1,10 @@
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear local clear = helpers.clear
local eq = helpers.eq local eq = helpers.eq
local eval = helpers.eval local eval = helpers.eval
local exec = helpers.exec
local command = helpers.command local command = helpers.command
local feed = helpers.feed local feed = helpers.feed
local meths = helpers.meths local meths = helpers.meths
@@ -89,11 +91,72 @@ describe('WinScrolled', function()
end) end)
end) end)
it('closing window in WinScrolled does not cause use-after-free #13265', function() describe('WinScrolled', function()
local lines = {'aaa', 'bbb'} -- oldtest: Test_WinScrolled_mouse()
meths.buf_set_lines(0, 0, -1, true, lines) it('is triggered by mouse scrolling in another window', function()
command('vsplit') local screen = Screen.new(75, 10)
command('autocmd WinScrolled * close') screen:attach()
feed('<C-E>') exec([[
assert_alive() set nowrap scrolloff=0
set mouse=a
call setline(1, ['foo']->repeat(32))
split
let g:scrolled = 0
au WinScrolled * let g:scrolled += 1
]])
eq(0, eval('g:scrolled'))
-- With the upper split focused, send a scroll-down event to the unfocused one.
meths.input_mouse('wheel', 'down', '', 0, 6, 0)
eq(1, eval('g:scrolled'))
-- Again, but this time while we're in insert mode.
feed('i')
meths.input_mouse('wheel', 'down', '', 0, 6, 0)
feed('<Esc>')
eq(2, eval('g:scrolled'))
end)
-- oldtest: Test_WinScrolled_close_curwin()
it('closing window does not cause use-after-free #13265', function()
exec([[
set nowrap scrolloff=0
call setline(1, ['aaa', 'bbb'])
vsplit
au WinScrolled * close
]])
-- This was using freed memory
feed('<C-E>')
assert_alive()
end)
it('is triggered by mouse scrolling in unfocused floating window #18222', function()
local screen = Screen.new(80, 24)
screen:attach()
local buf = meths.create_buf(true, true)
meths.buf_set_lines(buf, 0, -1, false, {'a', 'b', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'})
local win = meths.open_win(buf, false, {
height = 5,
width = 10,
col = 0,
row = 1,
relative = 'editor',
style = 'minimal'
})
exec([[
let g:scrolled = 0
autocmd WinScrolled * let g:scrolled += 1
autocmd WinScrolled * let g:amatch = expand('<amatch>')
]])
eq(0, eval('g:scrolled'))
meths.input_mouse('wheel', 'down', '', 0, 3, 3)
eq(1, eval('g:scrolled'))
eq(tostring(win.id), eval('g:amatch'))
meths.input_mouse('wheel', 'down', '', 0, 3, 3)
eq(2, eval('g:scrolled'))
eq(tostring(win.id), eval('g:amatch'))
end)
end) end)