vim-patch:partial:9.0.0917: the WinScrolled autocommand event is not enough (#21161)

Problem:    The WinScrolled autocommand event is not enough.
Solution:   Add WinResized and provide information about what changed.
            (closes vim/vim#11576)

35fc61cb5b

Omit "func_name" comment in tv_dict_extend(): Vim9 script only.
Skip layout locking and E1312.
Skip list_alloc_with_items() and list_set_item().

Since this overrides remaining changes in patch 9.0.0913, that patch can
now be marked as fully ported:

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

N/A patches for version.c:

vim-patch:9.0.0919: build failure with tiny features

Problem:    Build failure with tiny features.
Solution:   Adjust #ifdef's.

9c5b7cb4cf

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
zeertzjq
2022-11-23 09:54:48 +08:00
committed by GitHub
parent d41e93d5a8
commit 4571ba4d0a
11 changed files with 693 additions and 211 deletions

View File

@@ -1095,21 +1095,24 @@ WinNew When a new window was created. Not done for
Before WinEnter.
*WinScrolled*
WinScrolled After scrolling the content of a window or
resizing a window in the current tab page.
When more than one window scrolled or resized
only one WinScrolled event is triggered. You
can use the `winlayout()` and `getwininfo()`
functions to see what changed.
WinScrolled After any window in the current tab page
scrolled the text (horizontally or vertically)
or changed width or height. See
|win-scrolled-resized|.
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|.
|v:event| is set with information about size
and scroll changes. |WinScrolled-event|
Only starts triggering after startup finished
and the first screen redraw was done.
Does not trigger when defining the first
WinScrolled or WinResized event, but may
trigger when adding more.
Non-recursive: the event will not trigger
while executing commands for the WinScrolled
@@ -1117,8 +1120,17 @@ WinScrolled After scrolling the content of a window or
window to scroll or change size, then another
WinScrolled event will be triggered later.
Does not trigger when the command is added,
only after the first scroll or resize.
*WinResized*
WinResized After a window in the current tab page changed
width or height.
See |win-scrolled-resized|.
|v:event| is set with information about size
changes. |WinResized-event|
Same behavior as |WinScrolled| for the
pattern, triggering and recursiveness.
==============================================================================
6. Patterns *autocmd-pattern* *{aupat}*

View File

@@ -607,6 +607,51 @@ it).
The minimal height and width of a window is set with 'winminheight' and
'winminwidth'. These are hard values, a window will never become smaller.
WinScrolled and WinResized autocommands ~
*win-scrolled-resized*
If you want to get notified of changes in window sizes, the |WinResized|
autocommand event can be used.
If you want to get notified of text in windows scrolling vertically or
horizontally, the |WinScrolled| autocommand event can be used. This will also
trigger in window size changes.
*WinResized-event*
The |WinResized| event is triggered after updating the display, several
windows may have changed size then. A list of the IDs of windows that changed
since last time is provided in the v:event.windows variable, for example:
[1003, 1006]
*WinScrolled-event*
The |WinScrolled| event is triggered after |WinResized|, and also if a window
was scrolled. That can be vertically (the text at the top of the window
changed) or horizontally (when 'wrap' is off or when the first displayed part
of the first line changes). Note that |WinScrolled| will trigger many more
times than |WinResized|, it may slow down editing a bit.
The information provided by |WinScrolled| is a dictionary for each window that
has changes, using the window ID as the key, and a total count of the changes
with the key "all". Example value for |v:event|:
{
all: {width: 0, height: 2, leftcol: 0, topline: 1, skipcol: 0},
1003: {width: 0, height: -1, leftcol: 0, topline: 0, skipcol: 0},
1006: {width: 0, height: 1, leftcol: 0, topline: 1, skipcol: 0},
}
Note that the "all" entry has the absolute values of the individual windows
accumulated.
If you need more information about what changed, or you want to "debounce" the
events (not handle every event to avoid doing too much work), you may want to
use the `winlayout()` and `getwininfo()` functions.
|WinScrolled| and |WinResized| do not trigger when the first autocommand is
added, only after the first scroll or resize. They may trigger when switching
to another tab page.
The commands executed are expected to not cause window size or scroll changes.
If this happens anyway, the event will trigger again very soon. In other
words: Just before triggering the event, the current sizes and scroll
positions are stored and used to decide whether there was a change.
==============================================================================
7. Argument and buffer list commands *buffer-list*

View File

@@ -123,7 +123,8 @@ return {
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window
'WinNew', -- when entering a new window
'WinScrolled', -- after scrolling a window
'WinResized', -- after a window was resized
'WinScrolled', -- after a window was scrolled or resized
},
aliases = {
BufCreate = 'BufAdd',

View File

@@ -1141,10 +1141,11 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
curwin->w_last_cursormoved = curwin->w_cursor;
}
// Initialize the fields checked by the WinScrolled trigger to
// prevent it from firing right after the first autocmd is
// defined.
if (event == EVENT_WINSCROLLED && !has_event(EVENT_WINSCROLLED)) {
// Initialize the fields checked by the WinScrolled and
// WinResized trigger to prevent them from firing right after
// the first autocmd is defined.
if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED)
&& !(has_event(EVENT_WINSCROLLED) || has_event(EVENT_WINRESIZED))) {
tabpage_T *save_curtab = curtab;
FOR_ALL_TABS(tp) {
unuse_tabpage(curtab);
@@ -1781,7 +1782,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
|| event == EVENT_SIGNAL || event == EVENT_SPELLFILEMISSING
|| event == EVENT_SYNTAX || event == EVENT_TABCLOSED
|| event == EVENT_USER || event == EVENT_WINCLOSED
|| event == EVENT_WINSCROLLED) {
|| event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) {
fname = xstrdup(fname);
} else {
fname = FullName_save(fname, false);

View File

@@ -1339,8 +1339,7 @@ void ins_redraw(bool ready)
}
if (ready) {
// Trigger Scroll if viewport changed.
may_trigger_winscrolled();
may_trigger_win_scrolled_resized();
}
// Trigger BufModified if b_changed_invalid is set.

View File

@@ -2446,10 +2446,10 @@ void tv_dict_clear(dict_T *const d)
///
/// @param d1 Dictionary to extend.
/// @param[in] d2 Dictionary to extend with.
/// @param[in] action "error", "force", "keep":
///
/// @param[in] action "error", "force", "move", "keep":
/// e*, including "error": duplicate key gives an error.
/// f*, including "force": duplicate d2 keys override d1.
/// m*, including "move": move items instead of copying.
/// other, including "keep": duplicate d2 keys ignored.
void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action)
FUNC_ATTR_NONNULL_ALL
@@ -2458,19 +2458,31 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
const char *const arg_errmsg = _("extend() argument");
const size_t arg_errmsg_len = strlen(arg_errmsg);
TV_DICT_ITER(d2, di2, {
if (*action == 'm') {
hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove()
}
HASHTAB_ITER(&d2->dv_hashtab, hi2, {
dictitem_T *const di2 = TV_DICT_HI2DI(hi2);
dictitem_T *const di1 = tv_dict_find(d1, (const char *)di2->di_key, -1);
// Check the key to be valid when adding to any scope.
if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname((const char *)di2->di_key)) {
break;
}
if (di1 == NULL) {
dictitem_T *const new_di = tv_dict_item_copy(di2);
if (tv_dict_add(d1, new_di) == FAIL) {
tv_dict_item_free(new_di);
} else if (watched) {
tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv,
NULL);
if (*action == 'm') {
// cheap way to move a dict item from "d2" to "d1"
dictitem_T *const new_di = di2;
tv_dict_add(d1, new_di);
hash_remove(&d2->dv_hashtab, hi2);
tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL);
} else {
dictitem_T *const new_di = tv_dict_item_copy(di2);
if (tv_dict_add(d1, new_di) == FAIL) {
tv_dict_item_free(new_di);
} else if (watched) {
tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL);
}
}
} else if (*action == 'e') {
semsg(_("E737: Key already exists: %s"), di2->di_key);
@@ -2501,6 +2513,10 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
}
}
});
if (*action == 'm') {
hash_unlock(&d2->dv_hashtab);
}
}
/// Compare two dictionaries

