forked from aniani/vim
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)
This commit is contained in:
@@ -1371,21 +1371,24 @@ WinNew When a new window was created. Not done for
|
|||||||
Before a WinEnter event.
|
Before a WinEnter event.
|
||||||
|
|
||||||
*WinScrolled*
|
*WinScrolled*
|
||||||
WinScrolled After scrolling the content of a window or
|
WinScrolled After any window in the current tab page
|
||||||
resizing a window in the current tab page.
|
scrolled the text (horizontally or vertically)
|
||||||
|
or changed width or height. See
|
||||||
When more than one window scrolled or resized
|
|win-scrolled-resized|.
|
||||||
only one WinScrolled event is triggered. You
|
|
||||||
can use the `winlayout()` and `getwininfo()`
|
|
||||||
functions to see what changed.
|
|
||||||
|
|
||||||
The pattern is matched against the |window-ID|
|
The pattern is matched against the |window-ID|
|
||||||
of the first window that scrolled or resized.
|
of the first window that scrolled or resized.
|
||||||
Both <amatch> and <afile> are set to the
|
Both <amatch> and <afile> are set to the
|
||||||
|window-ID|.
|
|window-ID|.
|
||||||
|
|
||||||
|
|v:event| is set with information about size
|
||||||
|
and scroll changes. |WinScrolled-event|
|
||||||
|
|
||||||
Only starts triggering after startup finished
|
Only starts triggering after startup finished
|
||||||
and the first screen redraw was done.
|
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
|
Non-recursive: the event will not trigger
|
||||||
while executing commands for the WinScrolled
|
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
|
window to scroll or change size, then another
|
||||||
WinScrolled event will be triggered later.
|
WinScrolled event will be triggered later.
|
||||||
|
|
||||||
Does not trigger when the command is added,
|
|
||||||
only after the first scroll or resize.
|
*WinResized*
|
||||||
*E1312*
|
WinResized After a window in the current tab page changed
|
||||||
It is not allowed to change the window layout
|
width or height.
|
||||||
here (split, close or move windows).
|
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}*
|
6. Patterns *autocmd-patterns* *{aupat}*
|
||||||
|
@@ -631,6 +631,54 @@ it).
|
|||||||
The minimal height and width of a window is set with 'winminheight' and
|
The minimal height and width of a window is set with 'winminheight' and
|
||||||
'winminwidth'. These are hard values, a window will never become smaller.
|
'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*
|
7. Argument and buffer list commands *buffer-list*
|
||||||
|
|
||||||
|
@@ -191,6 +191,7 @@ static struct event_name
|
|||||||
{"WinClosed", EVENT_WINCLOSED},
|
{"WinClosed", EVENT_WINCLOSED},
|
||||||
{"WinEnter", EVENT_WINENTER},
|
{"WinEnter", EVENT_WINENTER},
|
||||||
{"WinLeave", EVENT_WINLEAVE},
|
{"WinLeave", EVENT_WINLEAVE},
|
||||||
|
{"WinResized", EVENT_WINRESIZED},
|
||||||
{"WinScrolled", EVENT_WINSCROLLED},
|
{"WinScrolled", EVENT_WINSCROLLED},
|
||||||
{"VimResized", EVENT_VIMRESIZED},
|
{"VimResized", EVENT_VIMRESIZED},
|
||||||
{"TextYankPost", EVENT_TEXTYANKPOST},
|
{"TextYankPost", EVENT_TEXTYANKPOST},
|
||||||
@@ -1263,10 +1264,11 @@ do_autocmd_event(
|
|||||||
if (event == EVENT_MODECHANGED && !has_modechanged())
|
if (event == EVENT_MODECHANGED && !has_modechanged())
|
||||||
get_mode(last_mode);
|
get_mode(last_mode);
|
||||||
#endif
|
#endif
|
||||||
// Initialize the fields checked by the WinScrolled trigger to
|
// Initialize the fields checked by the WinScrolled and
|
||||||
// prevent it from firing right after the first autocmd is
|
// WinResized trigger to prevent them from firing right after
|
||||||
// defined.
|
// the first autocmd is defined.
|
||||||
if (event == EVENT_WINSCROLLED && !has_winscrolled())
|
if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED)
|
||||||
|
&& !(has_winscrolled() || has_winresized()))
|
||||||
{
|
{
|
||||||
tabpage_T *save_curtab = curtab;
|
tabpage_T *save_curtab = curtab;
|
||||||
tabpage_T *tp;
|
tabpage_T *tp;
|
||||||
@@ -1810,6 +1812,15 @@ trigger_cursorhold(void)
|
|||||||
return FALSE;
|
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.
|
* Return TRUE when there is a WinScrolled autocommand defined.
|
||||||
*/
|
*/
|
||||||
@@ -2117,6 +2128,7 @@ apply_autocmds_group(
|
|||||||
|| event == EVENT_MENUPOPUP
|
|| event == EVENT_MENUPOPUP
|
||||||
|| event == EVENT_USER
|
|| event == EVENT_USER
|
||||||
|| event == EVENT_WINCLOSED
|
|| event == EVENT_WINCLOSED
|
||||||
|
|| event == EVENT_WINRESIZED
|
||||||
|| event == EVENT_WINSCROLLED)
|
|| event == EVENT_WINSCROLLED)
|
||||||
{
|
{
|
||||||
fname = vim_strsave(fname);
|
fname = vim_strsave(fname);
|
||||||
|
29
src/dict.c
29
src/dict.c
@@ -1082,13 +1082,14 @@ failret:
|
|||||||
* Go over all entries in "d2" and add them to "d1".
|
* 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 "error" then a duplicate key is an error.
|
||||||
* When "action" is "force" then a duplicate key is overwritten.
|
* 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").
|
* Otherwise duplicate keys are ignored ("action" is "keep").
|
||||||
|
* "func_name" is used for reporting where an error occurred.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name)
|
dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name)
|
||||||
{
|
{
|
||||||
dictitem_T *di1;
|
dictitem_T *di1;
|
||||||
hashitem_T *hi2;
|
|
||||||
int todo;
|
int todo;
|
||||||
char_u *arg_errmsg = (char_u *)N_("extend() argument");
|
char_u *arg_errmsg = (char_u *)N_("extend() argument");
|
||||||
type_T *type;
|
type_T *type;
|
||||||
@@ -1098,8 +1099,11 @@ dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name)
|
|||||||
else
|
else
|
||||||
type = NULL;
|
type = NULL;
|
||||||
|
|
||||||
|
if (*action == 'm')
|
||||||
|
hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove()
|
||||||
|
|
||||||
todo = (int)d2->dv_hashtab.ht_used;
|
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))
|
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)
|
if (di1 == NULL)
|
||||||
{
|
{
|
||||||
di1 = dictitem_copy(HI2DI(hi2));
|
if (*action == 'm')
|
||||||
if (di1 != NULL && dict_add(d1, di1) == FAIL)
|
{
|
||||||
dictitem_free(di1);
|
// 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')
|
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";
|
action = (char_u *)"force";
|
||||||
|
|
||||||
if (type != NULL && check_typval_arg_type(type, &argvars[1],
|
if (type != NULL && check_typval_arg_type(type, &argvars[1],
|
||||||
func_name, 2) == FAIL)
|
func_name, 2) == FAIL)
|
||||||
return;
|
return;
|
||||||
dict_extend(d1, d2, action, func_name);
|
dict_extend(d1, d2, action, func_name);
|
||||||
|
|
||||||
|
@@ -1510,7 +1510,7 @@ ins_redraw(int ready) // not busy with something
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ready)
|
if (ready)
|
||||||
may_trigger_winscrolled();
|
may_trigger_win_scrolled_resized();
|
||||||
|
|
||||||
// Trigger SafeState if nothing is pending.
|
// Trigger SafeState if nothing is pending.
|
||||||
may_trigger_safestate(ready
|
may_trigger_safestate(ready
|
||||||
|
@@ -5097,7 +5097,7 @@ gui_update_screen(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!finish_op)
|
if (!finish_op)
|
||||||
may_trigger_winscrolled();
|
may_trigger_win_scrolled_resized();
|
||||||
|
|
||||||
# ifdef FEAT_CONCEAL
|
# ifdef FEAT_CONCEAL
|
||||||
if (conceal_update_lines
|
if (conceal_update_lines
|
||||||
|
@@ -1358,7 +1358,7 @@ main_loop(
|
|||||||
validate_cursor();
|
validate_cursor();
|
||||||
|
|
||||||
if (!finish_op)
|
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
|
// If nothing is pending and we are going to wait for the user to
|
||||||
// type a character, trigger SafeState.
|
// type a character, trigger SafeState.
|
||||||
|
@@ -1171,7 +1171,7 @@ do_mousescroll(cmdarg_T *cap)
|
|||||||
leftcol = 0;
|
leftcol = 0;
|
||||||
do_mousescroll_horiz((long_u)leftcol);
|
do_mousescroll_horiz((long_u)leftcol);
|
||||||
}
|
}
|
||||||
may_trigger_winscrolled();
|
may_trigger_win_scrolled_resized();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -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_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 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 trigger_cursorhold(void);
|
||||||
|
int has_winresized(void);
|
||||||
int has_winscrolled(void);
|
int has_winscrolled(void);
|
||||||
int has_cursormoved(void);
|
int has_cursormoved(void);
|
||||||
int has_cursormovedI(void);
|
int has_cursormovedI(void);
|
||||||
|
@@ -20,7 +20,7 @@ int one_window(void);
|
|||||||
int win_close(win_T *win, int free_buf);
|
int win_close(win_T *win, int free_buf);
|
||||||
void snapshot_windows_scroll_size(void);
|
void snapshot_windows_scroll_size(void);
|
||||||
void may_make_initial_scroll_size_snapshot(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_close_othertab(win_T *win, int free_buf, tabpage_T *tp);
|
||||||
void win_free_all(void);
|
void win_free_all(void);
|
||||||
win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp);
|
win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp);
|
||||||
|
@@ -306,6 +306,61 @@ func Test_win_tab_autocmd()
|
|||||||
unlet g:record
|
unlet g:record
|
||||||
endfunc
|
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()
|
func Test_WinScrolled()
|
||||||
CheckRunVimInTerminal
|
CheckRunVimInTerminal
|
||||||
|
|
||||||
@@ -316,11 +371,15 @@ func Test_WinScrolled()
|
|||||||
endfor
|
endfor
|
||||||
let win_id = win_getid()
|
let win_id = win_getid()
|
||||||
let g:matched = v:false
|
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'
|
execute 'au WinScrolled' win_id 'let g:matched = v:true'
|
||||||
let g:scrolled = 0
|
let g:scrolled = 0
|
||||||
au WinScrolled * let g:scrolled += 1
|
au WinScrolled * let g:scrolled += 1
|
||||||
au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
|
au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
|
||||||
au WinScrolled * let g:afile = str2nr(expand('<afile>'))
|
au WinScrolled * let g:afile = str2nr(expand('<afile>'))
|
||||||
|
au WinScrolled * call WriteScrollEvent()
|
||||||
END
|
END
|
||||||
call writefile(lines, 'Xtest_winscrolled', 'D')
|
call writefile(lines, 'Xtest_winscrolled', 'D')
|
||||||
let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6})
|
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 term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>")
|
||||||
call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
|
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.
|
" Scroll up/down in Normal mode.
|
||||||
call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>")
|
call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>")
|
||||||
call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000)
|
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.
|
" 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, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>")
|
||||||
call term_sendkeys(buf, ":echo g:scrolled\<CR>")
|
call term_sendkeys(buf, ":echo g:scrolled\<CR>")
|
||||||
call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000)
|
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
|
" Scroll the window horizontally to focus the last letter of the third line
|
||||||
" containing only six characters. Moving to the previous and shorter lines
|
" containing only six characters. Moving to the previous and shorter lines
|
||||||
" should trigger another autocommand as Vim has to make them visible.
|
" 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 term_sendkeys(buf, ":echo g:scrolled\<CR>")
|
||||||
call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000)
|
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.
|
" Ensure the command was triggered for the specified window ID.
|
||||||
call term_sendkeys(buf, ":echo g:matched\<CR>")
|
call term_sendkeys(buf, ":echo g:matched\<CR>")
|
||||||
call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
|
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 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 WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
|
||||||
|
|
||||||
|
call delete('XscrollEvent')
|
||||||
call StopVimInTerminal(buf)
|
call StopVimInTerminal(buf)
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
@@ -695,6 +695,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
917,
|
||||||
/**/
|
/**/
|
||||||
916,
|
916,
|
||||||
/**/
|
/**/
|
||||||
|
@@ -1407,7 +1407,8 @@ enum auto_event
|
|||||||
EVENT_WINCLOSED, // after closing a window
|
EVENT_WINCLOSED, // after closing a window
|
||||||
EVENT_VIMSUSPEND, // before Vim is suspended
|
EVENT_VIMSUSPEND, // before Vim is suspended
|
||||||
EVENT_VIMRESUME, // after Vim is resumed
|
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
|
NUM_EVENTS // MUST be the last one
|
||||||
};
|
};
|
||||||
|
291
src/window.c
291
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.
|
||||||
*/
|
*/
|
||||||
void
|
static dict_T *
|
||||||
may_trigger_winscrolled(void)
|
make_win_info_dict(
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int topline,
|
||||||
|
int leftcol,
|
||||||
|
int skipcol)
|
||||||
{
|
{
|
||||||
static int recursive = FALSE;
|
dict_T *d = dict_alloc();
|
||||||
|
if (d == NULL)
|
||||||
|
return NULL;
|
||||||
|
d->dv_refcount = 1;
|
||||||
|
|
||||||
if (recursive
|
// not actually looping, for breaking out on error
|
||||||
|| !has_winscrolled()
|
while (1)
|
||||||
|| !did_initial_scroll_size_snapshot)
|
{
|
||||||
return;
|
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;
|
win_T *wp;
|
||||||
FOR_ALL_WINDOWS(wp)
|
FOR_ALL_WINDOWS(wp)
|
||||||
if (wp->w_last_topline != wp->w_topline
|
{
|
||||||
|| wp->w_last_leftcol != wp->w_leftcol
|
int size_changed = wp->w_last_width != wp->w_width
|
||||||
|| wp->w_last_skipcol != wp->w_skipcol
|
|| wp->w_last_height != wp->w_height;
|
||||||
|| wp->w_last_width != wp->w_width
|
if (size_changed)
|
||||||
|| wp->w_last_height != wp->w_height)
|
|
||||||
{
|
{
|
||||||
// WinScrolled is triggered only once, even when multiple windows
|
result |= CWSR_RESIZED;
|
||||||
// scrolled or changed size. Store the current values before
|
if (winlist != NULL)
|
||||||
// triggering the event, if a scroll or resize happens as a side
|
{
|
||||||
// effect then WinScrolled is triggered again later.
|
// Add this window to the list of changed windows.
|
||||||
snapshot_windows_scroll_size();
|
typval_T tv;
|
||||||
|
tv.v_lock = 0;
|
||||||
// "curwin" may be different from the actual current window, make
|
tv.v_type = VAR_NUMBER;
|
||||||
// sure it can be restored.
|
tv.vval.v_number = wp->w_id;
|
||||||
window_layout_lock();
|
list_set_item(winlist, listidx++, &tv);
|
||||||
|
}
|
||||||
recursive = TRUE;
|
else if (size_count != NULL)
|
||||||
char_u winid[NUMBUFLEN];
|
{
|
||||||
vim_snprintf((char *)winid, sizeof(winid), "%d", wp->w_id);
|
++*size_count;
|
||||||
apply_autocmds(EVENT_WINSCROLLED, winid, winid, FALSE,
|
if (*first_size_win == NULL)
|
||||||
wp->w_buffer);
|
*first_size_win = wp;
|
||||||
recursive = FALSE;
|
// For WinScrolled the first window with a size change is used
|
||||||
window_layout_unlock();
|
// even when it didn't scroll.
|
||||||
|
if (*first_scroll_win == NULL)
|
||||||
break;
|
*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_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
|
||||||
|
|| !(do_scroll || do_resize)
|
||||||
|
|| !did_initial_scroll_size_snapshot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
// "curwin" may be different from the actual current window, make
|
||||||
|
// sure it can be restored.
|
||||||
|
window_layout_lock();
|
||||||
|
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 (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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user