Patch 9.0.0917
Problem:    The WinScrolled autocommand event is not enough.
Solution:   Add WinResized and provide information about what changed.
            (closes #11576)
Files:      runtime/doc/autocmd.txt, runtime/doc/windows.txt,
            src/autocmd.c, src/proto/autocmd.pro, src/window.c,
            src/proto/window.pro, src/vim.h, src/edit.c, src/gui.c,
            src/main.c, src/mouse.c, src/dict.c, src/testdir/test_autocmd.vim


*** ../vim-9.0.0916/runtime/doc/autocmd.txt     2022-11-19 21:17:48.841226535 
+0000
--- runtime/doc/autocmd.txt     2022-11-22 12:33:47.490253876 +0000
***************
*** 1369,1389 ****
                                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.
  
                                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|.
  
                                Only starts triggering after startup finished
                                and the first screen redraw was done.
  
                                Non-recursive: the event will not trigger
                                while executing commands for the WinScrolled
--- 1372,1395 ----
                                Before a WinEnter event.
  
                                                        *WinScrolled*
! 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
***************
*** 1391,1401 ****
                                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).
  
  ==============================================================================
  6. Patterns                                   *autocmd-patterns* *{aupat}*
--- 1397,1413 ----
                                window to scroll or change size, then another
                                WinScrolled event will be triggered later.
  
! 
!                                                       *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}*
*** ../vim-9.0.0916/runtime/doc/windows.txt     2022-08-31 17:48:05.711547579 
+0100
--- runtime/doc/windows.txt     2022-11-22 12:38:40.458362177 +0000
***************
*** 631,636 ****
--- 631,684 ----
  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*
  
*** ../vim-9.0.0916/src/autocmd.c       2022-11-20 12:11:22.920741574 +0000
--- src/autocmd.c       2022-11-21 12:50:12.211329954 +0000
***************
*** 191,196 ****
--- 191,197 ----
      {"WinClosed",     EVENT_WINCLOSED},
      {"WinEnter",      EVENT_WINENTER},
      {"WinLeave",      EVENT_WINLEAVE},
+     {"WinResized",    EVENT_WINRESIZED},
      {"WinScrolled",   EVENT_WINSCROLLED},
      {"VimResized",    EVENT_VIMRESIZED},
      {"TextYankPost",  EVENT_TEXTYANKPOST},
***************
*** 1263,1272 ****
                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())
                {
                    tabpage_T *save_curtab = curtab;
                    tabpage_T *tp;
--- 1264,1274 ----
                if (event == EVENT_MODECHANGED && !has_modechanged())
                    get_mode(last_mode);
  #endif
!               // 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,1816 ****
--- 1813,1827 ----
  }
  
  /*
+  * 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,2122 ****
--- 2128,2134 ----
                || event == EVENT_MENUPOPUP
                || event == EVENT_USER
                || event == EVENT_WINCLOSED
+               || event == EVENT_WINRESIZED
                || event == EVENT_WINSCROLLED)
        {
            fname = vim_strsave(fname);
*** ../vim-9.0.0916/src/proto/autocmd.pro       2022-06-27 23:14:56.000000000 
+0100
--- src/proto/autocmd.pro       2022-11-22 12:25:27.714229336 +0000
***************
*** 16,21 ****
--- 16,22 ----
  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);
*** ../vim-9.0.0916/src/window.c        2022-11-20 12:11:22.924741577 +0000
--- src/window.c        2022-11-21 23:27:46.994253281 +0000
***************
*** 2873,2918 ****
  }
  
  /*
!  * Trigger WinScrolled if any window scrolled or changed size.
   */
      void
