From 35fc61cb5b5eba8bbb9d8f0700332fbab38f40ca Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 22 Nov 2022 12:40:50 +0000 Subject: patch 9.0.0917: the WinScrolled autocommand event is not enough Problem: The WinScrolled autocommand event is not enough. Solution: Add WinResized and provide information about what changed. (closes #11576) --- runtime/doc/autocmd.txt | 33 +++-- runtime/doc/windows.txt | 48 ++++++++ src/autocmd.c | 20 +++- src/dict.c | 29 ++++- src/edit.c | 2 +- src/gui.c | 2 +- src/main.c | 2 +- src/mouse.c | 2 +- src/proto/autocmd.pro | 1 + src/proto/window.pro | 2 +- src/testdir/test_autocmd.vim | 84 +++++++++++++ src/version.c | 2 + src/vim.h | 3 +- src/window.c | 279 +++++++++++++++++++++++++++++++++++++++---- 14 files changed, 455 insertions(+), 54 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 86ae60f67..fbd0b0df3 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1371,21 +1371,24 @@ WinNew When a new window was created. Not done for Before a WinEnter event. *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 and 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 @@ -1393,11 +1396,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. - *E1312* - It is not allowed to change the window layout - here (split, close or move windows). + + *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-patterns* *{aupat}* diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 2d96b043b..12676ff3a 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -631,6 +631,54 @@ 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| (|Vim9| syntax): + { + 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. + *E1312* +It is not allowed to change the window layout here (split, close or move +windows). + ============================================================================== 7. Argument and buffer list commands *buffer-list* diff --git a/src/autocmd.c b/src/autocmd.c index 999ee890c..11dc707d7 100644 --- a/src/autocmd.c +++ b/src/autocmd.c @@ -191,6 +191,7 @@ static struct event_name {"WinClosed", EVENT_WINCLOSED}, {"WinEnter", EVENT_WINENTER}, {"WinLeave", EVENT_WINLEAVE}, + {"WinResized", EVENT_WINRESIZED}, {"WinScrolled", EVENT_WINSCROLLED}, {"VimResized", EVENT_VIMRESIZED}, {"TextYankPost", EVENT_TEXTYANKPOST}, @@ -1263,10 +1264,11 @@ do_autocmd_event( if (event == EVENT_MODECHANGED && !has_modechanged()) get_mode(last_mode); #endif - // 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_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_winscrolled() || has_winresized())) { tabpage_T *save_curtab = curtab; tabpage_T *tp; @@ -1810,6 +1812,15 @@ trigger_cursorhold(void) return FALSE; } +/* + * Return TRUE when there is a WinResized autocommand defined. + */ + int +has_winresized(void) +{ + return (first_autopat[(int)EVENT_WINRESIZED] != NULL); +} + /* * Return TRUE when there is a WinScrolled autocommand defined. */ @@ -2117,6 +2128,7 @@ apply_autocmds_group( || event == EVENT_MENUPOPUP || event == EVENT_USER || event == EVENT_WINCLOSED + || event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) { fname = vim_strsave(fname); diff --git a/src/dict.c b/src/dict.c index c2f0fcc4d..30264a913 100644 --- a/src/dict.c +++ b/src/dict.c @@ -1082,13 +1082,14 @@ failret: * Go over all entries in "d2" and add them to "d1". * When "action" is "error" then a duplicate key is an error. * When "action" is "force" then a duplicate key is overwritten. + * When "action" is "move" then move items instead of copying. * Otherwise duplicate keys are ignored ("action" is "keep"). + * "func_name" is used for reporting where an error occurred. */ void dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name) { dictitem_T *di1; - hashitem_T *hi2; int todo; char_u *arg_errmsg = (char_u *)N_("extend() argument"); type_T *type; @@ -1098,8 +1099,11 @@ dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name) else type = NULL; + if (*action == 'm') + hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove() + todo = (int)d2->dv_hashtab.ht_used; - for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) + for (hashitem_T *hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) { if (!HASHITEM_EMPTY(hi2)) { @@ -1116,9 +1120,19 @@ dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name) if (di1 == NULL) { - di1 = dictitem_copy(HI2DI(hi2)); - if (di1 != NULL && dict_add(d1, di1) == FAIL) - dictitem_free(di1); + if (*action == 'm') + { + // cheap way to move a dict item from "d2" to "d1" + di1 = HI2DI(hi2); + dict_add(d1, di1); + hash_remove(&d2->dv_hashtab, hi2); + } + else + { + di1 = dictitem_copy(HI2DI(hi2)); + if (di1 != NULL && dict_add(d1, di1) == FAIL) + dictitem_free(di1); + } } else if (*action == 'e') { @@ -1138,6 +1152,9 @@ dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name) } } } + + if (*action == 'm') + hash_unlock(&d2->dv_hashtab); } /* @@ -1272,7 +1289,7 @@ dict_extend_func( action = (char_u *)"force"; if (type != NULL && check_typval_arg_type(type, &argvars[1], - func_name, 2) == FAIL) + func_name, 2) == FAIL) return; dict_extend(d1, d2, action, func_name); diff --git a/src/edit.c b/src/edit.c index cf114d8bd..2e6a98095 100644 --- a/src/edit.c +++ b/src/edit.c @@ -1510,7 +1510,7 @@ ins_redraw(int ready) // not busy with something } if (ready) - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); // Trigger SafeState if nothing is pending. may_trigger_safestate(ready diff --git a/src/gui.c b/src/gui.c index 45747ef09..585ead00d 100644 --- a/src/gui.c +++ b/src/gui.c @@ -5097,7 +5097,7 @@ gui_update_screen(void) } if (!finish_op) - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); # ifdef FEAT_CONCEAL if (conceal_update_lines diff --git a/src/main.c b/src/main.c index a01331f16..3a050cf5b 100644 --- a/src/main.c +++ b/src/main.c @@ -1358,7 +1358,7 @@ main_loop( validate_cursor(); if (!finish_op) - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); // If nothing is pending and we are going to wait for the user to // type a character, trigger SafeState. diff --git a/src/mouse.c b/src/mouse.c index 32407eb39..b83523a26 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -1171,7 +1171,7 @@ do_mousescroll(cmdarg_T *cap) leftcol = 0; do_mousescroll_horiz((long_u)leftcol); } - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); } /* diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro index 713ae245e..1f55f2d27 100644 --- a/src/proto/autocmd.pro +++ b/src/proto/autocmd.pro @@ -16,6 +16,7 @@ int apply_autocmds(event_T event, char_u *fname, char_u *fname_io, int force, bu int apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, exarg_T *eap); int apply_autocmds_retval(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, int *retval); int trigger_cursorhold(void); +int has_winresized(void); int has_winscrolled(void); int has_cursormoved(void); int has_cursormovedI(void); diff --git a/src/proto/window.pro b/src/proto/window.pro index d675b7189..6522466be 100644 --- a/src/proto/window.pro +++ b/src/proto/window.pro @@ -20,7 +20,7 @@ int one_window(void); int win_close(win_T *win, int free_buf); void snapshot_windows_scroll_size(void); void may_make_initial_scroll_size_snapshot(void); -void may_trigger_winscrolled(void); +void may_trigger_win_scrolled_resized(void); 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); diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index bcd4c53d2..aa204c4f3 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -306,6 +306,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!\") + call TermWait(buf) + + call term_sendkeys(buf, ":echo g:resized\") + call WaitForAssert({-> assert_match('^0$', term_getline(buf, 10))}, 1000) + + " increase window height, two windows will be reported + call term_sendkeys(buf, "\+") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:resized\") + 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, "\>") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:resized\") + 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 @@ -316,11 +371,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('')) au WinScrolled * let g:afile = str2nr(expand('')) + au WinScrolled * call WriteScrollEvent() END call writefile(lines, 'Xtest_winscrolled', 'D') let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6}) @@ -332,15 +391,33 @@ func Test_WinScrolled() call term_sendkeys(buf, "zlzh:echo g:scrolled\") 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, "\\:echo g:scrolled\") 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\\\i\\\") call term_sendkeys(buf, ":echo g:scrolled\") 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. @@ -348,6 +425,12 @@ func Test_WinScrolled() call term_sendkeys(buf, ":echo g:scrolled\") 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\") call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) @@ -356,6 +439,7 @@ func Test_WinScrolled() call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\") call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + call delete('XscrollEvent') call StopVimInTerminal(buf) endfunc diff --git a/src/version.c b/src/version.c index 8a71fd376..4a2ad7782 100644 --- a/src/version.c +++ b/src/version.c @@ -695,6 +695,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 917, /**/ 916, /**/ diff --git a/src/vim.h b/src/vim.h index be0a640ca..14630e601 100644 --- a/src/vim.h +++ b/src/vim.h @@ -1407,7 +1407,8 @@ 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 + EVENT_WINRESIZED, // after a window was resized + EVENT_WINSCROLLED, // after a window was scrolled or resized NUM_EVENTS // MUST be the last one }; diff --git a/src/window.c b/src/window.c index 35be397d2..04ffc4791 100644 --- a/src/window.c +++ b/src/window.c @@ -2873,46 +2873,273 @@ may_make_initial_scroll_size_snapshot(void) } /* - * Trigger WinScrolled if any window scrolled or changed size. + * Create a dictionary with information about size and scroll changes in a + * window. + * Returns the dictionary with refcount set to one. + * Returns NULL when out of memory. + */ + static dict_T * +make_win_info_dict( + int width, + int height, + int topline, + int leftcol, + int skipcol) +{ + dict_T *d = dict_alloc(); + if (d == NULL) + return NULL; + d->dv_refcount = 1; + + // not actually looping, for breaking out on error + while (1) + { + typval_T tv; + tv.v_lock = 0; + tv.v_type = VAR_NUMBER; + + tv.vval.v_number = width; + if (dict_add_tv(d, "width", &tv) == FAIL) + break; + tv.vval.v_number = height; + if (dict_add_tv(d, "height", &tv) == FAIL) + break; + tv.vval.v_number = topline; + if (dict_add_tv(d, "topline", &tv) == FAIL) + break; + tv.vval.v_number = leftcol; + if (dict_add_tv(d, "leftcol", &tv) == FAIL) + break; + tv.vval.v_number = skipcol; + if (dict_add_tv(d, "skipcol", &tv) == FAIL) + break; + return d; + } + dict_unref(d); + return NULL; +} + +// Return values of check_window_scroll_resize(): +#define CWSR_SCROLLED 1 // at least one window scrolled +#define 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; + + win_T *wp; + FOR_ALL_WINDOWS(wp) + { + int 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; + tv.v_lock = 0; + tv.v_type = VAR_NUMBER; + tv.vval.v_number = wp->w_id; + list_set_item(winlist, listidx++, &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; + } + } + + int 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]; + vim_snprintf(winid, sizeof(winid), "%d", wp->w_id); + if (dict_add_dict(v_event, winid, d) == FAIL) + { + 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 (dict_add_dict(v_event, "all", alldict) == FAIL) + 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_winscrolled(void) +may_trigger_win_scrolled_resized(void) { static int recursive = FALSE; + int do_resize = has_winresized(); + int do_scroll = has_winscrolled(); + // Do not trigger WinScrolled or WinResized recursively. Do not trigger + // before the initial snapshot of the w_last_ values was made. if (recursive - || !has_winscrolled() + || !(do_scroll || do_resize) || !did_initial_scroll_size_snapshot) return; - win_T *wp; - FOR_ALL_WINDOWS(wp) - 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) + 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 = list_alloc_with_items(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 = dict_alloc(); + if (scroll_dict != NULL) { - // 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(); + scroll_dict->dv_refcount = 1; + (void)check_window_scroll_resize(NULL, NULL, NULL, NULL, + scroll_dict); + } + } - // "curwin" may be different from the actual current window, make - // sure it can be restored. - window_layout_lock(); + // 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; - char_u winid[NUMBUFLEN]; - vim_snprintf((char *)winid, sizeof(winid), "%d", wp->w_id); - apply_autocmds(EVENT_WINSCROLLED, winid, winid, FALSE, - wp->w_buffer); - recursive = FALSE; - window_layout_unlock(); + // "curwin" may be different from the actual current window, make + // sure it can be restored. + window_layout_lock(); + recursive = TRUE; - break; + // 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 (dict_add_list(v_event, "windows", windows_list) == OK) + { + dict_set_items_ro(v_event); + + char_u winid[NUMBUFLEN]; + vim_snprintf((char *)winid, sizeof(winid), "%d", + first_size_win->w_id); + 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. + dict_extend(v_event, scroll_dict, (char_u *)"move", NULL); + dict_set_items_ro(v_event); + dict_unref(scroll_dict); + + char_u winid[NUMBUFLEN]; + vim_snprintf((char *)winid, sizeof(winid), "%d", + first_scroll_win->w_id); + apply_autocmds(EVENT_WINSCROLLED, winid, winid, FALSE, + first_scroll_win->w_buffer); + + restore_v_event(v_event, &save_v_event); + } + + recursive = FALSE; + window_layout_unlock(); } /* -- cgit v1.2.1