View File

@@ -1230,8 +1230,7 @@ static void normal_check_interrupt(NormalState *s)
static void normal_check_window_scrolled(NormalState *s)
{
if (!finish_op) {
// Trigger Scroll if the viewport changed.
may_trigger_winscrolled();
may_trigger_win_scrolled_resized();
}
}

View File

@@ -295,6 +295,61 @@ func Test_win_tab_autocmd()
unlet g:record
endfunc
func Test_WinResized()
CheckRunVimInTerminal
let lines =<< trim END
set scrolloff=0
call setline(1, ['111', '222'])
vnew
call setline(1, ['aaa', 'bbb'])
new
call setline(1, ['foo', 'bar'])
let g:resized = 0
au WinResized * let g:resized += 1
func WriteResizedEvent()
call writefile([json_encode(v:event)], 'XresizeEvent')
endfunc
au WinResized * call WriteResizedEvent()
END
call writefile(lines, 'Xtest_winresized', 'D')
let buf = RunVimInTerminal('-S Xtest_winresized', {'rows': 10})
" redraw now to avoid a redraw after the :echo command
call term_sendkeys(buf, ":redraw!\<CR>")
call TermWait(buf)
call term_sendkeys(buf, ":echo g:resized\<CR>")
call WaitForAssert({-> assert_match('^0$', term_getline(buf, 10))}, 1000)
" increase window height, two windows will be reported
call term_sendkeys(buf, "\<C-W>+")
call TermWait(buf)
call term_sendkeys(buf, ":echo g:resized\<CR>")
call WaitForAssert({-> assert_match('^1$', term_getline(buf, 10))}, 1000)
let event = readfile('XresizeEvent')[0]->json_decode()
call assert_equal({
\ 'windows': [1002, 1001],
\ }, event)
" increase window width, three windows will be reported
call term_sendkeys(buf, "\<C-W>>")
call TermWait(buf)
call term_sendkeys(buf, ":echo g:resized\<CR>")
call WaitForAssert({-> assert_match('^2$', term_getline(buf, 10))}, 1000)
let event = readfile('XresizeEvent')[0]->json_decode()
call assert_equal({
\ 'windows': [1002, 1001, 1000],
\ }, event)
call delete('XresizeEvent')
call StopVimInTerminal(buf)
endfunc
func Test_WinScrolled()
CheckRunVimInTerminal
@@ -305,11 +360,15 @@ func Test_WinScrolled()
endfor
let win_id = win_getid()
let g:matched = v:false
func WriteScrollEvent()
call writefile([json_encode(v:event)], 'XscrollEvent')
endfunc
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>'))
au WinScrolled * call WriteScrollEvent()
END
call writefile(lines, 'Xtest_winscrolled', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6})
@@ -321,15 +380,33 @@ func Test_WinScrolled()
call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>")
call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
let event = readfile('XscrollEvent')[0]->json_decode()
call assert_equal({
\ 'all': {'leftcol': 1, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0},
\ '1000': {'leftcol': -1, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0}
\ }, event)
" 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)
let event = readfile('XscrollEvent')[0]->json_decode()
call assert_equal({
\ 'all': {'leftcol': 0, 'topline': 1, 'width': 0, 'height': 0, 'skipcol': 0},
\ '1000': {'leftcol': 0, 'topline': -1, 'width': 0, 'height': 0, 'skipcol': 0}
\ }, event)
" 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)
let event = readfile('XscrollEvent')[0]->json_decode()
call assert_equal({
\ 'all': {'leftcol': 0, 'topline': 1, 'width': 0, 'height': 0, 'skipcol': 0},
\ '1000': {'leftcol': 0, 'topline': -1, 'width': 0, 'height': 0, 'skipcol': 0}
\ }, event)
" 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.
@@ -337,6 +414,12 @@ func Test_WinScrolled()
call term_sendkeys(buf, ":echo g:scrolled\<CR>")
call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000)
let event = readfile('XscrollEvent')[0]->json_decode()
call assert_equal({
\ 'all': {'leftcol': 5, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0},
\ '1000': {'leftcol': -5, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0}
\ }, event)
" 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)
@@ -345,6 +428,7 @@ func Test_WinScrolled()
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 delete('XscrollEvent')
call StopVimInTerminal(buf)
endfunc

