patch 9.1.0116: win_split_ins may not check available room

Commit: 
https://github.com/vim/vim/commit/0fd44a5ad81ade342cb54d8984965bdedd2272c8
Author: Sean Dewar <6256228+seande...@users.noreply.github.com>
Date:   Tue Feb 20 20:28:15 2024 +0100

    patch 9.1.0116: win_split_ins may not check available room
    
    Problem:  win_split_ins has no check for E36 when moving an existing
              window
    Solution: check for room and fix the issues in f_win_splitmove()
              (Sean Dewar)
    
    win_split_ins has no check for E36 when moving an existing window,
    allowing for layouts with many overlapping zero-sized windows to be
    created (which may also cause drawing issues with tablines and such).
    f_win_splitmove also has some bugs.
    
    So check for room and fix the issues in f_win_splitmove. Handle failure
    in the two relevant win_split_ins callers by restoring the original
    layout, and factor the common logic into win_splitmove.
    
    Don't check for room when opening an autocommand window, as it's a
    temporary window that's rarely interacted with or drawn anyhow, and is
    rather important for some autocommands.
    
    Issues fixed in f_win_splitmove:
    - Error if splitting is disallowed.
    - Fix heap-use-after-frees if autocommands fired from switching to 
"targetwin"
      close "wp" or "oldwin".
    - Fix splitting the wrong window if autocommands fired from switching to
      "targetwin" switch to a different window.
    - Ensure -1 is returned for all errors.
    
    Also handle allocation failure a bit earlier in make_snapshot (callers,
    except win_splitmove, don't really care if a snapshot can't be made, so
    just ignore the return value).
    
    Note: Test_smoothscroll_in_zero_width_window failed after these changes with
    E36, as it was using the previous behaviour to create a zero-width window.
    I've fixed the test such that it fails with UBSAN as expected when 
v9.0.1367 is
    reverted (and simplified it too).
    
    related: #14042
    
    Signed-off-by: Sean Dewar <6256228+seande...@users.noreply.github.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/autocmd.c b/src/autocmd.c
index 7e4a1b211..41a46958e 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -1607,7 +1607,7 @@ aucmd_prepbuf(
        p_acd = FALSE;
 #endif
 
-       (void)win_split_ins(0, WSP_TOP, auc_win, 0);
+       (void)win_split_ins(0, WSP_TOP | WSP_FORCE_ROOM, auc_win, 0);
        (void)win_comp_pos();   // recompute window positions
        p_ea = save_ea;
 #ifdef FEAT_AUTOCHDIR
diff --git a/src/evalwindow.c b/src/evalwindow.c
index 7d035d53b..5016eb458 100644
--- a/src/evalwindow.c
+++ b/src/evalwindow.c
@@ -952,60 +952,18 @@ f_win_screenpos(typval_T *argvars, typval_T *rettv)
     list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
 }
 
