• R/O
  • SSH

vim: Commit

Mirror of the Vim source from https://github.com/vim/vim


Commit MetaInfo

Revisiona86ee6c0309ec6b8f81239956e4d128da5ab31ef (tree)
Time2022-11-22 21:45:04
AuthorBram Moolenaar <Bram@vim....>
CommiterBram Moolenaar

Log Message

patch 9.0.0917: the WinScrolled autocommand event is not enough

Commit: https://github.com/vim/vim/commit/35fc61cb5b5eba8bbb9d8f0700332fbab38f40ca
Author: Bram Moolenaar <Bram@vim.org>
Date: Tue Nov 22 12:40:50 2022 +0000

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)

Change Summary

Incremental Difference

diff -r b1c24c44852a -r a86ee6c0309e runtime/doc/autocmd.txt
--- a/runtime/doc/autocmd.txt Mon Nov 21 21:00:07 2022 +0100
+++ b/runtime/doc/autocmd.txt Tue Nov 22 13:45:04 2022 +0100
@@ -1371,21 +1371,24 @@
13711371 Before a WinEnter event.
13721372
13731373 *WinScrolled*
1374-WinScrolled After scrolling the content of a window or
1375- resizing a window in the current tab page.
1376-
1377- When more than one window scrolled or resized
1378- only one WinScrolled event is triggered. You
1379- can use the `winlayout()` and `getwininfo()`
1380- functions to see what changed.
1374+WinScrolled After any window in the current tab page
1375+ scrolled the text (horizontally or vertically)
1376+ or changed width or height. See
1377+ |win-scrolled-resized|.
13811378
13821379 The pattern is matched against the |window-ID|
13831380 of the first window that scrolled or resized.
13841381 Both <amatch> and <afile> are set to the
13851382 |window-ID|.
13861383
1384+ |v:event| is set with information about size
1385+ and scroll changes. |WinScrolled-event|
1386+
13871387 Only starts triggering after startup finished
13881388 and the first screen redraw was done.
1389+ Does not trigger when defining the first
1390+ WinScrolled or WinResized event, but may
1391+ trigger when adding more.
13891392
13901393 Non-recursive: the event will not trigger
13911394 while executing commands for the WinScrolled
@@ -1393,11 +1396,17 @@
13931396 window to scroll or change size, then another
13941397 WinScrolled event will be triggered later.
13951398
1396- Does not trigger when the command is added,
1397- only after the first scroll or resize.
1398- *E1312*
1399- It is not allowed to change the window layout
1400- here (split, close or move windows).
1399+
1400+ *WinResized*
1401+WinResized After a window in the current tab page changed
1402+ width or height.
1403+ See |win-scrolled-resized|.
1404+
1405+ |v:event| is set with information about size
1406+ changes. |WinResized-event|
1407+
1408+ Same behavior as |WinScrolled| for the
1409+ pattern, triggering and recursiveness.
14011410
14021411 ==============================================================================
14031412 6. Patterns *autocmd-patterns* *{aupat}*
diff -r b1c24c44852a -r a86ee6c0309e runtime/doc/windows.txt
--- a/runtime/doc/windows.txt Mon Nov 21 21:00:07 2022 +0100
+++ b/runtime/doc/windows.txt Tue Nov 22 13:45:04 2022 +0100
@@ -631,6 +631,54 @@
631631 The minimal height and width of a window is set with 'winminheight' and
632632 'winminwidth'. These are hard values, a window will never become smaller.
633633
634+
635+WinScrolled and WinResized autocommands ~
636+ *win-scrolled-resized*
637+If you want to get notified of changes in window sizes, the |WinResized|
638+autocommand event can be used.
639+If you want to get notified of text in windows scrolling vertically or
640+horizontally, the |WinScrolled| autocommand event can be used. This will also
641+trigger in window size changes.
642+ *WinResized-event*
643+The |WinResized| event is triggered after updating the display, several
644+windows may have changed size then. A list of the IDs of windows that changed
645+since last time is provided in the v:event.windows variable, for example:
646+ [1003, 1006]
647+ *WinScrolled-event*
648+The |WinScrolled| event is triggered after |WinResized|, and also if a window
649+was scrolled. That can be vertically (the text at the top of the window
650+changed) or horizontally (when 'wrap' is off or when the first displayed part
651+of the first line changes). Note that |WinScrolled| will trigger many more
652+times than |WinResized|, it may slow down editing a bit.
653+
654+The information provided by |WinScrolled| is a dictionary for each window that
655+has changes, using the window ID as the key, and a total count of the changes
656+with the key "all". Example value for |v:event| (|Vim9| syntax):
657+ {
658+ all: {width: 0, height: 2, leftcol: 0, topline: 1, skipcol: 0},
659+ 1003: {width: 0, height: -1, leftcol: 0, topline: 0, skipcol: 0},
660+ 1006: {width: 0, height: 1, leftcol: 0, topline: 1, skipcol: 0},
661+ }
662+
663+Note that the "all" entry has the absolute values of the individual windows
664+accumulated.
665+
666+If you need more information about what changed, or you want to "debounce" the
667+events (not handle every event to avoid doing too much work), you may want to
668+use the `winlayout()` and `getwininfo()` functions.
669+
670+|WinScrolled| and |WinResized| do not trigger when the first autocommand is
671+added, only after the first scroll or resize. They may trigger when switching
672+to another tab page.
673+
674+The commands executed are expected to not cause window size or scroll changes.
675+If this happens anyway, the event will trigger again very soon. In other
676+words: Just before triggering the event, the current sizes and scroll
677+positions are stored and used to decide whether there was a change.
678+ *E1312*
679+It is not allowed to change the window layout here (split, close or move
680+windows).
681+
634682 ==============================================================================
635683 7. Argument and buffer list commands *buffer-list*
636684
diff -r b1c24c44852a -r a86ee6c0309e src/autocmd.c
--- a/src/autocmd.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/autocmd.c Tue Nov 22 13:45:04 2022 +0100
@@ -191,6 +191,7 @@
191191 {"WinClosed", EVENT_WINCLOSED},
192192 {"WinEnter", EVENT_WINENTER},
193193 {"WinLeave", EVENT_WINLEAVE},
194+ {"WinResized", EVENT_WINRESIZED},
194195 {"WinScrolled", EVENT_WINSCROLLED},
195196 {"VimResized", EVENT_VIMRESIZED},
196197 {"TextYankPost", EVENT_TEXTYANKPOST},
@@ -1263,10 +1264,11 @@
12631264 if (event == EVENT_MODECHANGED && !has_modechanged())
12641265 get_mode(last_mode);
12651266 #endif
1266- // Initialize the fields checked by the WinScrolled trigger to
1267- // prevent it from firing right after the first autocmd is
1268- // defined.
1269- if (event == EVENT_WINSCROLLED && !has_winscrolled())
1267+ // Initialize the fields checked by the WinScrolled and
1268+ // WinResized trigger to prevent them from firing right after
1269+ // the first autocmd is defined.
1270+ if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED)
1271+ && !(has_winscrolled() || has_winresized()))
12701272 {
12711273 tabpage_T *save_curtab = curtab;
12721274 tabpage_T *tp;
@@ -1811,6 +1813,15 @@
18111813 }
18121814
18131815 /*
1816+ * Return TRUE when there is a WinResized autocommand defined.
1817+ */
1818+ int
1819+has_winresized(void)
1820+{
1821+ return (first_autopat[(int)EVENT_WINRESIZED] != NULL);
1822+}
1823+
1824+/*
18141825 * Return TRUE when there is a WinScrolled autocommand defined.
18151826 */
18161827 int
@@ -2117,6 +2128,7 @@
21172128 || event == EVENT_MENUPOPUP
21182129 || event == EVENT_USER
21192130 || event == EVENT_WINCLOSED
2131+ || event == EVENT_WINRESIZED
21202132 || event == EVENT_WINSCROLLED)
21212133 {
21222134 fname = vim_strsave(fname);
diff -r b1c24c44852a -r a86ee6c0309e src/dict.c
--- a/src/dict.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/dict.c Tue Nov 22 13:45:04 2022 +0100
@@ -1082,13 +1082,14 @@
10821082 * Go over all entries in "d2" and add them to "d1".
10831083 * When "action" is "error" then a duplicate key is an error.
10841084 * When "action" is "force" then a duplicate key is overwritten.
1085+ * When "action" is "move" then move items instead of copying.
10851086 * Otherwise duplicate keys are ignored ("action" is "keep").
1087+ * "func_name" is used for reporting where an error occurred.
10861088 */
10871089 void
10881090 dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name)
10891091 {
10901092 dictitem_T *di1;
1091- hashitem_T *hi2;
10921093 int todo;
10931094 char_u *arg_errmsg = (char_u *)N_("extend() argument");
10941095 type_T *type;
@@ -1098,8 +1099,11 @@
10981099 else
10991100 type = NULL;
11001101
1102+ if (*action == 'm')
1103+ hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove()
1104+
11011105 todo = (int)d2->dv_hashtab.ht_used;
1102- for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
1106+ for (hashitem_T *hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
11031107 {
11041108 if (!HASHITEM_EMPTY(hi2))
11051109 {
@@ -1116,9 +1120,19 @@
11161120
11171121 if (di1 == NULL)
11181122 {
1119- di1 = dictitem_copy(HI2DI(hi2));
1120- if (di1 != NULL && dict_add(d1, di1) == FAIL)
1121- dictitem_free(di1);
1123+ if (*action == 'm')
1124+ {
1125+ // cheap way to move a dict item from "d2" to "d1"
1126+ di1 = HI2DI(hi2);
1127+ dict_add(d1, di1);
1128+ hash_remove(&d2->dv_hashtab, hi2);
1129+ }
1130+ else
1131+ {
1132+ di1 = dictitem_copy(HI2DI(hi2));
1133+ if (di1 != NULL && dict_add(d1, di1) == FAIL)
1134+ dictitem_free(di1);
1135+ }
11221136 }
11231137 else if (*action == 'e')
11241138 {
@@ -1138,6 +1152,9 @@
11381152 }
11391153 }
11401154 }
1155+
1156+ if (*action == 'm')
1157+ hash_unlock(&d2->dv_hashtab);
11411158 }
11421159
11431160 /*
@@ -1272,7 +1289,7 @@
12721289 action = (char_u *)"force";
12731290
12741291 if (type != NULL && check_typval_arg_type(type, &argvars[1],
1275- func_name, 2) == FAIL)
1292+ func_name, 2) == FAIL)
12761293 return;
12771294 dict_extend(d1, d2, action, func_name);
12781295
diff -r b1c24c44852a -r a86ee6c0309e src/edit.c
--- a/src/edit.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/edit.c Tue Nov 22 13:45:04 2022 +0100
@@ -1510,7 +1510,7 @@
15101510 }
15111511
15121512 if (ready)
1513- may_trigger_winscrolled();
1513+ may_trigger_win_scrolled_resized();
15141514
15151515 // Trigger SafeState if nothing is pending.
15161516 may_trigger_safestate(ready
diff -r b1c24c44852a -r a86ee6c0309e src/gui.c
--- a/src/gui.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/gui.c Tue Nov 22 13:45:04 2022 +0100
@@ -5097,7 +5097,7 @@
50975097 }
50985098
50995099 if (!finish_op)
5100- may_trigger_winscrolled();
5100+ may_trigger_win_scrolled_resized();
51015101
51025102 # ifdef FEAT_CONCEAL
51035103 if (conceal_update_lines
diff -r b1c24c44852a -r a86ee6c0309e src/main.c
--- a/src/main.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/main.c Tue Nov 22 13:45:04 2022 +0100
@@ -1358,7 +1358,7 @@
13581358 validate_cursor();
13591359
13601360 if (!finish_op)
1361- may_trigger_winscrolled();
1361+ may_trigger_win_scrolled_resized();
13621362
13631363 // If nothing is pending and we are going to wait for the user to
13641364 // type a character, trigger SafeState.
diff -r b1c24c44852a -r a86ee6c0309e src/mouse.c
--- a/src/mouse.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/mouse.c Tue Nov 22 13:45:04 2022 +0100
@@ -1171,7 +1171,7 @@
11711171 leftcol = 0;
11721172 do_mousescroll_horiz((long_u)leftcol);
11731173 }
1174- may_trigger_winscrolled();
1174+ may_trigger_win_scrolled_resized();
11751175 }
11761176
11771177 /*
diff -r b1c24c44852a -r a86ee6c0309e src/proto/autocmd.pro
--- a/src/proto/autocmd.pro Mon Nov 21 21:00:07 2022 +0100
+++ b/src/proto/autocmd.pro Tue Nov 22 13:45:04 2022 +0100
@@ -16,6 +16,7 @@
1616 int apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, exarg_T *eap);
1717 int apply_autocmds_retval(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, int *retval);
1818 int trigger_cursorhold(void);
19+int has_winresized(void);
1920 int has_winscrolled(void);
2021 int has_cursormoved(void);
2122 int has_cursormovedI(void);
diff -r b1c24c44852a -r a86ee6c0309e src/proto/window.pro
--- a/src/proto/window.pro Mon Nov 21 21:00:07 2022 +0100
+++ b/src/proto/window.pro Tue Nov 22 13:45:04 2022 +0100
@@ -20,7 +20,7 @@
2020 int win_close(win_T *win, int free_buf);
2121 void snapshot_windows_scroll_size(void);
2222 void may_make_initial_scroll_size_snapshot(void);
23-void may_trigger_winscrolled(void);
23+void may_trigger_win_scrolled_resized(void);
2424 void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp);
2525 void win_free_all(void);
2626 win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp);
diff -r b1c24c44852a -r a86ee6c0309e src/testdir/test_autocmd.vim
--- a/src/testdir/test_autocmd.vim Mon Nov 21 21:00:07 2022 +0100
+++ b/src/testdir/test_autocmd.vim Tue Nov 22 13:45:04 2022 +0100
@@ -306,6 +306,61 @@
306306 unlet g:record
307307 endfunc
308308
309+func Test_WinResized()
310+ CheckRunVimInTerminal
311+
312+ let lines =<< trim END
313+ set scrolloff=0
314+ call setline(1, ['111', '222'])
315+ vnew
316+ call setline(1, ['aaa', 'bbb'])
317+ new
318+ call setline(1, ['foo', 'bar'])
319+
320+ let g:resized = 0
321+ au WinResized * let g:resized += 1
322+
323+ func WriteResizedEvent()
324+ call writefile([json_encode(v:event)], 'XresizeEvent')
325+ endfunc
326+ au WinResized * call WriteResizedEvent()
327+ END
328+ call writefile(lines, 'Xtest_winresized', 'D')
329+ let buf = RunVimInTerminal('-S Xtest_winresized', {'rows': 10})
330+
331+ " redraw now to avoid a redraw after the :echo command
332+ call term_sendkeys(buf, ":redraw!\<CR>")
333+ call TermWait(buf)
334+
335+ call term_sendkeys(buf, ":echo g:resized\<CR>")
336+ call WaitForAssert({-> assert_match('^0$', term_getline(buf, 10))}, 1000)
337+
338+ " increase window height, two windows will be reported
339+ call term_sendkeys(buf, "\<C-W>+")
340+ call TermWait(buf)
341+ call term_sendkeys(buf, ":echo g:resized\<CR>")
342+ call WaitForAssert({-> assert_match('^1$', term_getline(buf, 10))}, 1000)
343+
344+ let event = readfile('XresizeEvent')[0]->json_decode()
345+ call assert_equal({
346+ \ 'windows': [1002, 1001],
347+ \ }, event)
348+
349+ " increase window width, three windows will be reported
350+ call term_sendkeys(buf, "\<C-W>>")
351+ call TermWait(buf)
352+ call term_sendkeys(buf, ":echo g:resized\<CR>")
353+ call WaitForAssert({-> assert_match('^2$', term_getline(buf, 10))}, 1000)
354+
355+ let event = readfile('XresizeEvent')[0]->json_decode()
356+ call assert_equal({
357+ \ 'windows': [1002, 1001, 1000],
358+ \ }, event)
359+
360+ call delete('XresizeEvent')
361+ call StopVimInTerminal(buf)
362+endfunc
363+
309364 func Test_WinScrolled()
310365 CheckRunVimInTerminal
311366
@@ -316,11 +371,15 @@
316371 endfor
317372 let win_id = win_getid()
318373 let g:matched = v:false
374+ func WriteScrollEvent()
375+ call writefile([json_encode(v:event)], 'XscrollEvent')
376+ endfunc
319377 execute 'au WinScrolled' win_id 'let g:matched = v:true'
320378 let g:scrolled = 0
321379 au WinScrolled * let g:scrolled += 1
322380 au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
323381 au WinScrolled * let g:afile = str2nr(expand('<afile>'))
382+ au WinScrolled * call WriteScrollEvent()
324383 END
325384 call writefile(lines, 'Xtest_winscrolled', 'D')
326385 let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6})
@@ -332,15 +391,33 @@
332391 call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>")
333392 call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
334393
394+ let event = readfile('XscrollEvent')[0]->json_decode()
395+ call assert_equal({
396+ \ 'all': {'leftcol': 1, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0},
397+ \ '1000': {'leftcol': -1, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0}
398+ \ }, event)
399+
335400 " Scroll up/down in Normal mode.
336401 call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>")
337402 call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000)
338403
404+ let event = readfile('XscrollEvent')[0]->json_decode()
405+ call assert_equal({
406+ \ 'all': {'leftcol': 0, 'topline': 1, 'width': 0, 'height': 0, 'skipcol': 0},
407+ \ '1000': {'leftcol': 0, 'topline': -1, 'width': 0, 'height': 0, 'skipcol': 0}
408+ \ }, event)
409+
339410 " Scroll up/down in Insert mode.
340411 call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>")
341412 call term_sendkeys(buf, ":echo g:scrolled\<CR>")
342413 call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000)
343414
415+ let event = readfile('XscrollEvent')[0]->json_decode()
416+ call assert_equal({
417+ \ 'all': {'leftcol': 0, 'topline': 1, 'width': 0, 'height': 0, 'skipcol': 0},
418+ \ '1000': {'leftcol': 0, 'topline': -1, 'width': 0, 'height': 0, 'skipcol': 0}
419+ \ }, event)
420+
344421 " Scroll the window horizontally to focus the last letter of the third line
345422 " containing only six characters. Moving to the previous and shorter lines
346423 " should trigger another autocommand as Vim has to make them visible.
@@ -348,6 +425,12 @@
348425 call term_sendkeys(buf, ":echo g:scrolled\<CR>")
349426 call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000)
350427
428+ let event = readfile('XscrollEvent')[0]->json_decode()
429+ call assert_equal({
430+ \ 'all': {'leftcol': 5, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0},
431+ \ '1000': {'leftcol': -5, 'topline': 0, 'width': 0, 'height': 0, 'skipcol': 0}
432+ \ }, event)
433+
351434 " Ensure the command was triggered for the specified window ID.
352435 call term_sendkeys(buf, ":echo g:matched\<CR>")
353436 call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
@@ -356,6 +439,7 @@
356439 call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>")
357440 call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
358441
442+ call delete('XscrollEvent')
359443 call StopVimInTerminal(buf)
360444 endfunc
361445
diff -r b1c24c44852a -r a86ee6c0309e src/version.c
--- a/src/version.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/version.c Tue Nov 22 13:45:04 2022 +0100
@@ -696,6 +696,8 @@
696696 static int included_patches[] =
697697 { /* Add new patch number below this line */
698698 /**/
699+ 917,
700+/**/
699701 916,
700702 /**/
701703 915,
diff -r b1c24c44852a -r a86ee6c0309e src/vim.h
--- a/src/vim.h Mon Nov 21 21:00:07 2022 +0100
+++ b/src/vim.h Tue Nov 22 13:45:04 2022 +0100
@@ -1407,7 +1407,8 @@
14071407 EVENT_WINCLOSED, // after closing a window
14081408 EVENT_VIMSUSPEND, // before Vim is suspended
14091409 EVENT_VIMRESUME, // after Vim is resumed
1410- EVENT_WINSCROLLED, // after Vim window was scrolled
1410+ EVENT_WINRESIZED, // after a window was resized
1411+ EVENT_WINSCROLLED, // after a window was scrolled or resized
14111412
14121413 NUM_EVENTS // MUST be the last one
14131414 };
diff -r b1c24c44852a -r a86ee6c0309e src/window.c
--- a/src/window.c Mon Nov 21 21:00:07 2022 +0100
+++ b/src/window.c Tue Nov 22 13:45:04 2022 +0100
@@ -2873,46 +2873,273 @@
28732873 }
28742874
28752875 /*
2876- * Trigger WinScrolled if any window scrolled or changed size.
2877- */
2878- void
2879-may_trigger_winscrolled(void)
2880-{
2881- static int recursive = FALSE;
2882-
2883- if (recursive
2884- || !has_winscrolled()
2885- || !did_initial_scroll_size_snapshot)
2886- return;
2876+ * Create a dictionary with information about size and scroll changes in a
2877+ * window.
2878+ * Returns the dictionary with refcount set to one.
2879+ * Returns NULL when out of memory.
2880+ */
2881+ static dict_T *
2882+make_win_info_dict(
2883+ int width,
2884+ int height,
2885+ int topline,
2886+ int leftcol,
2887+ int skipcol)
2888+{
2889+ dict_T *d = dict_alloc();
2890+ if (d == NULL)
2891+ return NULL;
2892+ d->dv_refcount = 1;
2893+
2894+ // not actually looping, for breaking out on error
2895+ while (1)
2896+ {
2897+ typval_T tv;
2898+ tv.v_lock = 0;
2899+ tv.v_type = VAR_NUMBER;
2900+
2901+ tv.vval.v_number = width;
2902+ if (dict_add_tv(d, "width", &tv) == FAIL)
2903+ break;
2904+ tv.vval.v_number = height;
2905+ if (dict_add_tv(d, "height", &tv) == FAIL)
2906+ break;
2907+ tv.vval.v_number = topline;
2908+ if (dict_add_tv(d, "topline", &tv) == FAIL)
2909+ break;
2910+ tv.vval.v_number = leftcol;
2911+ if (dict_add_tv(d, "leftcol", &tv) == FAIL)
2912+ break;
2913+ tv.vval.v_number = skipcol;
2914+ if (dict_add_tv(d, "skipcol", &tv) == FAIL)
2915+ break;
2916+ return d;
2917+ }
2918+ dict_unref(d);
2919+ return NULL;
2920+}
2921+
2922+// Return values of check_window_scroll_resize():
2923+#define CWSR_SCROLLED 1 // at least one window scrolled
2924+#define CWSR_RESIZED 2 // at least one window size changed
2925+
2926+/*
2927+ * This function is used for three purposes:
2928+ * 1. Goes over all windows in the current tab page and returns:
2929+ * 0 no scrolling and no size changes found
2930+ * CWSR_SCROLLED at least one window scrolled
2931+ * CWSR_RESIZED at least one window changed size
2932+ * CWSR_SCROLLED + CWSR_RESIZED both
2933+ * "size_count" is set to the nr of windows with size changes.
2934+ * "first_scroll_win" is set to the first window with any relevant changes.
2935+ * "first_size_win" is set to the first window with size changes.
2936+ *
2937+ * 2. When the first three arguments are NULL and "winlist" is not NULL,
2938+ * "winlist" is set to the list of window IDs with size changes.
2939+ *
2940+ * 3. When the first three arguments are NULL and "v_event" is not NULL,
2941+ * information about changed windows is added to "v_event".
2942+ */
2943+ static int
2944+check_window_scroll_resize(
2945+ int *size_count,
2946+ win_T **first_scroll_win,
2947+ win_T **first_size_win,
2948+ list_T *winlist,
2949+ dict_T *v_event)
2950+{
2951+ int result = 0;
2952+ int listidx = 0;
2953+ int tot_width = 0;
2954+ int tot_height = 0;
2955+ int tot_topline = 0;
2956+ int tot_leftcol = 0;
2957+ int tot_skipcol = 0;
28872958
28882959 win_T *wp;
28892960 FOR_ALL_WINDOWS(wp)
2890- if (wp->w_last_topline != wp->w_topline
2891- || wp->w_last_leftcol != wp->w_leftcol
2892- || wp->w_last_skipcol != wp->w_skipcol
2893- || wp->w_last_width != wp->w_width
2894- || wp->w_last_height != wp->w_height)
2961+ {
2962+ int size_changed = wp->w_last_width != wp->w_width
2963+ || wp->w_last_height != wp->w_height;
2964+ if (size_changed)
28952965 {
2896- // WinScrolled is triggered only once, even when multiple windows
2897- // scrolled or changed size. Store the current values before
2898- // triggering the event, if a scroll or resize happens as a side
2899- // effect then WinScrolled is triggered again later.
2900- snapshot_windows_scroll_size();
2901-
2902- // "curwin" may be different from the actual current window, make
2903- // sure it can be restored.
2904- window_layout_lock();
2905-
2906- recursive = TRUE;
2966+ result |= CWSR_RESIZED;
2967+ if (winlist != NULL)
2968+ {
2969+ // Add this window to the list of changed windows.
2970+ typval_T tv;
2971+ tv.v_lock = 0;
2972+ tv.v_type = VAR_NUMBER;
2973+ tv.vval.v_number = wp->w_id;
2974+ list_set_item(winlist, listidx++, &tv);
2975+ }
2976+ else if (size_count != NULL)
2977+ {
2978+ ++*size_count;
2979+ if (*first_size_win == NULL)
2980+ *first_size_win = wp;
2981+ // For WinScrolled the first window with a size change is used
2982+ // even when it didn't scroll.
2983+ if (*first_scroll_win == NULL)
2984+ *first_scroll_win = wp;
2985+ }
2986+ }
2987+
2988+ int scroll_changed = wp->w_last_topline != wp->w_topline
2989+ || wp->w_last_leftcol != wp->w_leftcol
2990+ || wp->w_last_skipcol != wp->w_skipcol;
2991+ if (scroll_changed)
2992+ {
2993+ result |= CWSR_SCROLLED;
2994+ if (first_scroll_win != NULL && *first_scroll_win == NULL)
2995+ *first_scroll_win = wp;
2996+ }
2997+
2998+ if ((size_changed || scroll_changed) && v_event != NULL)
2999+ {
3000+ // Add info about this window to the v:event dictionary.
3001+ int width = wp->w_width - wp->w_last_width;
3002+ int height = wp->w_height - wp->w_last_height;
3003+ int topline = wp->w_topline - wp->w_last_topline;
3004+ int leftcol = wp->w_leftcol - wp->w_last_leftcol;
3005+ int skipcol = wp->w_skipcol - wp->w_last_skipcol;
3006+ dict_T *d = make_win_info_dict(width, height,
3007+ topline, leftcol, skipcol);
3008+ if (d == NULL)
3009+ break;
3010+ char winid[NUMBUFLEN];
3011+ vim_snprintf(winid, sizeof(winid), "%d", wp->w_id);
3012+ if (dict_add_dict(v_event, winid, d) == FAIL)
3013+ {
3014+ dict_unref(d);
3015+ break;
3016+ }
3017+ --d->dv_refcount;
3018+
3019+ tot_width += abs(width);
3020+ tot_height += abs(height);
3021+ tot_topline += abs(topline);
3022+ tot_leftcol += abs(leftcol);
3023+ tot_skipcol += abs(skipcol);
3024+ }
3025+ }
3026+
3027+ if (v_event != NULL)
3028+ {
3029+ dict_T *alldict = make_win_info_dict(tot_width, tot_height,
3030+ tot_topline, tot_leftcol, tot_skipcol);
3031+ if (alldict != NULL)
3032+ {
3033+ if (dict_add_dict(v_event, "all", alldict) == FAIL)
3034+ dict_unref(alldict);
3035+ else
3036+ --alldict->dv_refcount;
3037+ }
3038+ }
3039+
3040+ return result;
3041+}
3042+
3043+/*
3044+ * Trigger WinScrolled and/or WinResized if any window in the current tab page
3045+ * scrolled or changed size.
3046+ */
3047+ void
3048+may_trigger_win_scrolled_resized(void)
3049+{
3050+ static int recursive = FALSE;
3051+ int do_resize = has_winresized();
3052+ int do_scroll = has_winscrolled();
3053+
3054+ // Do not trigger WinScrolled or WinResized recursively. Do not trigger
3055+ // before the initial snapshot of the w_last_ values was made.
3056+ if (recursive
3057+ || !(do_scroll || do_resize)
3058+ || !did_initial_scroll_size_snapshot)
3059+ return;
3060+
3061+ int size_count = 0;
3062+ win_T *first_scroll_win = NULL, *first_size_win = NULL;
3063+ int cwsr = check_window_scroll_resize(&size_count,
3064+ &first_scroll_win, &first_size_win,
3065+ NULL, NULL);
3066+ int trigger_resize = do_resize && size_count > 0;
3067+ int trigger_scroll = do_scroll && cwsr != 0;
3068+ if (!trigger_resize && !trigger_scroll)
3069+ return; // no relevant changes
3070+
3071+ list_T *windows_list = NULL;
3072+ if (trigger_resize)
3073+ {
3074+ // Create the list for v:event.windows before making the snapshot.
3075+ windows_list = list_alloc_with_items(size_count);
3076+ (void)check_window_scroll_resize(NULL, NULL, NULL, windows_list, NULL);
3077+ }
3078+
3079+ dict_T *scroll_dict = NULL;
3080+ if (trigger_scroll)
3081+ {
3082+ // Create the dict with entries for v:event before making the snapshot.
3083+ scroll_dict = dict_alloc();
3084+ if (scroll_dict != NULL)
3085+ {
3086+ scroll_dict->dv_refcount = 1;
3087+ (void)check_window_scroll_resize(NULL, NULL, NULL, NULL,
3088+ scroll_dict);
3089+ }
3090+ }
3091+
3092+ // WinScrolled/WinResized are triggered only once, even when multiple
3093+ // windows scrolled or changed size. Store the current values before
3094+ // triggering the event, if a scroll or resize happens as a side effect
3095+ // then WinScrolled/WinResized is triggered for that later.
3096+ snapshot_windows_scroll_size();
3097+
3098+ // "curwin" may be different from the actual current window, make
3099+ // sure it can be restored.
3100+ window_layout_lock();
3101+ recursive = TRUE;
3102+
3103+ // If both are to be triggered do WinResized first.
3104+ if (trigger_resize)
3105+ {
3106+ save_v_event_T save_v_event;
3107+ dict_T *v_event = get_v_event(&save_v_event);
3108+
3109+ if (dict_add_list(v_event, "windows", windows_list) == OK)
3110+ {
3111+ dict_set_items_ro(v_event);
3112+
29073113 char_u winid[NUMBUFLEN];
2908- vim_snprintf((char *)winid, sizeof(winid), "%d", wp->w_id);
2909- apply_autocmds(EVENT_WINSCROLLED, winid, winid, FALSE,
2910- wp->w_buffer);
2911- recursive = FALSE;
2912- window_layout_unlock();
2913-
2914- break;
3114+ vim_snprintf((char *)winid, sizeof(winid), "%d",
3115+ first_size_win->w_id);
3116+ apply_autocmds(EVENT_WINRESIZED, winid, winid, FALSE,
3117+ first_size_win->w_buffer);
29153118 }
3119+ restore_v_event(v_event, &save_v_event);
3120+ }
3121+
3122+ if (trigger_scroll)
3123+ {
3124+ save_v_event_T save_v_event;
3125+ dict_T *v_event = get_v_event(&save_v_event);
3126+
3127+ // Move the entries from scroll_dict to v_event.
3128+ dict_extend(v_event, scroll_dict, (char_u *)"move", NULL);
3129+ dict_set_items_ro(v_event);
3130+ dict_unref(scroll_dict);
3131+
3132+ char_u winid[NUMBUFLEN];
3133+ vim_snprintf((char *)winid, sizeof(winid), "%d",
3134+ first_scroll_win->w_id);
3135+ apply_autocmds(EVENT_WINSCROLLED, winid, winid, FALSE,
3136+ first_scroll_win->w_buffer);
3137+
3138+ restore_v_event(v_event, &save_v_event);
3139+ }
3140+
3141+ recursive = FALSE;
3142+ window_layout_unlock();
29163143 }
29173144
29183145 /*
Show on old repository browser