View File

@@ -5301,39 +5301,241 @@ void may_make_initial_scroll_size_snapshot(void)
}
}
/// Trigger WinScrolled if any window scrolled or changed size.
void may_trigger_winscrolled(void)
/// Create a dictionary with information about size and scroll changes in a
/// window.
/// Returns the dictionary with refcount set to one.
/// Returns NULL on internal error.
static dict_T *make_win_info_dict(int width, int height, int topline, int leftcol, int skipcol)
{
dict_T *const d = tv_dict_alloc();
d->dv_refcount = 1;
// not actually looping, for breaking out on error
while (1) {
typval_T tv = {
.v_lock = VAR_UNLOCKED,
.v_type = VAR_NUMBER,
};
tv.vval.v_number = width;
if (tv_dict_add_tv(d, S_LEN("width"), &tv) == FAIL) {
break;
}
tv.vval.v_number = height;
if (tv_dict_add_tv(d, S_LEN("height"), &tv) == FAIL) {
break;
}
tv.vval.v_number = topline;
if (tv_dict_add_tv(d, S_LEN("topline"), &tv) == FAIL) {
break;
}
tv.vval.v_number = leftcol;
if (tv_dict_add_tv(d, S_LEN("leftcol"), &tv) == FAIL) {
break;
}
tv.vval.v_number = skipcol;
if (tv_dict_add_tv(d, S_LEN("skipcol"), &tv) == FAIL) {
break;
}
return d;
}
tv_dict_unref(d);
return NULL;
}
/// Return values of check_window_scroll_resize():
enum {
CWSR_SCROLLED = 1, ///< at least one window scrolled
CWSR_RESIZED = 2, ///< at least one window size changed
};
/// This function is used for three purposes:
/// 1. Goes over all windows in the current tab page and returns:
/// 0 no scrolling and no size changes found
/// CWSR_SCROLLED at least one window scrolled
/// CWSR_RESIZED at least one window changed size
/// CWSR_SCROLLED + CWSR_RESIZED both
/// "size_count" is set to the nr of windows with size changes.
/// "first_scroll_win" is set to the first window with any relevant changes.
/// "first_size_win" is set to the first window with size changes.
///
/// 2. When the first three arguments are NULL and "winlist" is not NULL,
/// "winlist" is set to the list of window IDs with size changes.
///
/// 3. When the first three arguments are NULL and "v_event" is not NULL,
/// information about changed windows is added to "v_event".
static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win,
win_T **first_size_win, list_T *winlist, dict_T *v_event)
{
int result = 0;
// int listidx = 0;
int tot_width = 0;
int tot_height = 0;
int tot_topline = 0;
int tot_leftcol = 0;
int tot_skipcol = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
const bool size_changed = wp->w_last_width != wp->w_width
|| wp->w_last_height != wp->w_height;
if (size_changed) {
result |= CWSR_RESIZED;
if (winlist != NULL) {
// Add this window to the list of changed windows.
typval_T tv = {
.v_lock = VAR_UNLOCKED,
.v_type = VAR_NUMBER,
.vval.v_number = wp->handle,
};
// tv_list_set_item(winlist, listidx++, &tv);
tv_list_append_owned_tv(winlist, tv);
} else if (size_count != NULL) {
(*size_count)++;
if (*first_size_win == NULL) {
*first_size_win = wp;
}
// For WinScrolled the first window with a size change is used
// even when it didn't scroll.
if (*first_scroll_win == NULL) {
*first_scroll_win = wp;
}
}
}
const bool scroll_changed = wp->w_last_topline != wp->w_topline
|| wp->w_last_leftcol != wp->w_leftcol
|| wp->w_last_skipcol != wp->w_skipcol;
if (scroll_changed) {
result |= CWSR_SCROLLED;
if (first_scroll_win != NULL && *first_scroll_win == NULL) {
*first_scroll_win = wp;
}
}
if ((size_changed || scroll_changed) && v_event != NULL) {
// Add info about this window to the v:event dictionary.
int width = wp->w_width - wp->w_last_width;
int height = wp->w_height - wp->w_last_height;
int topline = wp->w_topline - wp->w_last_topline;
int leftcol = wp->w_leftcol - wp->w_last_leftcol;
int skipcol = wp->w_skipcol - wp->w_last_skipcol;
dict_T *d = make_win_info_dict(width, height,
topline, leftcol, skipcol);
if (d == NULL) {
break;
}
char winid[NUMBUFLEN];
int key_len = vim_snprintf(winid, sizeof(winid), "%d", wp->handle);
if (tv_dict_add_dict(v_event, winid, (size_t)key_len, d) == FAIL) {
tv_dict_unref(d);
break;
}
d->dv_refcount--;
tot_width += abs(width);
tot_height += abs(height);
tot_topline += abs(topline);
tot_leftcol += abs(leftcol);
tot_skipcol += abs(skipcol);
}
}
if (v_event != NULL) {
dict_T *alldict = make_win_info_dict(tot_width, tot_height,
tot_topline, tot_leftcol, tot_skipcol);
if (alldict != NULL) {
if (tv_dict_add_dict(v_event, S_LEN("all"), alldict) == FAIL) {
tv_dict_unref(alldict);
} else {
alldict->dv_refcount--;
}
}
}
return result;
}
/// Trigger WinScrolled and/or WinResized if any window in the current tab page
/// scrolled or changed size.
void may_trigger_win_scrolled_resized(void)
{
static bool recursive = false;
const bool do_resize = has_event(EVENT_WINRESIZED);
const bool do_scroll = has_event(EVENT_WINSCROLLED);
if (recursive
|| !has_event(EVENT_WINSCROLLED)
|| !(do_scroll || do_resize)
|| !did_initial_scroll_size_snapshot) {
return;
}
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (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) {
// WinScrolled is triggered only once, even when multiple windows
// 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();
int size_count = 0;
win_T *first_scroll_win = NULL, *first_size_win = NULL;
int cwsr = check_window_scroll_resize(&size_count,
&first_scroll_win, &first_size_win,
NULL, NULL);
int trigger_resize = do_resize && size_count > 0;
int trigger_scroll = do_scroll && cwsr != 0;
if (!trigger_resize && !trigger_scroll) {
return; // no relevant changes
}
list_T *windows_list = NULL;
if (trigger_resize) {
// Create the list for v:event.windows before making the snapshot.
// windows_list = tv_list_alloc_with_items(size_count);
windows_list = tv_list_alloc(size_count);
(void)check_window_scroll_resize(NULL, NULL, NULL, windows_list, NULL);
}
dict_T *scroll_dict = NULL;
if (trigger_scroll) {
// Create the dict with entries for v:event before making the snapshot.
scroll_dict = tv_dict_alloc();
scroll_dict->dv_refcount = 1;
(void)check_window_scroll_resize(NULL, NULL, NULL, NULL, scroll_dict);
}
// WinScrolled/WinResized are triggered only once, even when multiple
// windows scrolled or changed size. Store the current values before
// triggering the event, if a scroll or resize happens as a side effect
// then WinScrolled/WinResized is triggered for that later.
snapshot_windows_scroll_size();
recursive = true;
// If both are to be triggered do WinResized first.
if (trigger_resize) {
save_v_event_T save_v_event;
dict_T *v_event = get_v_event(&save_v_event);
if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) {
tv_dict_set_keys_readonly(v_event);
char winid[NUMBUFLEN];
vim_snprintf(winid, sizeof(winid), "%d", wp->handle);
recursive = true;
apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer);
recursive = false;
break;
vim_snprintf(winid, sizeof(winid), "%d", first_size_win->handle);
apply_autocmds(EVENT_WINRESIZED, winid, winid, false, first_size_win->w_buffer);
}
restore_v_event(v_event, &save_v_event);
}
if (trigger_scroll) {
save_v_event_T save_v_event;
dict_T *v_event = get_v_event(&save_v_event);
// Move the entries from scroll_dict to v_event.
tv_dict_extend(v_event, scroll_dict, "move");
tv_dict_set_keys_readonly(v_event);
tv_dict_unref(scroll_dict);
char winid[NUMBUFLEN];
vim_snprintf(winid, sizeof(winid), "%d", first_scroll_win->handle);
apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, first_scroll_win->w_buffer);
restore_v_event(v_event, &save_v_event);
}
recursive = false;
}
// Save the size of all windows in "gap".