-/*
- * Move the window wp into a new split of targetwin in a given direction
- */
-    static void
-win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags)
-{
-    int            dir;
-    int            height = wp->w_height;
-    win_T   *oldwin = curwin;
-
-    if (wp == targetwin)
-       return;
-
-    // Jump to the target window
-    if (curwin != targetwin)
-       win_goto(targetwin);
-
-    // Remove the old window and frame from the tree of frames
-    (void)winframe_remove(wp, &dir, NULL);
-    win_remove(wp, NULL);
-    last_status(FALSE);            // may need to remove last status line
-    (void)win_comp_pos();   // recompute window positions
-
-    // Split a window on the desired side and put the old window there
-    (void)win_split_ins(size, flags, wp, dir);
-
-    // If splitting horizontally, try to preserve height
-    if (size == 0 && !(flags & WSP_VERT))
-    {
-       win_setheight_win(height, wp);
-       if (p_ea)
-           win_equal(wp, TRUE, 'v');
-    }
-
-#if defined(FEAT_GUI)
-    // When 'guioptions' includes 'L' or 'R' may have to remove or add
-    // scrollbars.  Have to update them anyway.
-    gui_may_update_scrollbars();
-#endif
-
-    if (oldwin != curwin)
-       win_goto(oldwin);
-}
-
 /*
  * "win_splitmove()" function
  */
     void
 f_win_splitmove(typval_T *argvars, typval_T *rettv)
 {
-    win_T   *wp;
-    win_T   *targetwin;
+    win_T   *wp, *targetwin;
+    win_T   *oldwin = curwin;
     int     flags = 0, size = 0;
 
+    rettv->vval.v_number = -1;
+
     if (in_vim9script()
            && (check_for_number_arg(argvars, 0) == FAIL
                || check_for_number_arg(argvars, 1) == FAIL
@@ -1020,7 +978,6 @@ f_win_splitmove(typval_T *argvars, typval_T *rettv)
            || win_valid_popup(wp) || win_valid_popup(targetwin))
     {
        emsg(_(e_invalid_window_number));
-       rettv->vval.v_number = -1;
        return;
     }
 
@@ -1040,7 +997,24 @@ f_win_splitmove(typval_T *argvars, typval_T *rettv)
        size = (int)dict_get_number(d, "size");
     }
 
-    win_move_into_split(wp, targetwin, size, flags);
+    // Check if we can split the target before we bother switching windows.
+    if (check_split_disallowed(targetwin) == FAIL)
+       return;
+
+    if (curwin != targetwin)
+       win_goto(targetwin);
+
+    // Autocommands may have sent us elsewhere or closed "wp" or "oldwin".
+    if (curwin == targetwin && win_valid(wp))
+    {
+       if (win_splitmove(wp, size, flags) == OK)
+           rettv->vval.v_number = 0;
+    }
+    else
+       emsg(_(e_autocommands_caused_command_to_abort));
+
+    if (oldwin != curwin && win_valid(oldwin))
+       win_goto(oldwin);
 }
 
 /*
diff --git a/src/proto/window.pro b/src/proto/window.pro
index def28bbe4..97a25dfa2 100644
--- a/src/proto/window.pro
+++ b/src/proto/window.pro
@@ -4,7 +4,9 @@ win_T *prevwin_curwin(void);
 win_T *swbuf_goto_win_with_buf(buf_T *buf);
 void do_window(int nchar, long Prenum, int xchar);
 void get_wincmd_addr_type(char_u *arg, exarg_T *eap);
+int check_split_disallowed(win_T *wp);
 int win_split(int size, int flags);
+int win_splitmove(win_T *wp, int size, int flags);
 int win_split_ins(int size, int flags, win_T *new_wp, int dir);
 int win_valid_popup(win_T *win);
 int win_valid(win_T *win);
@@ -88,7 +90,7 @@ int only_one_window(void);
 void check_lnums(int do_curwin);
 void check_lnums_nested(int do_curwin);
 void reset_lnums(void);
-void make_snapshot(int idx);
+int make_snapshot(int idx);
 void restore_snapshot(int idx, int close_curwin);
 int win_hasvertsplit(void);
 int get_win_number(win_T *wp, win_T *first_win);
diff --git a/src/testdir/test_window_cmd.vim b/src/testdir/test_window_cmd.vim
index 6b7dccbb0..c898a233a 100644
--- a/src/testdir/test_window_cmd.vim
+++ b/src/testdir/test_window_cmd.vim
@@ -290,6 +290,16 @@ func Test_window_split_no_room()
   for s in range(1, hor_split_count) | split | endfor
   call assert_fails('split', 'E36:')
 
+  botright vsplit
+  wincmd |
+  let layout = winlayout()
+  let restcmd = winrestcmd()
+  call assert_fails('wincmd J', 'E36:')
+  call assert_fails('wincmd K', 'E36:')
+  call assert_equal(layout, winlayout())
+  call assert_equal(restcmd, winrestcmd())
+  only
+
   " N vertical windows need >= 2*(N - 1) + 1 columns:
   " - 1 column + 1 separator for each window (except last window)
   " - 1 column for the last window which does not have separator
@@ -302,7 +312,35 @@ func Test_window_split_no_room()
   for s in range(1, ver_split_count) | vsplit | endfor
   call assert_fails('vsplit', 'E36:')
 
+  split
+  wincmd |
+  let layout = winlayout()
+  let restcmd = winrestcmd()
+  call assert_fails('wincmd H', 'E36:')
+  call assert_fails('wincmd L', 'E36:')
+  call assert_equal(layout, winlayout())
+  call assert_equal(restcmd, winrestcmd())
+
+  " Check that the last statusline isn't lost.
+  set laststatus=0
+  let restcmd = winrestcmd()
+  wincmd j
+  call setwinvar(winnr('k'), '&statusline', '@#')
+  let last_stl_row = win_screenpos(0)[0] - 1
+  redraw
+  call assert_equal('@#|', GetScreenStr(last_stl_row))
+  call assert_equal('~ |', GetScreenStr(&lines - &cmdheight))
+  call assert_fails('wincmd H', 'E36:')
+  call assert_fails('wincmd L', 'E36:')
+  call assert_equal(layout, winlayout())
+  call assert_equal(restcmd, winrestcmd())
+  call setwinvar(winnr('k'), '&statusline', '=-')
+  redraw
+  call assert_equal('=-|', GetScreenStr(last_stl_row))
+  call assert_equal('~ |', GetScreenStr(&lines - &cmdheight))
+
   %bw!
+  set laststatus&
 endfunc
 
 func Test_window_exchange()
@@ -1097,6 +1135,44 @@ func Test_win_splitmove()
   tabnew
   call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:')
   tabclose
+
+  split
+  augroup WinSplitMove
+    au!
+    au WinEnter * ++once call win_gotoid(win_getid(winnr('#')))
+  augroup END
+  call assert_fails('call win_splitmove(winnr(), winnr("#"))', 'E855:')
+
+  augroup WinSplitMove
+    au!
+    au WinLeave * ++once quit
+  augroup END
+  call assert_fails('call win_splitmove(winnr(), winnr("#"))', 'E855:')
+
+  split
+  split
+  augroup WinSplitMove
+    au!
+    au WinEnter * ++once let s:triggered = v:true
+          \| call assert_fails('call win_splitmove(winnr("$"), winnr())', 
'E242:')
+  augroup END
+  quit
+  call assert_equal(v:true, s:triggered)
+  unlet! s:triggered
+
+  new
+  augroup WinSplitMove
+    au!
+    au BufHidden * ++once let s:triggered = v:true
+          \| call assert_fails('call win_splitmove(winnr("#"), winnr())', 
'E1159:')
+  augroup END
+  hide
+  call assert_equal(v:true, s:triggered)
+  unlet! s:triggered
+
+  au! WinSplitMove
+  augroup! WinSplitMove
+  %bw!
 endfunc
 
 " Test for the :only command
@@ -2061,23 +2137,75 @@ func Test_new_help_window_on_error()
 endfunc
 
 func Test_smoothscroll_in_zero_width_window()
-  let save_lines = &lines
-  let save_columns = &columns
+  set cpo+=n number smoothscroll
+  set winwidth=99999 winminwidth=0
 
-  winsize 0 24
-  set cpo+=n
-  exe "noremap 0 \<C-W>n\<C-W>L"
-  norm 000000
-  set number smoothscroll
-  exe "norm \<C-Y>"
+  vsplit
+  call assert_equal(0, winwidth(winnr('#')))
+  call win_execute(win_getid(winnr('#')), "norm! \<C-Y>")
+
+  only!
+  set winwidth& winminwidth&
+  set cpo-=n nonumber nosmoothscroll
+endfunc
+
+func Test_splitmove_flatten_frame()
+  split
+  vsplit
+
+  wincmd L
+  let layout = winlayout()
+  wincmd K
+  wincmd L
+  call assert_equal(winlayout(), layout)
 
   only!
-  let &lines = save_lines
-  let &columns = save_columns
-  set cpo-=n
-  unmap 0
-  set nonumber nosmoothscroll
 endfunc
 
+func Test_splitmove_autocmd_window_no_room()
+  " Open as many windows as possible
+  while v:true
+    try
+      split
+    catch /E36:/
+      break
+    endtry
+  endwhile
+  while v:true
+    try
+      vsplit
+    catch /E36:/
+      break
+    endtry
+  endwhile
+
+  wincmd j
+  vsplit
+  call assert_fails('wincmd H', 'E36:')
+  call assert_fails('wincmd J', 'E36:')
+  call assert_fails('wincmd K', 'E36:')
+  call assert_fails('wincmd L', 'E36:')
+
+  edit unload me
+  enew
+  bunload! unload\ me
+  augroup SplitMoveAucmdWin
+    au!
+    au BufEnter * ++once let s:triggered = v:true
+                      \| call assert_equal('autocmd', win_gettype())
+  augroup END
+  let layout = winlayout()
+  let restcmd = winrestcmd()
+  " bufload opening the autocommand window shouldn't give E36.
+  call bufload('unload me')
+  call assert_equal(v:true, s:triggered)
+  call assert_equal(winlayout(), layout)
+  call assert_equal(winrestcmd(), restcmd)
+
+  unlet! s:triggered
+  au! SplitMoveAucmdWin
+  augroup! SplitMoveAucmdWin
+  %bw!
+endfunc
 
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 80cdfbcf3..cf1c9da78 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    116,
 /**/
     115,
 /**/
