diff options
author | zeertzjq <zeertzjq@outlook.com> | 2022-11-23 09:54:48 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-23 09:54:48 +0800 |
commit | 4571ba4d0a5234408e544c3a98f107688a792f0d (patch) | |
tree | 0418fe6a170f8d2f6f97dd6182753dfdd89eb7bf /src/nvim/window.c | |
parent | d41e93d5a83642f90898cae211e017d99ff97fd9 (diff) | |
download | rneovim-4571ba4d0a5234408e544c3a98f107688a792f0d.tar.gz rneovim-4571ba4d0a5234408e544c3a98f107688a792f0d.tar.bz2 rneovim-4571ba4d0a5234408e544c3a98f107688a792f0d.zip |
vim-patch:partial:9.0.0917: the WinScrolled autocommand event is not enough (#21161)
Problem: The WinScrolled autocommand event is not enough.
Solution: Add WinResized and provide information about what changed.
(closes vim/vim#11576)
https://github.com/vim/vim/commit/35fc61cb5b5eba8bbb9d8f0700332fbab38f40ca
Omit "func_name" comment in tv_dict_extend(): Vim9 script only.
Skip layout locking and E1312.
Skip list_alloc_with_items() and list_set_item().
Since this overrides remaining changes in patch 9.0.0913, that patch can
now be marked as fully ported:
vim-patch:9.0.0913: only change in current window triggers the WinScrolled event
N/A patches for version.c:
vim-patch:9.0.0919: build failure with tiny features
Problem: Build failure with tiny features.
Solution: Adjust #ifdef's.
https://github.com/vim/vim/commit/9c5b7cb4cf67c64648a324e9dfd1e17d793335a4
Co-authored-by: Bram Moolenaar <Bram@vim.org>
Diffstat (limited to 'src/nvim/window.c')
-rw-r--r-- | src/nvim/window.c | 242 |
1 files changed, 222 insertions, 20 deletions
diff --git a/src/nvim/window.c b/src/nvim/window.c index f25e25e905..beb96aaa03 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5301,39 +5301,241 @@ void may_make_initial_scroll_size_snapshot(void) } } -/// Trigger WinScrolled if any window scrolled or changed size. -void may_trigger_winscrolled(void) +/// Create a dictionary with information about size and scroll changes in a +/// window. +/// Returns the dictionary with refcount set to one. +/// Returns NULL on internal error. +static dict_T *make_win_info_dict(int width, int height, int topline, int leftcol, int skipcol) +{ + dict_T *const d = tv_dict_alloc(); + d->dv_refcount = 1; + + // not actually looping, for breaking out on error + while (1) { + typval_T tv = { + .v_lock = VAR_UNLOCKED, + .v_type = VAR_NUMBER, + }; + + tv.vval.v_number = width; + if (tv_dict_add_tv(d, S_LEN("width"), &tv) == FAIL) { + break; + } + tv.vval.v_number = height; + if (tv_dict_add_tv(d, S_LEN("height"), &tv) == FAIL) { + break; + } + tv.vval.v_number = topline; + if (tv_dict_add_tv(d, S_LEN("topline"), &tv) == FAIL) { + break; + } + tv.vval.v_number = leftcol; + if (tv_dict_add_tv(d, S_LEN("leftcol"), &tv) == FAIL) { + break; + } + tv.vval.v_number = skipcol; + if (tv_dict_add_tv(d, S_LEN("skipcol"), &tv) == FAIL) { + break; + } + return d; + } + tv_dict_unref(d); + return NULL; +} + +/// Return values of check_window_scroll_resize(): +enum { + CWSR_SCROLLED = 1, ///< at least one window scrolled + CWSR_RESIZED = 2, ///< at least one window size changed +}; + +/// This function is used for three purposes: +/// 1. Goes over all windows in the current tab page and returns: +/// 0 no scrolling and no size changes found +/// CWSR_SCROLLED at least one window scrolled +/// CWSR_RESIZED at least one window changed size +/// CWSR_SCROLLED + CWSR_RESIZED both +/// "size_count" is set to the nr of windows with size changes. +/// "first_scroll_win" is set to the first window with any relevant changes. +/// "first_size_win" is set to the first window with size changes. +/// +/// 2. When the first three arguments are NULL and "winlist" is not NULL, +/// "winlist" is set to the list of window IDs with size changes. +/// +/// 3. When the first three arguments are NULL and "v_event" is not NULL, +/// information about changed windows is added to "v_event". +static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win, + win_T **first_size_win, list_T *winlist, dict_T *v_event) +{ + int result = 0; + // int listidx = 0; + int tot_width = 0; + int tot_height = 0; + int tot_topline = 0; + int tot_leftcol = 0; + int tot_skipcol = 0; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + const bool size_changed = wp->w_last_width != wp->w_width + || wp->w_last_height != wp->w_height; + if (size_changed) { + result |= CWSR_RESIZED; + if (winlist != NULL) { + // Add this window to the list of changed windows. + typval_T tv = { + .v_lock = VAR_UNLOCKED, + .v_type = VAR_NUMBER, + .vval.v_number = wp->handle, + }; + // tv_list_set_item(winlist, listidx++, &tv); + tv_list_append_owned_tv(winlist, tv); + } else if (size_count != NULL) { + (*size_count)++; + if (*first_size_win == NULL) { + *first_size_win = wp; + } + // For WinScrolled the first window with a size change is used + // even when it didn't scroll. + if (*first_scroll_win == NULL) { + *first_scroll_win = wp; + } + } + } + + const bool scroll_changed = wp->w_last_topline != wp->w_topline + || wp->w_last_leftcol != wp->w_leftcol + || wp->w_last_skipcol != wp->w_skipcol; + if (scroll_changed) { + result |= CWSR_SCROLLED; + if (first_scroll_win != NULL && *first_scroll_win == NULL) { + *first_scroll_win = wp; + } + } + + if ((size_changed || scroll_changed) && v_event != NULL) { + // Add info about this window to the v:event dictionary. + int width = wp->w_width - wp->w_last_width; + int height = wp->w_height - wp->w_last_height; + int topline = wp->w_topline - wp->w_last_topline; + int leftcol = wp->w_leftcol - wp->w_last_leftcol; + int skipcol = wp->w_skipcol - wp->w_last_skipcol; + dict_T *d = make_win_info_dict(width, height, + topline, leftcol, skipcol); + if (d == NULL) { + break; + } + char winid[NUMBUFLEN]; + int key_len = vim_snprintf(winid, sizeof(winid), "%d", wp->handle); + if (tv_dict_add_dict(v_event, winid, (size_t)key_len, d) == FAIL) { + tv_dict_unref(d); + break; + } + d->dv_refcount--; + + tot_width += abs(width); + tot_height += abs(height); + tot_topline += abs(topline); + tot_leftcol += abs(leftcol); + tot_skipcol += abs(skipcol); + } + } + + if (v_event != NULL) { + dict_T *alldict = make_win_info_dict(tot_width, tot_height, + tot_topline, tot_leftcol, tot_skipcol); + if (alldict != NULL) { + if (tv_dict_add_dict(v_event, S_LEN("all"), alldict) == FAIL) { + tv_dict_unref(alldict); + } else { + alldict->dv_refcount--; + } + } + } + + return result; +} + +/// Trigger WinScrolled and/or WinResized if any window in the current tab page +/// scrolled or changed size. +void may_trigger_win_scrolled_resized(void) { static bool recursive = false; + const bool do_resize = has_event(EVENT_WINRESIZED); + const bool do_scroll = has_event(EVENT_WINSCROLLED); if (recursive - || !has_event(EVENT_WINSCROLLED) + || !(do_scroll || do_resize) || !did_initial_scroll_size_snapshot) { return; } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_last_topline != wp->w_topline - || wp->w_last_leftcol != wp->w_leftcol - || wp->w_last_skipcol != wp->w_skipcol - || wp->w_last_width != wp->w_width - || wp->w_last_height != wp->w_height) { - // WinScrolled is triggered only once, even when multiple windows - // scrolled or changed size. Store the current values before - // triggering the event, if a scroll or resize happens as a side - // effect then WinScrolled is triggered again later. - snapshot_windows_scroll_size(); + int size_count = 0; + win_T *first_scroll_win = NULL, *first_size_win = NULL; + int cwsr = check_window_scroll_resize(&size_count, + &first_scroll_win, &first_size_win, + NULL, NULL); + int trigger_resize = do_resize && size_count > 0; + int trigger_scroll = do_scroll && cwsr != 0; + if (!trigger_resize && !trigger_scroll) { + return; // no relevant changes + } - char winid[NUMBUFLEN]; - vim_snprintf(winid, sizeof(winid), "%d", wp->handle); + list_T *windows_list = NULL; + if (trigger_resize) { + // Create the list for v:event.windows before making the snapshot. + // windows_list = tv_list_alloc_with_items(size_count); + windows_list = tv_list_alloc(size_count); + (void)check_window_scroll_resize(NULL, NULL, NULL, windows_list, NULL); + } - recursive = true; - apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer); - recursive = false; + dict_T *scroll_dict = NULL; + if (trigger_scroll) { + // Create the dict with entries for v:event before making the snapshot. + scroll_dict = tv_dict_alloc(); + scroll_dict->dv_refcount = 1; + (void)check_window_scroll_resize(NULL, NULL, NULL, NULL, scroll_dict); + } - break; + // WinScrolled/WinResized are triggered only once, even when multiple + // windows scrolled or changed size. Store the current values before + // triggering the event, if a scroll or resize happens as a side effect + // then WinScrolled/WinResized is triggered for that later. + snapshot_windows_scroll_size(); + + recursive = true; + + // If both are to be triggered do WinResized first. + if (trigger_resize) { + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + + if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) { + tv_dict_set_keys_readonly(v_event); + + char winid[NUMBUFLEN]; + vim_snprintf(winid, sizeof(winid), "%d", first_size_win->handle); + apply_autocmds(EVENT_WINRESIZED, winid, winid, false, first_size_win->w_buffer); } + restore_v_event(v_event, &save_v_event); } + + if (trigger_scroll) { + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + + // Move the entries from scroll_dict to v_event. + tv_dict_extend(v_event, scroll_dict, "move"); + tv_dict_set_keys_readonly(v_event); + tv_dict_unref(scroll_dict); + + char winid[NUMBUFLEN]; + vim_snprintf(winid, sizeof(winid), "%d", first_scroll_win->handle); + apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, first_scroll_win->w_buffer); + + restore_v_event(v_event, &save_v_event); + } + + recursive = false; } // Save the size of all windows in "gap". |