aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/window.c
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2022-11-23 09:54:48 +0800
committerGitHub <noreply@github.com>2022-11-23 09:54:48 +0800
commit4571ba4d0a5234408e544c3a98f107688a792f0d (patch)
tree0418fe6a170f8d2f6f97dd6182753dfdd89eb7bf /src/nvim/window.c
parentd41e93d5a83642f90898cae211e017d99ff97fd9 (diff)
downloadrneovim-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.c242
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".