diff --git a/src/vim.h b/src/vim.h
index 45cf8cad0..7ef9b0033 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1255,6 +1255,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t 
*envstring);
 #define WSP_BELOW      0x40    // put new window below/right
 #define WSP_ABOVE      0x80    // put new window above/left
 #define WSP_NEWLOC     0x100   // don't copy location list
+#define WSP_FORCE_ROOM 0x200   // ignore "not enough room" errors
 
 /*
  * arguments for gui_set_shellsize()
diff --git a/src/window.c b/src/window.c
index a7d9319bd..de4347665 100644
--- a/src/window.c
+++ b/src/window.c
@@ -17,7 +17,6 @@ static void frame_setheight(frame_T *curfrp, int height);
 static void frame_setwidth(frame_T *curfrp, int width);
 static void win_exchange(long);
 static void win_rotate(int, int);
-static void win_totop(int size, int flags);
 static void win_equal_rec(win_T *next_curwin, int current, frame_T *topfr, int 
dir, int col, int row, int width, int height);
 static void trigger_winnewpre(void);
 static void trigger_winclosed(win_T *win);
@@ -31,7 +30,7 @@ static void win_fix_cursor(int normal);
 static void frame_new_height(frame_T *topfrp, int height, int topfirst, int 
wfh);
 static int frame_fixed_height(frame_T *frp);
 static int frame_fixed_width(frame_T *frp);
-static void frame_add_statusline(frame_T *frp);
+static void frame_add_statusline(frame_T *frp, int adjust_winheight);
 static void frame_new_width(frame_T *topfrp, int width, int leftfirst, int 
wfw);
 static void frame_add_vsep(frame_T *frp);
 static int frame_minwidth(frame_T *topfrp, win_T *next_curwin);
@@ -55,12 +54,15 @@ static void win_goto_hor(int left, long count);
 static void frame_add_height(frame_T *frp, int n);
 static void last_status_rec(frame_T *fr, int statusline);
 
-static void make_snapshot_rec(frame_T *fr, frame_T **frp);
+static int make_snapshot_rec(frame_T *fr, frame_T **frp, int snap_wins);
 static void clear_snapshot(tabpage_T *tp, int idx);
 static void clear_snapshot_rec(frame_T *fr);
 static int check_snapshot_rec(frame_T *sn, frame_T *fr);
 static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr);
 static win_T *get_snapshot_curwin(int idx);
+static frame_T *make_full_snapshot(void);
+static void restore_full_snapshot(frame_T *sn);
+static void restore_full_snapshot_rec(frame_T *sn);
 
 static int frame_check_height(frame_T *topfrp, int height);
 static int frame_check_width(frame_T *topfrp, int width);
@@ -494,9 +496,15 @@ newwindow:
     case 'H':
     case 'L':
                CHECK_CMDWIN;
-               win_totop((int)Prenum,
-                       ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
-                       | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT));
+               if (ONE_WINDOW)
+                   beep_flush();
+               else
+               {
+                   int dir = ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
+                        | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT);
+
+                   (void)win_splitmove(curwin, (int)Prenum, dir);
+               }
                break;
 
 // make all windows the same width and/or height
@@ -858,18 +866,18 @@ cmd_with_count(
 }
 
 /*
- * If "split_disallowed" is set give an error and return FAIL.
+ * If "split_disallowed" is set for "wp", give an error and return FAIL.
  * Otherwise return OK.
  */