! may_trigger_winscrolled(void)
  {
      static int            recursive = FALSE;
  
      if (recursive
!           || !has_winscrolled()
            || !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)
!       {
!           // 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();
! 
!           // "curwin" may be different from the actual current window, make
!           // sure it can be restored.
!           window_layout_lock();
  
!           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();
  
!           break;
        }
  }
  
  /*
--- 2873,3145 ----
  }
  
  /*
!  * 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_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();
  }
  
  /*
*** ../vim-9.0.0916/src/proto/window.pro        2022-11-20 12:11:22.924741577 
+0000
--- src/proto/window.pro        2022-11-22 12:25:20.594228565 +0000
***************
*** 20,26 ****
  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 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);
--- 20,26 ----
  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_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);
*** ../vim-9.0.0916/src/vim.h   2022-11-05 14:23:09.229901363 +0000
--- src/vim.h   2022-11-21 12:51:20.583252778 +0000
***************
*** 1407,1413 ****
      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
  
      NUM_EVENTS                        // MUST be the last one
  };
--- 1407,1414 ----
      EVENT_WINCLOSED,          // after closing a window
      EVENT_VIMSUSPEND,         // before Vim is suspended
      EVENT_VIMRESUME,          // after Vim is resumed
!     EVENT_WINRESIZED,         // after a window was resized
!     EVENT_WINSCROLLED,                // after a window was scrolled or 
resized
  
      NUM_EVENTS                        // MUST be the last one
  };
*** ../vim-9.0.0916/src/edit.c  2022-11-15 17:43:28.442135533 +0000
--- src/edit.c  2022-11-21 12:52:15.119192696 +0000
***************
*** 1510,1516 ****
      }
  
      if (ready)
!       may_trigger_winscrolled();
  
      // Trigger SafeState if nothing is pending.
      may_trigger_safestate(ready
--- 1510,1516 ----
      }
  
      if (ready)
!       may_trigger_win_scrolled_resized();
  
      // Trigger SafeState if nothing is pending.
      may_trigger_safestate(ready
*** ../vim-9.0.0916/src/gui.c   2022-11-15 17:43:28.442135533 +0000
--- src/gui.c   2022-11-21 12:52:28.655177972 +0000
***************
*** 5097,5103 ****
      }
  
      if (!finish_op)
!       may_trigger_winscrolled();
  
  # ifdef FEAT_CONCEAL
      if (conceal_update_lines
--- 5097,5103 ----
      }
  
      if (!finish_op)
!       may_trigger_win_scrolled_resized();
  
  # ifdef FEAT_CONCEAL
      if (conceal_update_lines
*** ../vim-9.0.0916/src/main.c  2022-11-19 21:17:48.841226535 +0000
--- src/main.c  2022-11-21 12:52:40.059165620 +0000
***************
*** 1358,1364 ****
            validate_cursor();
  
            if (!finish_op)
!               may_trigger_winscrolled();
  
            // If nothing is pending and we are going to wait for the user to
            // type a character, trigger SafeState.
--- 1358,1364 ----
            validate_cursor();
  
            if (!finish_op)
!               may_trigger_win_scrolled_resized();
  
            // If nothing is pending and we are going to wait for the user to
            // type a character, trigger SafeState.
*** ../vim-9.0.0916/src/mouse.c 2022-11-19 14:31:04.356796241 +0000
--- src/mouse.c 2022-11-21 12:52:52.531152172 +0000
***************
*** 1171,1177 ****
            leftcol = 0;
        do_mousescroll_horiz((long_u)leftcol);
      }
!     may_trigger_winscrolled();
  }
  
  /*
--- 1171,1177 ----
            leftcol = 0;
        do_mousescroll_horiz((long_u)leftcol);
      }
!     may_trigger_win_scrolled_resized();
  }
  
  /*
*** ../vim-9.0.0916/src/dict.c  2022-11-05 20:21:50.597151474 +0000
--- src/dict.c  2022-11-22 11:28:47.971367670 +0000
***************
*** 1082,1094 ****
   * 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.
   * Otherwise duplicate keys are ignored ("action" is "keep").
   */
      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;
--- 1082,1095 ----
   * 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;
      int               todo;
      char_u    *arg_errmsg = (char_u *)N_("extend() argument");
      type_T    *type;
***************
*** 1098,1105 ****
      else
        type = NULL;
  
      todo = (int)d2->dv_hashtab.ht_used;
!     for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
      {
        if (!HASHITEM_EMPTY(hi2))
        {
--- 1099,1109 ----
      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 (hashitem_T *hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
      {
        if (!HASHITEM_EMPTY(hi2))
        {
***************
*** 1116,1124 ****
  
            if (di1 == NULL)
            {
!               di1 = dictitem_copy(HI2DI(hi2));
!               if (di1 != NULL && dict_add(d1, di1) == FAIL)
!                   dictitem_free(di1);
            }
            else if (*action == 'e')
            {
--- 1120,1138 ----
  
            if (di1 == NULL)
            {
!               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,1143 ****
--- 1152,1160 ----
            }
        }
      }
+ 
+     if (*action == 'm')
+       hash_unlock(&d2->dv_hashtab);
  }
  
  /*
***************
*** 1272,1278 ****
            action = (char_u *)"force";
  
        if (type != NULL && check_typval_arg_type(type, &argvars[1],
!                   func_name, 2) == FAIL)
            return;
        dict_extend(d1, d2, action, func_name);
  
--- 1289,1295 ----
            action = (char_u *)"force";
  
        if (type != NULL && check_typval_arg_type(type, &argvars[1],
!                                                        func_name, 2) == FAIL)
            return;
        dict_extend(d1, d2, action, func_name);
  
*** ../vim-9.0.0916/src/testdir/test_autocmd.vim        2022-11-20 
12:11:22.924741577 +0000
--- src/testdir/test_autocmd.vim        2022-11-22 12:23:02.490210044 +0000
***************
*** 306,311 ****
--- 306,366 ----
    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,326 ****
--- 371,385 ----
      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,346 ****
--- 391,423 ----
    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,353 ****
--- 425,436 ----
    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,361 ****
--- 439,445 ----
    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
  
*** ../vim-9.0.0916/src/version.c       2022-11-21 19:56:59.403412744 +0000
--- src/version.c       2022-11-21 22:18:31.256776712 +0000
***************
*** 697,698 ****
--- 697,700 ----
  {   /* Add new patch number below this line */
+ /**/
+     917,
  /**/

-- 
hundred-and-one symptoms of being an internet addict:
104. When people ask about the Presidential Election you ask "Which country?"

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20221122124123.ADDEF1C211B%40moolenaar.net.

Raspunde prin e-mail lui