summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-11-22 12:40:50 +0000
committerBram Moolenaar <Bram@vim.org>2022-11-22 12:40:50 +0000
commit35fc61cb5b5eba8bbb9d8f0700332fbab38f40ca (patch)
tree352599f641e6c8c88d8574871f8b8e86b6168dba
parentce30ccc06af7f2c03762e5b18dde37b26ea6ec42 (diff)
downloadvim-git-35fc61cb5b5eba8bbb9d8f0700332fbab38f40ca.tar.gz
patch 9.0.0917: the WinScrolled autocommand event is not enoughv9.0.0917
Problem: The WinScrolled autocommand event is not enough. Solution: Add WinResized and provide information about what changed. (closes #11576)
-rw-r--r--runtime/doc/autocmd.txt33
-rw-r--r--runtime/doc/windows.txt48
-rw-r--r--src/autocmd.c20
-rw-r--r--src/dict.c29
-rw-r--r--src/edit.c2
-rw-r--r--src/gui.c2
-rw-r--r--src/main.c2
-rw-r--r--src/mouse.c2
-rw-r--r--src/proto/autocmd.pro1
-rw-r--r--src/proto/window.pro2
-rw-r--r--src/testdir/test_autocmd.vim84
-rw-r--r--src/version.c2
-rw-r--r--src/vim.h3
-rw-r--r--src/window.c279
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 <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
@@ -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;
@@ -1811,6 +1813,15 @@ trigger_cursorhold(void)
}
/*
+ * 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.
*/
int
@@ -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!\<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
@@ -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('<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})
@@ -332,15 +391,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.
@@ -348,6 +425,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)
@@ -356,6 +439,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
diff --git a/src/version.c b/src/version.c
index 8a71fd376..4a2ad7782 100644
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 917,
+/**/
916,
/**/
915,
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();
}
/*