-    static int
-check_split_disallowed(void)
+    int
+check_split_disallowed(win_T *wp)
 {
     if (split_disallowed > 0)
     {
        emsg(_(e_cant_split_window_while_closing_another));
        return FAIL;
     }
-    if (curwin->w_buffer->b_locked_split)
+    if (wp->w_buffer->b_locked_split)
     {
        emsg(_(e_cannot_split_window_when_closing_buffer));
        return FAIL;
@@ -898,7 +906,7 @@ win_split(int size, int flags)
     if (ERROR_IF_ANY_POPUP_WINDOW)
        return FAIL;
 
-    if (check_split_disallowed() == FAIL)
+    if (check_split_disallowed(curwin) == FAIL)
        return FAIL;
 
     // When the ":tab" modifier was used open a new tab page instead.
@@ -968,7 +976,7 @@ win_split_ins(
     // add a status line when p_ls == 1 and splitting the first window
     if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0)
     {
-       if (VISIBLE_HEIGHT(oldwin) <= p_wmh && new_wp == NULL)
+       if (!(flags & WSP_FORCE_ROOM) && VISIBLE_HEIGHT(oldwin) <= p_wmh)
        {
            emsg(_(e_not_enough_room));
            goto theend;
@@ -1026,7 +1034,7 @@ win_split_ins(
            available = oldwin->w_frame->fr_width;
            needed += minwidth;
        }
-       if (available < needed && new_wp == NULL)
+       if (!(flags & WSP_FORCE_ROOM) && available < needed)
        {
            emsg(_(e_not_enough_room));
            goto theend;
@@ -1109,7 +1117,7 @@ win_split_ins(
            available = oldwin->w_frame->fr_height;
            needed += minheight;
        }
-       if (available < needed && new_wp == NULL)
+       if (!(flags & WSP_FORCE_ROOM) && available < needed)
        {
            emsg(_(e_not_enough_room));
            goto theend;
@@ -1360,7 +1368,7 @@ win_split_ins(
            if (!((flags & WSP_BOT) && p_ls == 0))
                new_fr_height -= STATUS_HEIGHT;
            if (flags & WSP_BOT)
-               frame_add_statusline(curfrp);
+               frame_add_statusline(curfrp, FALSE);
            frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, FALSE);
        }
        else
@@ -1900,35 +1908,69 @@ win_rotate(int upwards, int count)
 }
 
 /*
- * Move the current window to the very top/bottom/left/right of the screen.
+ * Move "wp" into a new split in a given direction, possibly relative to the
+ * current window.
+ * "wp" must be valid in the current tabpage.
+ * Returns FAIL for failure, OK otherwise.
  */
-    static void
-win_totop(int size, int flags)
+    int
+win_splitmove(win_T *wp, int size, int flags)
 {
     int                dir;
-    int                height = curwin->w_height;
+    int                height = wp->w_height;
+    frame_T    *frp;
 
     if (ONE_WINDOW)
+       return OK;      // nothing to do
+    if (check_split_disallowed(wp) == FAIL)
+       return FAIL;
+
+    // Undoing changes to frames if splitting fails is complicated.
+    // Save a full snapshot to restore instead.
+    frp = make_full_snapshot();
+    if (frp == NULL)
     {
-       beep_flush();
-       return;
+       emsg(_(e_out_of_memory));
+       return FAIL;
     }
-    if (check_split_disallowed() == FAIL)
-       return;
 
     // Remove the window and frame from the tree of frames.
-    (void)winframe_remove(curwin, &dir, NULL);
-    win_remove(curwin, NULL);
+    (void)winframe_remove(wp, &dir, NULL);
+    win_remove(wp, NULL);
     last_status(FALSE);            // may need to remove last status line
     (void)win_comp_pos();   // recompute window positions
 
-    // Split a window on the desired side and put the window there.
-    (void)win_split_ins(size, flags, curwin, dir);
-    if (!(flags & WSP_VERT))
+    // Split a window on the desired side and put "wp" there.
+    if (win_split_ins(size, flags, wp, dir) == FAIL)
     {
-       win_setheight(height);
+       // Restore the previous layout from the snapshot.
+       vim_free(wp->w_frame);
+       restore_full_snapshot(frp);
+
+       // Vertical separators to the left may have been lost.  Restore them.
+       frp = wp->w_frame;
+       if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL)
+           frame_add_vsep(frp->fr_prev);
+
+       // Statuslines above may have been lost.  Restore them.
+       if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL)
+           frame_add_statusline(frp->fr_prev, TRUE);
+
+       win_append(wp->w_prev, wp);
+       return FAIL;
+    }
+    clear_snapshot_rec(frp);
+
+    // If splitting horizontally, try to preserve height.
+    if (size == 0 && !(flags & WSP_VERT))
+    {
+       win_setheight_win(height, wp);
        if (p_ea)
-           win_equal(curwin, TRUE, 'v');
+       {
+           // Equalize windows.  Note that win_split_ins autocommands may have
+           // made a window other than "wp" current.
+           win_equal(curwin, curwin == wp, 'v');
+       }
     }
 
 #if defined(FEAT_GUI)
@@ -1936,6 +1978,7 @@ win_totop(int size, int flags)
     // scrollbars.  Have to update them anyway.
     gui_may_update_scrollbars();
 #endif
+    return OK;
 }
 
 /*
@@ -3849,30 +3892,34 @@ frame_fixed_width(frame_T *frp)
 
 /*
  * Add a status line to windows at the bottom of "frp".
- * Note: Does not check if there is room!
+ * If "adjust_winheight" is set, reduce the height of windows without a
+ * statusline to accommodate one; otherwise, there is no check for room!
  */
     static void
-frame_add_statusline(frame_T *frp)
+frame_add_statusline(frame_T *frp, int adjust_winheight)
 {
     win_T      *wp;
 
     if (frp->fr_layout == FR_LEAF)
     {
        wp = frp->fr_win;
+        if (adjust_winheight && wp->w_status_height == 0
+            && wp->w_height >= STATUS_HEIGHT)  // don't make it negative
+            wp->w_height -= STATUS_HEIGHT - wp->w_status_height;
        wp->w_status_height = STATUS_HEIGHT;
     }
     else if (frp->fr_layout == FR_ROW)
     {
        // Handle all the frames in the row.
        FOR_ALL_FRAMES(frp, frp->fr_child)
-           frame_add_statusline(frp);
+           frame_add_statusline(frp, adjust_winheight);
     }
     else // frp->fr_layout == FR_COL
     {
        // Only need to handle the last frame in the column.
        for (frp = frp->fr_child; frp->fr_next != NULL; frp = frp->fr_next)
            ;
-       frame_add_statusline(frp);
+       frame_add_statusline(frp, adjust_winheight);
     }
 }
 
@@ -7498,29 +7545,44 @@ reset_lnums(void)
 /*
  * Create a snapshot of the current frame sizes.
  * "idx" is SNAP_HELP_IDX or SNAP_AUCMD_IDX.
+ * Return FAIL if out of memory, OK otherwise.
  */
-    void
+    int
 make_snapshot(int idx)
 {
     clear_snapshot(curtab, idx);
-    make_snapshot_rec(topframe, &curtab->tp_snapshot[idx]);
+    if (make_snapshot_rec(topframe, &curtab->tp_snapshot[idx], FALSE) == FAIL)
+    {
+       clear_snapshot(curtab, idx);
+       return FAIL;
+    }
+    return OK;
 }
 
-    static void
-make_snapshot_rec(frame_T *fr, frame_T **frp)
+    static int
+make_snapshot_rec(frame_T *fr, frame_T **frp, int snap_wins)
 {
     *frp = ALLOC_CLEAR_ONE(frame_T);
     if (*frp == NULL)
-       return;
+       return FAIL;
     (*frp)->fr_layout = fr->fr_layout;
     (*frp)->fr_width = fr->fr_width;
     (*frp)->fr_height = fr->fr_height;
     if (fr->fr_next != NULL)
-       make_snapshot_rec(fr->fr_next, &((*frp)->fr_next));
+    {
+       if (make_snapshot_rec(fr->fr_next, &((*frp)->fr_next), snap_wins)
+               == FAIL)
+           return FAIL;
+    }
     if (fr->fr_child != NULL)
-       make_snapshot_rec(fr->fr_child, &((*frp)->fr_child));
-    if (fr->fr_layout == FR_LEAF && fr->fr_win == curwin)
-       (*frp)->fr_win = curwin;
+    {
+       if (make_snapshot_rec(fr->fr_child, &((*frp)->fr_child), snap_wins)
+               == FAIL)
+           return FAIL;
+    }
+    if (fr->fr_layout == FR_LEAF && (snap_wins || fr->fr_win == curwin))
+       (*frp)->fr_win = fr->fr_win;
+    return OK;
 }
 
 /*
@@ -7657,6 +7719,86 @@ restore_snapshot_rec(frame_T *sn, frame_T *fr)
     return wp;
 }
 
+/*
+ * Return a snapshot of all frames in the current tabpage and which windows are
+ * in them, or NULL if out of memory.
+ * Use clear_snapshot_rec to free the snapshot.
+ */
+    static frame_T *
+make_full_snapshot(void)
+{
+    frame_T    *frp;
+
+    if (make_snapshot_rec(topframe, &frp, TRUE) == FAIL)
+    {
+       clear_snapshot_rec(frp);
+       return NULL;
+    }
+    return frp;
+}
+
+/*
+ * Restore all frames in the full snapshot "sn" for the current tabpage.
+ * Caller must ensure that the screen size didn't change, no windows with 
frames
+ * in the snapshot were freed, and windows with frames not in the snapshot are
+ * removed from their frames!
+ * Doesn't restore changed window vertical separators or statuslines.
+ * Frees the old frames.  Don't call clear_snapshot_rec on "sn" afterwards!
+ */
+    static void
+restore_full_snapshot(frame_T *sn)
+{
+    if (sn == NULL)
+       return;
+
+    clear_snapshot_rec(topframe);
+    restore_full_snapshot_rec(sn);
+    curtab->tp_topframe = topframe = sn;
+    last_status(FALSE);
+
+    // If the amount of space available changed, first try setting the sizes of
+    // windows with 'winfix{width,height}'. If that doesn't result in the right
+    // size, forget about that option.
+    if (topframe->fr_width != Columns)
+    {
+       frame_new_width(topframe, Columns, FALSE, TRUE);
+       if (!frame_check_width(topframe, Columns))
+           frame_new_width(topframe, Columns, FALSE, FALSE);
+    }
+    if (topframe->fr_height != ROWS_AVAIL)
+    {
+       frame_new_height(topframe, ROWS_AVAIL, FALSE, TRUE);
+       if (!frame_check_height(topframe, ROWS_AVAIL))
+           frame_new_height(topframe, ROWS_AVAIL, FALSE, FALSE);
+    }
+
+    win_comp_pos();
+}
+
+    static void
+restore_full_snapshot_rec(frame_T *sn)
+{
+    if (sn == NULL)
+       return;
+
+    if (sn->fr_child != NULL)
+       sn->fr_child->fr_parent = sn;
+    if (sn->fr_next != NULL)
+    {
+       sn->fr_next->fr_parent = sn->fr_parent;
+       sn->fr_next->fr_prev = sn;
+    }
+    if (sn->fr_win != NULL)
+    {
+       sn->fr_win->w_frame = sn;
+       // Resize window to fit the frame.
+       frame_new_height(sn, sn->fr_height, FALSE, FALSE);
+       frame_new_width(sn, sn->fr_width, FALSE, FALSE);
+    }
+    restore_full_snapshot_rec(sn->fr_child);
+    restore_full_snapshot_rec(sn->fr_next);
+}
+
 #if defined(FEAT_GUI) || defined(PROTO)
 /*
  * Return TRUE if there is any vertically split window.

-- 
-- 
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 vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1rcXgi-0099hq-GQ%40256bit.org.

Raspunde prin e-mail lui