View File

@@ -0,0 +1,285 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
local exec = helpers.exec
local command = helpers.command
local feed = helpers.feed
local meths = helpers.meths
local assert_alive = helpers.assert_alive
before_each(clear)
describe('WinResized', function()
-- oldtest: Test_WinResized()
it('works', function()
exec([[
set scrolloff=0
call setline(1, ['111', '222'])
vnew
call setline(1, ['aaa', 'bbb'])
new
call setline(1, ['foo', 'bar'])
let g:resized = 0
au WinResized * let g:resized += 1
au WinResized * let g:v_event = deepcopy(v:event)
]])
eq(0, eval('g:resized'))
-- increase window height, two windows will be reported
feed('<C-W>+')
eq(1, eval('g:resized'))
eq({windows = {1002, 1001}}, eval('g:v_event'))
-- increase window width, three windows will be reported
feed('<C-W>>')
eq(2, eval('g:resized'))
eq({windows = {1002, 1001, 1000}}, eval('g:v_event'))
end)
end)
describe('WinScrolled', function()
local win_id
before_each(function()
win_id = meths.get_current_win().id
command(string.format('autocmd WinScrolled %d let g:matched = v:true', win_id))
exec([[
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>'))
au WinScrolled * let g:v_event = deepcopy(v:event)
]])
end)
after_each(function()
eq(true, eval('g:matched'))
eq(win_id, eval('g:amatch'))
eq(win_id, eval('g:afile'))
end)
it('is triggered by scrolling vertically', function()
local lines = {'123', '123'}
meths.buf_set_lines(0, 0, -1, true, lines)
eq(0, eval('g:scrolled'))
feed('<C-E>')
eq(1, eval('g:scrolled'))
eq({
all = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
feed('<C-Y>')
eq(2, eval('g:scrolled'))
eq({
all = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 0, topline = -1, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
end)
it('is triggered by scrolling horizontally', function()
command('set nowrap')
local width = meths.win_get_width(0)
local line = '123' .. ('*'):rep(width * 2)
local lines = {line, line}
meths.buf_set_lines(0, 0, -1, true, lines)
eq(0, eval('g:scrolled'))
feed('zl')
eq(1, eval('g:scrolled'))
eq({
all = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
feed('zh')
eq(2, eval('g:scrolled'))
eq({
all = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = -1, topline = 0, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
end)
it('is triggered by horizontal scrolling from cursor move', function()
command('set nowrap')
local lines = {'', '', 'Foo'}
meths.buf_set_lines(0, 0, -1, true, lines)
meths.win_set_cursor(0, {3, 0})
eq(0, eval('g:scrolled'))
feed('zl')
eq(1, eval('g:scrolled'))
eq({
all = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
feed('zl')
eq(2, eval('g:scrolled'))
eq({
all = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
feed('h')
eq(3, eval('g:scrolled'))
eq({
all = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = -1, topline = 0, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
feed('zh')
eq(4, eval('g:scrolled'))
eq({
all = {leftcol = 1, topline = 0, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = -1, topline = 0, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
end)
-- oldtest: Test_WinScrolled_long_wrapped()
it('is triggered by scrolling on a long wrapped line #19968', function()
local height = meths.win_get_height(0)
local width = meths.win_get_width(0)
meths.buf_set_lines(0, 0, -1, true, {('foo'):rep(height * width)})
meths.win_set_cursor(0, {1, height * width - 1})
eq(0, eval('g:scrolled'))
feed('gj')
eq(1, eval('g:scrolled'))
eq({
all = {leftcol = 0, topline = 0, width = 0, height = 0, skipcol = width},
['1000'] = {leftcol = 0, topline = 0, width = 0, height = 0, skipcol = width},
}, eval('g:v_event'))
feed('0')
eq(2, eval('g:scrolled'))
eq({
all = {leftcol = 0, topline = 0, width = 0, height = 0, skipcol = width},
['1000'] = {leftcol = 0, topline = 0, width = 0, height = 0, skipcol = -width},
}, eval('g:v_event'))
feed('$')
eq(3, eval('g:scrolled'))
end)
it('is triggered when the window scrolls in Insert mode', function()
local height = meths.win_get_height(0)
local lines = {}
for i = 1, height * 2 do
lines[i] = tostring(i)
end
meths.buf_set_lines(0, 0, -1, true, lines)
feed('M')
eq(0, eval('g:scrolled'))
feed('i<C-X><C-E><Esc>')
eq(1, eval('g:scrolled'))
eq({
all = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
feed('i<C-X><C-Y><Esc>')
eq(2, eval('g:scrolled'))
eq({
all = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 0, topline = -1, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
feed('L')
eq(2, eval('g:scrolled'))
feed('A<CR><Esc>')
eq(3, eval('g:scrolled'))
eq({
all = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
['1000'] = {leftcol = 0, topline = 1, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
end)
end)
describe('WinScrolled', function()
-- oldtest: Test_WinScrolled_mouse()
it('is triggered by mouse scrolling in another window', function()
local screen = Screen.new(75, 10)
screen:attach()
exec([[
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'
})
local winid_str = tostring(win.id)
exec([[
let g:scrolled = 0
autocmd WinScrolled * let g:scrolled += 1
autocmd WinScrolled * let g:amatch = expand('<amatch>')
autocmd WinScrolled * let g:v_event = deepcopy(v:event)
]])
eq(0, eval('g:scrolled'))
meths.input_mouse('wheel', 'down', '', 0, 3, 3)
eq(1, eval('g:scrolled'))
eq(winid_str, eval('g:amatch'))
eq({
all = {leftcol = 0, topline = 3, width = 0, height = 0, skipcol = 0},
[winid_str] = {leftcol = 0, topline = 3, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
meths.input_mouse('wheel', 'up', '', 0, 3, 3)
eq(2, eval('g:scrolled'))
eq(tostring(win.id), eval('g:amatch'))
eq({
all = {leftcol = 0, topline = 3, width = 0, height = 0, skipcol = 0},
[winid_str] = {leftcol = 0, topline = -3, width = 0, height = 0, skipcol = 0},
}, eval('g:v_event'))
end)
end)

View File

@@ -1,162 +0,0 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
local exec = helpers.exec
local command = helpers.command
local feed = helpers.feed
local meths = helpers.meths
local assert_alive = helpers.assert_alive
before_each(clear)
describe('WinScrolled', function()
local win_id
before_each(function()
win_id = meths.get_current_win().id
command(string.format('autocmd WinScrolled %d let g:matched = v:true', win_id))
command('let g:scrolled = 0')
command('autocmd WinScrolled * let g:scrolled += 1')
command([[autocmd WinScrolled * let g:amatch = str2nr(expand('<amatch>'))]])
command([[autocmd WinScrolled * let g:afile = str2nr(expand('<afile>'))]])
end)
after_each(function()
eq(true, eval('g:matched'))
eq(win_id, eval('g:amatch'))
eq(win_id, eval('g:afile'))
end)
it('is triggered by scrolling vertically', function()
local lines = {'123', '123'}
meths.buf_set_lines(0, 0, -1, true, lines)
eq(0, eval('g:scrolled'))
feed('<C-E>')
eq(1, eval('g:scrolled'))
end)
it('is triggered by scrolling horizontally', function()
command('set nowrap')
local width = meths.win_get_width(0)
local line = '123' .. ('*'):rep(width * 2)
local lines = {line, line}
meths.buf_set_lines(0, 0, -1, true, lines)
eq(0, eval('g:scrolled'))
feed('zl')
eq(1, eval('g:scrolled'))
end)
it('is triggered by horizontal scrolling from cursor move', function()
command('set nowrap')
local lines = {'', '', 'Foo'}
meths.buf_set_lines(0, 0, -1, true, lines)
meths.win_set_cursor(0, {3, 0})
eq(0, eval('g:scrolled'))
feed('zl')
eq(1, eval('g:scrolled'))
feed('zl')
eq(2, eval('g:scrolled'))
feed('h')
eq(3, eval('g:scrolled'))
end)
it('is triggered by scrolling on a long wrapped line #19968', function()
local height = meths.win_get_height(0)
local width = meths.win_get_width(0)
meths.buf_set_lines(0, 0, -1, true, {('foo'):rep(height * width)})
meths.win_set_cursor(0, {1, height * width - 1})
eq(0, eval('g:scrolled'))
feed('gj')
eq(1, eval('g:scrolled'))
feed('0')
eq(2, eval('g:scrolled'))
feed('$')
eq(3, eval('g:scrolled'))
end)
it('is triggered when the window scrolls in Insert mode', function()
local height = meths.win_get_height(0)
local lines = {}
for i = 1, height * 2 do
lines[i] = tostring(i)
end
meths.buf_set_lines(0, 0, -1, true, lines)
feed('L')
eq(0, eval('g:scrolled'))
feed('A<CR><Esc>')
eq(1, eval('g:scrolled'))
end)
end)
describe('WinScrolled', function()
-- oldtest: Test_WinScrolled_mouse()
it('is triggered by mouse scrolling in another window', function()
local screen = Screen.new(75, 10)
screen:attach()
exec([[
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)