vim-patch:partial:9.0.0913: only change in current window triggers the WinScrolled event

Problem:    Only a change in the current window triggers the WinScrolled
            event.
Solution:   Trigger WinScrolled if any window scrolled or changed size.
            (issue vim/vim#11576)

0a60f79fd0

Skip locking of window layout and E1312.
Copy the latest version of all WinScrolled tests from Vim.
Note: patch 9.0.0915 is needed for the Lua tests to pass.

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
zeertzjq 2022-11-20 08:38:46 +08:00
parent 822eabc5e1
commit 035d41ac5e
5 changed files with 172 additions and 44 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

@ -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,38 @@ 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
func Test_WinScrolled_long_wrapped() func Test_WinScrolled_long_wrapped()
CheckRunVimInTerminal CheckRunVimInTerminal
@ -385,7 +440,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 +457,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 +2841,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

@ -5263,35 +5263,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.
static 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,42 @@ 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
]])
-- 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)
end) end)