neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

window.c (232651B)


      1 #include <assert.h>
      2 #include <inttypes.h>
      3 #include <limits.h>
      4 #include <stdbool.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 
      8 #include "klib/kvec.h"
      9 #include "nvim/api/private/defs.h"
     10 #include "nvim/api/private/helpers.h"
     11 #include "nvim/arglist.h"
     12 #include "nvim/ascii_defs.h"
     13 #include "nvim/autocmd.h"
     14 #include "nvim/autocmd_defs.h"
     15 #include "nvim/buffer.h"
     16 #include "nvim/buffer_defs.h"
     17 #include "nvim/charset.h"
     18 #include "nvim/cursor.h"
     19 #include "nvim/decoration.h"
     20 #include "nvim/diff.h"
     21 #include "nvim/drawscreen.h"
     22 #include "nvim/edit.h"
     23 #include "nvim/errors.h"
     24 #include "nvim/eval.h"
     25 #include "nvim/eval/typval.h"
     26 #include "nvim/eval/typval_defs.h"
     27 #include "nvim/eval/vars.h"
     28 #include "nvim/eval/window.h"
     29 #include "nvim/ex_cmds.h"
     30 #include "nvim/ex_cmds2.h"
     31 #include "nvim/ex_cmds_defs.h"
     32 #include "nvim/ex_docmd.h"
     33 #include "nvim/ex_eval.h"
     34 #include "nvim/ex_getln.h"
     35 #include "nvim/file_search.h"
     36 #include "nvim/fileio.h"
     37 #include "nvim/fold.h"
     38 #include "nvim/garray.h"
     39 #include "nvim/getchar.h"
     40 #include "nvim/gettext_defs.h"
     41 #include "nvim/globals.h"
     42 #include "nvim/grid.h"
     43 #include "nvim/grid_defs.h"
     44 #include "nvim/hashtab.h"
     45 #include "nvim/keycodes.h"
     46 #include "nvim/macros_defs.h"
     47 #include "nvim/main.h"
     48 #include "nvim/map_defs.h"
     49 #include "nvim/mapping.h"
     50 #include "nvim/mark.h"
     51 #include "nvim/mark_defs.h"
     52 #include "nvim/match.h"
     53 #include "nvim/memory.h"
     54 #include "nvim/message.h"
     55 #include "nvim/mouse.h"
     56 #include "nvim/move.h"
     57 #include "nvim/normal.h"
     58 #include "nvim/option.h"
     59 #include "nvim/option_defs.h"
     60 #include "nvim/option_vars.h"
     61 #include "nvim/os/fs.h"
     62 #include "nvim/os/os_defs.h"
     63 #include "nvim/path.h"
     64 #include "nvim/plines.h"
     65 #include "nvim/popupmenu.h"
     66 #include "nvim/pos_defs.h"
     67 #include "nvim/quickfix.h"
     68 #include "nvim/search.h"
     69 #include "nvim/state.h"
     70 #include "nvim/state_defs.h"
     71 #include "nvim/statusline.h"
     72 #include "nvim/strings.h"
     73 #include "nvim/syntax.h"
     74 #include "nvim/tag.h"
     75 #include "nvim/terminal.h"
     76 #include "nvim/types_defs.h"
     77 #include "nvim/ui.h"
     78 #include "nvim/ui_compositor.h"
     79 #include "nvim/ui_defs.h"
     80 #include "nvim/undo.h"
     81 #include "nvim/vim_defs.h"
     82 #include "nvim/window.h"
     83 #include "nvim/winfloat.h"
     84 
     85 #include "window.c.generated.h"
     86 
     87 #define NOWIN           ((win_T *)-1)   // non-existing window
     88 
     89 #define ROWS_AVAIL (Rows - p_ch - tabline_height() - global_stl_height())
     90 
     91 /// flags for win_enter_ext()
     92 typedef enum {
     93  WEE_UNDO_SYNC = 0x01,
     94  WEE_CURWIN_INVALID = 0x02,
     95  WEE_TRIGGER_NEW_AUTOCMDS = 0x04,
     96  WEE_TRIGGER_ENTER_AUTOCMDS = 0x08,
     97  WEE_TRIGGER_LEAVE_AUTOCMDS = 0x10,
     98 } wee_flags_T;
     99 
    100 static const char e_cannot_close_last_window[]
    101  = N_("E444: Cannot close last window");
    102 static const char e_cannot_split_window_when_closing_buffer[]
    103  = N_("E1159: Cannot split a window when closing the buffer");
    104 
    105 static char *m_onlyone = N_("Already only one window");
    106 
    107 /// When non-zero splitting a window is forbidden.  Used to avoid that nasty
    108 /// autocommands mess up the window structure.
    109 static int split_disallowed = 0;
    110 
    111 /// When non-zero closing a window is forbidden.  Used to avoid that nasty
    112 /// autocommands mess up the window structure.
    113 static int close_disallowed = 0;
    114 
    115 /// When non-zero changing the window frame structure is forbidden.  Used
    116 /// to avoid that winframe_remove() is called recursively
    117 static int frame_locked = 0;
    118 
    119 /// Disallow changing the window layout (split window, close window, move
    120 /// window).  Resizing is still allowed.
    121 /// Used for autocommands that temporarily use another window and need to
    122 /// make sure the previously selected window is still there.
    123 /// Must be matched with exactly one call to window_layout_unlock()!
    124 void window_layout_lock(void)
    125 {
    126  split_disallowed++;
    127  close_disallowed++;
    128 }
    129 
    130 void window_layout_unlock(void)
    131 {
    132  split_disallowed--;
    133  close_disallowed--;
    134 }
    135 
    136 bool frames_locked(void)
    137 {
    138  return frame_locked;
    139 }
    140 
    141 /// When the window layout cannot be changed give an error and return true.
    142 /// "cmd" indicates the action being performed and is used to pick the relevant
    143 /// error message.  When closing window(s) and the command isn't easy to know,
    144 /// passing CMD_SIZE will also work.
    145 bool window_layout_locked(cmdidx_T cmd)
    146 {
    147  if (split_disallowed > 0 || close_disallowed > 0) {
    148    if (close_disallowed == 0 && cmd == CMD_tabnew) {
    149      emsg(_(e_cannot_split_window_when_closing_buffer));
    150    } else {
    151      emsg(_(e_not_allowed_to_change_window_layout_in_this_autocmd));
    152    }
    153    return true;
    154  }
    155  return false;
    156 }
    157 
    158 // #define WIN_DEBUG
    159 #ifdef WIN_DEBUG
    160 /// Call this method to log the current window layout.
    161 static void log_frame_layout(frame_T *frame)
    162 {
    163  DLOG("layout %s, wi: %d, he: %d, wwi: %d, whe: %d, id: %d",
    164       frame->fr_layout == FR_LEAF ? "LEAF" : frame->fr_layout == FR_ROW ? "ROW" : "COL",
    165       frame->fr_width,
    166       frame->fr_height,
    167       frame->fr_win == NULL ? -1 : frame->fr_win->w_width,
    168       frame->fr_win == NULL ? -1 : frame->fr_win->w_height,
    169       frame->fr_win == NULL ? -1 : frame->fr_win->w_id);
    170  if (frame->fr_child != NULL) {
    171    DLOG("children");
    172    log_frame_layout(frame->fr_child);
    173    if (frame->fr_next != NULL) {
    174      DLOG("END of children");
    175    }
    176  }
    177  if (frame->fr_next != NULL) {
    178    log_frame_layout(frame->fr_next);
    179  }
    180 }
    181 #endif
    182 
    183 /// Check if the current window is allowed to move to a different buffer.
    184 ///
    185 /// @return If the window has 'winfixbuf', or this function will return false.
    186 bool check_can_set_curbuf_disabled(void)
    187 {
    188  if (curwin->w_p_wfb) {
    189    emsg(_(e_winfixbuf_cannot_go_to_buffer));
    190    return false;
    191  }
    192 
    193  return true;
    194 }
    195 
    196 /// Check if the current window is allowed to move to a different buffer.
    197 ///
    198 /// @param forceit If true, do not error. If false and 'winfixbuf' is enabled, error.
    199 ///
    200 /// @return If the window has 'winfixbuf', then forceit must be true
    201 ///     or this function will return false.
    202 bool check_can_set_curbuf_forceit(int forceit)
    203 {
    204  if (!forceit && curwin->w_p_wfb) {
    205    emsg(_(e_winfixbuf_cannot_go_to_buffer));
    206    return false;
    207  }
    208 
    209  return true;
    210 }
    211 
    212 /// @return the current window, unless in the cmdline window and "prevwin" is
    213 /// set, then return "prevwin".
    214 win_T *prevwin_curwin(void)
    215  FUNC_ATTR_WARN_UNUSED_RESULT
    216 {
    217  // In cmdwin, the alternative buffer should be used.
    218  return is_in_cmdwin() && prevwin != NULL ? prevwin : curwin;
    219 }
    220 
    221 /// If the 'switchbuf' option contains "useopen" or "usetab", then try to jump
    222 /// to a window containing "buf".
    223 /// Returns the pointer to the window that was jumped to or NULL.
    224 win_T *swbuf_goto_win_with_buf(buf_T *buf)
    225 {
    226  win_T *wp = NULL;
    227 
    228  if (buf == NULL) {
    229    return wp;
    230  }
    231 
    232  // If 'switchbuf' contains "useopen": jump to first window in the current
    233  // tab page containing "buf" if one exists.
    234  if (swb_flags & kOptSwbFlagUseopen) {
    235    wp = buf_jump_open_win(buf);
    236  }
    237 
    238  // If 'switchbuf' contains "usetab": jump to first window in any tab page
    239  // containing "buf" if one exists.
    240  if (wp == NULL && (swb_flags & kOptSwbFlagUsetab)) {
    241    wp = buf_jump_open_tab(buf);
    242  }
    243 
    244  return wp;
    245 }
    246 
    247 // 'cmdheight' value explicitly set by the user: window commands are allowed to
    248 // resize the topframe to values higher than this minimum, but not lower.
    249 static OptInt min_set_ch = 1;
    250 
    251 /// all CTRL-W window commands are handled here, called from normal_cmd().
    252 ///
    253 /// @param xchar  extra char from ":wincmd gx" or NUL
    254 void do_window(int nchar, int Prenum, int xchar)
    255 {
    256  int type = FIND_DEFINE;
    257  char cbuf[40];
    258 
    259  int Prenum1 = Prenum == 0 ? 1 : Prenum;
    260 
    261 #define CHECK_CMDWIN \
    262  do { \
    263    if (cmdwin_type != 0) { \
    264      emsg(_(e_cmdwin)); \
    265      return; \
    266    } \
    267  } while (0)
    268 
    269  switch (nchar) {
    270  // split current window in two parts, horizontally
    271  case 'S':
    272  case Ctrl_S:
    273  case 's':
    274    CHECK_CMDWIN;
    275    reset_VIsual_and_resel();  // stop Visual mode
    276    // When splitting the quickfix window open a new buffer in it,
    277    // don't replicate the quickfix buffer.
    278    if (bt_quickfix(curbuf)) {
    279      goto newwindow;
    280    }
    281    win_split(Prenum, 0);
    282    break;
    283 
    284  // split current window in two parts, vertically
    285  case Ctrl_V:
    286  case 'v':
    287    CHECK_CMDWIN;
    288    reset_VIsual_and_resel();  // stop Visual mode
    289    // When splitting the quickfix window open a new buffer in it,
    290    // don't replicate the quickfix buffer.
    291    if (bt_quickfix(curbuf)) {
    292      goto newwindow;
    293    }
    294    win_split(Prenum, WSP_VERT);
    295    break;
    296 
    297  // split current window and edit alternate file
    298  case Ctrl_HAT:
    299  case '^':
    300    CHECK_CMDWIN;
    301    reset_VIsual_and_resel();  // stop Visual mode
    302 
    303    if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : Prenum) == NULL) {
    304      if (Prenum == 0) {
    305        emsg(_(e_noalt));
    306      } else {
    307        semsg(_(e_buffer_nr_not_found), (int64_t)Prenum);
    308      }
    309      break;
    310    }
    311 
    312    if (!curbuf_locked() && win_split(0, 0) == OK) {
    313      buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : Prenum,
    314                      0, GETF_ALT, false);
    315    }
    316    break;
    317 
    318  // open new window
    319  case Ctrl_N:
    320  case 'n':
    321    CHECK_CMDWIN;
    322    reset_VIsual_and_resel();  // stop Visual mode
    323 newwindow:
    324    if (Prenum) {
    325      // window height
    326      vim_snprintf(cbuf, sizeof(cbuf) - 5, "%" PRId64, (int64_t)Prenum);
    327    } else {
    328      cbuf[0] = NUL;
    329    }
    330    if (nchar == 'v' || nchar == Ctrl_V) {
    331      xstrlcat(cbuf, "v", sizeof(cbuf));
    332    }
    333    xstrlcat(cbuf, "new", sizeof(cbuf));
    334    do_cmdline_cmd(cbuf);
    335    break;
    336 
    337  // quit current window
    338  case Ctrl_Q:
    339  case 'q':
    340    reset_VIsual_and_resel();                   // stop Visual mode
    341    cmd_with_count("quit", cbuf, sizeof(cbuf), Prenum);
    342    do_cmdline_cmd(cbuf);
    343    break;
    344 
    345  // close current window
    346  case Ctrl_C:
    347  case 'c':
    348    reset_VIsual_and_resel();                   // stop Visual mode
    349    cmd_with_count("close", cbuf, sizeof(cbuf), Prenum);
    350    do_cmdline_cmd(cbuf);
    351    break;
    352 
    353  // close preview window
    354  case Ctrl_Z:
    355  case 'z':
    356    CHECK_CMDWIN;
    357    reset_VIsual_and_resel();  // stop Visual mode
    358    do_cmdline_cmd("pclose");
    359    break;
    360 
    361  // cursor to preview window
    362  case 'P': {
    363    win_T *wp = NULL;
    364    FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
    365      if (wp2->w_p_pvw) {
    366        wp = wp2;
    367        break;
    368      }
    369    }
    370    if (wp == NULL) {
    371      emsg(_("E441: There is no preview window"));
    372    } else {
    373      win_goto(wp);
    374    }
    375    break;
    376  }
    377 
    378  // close all but current window
    379  case Ctrl_O:
    380  case 'o':
    381    CHECK_CMDWIN;
    382    reset_VIsual_and_resel();  // stop Visual mode
    383    cmd_with_count("only", cbuf, sizeof(cbuf), Prenum);
    384    do_cmdline_cmd(cbuf);
    385    break;
    386 
    387  // cursor to next window with wrap around
    388  case Ctrl_W:
    389  case 'w':
    390  // cursor to previous window with wrap around
    391  case 'W':
    392    CHECK_CMDWIN;
    393    if (ONE_WINDOW && Prenum != 1) {  // just one window
    394      beep_flush();
    395    } else {
    396      win_T *wp;
    397      if (Prenum) {  // go to specified window
    398        win_T *last_focusable = firstwin;
    399        for (wp = firstwin; --Prenum > 0;) {
    400          if (!wp->w_floating || (!wp->w_config.hide && wp->w_config.focusable)) {
    401            last_focusable = wp;
    402          }
    403          if (wp->w_next == NULL) {
    404            break;
    405          }
    406          wp = wp->w_next;
    407        }
    408        while (wp != NULL && wp->w_floating
    409               && (wp->w_config.hide || !wp->w_config.focusable)) {
    410          wp = wp->w_next;
    411        }
    412        if (wp == NULL) {  // went past the last focusable window
    413          wp = last_focusable;
    414        }
    415      } else {
    416        if (nchar == 'W') {  // go to previous window
    417          wp = curwin->w_prev;
    418          if (wp == NULL) {
    419            wp = lastwin;  // wrap around
    420          }
    421          while (wp != NULL && wp->w_floating
    422                 && (wp->w_config.hide || !wp->w_config.focusable)) {
    423            wp = wp->w_prev;
    424          }
    425        } else {  // go to next window
    426          wp = curwin->w_next;
    427          while (wp != NULL && wp->w_floating
    428                 && (wp->w_config.hide || !wp->w_config.focusable)) {
    429            wp = wp->w_next;
    430          }
    431          if (wp == NULL) {
    432            wp = firstwin;  // wrap around
    433          }
    434        }
    435      }
    436      win_goto(wp);
    437    }
    438    break;
    439 
    440  // cursor to window below
    441  case 'j':
    442  case K_DOWN:
    443  case Ctrl_J:
    444    CHECK_CMDWIN;
    445    win_goto_ver(false, Prenum1);
    446    break;
    447 
    448  // cursor to window above
    449  case 'k':
    450  case K_UP:
    451  case Ctrl_K:
    452    CHECK_CMDWIN;
    453    win_goto_ver(true, Prenum1);
    454    break;
    455 
    456  // cursor to left window
    457  case 'h':
    458  case K_LEFT:
    459  case Ctrl_H:
    460  case K_BS:
    461    CHECK_CMDWIN;
    462    win_goto_hor(true, Prenum1);
    463    break;
    464 
    465  // cursor to right window
    466  case 'l':
    467  case K_RIGHT:
    468  case Ctrl_L:
    469    CHECK_CMDWIN;
    470    win_goto_hor(false, Prenum1);
    471    break;
    472 
    473  // move window to new tab page
    474  case 'T':
    475    CHECK_CMDWIN;
    476    if (one_window(curwin, NULL)) {
    477      msg(_(m_onlyone), 0);
    478    } else {
    479      tabpage_T *oldtab = curtab;
    480 
    481      // First create a new tab with the window, then go back to
    482      // the old tab and close the window there.
    483      win_T *wp = curwin;
    484      if (win_new_tabpage(Prenum, NULL) == OK
    485          && valid_tabpage(oldtab)) {
    486        tabpage_T *newtab = curtab;
    487        goto_tabpage_tp(oldtab, true, true);
    488        if (curwin == wp) {
    489          win_close(curwin, false, false);
    490        }
    491        if (valid_tabpage(newtab)) {
    492          goto_tabpage_tp(newtab, true, true);
    493          apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf);
    494        }
    495      }
    496    }
    497    break;
    498 
    499  // cursor to top-left window
    500  case 't':
    501  case Ctrl_T:
    502    win_goto(firstwin);
    503    break;
    504 
    505  // cursor to bottom-right window
    506  case 'b':
    507  case Ctrl_B:
    508    win_goto(lastwin_nofloating());
    509    break;
    510 
    511  // cursor to last accessed (previous) window
    512  case 'p':
    513  case Ctrl_P:
    514    if (!win_valid(prevwin) || prevwin->w_config.hide || !prevwin->w_config.focusable) {
    515      beep_flush();
    516    } else {
    517      win_goto(prevwin);
    518    }
    519    break;
    520 
    521  // exchange current and next window
    522  case 'x':
    523  case Ctrl_X:
    524    CHECK_CMDWIN;
    525    win_exchange(Prenum);
    526    break;
    527 
    528  // rotate windows downwards
    529  case Ctrl_R:
    530  case 'r':
    531    CHECK_CMDWIN;
    532    reset_VIsual_and_resel();  // stop Visual mode
    533    win_rotate(false, Prenum1);  // downwards
    534    break;
    535 
    536  // rotate windows upwards
    537  case 'R':
    538    CHECK_CMDWIN;
    539    reset_VIsual_and_resel();  // stop Visual mode
    540    win_rotate(true, Prenum1);  // upwards
    541    break;
    542 
    543  // move window to the very top/bottom/left/right
    544  case 'K':
    545  case 'J':
    546  case 'H':
    547  case 'L':
    548    CHECK_CMDWIN;
    549    if (one_window(curwin, NULL)) {
    550      beep_flush();
    551    } else {
    552      const int dir = ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
    553                      | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT);
    554 
    555      win_splitmove(curwin, Prenum, dir);
    556    }
    557    break;
    558 
    559  // make all windows the same width and/or height
    560  case '=': {
    561    int mod = cmdmod.cmod_split & (WSP_VERT | WSP_HOR);
    562    win_equal(NULL, false, mod == WSP_VERT ? 'v' : mod == WSP_HOR ? 'h' : 'b');
    563    break;
    564  }
    565 
    566  // increase current window height
    567  case '+':
    568    win_setheight(curwin->w_height + Prenum1);
    569    break;
    570 
    571  // decrease current window height
    572  case '-':
    573    win_setheight(curwin->w_height - Prenum1);
    574    break;
    575 
    576  // set current window height
    577  case Ctrl__:
    578  case '_':
    579    win_setheight(Prenum ? Prenum : Rows - (int)min_set_ch);
    580    break;
    581 
    582  // increase current window width
    583  case '>':
    584    win_setwidth(curwin->w_width + Prenum1);
    585    break;
    586 
    587  // decrease current window width
    588  case '<':
    589    win_setwidth(curwin->w_width - Prenum1);
    590    break;
    591 
    592  // set current window width
    593  case '|':
    594    win_setwidth(Prenum != 0 ? Prenum : Columns);
    595    break;
    596 
    597  // jump to tag and split window if tag exists (in preview window)
    598  case '}':
    599    CHECK_CMDWIN;
    600    if (Prenum) {
    601      g_do_tagpreview = Prenum;
    602    } else {
    603      g_do_tagpreview = (int)p_pvh;
    604    }
    605    FALLTHROUGH;
    606  case ']':
    607  case Ctrl_RSB:
    608    CHECK_CMDWIN;
    609    // Keep visual mode, can select words to use as a tag.
    610    if (Prenum) {
    611      postponed_split = Prenum;
    612    } else {
    613      postponed_split = -1;
    614    }
    615 
    616    if (nchar != '}') {
    617      g_do_tagpreview = 0;
    618    }
    619 
    620    // Execute the command right here, required when
    621    // "wincmd ]" was used in a function.
    622    do_nv_ident(Ctrl_RSB, NUL);
    623    postponed_split = 0;
    624    break;
    625 
    626  // edit file name under cursor in a new window
    627  case 'f':
    628  case 'F':
    629  case Ctrl_F: {
    630 wingotofile:
    631    CHECK_CMDWIN;
    632    if (check_text_or_curbuf_locked(NULL)) {
    633      break;
    634    }
    635 
    636    linenr_T lnum = -1;
    637    char *ptr = grab_file_name(Prenum1, &lnum);
    638    if (ptr != NULL) {
    639      tabpage_T *oldtab = curtab;
    640      win_T *oldwin = curwin;
    641      setpcmark();
    642 
    643      // If 'switchbuf' is set to 'useopen' or 'usetab' and the
    644      // file is already opened in a window, then jump to it.
    645      win_T *wp = NULL;
    646      if ((swb_flags & (kOptSwbFlagUseopen | kOptSwbFlagUsetab))
    647          && cmdmod.cmod_tab == 0) {
    648        wp = swbuf_goto_win_with_buf(buflist_findname_exp(ptr));
    649      }
    650 
    651      if (wp == NULL && win_split(0, 0) == OK) {
    652        RESET_BINDING(curwin);
    653        if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
    654          // Failed to open the file, close the window opened for it.
    655          win_close(curwin, false, false);
    656          goto_tabpage_win(oldtab, oldwin);
    657        } else {
    658          wp = curwin;
    659        }
    660      }
    661 
    662      if (wp != NULL && nchar == 'F' && lnum >= 0) {
    663        curwin->w_cursor.lnum = lnum;
    664        check_cursor_lnum(curwin);
    665        beginline(BL_SOL | BL_FIX);
    666      }
    667      xfree(ptr);
    668    }
    669    break;
    670  }
    671 
    672  // Go to the first occurrence of the identifier under cursor along path in a
    673  // new window -- webb
    674  case 'i':                         // Go to any match
    675  case Ctrl_I:
    676    type = FIND_ANY;
    677    FALLTHROUGH;
    678  case 'd':                         // Go to definition, using 'define'
    679  case Ctrl_D: {
    680    CHECK_CMDWIN;
    681    size_t len;
    682    char *ptr;
    683    if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) {
    684      break;
    685    }
    686 
    687    // Make a copy, if the line was changed it will be freed.
    688    ptr = xmemdupz(ptr, len);
    689 
    690    find_pattern_in_path(ptr, 0, len, true, Prenum == 0, type,
    691                         Prenum1, ACTION_SPLIT, 1, MAXLNUM, false, false);
    692    xfree(ptr);
    693    curwin->w_set_curswant = true;
    694    break;
    695  }
    696 
    697  // Quickfix window only: view the result under the cursor in a new split.
    698  case K_KENTER:
    699  case CAR:
    700    if (bt_quickfix(curbuf)) {
    701      qf_view_result(true);
    702    }
    703    break;
    704 
    705  // CTRL-W g  extended commands
    706  case 'g':
    707  case Ctrl_G:
    708    CHECK_CMDWIN;
    709    no_mapping++;
    710    allow_keys++;               // no mapping for xchar, but allow key codes
    711    if (xchar == NUL) {
    712      xchar = plain_vgetc();
    713    }
    714    LANGMAP_ADJUST(xchar, true);
    715    no_mapping--;
    716    allow_keys--;
    717    add_to_showcmd(xchar);
    718 
    719    switch (xchar) {
    720    case '}':
    721      xchar = Ctrl_RSB;
    722      if (Prenum) {
    723        g_do_tagpreview = Prenum;
    724      } else {
    725        g_do_tagpreview = (int)p_pvh;
    726      }
    727      FALLTHROUGH;
    728    case ']':
    729    case Ctrl_RSB:
    730      // Keep visual mode, can select words to use as a tag.
    731      if (Prenum) {
    732        postponed_split = Prenum;
    733      } else {
    734        postponed_split = -1;
    735      }
    736 
    737      // Execute the command right here, required when
    738      // "wincmd g}" was used in a function.
    739      do_nv_ident('g', xchar);
    740      postponed_split = 0;
    741      break;
    742 
    743    case 'f':                       // CTRL-W gf: "gf" in a new tab page
    744    case 'F':                       // CTRL-W gF: "gF" in a new tab page
    745      cmdmod.cmod_tab = tabpage_index(curtab) + 1;
    746      nchar = xchar;
    747      goto wingotofile;
    748 
    749    case 't':                       // CTRL-W gt: go to next tab page
    750      goto_tabpage(Prenum);
    751      break;
    752 
    753    case 'T':                       // CTRL-W gT: go to previous tab page
    754      goto_tabpage(-Prenum1);
    755      break;
    756 
    757    case TAB:                       // CTRL-W g<Tab>: go to last used tab page
    758      if (!goto_tabpage_lastused()) {
    759        beep_flush();
    760      }
    761      break;
    762 
    763    case 'e':
    764      if (curwin->w_floating || !ui_has(kUIMultigrid)) {
    765        beep_flush();
    766        break;
    767      }
    768      WinConfig config = WIN_CONFIG_INIT;
    769      config.width = curwin->w_width;
    770      config.height = curwin->w_height;
    771      config.external = true;
    772      Error err = ERROR_INIT;
    773      if (!win_new_float(curwin, false, config, &err)) {
    774        emsg(err.msg);
    775        api_clear_error(&err);
    776        beep_flush();
    777      }
    778      break;
    779    default:
    780      beep_flush();
    781      break;
    782    }
    783    break;
    784 
    785  default:
    786    beep_flush();
    787    break;
    788  }
    789 }
    790 
    791 static void cmd_with_count(char *cmd, char *bufp, size_t bufsize, int64_t Prenum)
    792 {
    793  size_t len = xstrlcpy(bufp, cmd, bufsize);
    794 
    795  if (Prenum > 0 && len < bufsize) {
    796    vim_snprintf(bufp + len, bufsize - len, "%" PRId64, Prenum);
    797  }
    798 }
    799 
    800 void win_set_buf(win_T *win, buf_T *buf, Error *err)
    801  FUNC_ATTR_NONNULL_ALL
    802 {
    803  const handle_T win_handle = win->handle;
    804  tabpage_T *tab = win_find_tabpage(win);
    805 
    806  // no redrawing and don't set the window title
    807  RedrawingDisabled++;
    808 
    809  switchwin_T switchwin;
    810  int win_result;
    811 
    812  TRY_WRAP(err, {
    813    win_result = switch_win_noblock(&switchwin, win, tab, true);
    814    if (win_result != FAIL) {
    815      const int save_acd = p_acd;
    816      if (!switchwin.sw_same_win) {
    817        // Temporarily disable 'autochdir' when setting buffer in another window.
    818        p_acd = false;
    819      }
    820 
    821      do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
    822 
    823      if (!switchwin.sw_same_win) {
    824        p_acd = save_acd;
    825      }
    826    }
    827  });
    828  if (win_result == FAIL && !ERROR_SET(err)) {
    829    api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win_handle);
    830  }
    831 
    832  // If window is not current, state logic will not validate its cursor. So do it now.
    833  // Still needed if do_buffer returns FAIL (e.g: autocmds abort script after buffer was set).
    834  validate_cursor(curwin);
    835 
    836  restore_win_noblock(&switchwin, true);
    837  RedrawingDisabled--;
    838 }
    839 
    840 /// Return the number of fold columns to display
    841 int win_fdccol_count(win_T *wp)
    842 {
    843  const char *fdc = wp->w_p_fdc;
    844 
    845  // auto:<NUM>
    846  if (strncmp(fdc, "auto", 4) == 0) {
    847    const int fdccol = fdc[4] == ':' ? fdc[5] - '0' : 1;
    848    int needed_fdccols = getDeepestNesting(wp);
    849    return MIN(fdccol, needed_fdccols);
    850  }
    851  return fdc[0] - '0';
    852 }
    853 
    854 /// Merges two window configs, freeing replaced fields if necessary.
    855 void merge_win_config(WinConfig *dst, const WinConfig src)
    856  FUNC_ATTR_NONNULL_ALL
    857 {
    858  if (dst->title_chunks.items != src.title_chunks.items) {
    859    clear_virttext(&dst->title_chunks);
    860  }
    861  if (dst->footer_chunks.items != src.footer_chunks.items) {
    862    clear_virttext(&dst->footer_chunks);
    863  }
    864  *dst = src;
    865 }
    866 
    867 void ui_ext_win_position(win_T *wp, bool validate)
    868 {
    869  wp->w_pos_changed = false;
    870  if (!wp->w_floating) {
    871    if (ui_has(kUIMultigrid)) {
    872      // Windows on the default grid don't necessarily have comp_col and comp_row set
    873      // But the rest of the calculations relies on it
    874      wp->w_grid_alloc.comp_col = wp->w_wincol;
    875      wp->w_grid_alloc.comp_row = wp->w_winrow;
    876    }
    877    ui_call_win_pos(wp->w_grid_alloc.handle, wp->handle, wp->w_winrow,
    878                    wp->w_wincol, wp->w_width, wp->w_height);
    879    return;
    880  }
    881 
    882  WinConfig c = wp->w_config;
    883  if (!c.external) {
    884    ScreenGrid *grid = &default_grid;
    885    Float row = c.row;
    886    Float col = c.col;
    887    if (c.relative == kFloatRelativeWindow) {
    888      Error dummy = ERROR_INIT;
    889      win_T *win = find_window_by_handle(c.window, &dummy);
    890      api_clear_error(&dummy);
    891      if (win != NULL) {
    892        // When a floating window is anchored to another window,
    893        // update the position of its anchored window first.
    894        if (win->w_pos_changed && win->w_grid_alloc.chars != NULL && win_valid(win)) {
    895          ui_ext_win_position(win, validate);
    896        }
    897        int row_off = 0;
    898        int col_off = 0;
    899        win_grid_alloc(win);
    900        grid = grid_adjust(&win->w_grid, &row_off, &col_off);
    901        row += row_off;
    902        col += col_off;
    903        if (c.bufpos.lnum >= 0) {
    904          int lnum = MIN(c.bufpos.lnum + 1, win->w_buffer->b_ml.ml_line_count);
    905          pos_T pos = { lnum, c.bufpos.col, 0 };
    906          int trow, tcol, tcolc, tcole;
    907          textpos2screenpos(win, &pos, &trow, &tcol, &tcolc, &tcole, true);
    908          row += trow - 1;
    909          col += tcol - 1;
    910        }
    911      }
    912    } else if (c.relative == kFloatRelativeLaststatus) {
    913      row += Rows - (int)p_ch - last_stl_height(false);
    914    } else if (c.relative == kFloatRelativeTabline) {
    915      row += tabline_height();
    916    }
    917 
    918    bool resort = wp->w_grid_alloc.comp_index != 0
    919                  && wp->w_grid_alloc.zindex != wp->w_config.zindex;
    920    bool raise = resort && wp->w_grid_alloc.zindex < wp->w_config.zindex;
    921    wp->w_grid_alloc.zindex = wp->w_config.zindex;
    922    if (resort) {
    923      ui_comp_layers_adjust(wp->w_grid_alloc.comp_index, raise);
    924    }
    925    bool valid = (wp->w_redr_type == 0 || ui_has(kUIMultigrid));
    926    if (!valid && !validate) {
    927      wp->w_pos_changed = true;
    928      return;
    929    }
    930 
    931    // TODO(bfredl): ideally, compositor should work like any multigrid UI
    932    // and use standard win_pos events.
    933    bool east = c.anchor & kFloatAnchorEast;
    934    bool south = c.anchor & kFloatAnchorSouth;
    935 
    936    int comp_row = (int)row - (south ? wp->w_height_outer : 0);
    937    int comp_col = (int)col - (east ? wp->w_width_outer : 0);
    938    int above_ch = wp->w_config.zindex < kZIndexMessages ? (int)p_ch : 0;
    939    comp_row += grid->comp_row;
    940    comp_col += grid->comp_col;
    941    comp_row = MAX(MIN(comp_row, Rows - wp->w_height_outer - above_ch), 0);
    942    if (!c.fixed || east) {
    943      comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0);
    944    }
    945    wp->w_winrow = comp_row;
    946    wp->w_wincol = comp_col;
    947 
    948    if (!c.hide) {
    949      ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
    950                       wp->w_height_outer, wp->w_width_outer, valid, false);
    951      if (ui_has(kUIMultigrid)) {
    952        String anchor = cstr_as_string(float_anchor_str[c.anchor]);
    953        ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
    954                              grid->handle, row, col, c.mouse,
    955                              wp->w_grid_alloc.zindex, (int)wp->w_grid_alloc.comp_index,
    956                              wp->w_winrow,
    957                              wp->w_wincol);
    958      }
    959      ui_check_cursor_grid(wp->w_grid_alloc.handle);
    960      wp->w_grid_alloc.mouse_enabled = wp->w_config.mouse;
    961      if (!valid) {
    962        wp->w_grid_alloc.valid = false;
    963        redraw_later(wp, UPD_NOT_VALID);
    964      }
    965    } else {
    966      if (ui_has(kUIMultigrid)) {
    967        ui_call_win_hide(wp->w_grid_alloc.handle);
    968      }
    969      ui_comp_remove_grid(&wp->w_grid_alloc);
    970    }
    971  } else {
    972    ui_call_win_external_pos(wp->w_grid_alloc.handle, wp->handle);
    973  }
    974 }
    975 
    976 void ui_ext_win_viewport(win_T *wp)
    977 {
    978  // NOTE: The win_viewport command is delayed until the next flush when there are pending updates.
    979  // This ensures that the updates and the viewport are sent together.
    980  if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid && wp->w_redr_type == 0) {
    981    const linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
    982    // Avoid ml_get errors when producing "scroll_delta".
    983    const linenr_T cur_topline = MIN(wp->w_topline, line_count);
    984    const linenr_T cur_botline = MIN(wp->w_botline, line_count);
    985    int64_t delta = 0;
    986    linenr_T last_topline = wp->w_viewport_last_topline;
    987    linenr_T last_botline = wp->w_viewport_last_botline;
    988    int last_topfill = wp->w_viewport_last_topfill;
    989    int64_t last_skipcol = wp->w_viewport_last_skipcol;
    990    if (last_topline > line_count) {
    991      delta -= last_topline - line_count;
    992      last_topline = line_count;
    993      last_topfill = 0;
    994      last_skipcol = MAXCOL;
    995    }
    996    last_botline = MIN(last_botline, line_count);
    997    if (cur_topline < last_topline
    998        || (cur_topline == last_topline && wp->w_skipcol < last_skipcol)) {
    999      int64_t vcole = last_skipcol;
   1000      linenr_T lnume = last_topline;
   1001      if (last_topline > 0 && cur_botline < last_topline) {
   1002        // Scrolling too many lines: only give an approximate "scroll_delta".
   1003        delta -= last_topline - cur_botline;
   1004        lnume = cur_botline;
   1005        vcole = 0;
   1006      }
   1007      delta -= win_text_height(wp, cur_topline, wp->w_skipcol, &lnume, &vcole, NULL, INT64_MAX);
   1008    } else if (cur_topline > last_topline
   1009               || (cur_topline == last_topline && wp->w_skipcol > last_skipcol)) {
   1010      int64_t vcole = wp->w_skipcol;
   1011      linenr_T lnume = cur_topline;
   1012      if (last_botline > 0 && cur_topline > last_botline) {
   1013        // Scrolling too many lines: only give an approximate "scroll_delta".
   1014        delta += cur_topline - last_botline;
   1015        lnume = last_botline;
   1016        vcole = 0;
   1017      }
   1018      delta += win_text_height(wp, last_topline, last_skipcol, &lnume, &vcole, NULL, INT64_MAX);
   1019    }
   1020    delta += last_topfill;
   1021    delta -= wp->w_topfill;
   1022    linenr_T ev_botline = wp->w_botline;
   1023    if (ev_botline == line_count + 1 && wp->w_empty_rows == 0) {
   1024      // TODO(bfredl): The might be more cases to consider, like how does this
   1025      // interact with incomplete final line? Diff filler lines?
   1026      ev_botline = line_count;
   1027    }
   1028    ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline - 1, ev_botline,
   1029                         wp->w_cursor.lnum - 1, wp->w_cursor.col, line_count, delta);
   1030    wp->w_viewport_invalid = false;
   1031    wp->w_viewport_last_topline = wp->w_topline;
   1032    wp->w_viewport_last_botline = wp->w_botline;
   1033    wp->w_viewport_last_topfill = wp->w_topfill;
   1034    wp->w_viewport_last_skipcol = wp->w_skipcol;
   1035  }
   1036 }
   1037 
   1038 /// If "split_disallowed" is set, or "wp"'s buffer is closing, give an error and return FAIL.
   1039 /// Otherwise return OK.
   1040 int check_split_disallowed(const win_T *wp)
   1041  FUNC_ATTR_NONNULL_ALL
   1042 {
   1043  Error err = ERROR_INIT;
   1044  const bool ok = check_split_disallowed_err(wp, &err);
   1045  if (ERROR_SET(&err)) {
   1046    emsg(_(err.msg));
   1047    api_clear_error(&err);
   1048  }
   1049  return ok ? OK : FAIL;
   1050 }
   1051 
   1052 /// Like `check_split_disallowed`, but set `err` to the (untranslated) error message on failure and
   1053 /// return false. Otherwise return true.
   1054 /// @see check_split_disallowed
   1055 bool check_split_disallowed_err(const win_T *wp, Error *err)
   1056  FUNC_ATTR_NONNULL_ALL
   1057 {
   1058  if (split_disallowed > 0) {
   1059    api_set_error(err, kErrorTypeException, "E242: Can't split a window while closing another");
   1060    return false;
   1061  }
   1062  if (wp->w_buffer->b_locked_split) {
   1063    api_set_error(err, kErrorTypeException, "%s", e_cannot_split_window_when_closing_buffer);
   1064    return false;
   1065  }
   1066  return true;
   1067 }
   1068 
   1069 // split the current window, implements CTRL-W s and :split
   1070 //
   1071 // "size" is the height or width for the new window, 0 to use half of current
   1072 // height or width.
   1073 //
   1074 // "flags":
   1075 // WSP_ROOM: require enough room for new window
   1076 // WSP_VERT: vertical split.
   1077 // WSP_TOP:  open window at the top-left of the screen (help window).
   1078 // WSP_BOT:  open window at the bottom-right of the screen (quickfix window).
   1079 // WSP_HELP: creating the help window, keep layout snapshot
   1080 // WSP_NOENTER: do not enter the new window or trigger WinNew autocommands
   1081 //
   1082 // return FAIL for failure, OK otherwise
   1083 int win_split(int size, int flags)
   1084 {
   1085  if (check_split_disallowed(curwin) == FAIL) {
   1086    return FAIL;
   1087  }
   1088 
   1089  // When the ":tab" modifier was used open a new tab page instead.
   1090  if (may_open_tabpage() == OK) {
   1091    return OK;
   1092  }
   1093 
   1094  // Add flags from ":vertical", ":topleft" and ":botright".
   1095  flags |= cmdmod.cmod_split;
   1096  if ((flags & WSP_TOP) && (flags & WSP_BOT)) {
   1097    emsg(_("E442: Can't split topleft and botright at the same time"));
   1098    return FAIL;
   1099  }
   1100 
   1101  // When creating the help window make a snapshot of the window layout.
   1102  // Otherwise clear the snapshot, it's now invalid.
   1103  if (flags & WSP_HELP) {
   1104    make_snapshot(SNAP_HELP_IDX);
   1105  } else {
   1106    clear_snapshot(curtab, SNAP_HELP_IDX);
   1107  }
   1108 
   1109  if (flags & WSP_QUICKFIX) {
   1110    make_snapshot(SNAP_QUICKFIX_IDX);
   1111  } else {
   1112    clear_snapshot(curtab, SNAP_QUICKFIX_IDX);
   1113  }
   1114 
   1115  return win_split_ins(size, flags, NULL, 0, NULL) == NULL ? FAIL : OK;
   1116 }
   1117 
   1118 /// When "new_wp" is NULL: split the current window in two.
   1119 /// When "new_wp" is not NULL: insert this window at the far
   1120 /// top/left/right/bottom.
   1121 /// When "to_flatten" is not NULL: flatten this frame before reorganising frames;
   1122 /// remains unflattened on failure.
   1123 ///
   1124 /// On failure, if "new_wp" was not NULL, no changes will have been made to the
   1125 /// window layout or sizes.
   1126 /// @return  NULL for failure, or pointer to new window
   1127 win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_flatten)
   1128 {
   1129  win_T *wp = new_wp;
   1130 
   1131  // aucmd_win[] should always remain floating
   1132  if (new_wp != NULL && is_aucmd_win(new_wp)) {
   1133    return NULL;
   1134  }
   1135 
   1136  if (new_wp == NULL) {
   1137    trigger_winnewpre();
   1138  }
   1139 
   1140  win_T *oldwin;
   1141  if (flags & WSP_TOP) {
   1142    oldwin = firstwin;
   1143  } else if (flags & WSP_BOT || curwin->w_floating) {
   1144    // can't split float, use last nonfloating window instead
   1145    oldwin = lastwin_nofloating();
   1146  } else {
   1147    oldwin = curwin;
   1148  }
   1149 
   1150  int need_status = 0;
   1151  int new_size = size;
   1152  bool vertical = flags & WSP_VERT;
   1153  bool toplevel = flags & (WSP_TOP | WSP_BOT);
   1154 
   1155  // add a status line when p_ls == 1 and splitting the first window
   1156  if (one_window(firstwin, NULL) && p_ls == 1 && oldwin->w_status_height == 0) {
   1157    if (oldwin->w_height <= p_wmh) {
   1158      emsg(_(e_noroom));
   1159      return NULL;
   1160    }
   1161    need_status = STATUS_HEIGHT;
   1162    win_float_anchor_laststatus();
   1163  }
   1164 
   1165  bool do_equal = false;
   1166  int oldwin_height = 0;
   1167  const int layout = vertical ? FR_ROW : FR_COL;
   1168  bool did_set_fraction = false;
   1169 
   1170  if (vertical) {
   1171    // Check if we are able to split the current window and compute its
   1172    // width.
   1173    // Current window requires at least 1 space.
   1174    int wmw1 = (p_wmw == 0 ? 1 : (int)p_wmw);
   1175    int needed = wmw1 + 1;
   1176    if (flags & WSP_ROOM) {
   1177      needed += (int)p_wiw - wmw1;
   1178    }
   1179    int minwidth;
   1180    int available;
   1181    if (toplevel) {
   1182      minwidth = frame_minwidth(topframe, NOWIN);
   1183      available = topframe->fr_width;
   1184      needed += minwidth;
   1185    } else if (p_ea) {
   1186      minwidth = frame_minwidth(oldwin->w_frame, NOWIN);
   1187      frame_T *prevfrp = oldwin->w_frame;
   1188      for (frame_T *frp = oldwin->w_frame->fr_parent; frp != NULL;
   1189           frp = frp->fr_parent) {
   1190        if (frp->fr_layout == FR_ROW) {
   1191          frame_T *frp2;
   1192          FOR_ALL_FRAMES(frp2, frp->fr_child) {
   1193            if (frp2 != prevfrp) {
   1194              minwidth += frame_minwidth(frp2, NOWIN);
   1195            }
   1196          }
   1197        }
   1198        prevfrp = frp;
   1199      }
   1200      available = topframe->fr_width;
   1201      needed += minwidth;
   1202    } else {
   1203      minwidth = frame_minwidth(oldwin->w_frame, NOWIN);
   1204      available = oldwin->w_frame->fr_width;
   1205      needed += minwidth;
   1206    }
   1207    if (available < needed) {
   1208      emsg(_(e_noroom));
   1209      return NULL;
   1210    }
   1211    if (new_size == 0) {
   1212      new_size = oldwin->w_width / 2;
   1213    }
   1214    new_size = MAX(MIN(new_size, available - minwidth - 1), wmw1);
   1215 
   1216    // if it doesn't fit in the current window, need win_equal()
   1217    if (oldwin->w_width - new_size - 1 < p_wmw) {
   1218      do_equal = true;
   1219    }
   1220 
   1221    // We don't like to take lines for the new window from a
   1222    // 'winfixwidth' window.  Take them from a window to the left or right
   1223    // instead, if possible. Add one for the separator.
   1224    if (oldwin->w_p_wfw) {
   1225      win_setwidth_win(oldwin->w_width + new_size + 1, oldwin);
   1226    }
   1227 
   1228    // Only make all windows the same width if one of them (except oldwin)
   1229    // is wider than one of the split windows.
   1230    if (!do_equal && p_ea && size == 0 && *p_ead != 'v'
   1231        && oldwin->w_frame->fr_parent != NULL) {
   1232      frame_T *frp = oldwin->w_frame->fr_parent->fr_child;
   1233      while (frp != NULL) {
   1234        if (frp->fr_win != oldwin && frp->fr_win != NULL
   1235            && (frp->fr_win->w_width > new_size
   1236                || frp->fr_win->w_width > (oldwin->w_width
   1237                                           - new_size - 1))) {
   1238          do_equal = true;
   1239          break;
   1240        }
   1241        frp = frp->fr_next;
   1242      }
   1243    }
   1244  } else {
   1245    // Check if we are able to split the current window and compute its height.
   1246    // Current window requires at least 1 space plus space for the window bar.
   1247    int wmh1 = MAX((int)p_wmh, 1) + oldwin->w_winbar_height;
   1248    int needed = wmh1 + STATUS_HEIGHT;
   1249    if (flags & WSP_ROOM) {
   1250      needed += (int)p_wh - wmh1 + oldwin->w_winbar_height;
   1251    }
   1252    if (p_ch < 1) {
   1253      needed += 1;  // Adjust for cmdheight=0.
   1254    }
   1255    int minheight;
   1256    int available;
   1257    if (toplevel) {
   1258      minheight = frame_minheight(topframe, NOWIN) + need_status;
   1259      available = topframe->fr_height;
   1260      needed += minheight;
   1261    } else if (p_ea) {
   1262      minheight = frame_minheight(oldwin->w_frame, NOWIN) + need_status;
   1263      frame_T *prevfrp = oldwin->w_frame;
   1264      for (frame_T *frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) {
   1265        if (frp->fr_layout == FR_COL) {
   1266          frame_T *frp2;
   1267          FOR_ALL_FRAMES(frp2, frp->fr_child) {
   1268            if (frp2 != prevfrp) {
   1269              minheight += frame_minheight(frp2, NOWIN);
   1270            }
   1271          }
   1272        }
   1273        prevfrp = frp;
   1274      }
   1275      available = topframe->fr_height;
   1276      needed += minheight;
   1277    } else {
   1278      minheight = frame_minheight(oldwin->w_frame, NOWIN) + need_status;
   1279      available = oldwin->w_frame->fr_height;
   1280      needed += minheight;
   1281    }
   1282    if (available < needed) {
   1283      emsg(_(e_noroom));
   1284      return NULL;
   1285    }
   1286    oldwin_height = oldwin->w_height;
   1287    if (need_status) {
   1288      oldwin->w_status_height = STATUS_HEIGHT;
   1289      oldwin_height -= STATUS_HEIGHT;
   1290    }
   1291    if (new_size == 0) {
   1292      new_size = oldwin_height / 2;
   1293    }
   1294 
   1295    new_size = MAX(MIN(new_size, available - minheight - STATUS_HEIGHT), wmh1);
   1296 
   1297    // if it doesn't fit in the current window, need win_equal()
   1298    if (oldwin_height - new_size - STATUS_HEIGHT < p_wmh) {
   1299      do_equal = true;
   1300    }
   1301 
   1302    // We don't like to take lines for the new window from a
   1303    // 'winfixheight' window.  Take them from a window above or below
   1304    // instead, if possible.
   1305    if (oldwin->w_p_wfh) {
   1306      // Set w_fraction now so that the cursor keeps the same relative
   1307      // vertical position using the old height.
   1308      set_fraction(oldwin);
   1309      did_set_fraction = true;
   1310 
   1311      win_setheight_win(oldwin->w_height + new_size + STATUS_HEIGHT,
   1312                        oldwin);
   1313      oldwin_height = oldwin->w_height;
   1314      if (need_status) {
   1315        oldwin_height -= STATUS_HEIGHT;
   1316      }
   1317    }
   1318 
   1319    // Only make all windows the same height if one of them (except oldwin)
   1320    // is higher than one of the split windows.
   1321    if (!do_equal && p_ea && size == 0
   1322        && *p_ead != 'h'
   1323        && oldwin->w_frame->fr_parent != NULL) {
   1324      frame_T *frp = oldwin->w_frame->fr_parent->fr_child;
   1325      while (frp != NULL) {
   1326        if (frp->fr_win != oldwin && frp->fr_win != NULL
   1327            && (frp->fr_win->w_height > new_size
   1328                || frp->fr_win->w_height > oldwin_height - new_size - STATUS_HEIGHT)) {
   1329          do_equal = true;
   1330          break;
   1331        }
   1332        frp = frp->fr_next;
   1333      }
   1334    }
   1335  }
   1336 
   1337  // allocate new window structure and link it in the window list
   1338  if ((flags & WSP_TOP) == 0
   1339      && ((flags & WSP_BOT)
   1340          || (flags & WSP_BELOW)
   1341          || (!(flags & WSP_ABOVE)
   1342              && (vertical ? p_spr : p_sb)))) {
   1343    // new window below/right of current one
   1344    if (new_wp == NULL) {
   1345      wp = win_alloc(oldwin, false);
   1346    } else {
   1347      win_append(oldwin, wp, NULL);
   1348    }
   1349  } else {
   1350    if (new_wp == NULL) {
   1351      wp = win_alloc(oldwin->w_prev, false);
   1352    } else {
   1353      win_append(oldwin->w_prev, wp, NULL);
   1354    }
   1355  }
   1356 
   1357  if (new_wp == NULL) {
   1358    if (wp == NULL) {
   1359      return NULL;
   1360    }
   1361 
   1362    new_frame(wp);
   1363 
   1364    // make the contents of the new window the same as the current one
   1365    win_init(wp, curwin, flags);
   1366  } else if (wp->w_floating) {
   1367    WinStyle saved_style = wp->w_config.style;
   1368    ui_comp_remove_grid(&wp->w_grid_alloc);
   1369    if (ui_has(kUIMultigrid)) {
   1370      wp->w_pos_changed = true;
   1371    } else {
   1372      // No longer a float, a non-multigrid UI shouldn't draw it as such
   1373      ui_call_win_hide(wp->w_grid_alloc.handle);
   1374      win_free_grid(wp, true);
   1375    }
   1376 
   1377    // External windows are independent of tabpages, and may have been the curwin of others.
   1378    if (wp->w_config.external) {
   1379      FOR_ALL_TABS(tp) {
   1380        if (tp != curtab && tp->tp_curwin == wp) {
   1381          tp->tp_curwin = tp->tp_firstwin;
   1382        }
   1383      }
   1384    }
   1385 
   1386    wp->w_floating = false;
   1387    new_frame(wp);
   1388 
   1389    // non-floating window doesn't store float config or have a border.
   1390    merge_win_config(&wp->w_config, WIN_CONFIG_INIT);
   1391    CLEAR_FIELD(wp->w_border_adj);
   1392    // Restore WinConfig style. #37067
   1393    wp->w_config.style = saved_style;
   1394  }
   1395 
   1396  // Going to reorganize frames now, make sure they're flat.
   1397  if (to_flatten != NULL) {
   1398    frame_flatten(to_flatten);
   1399  }
   1400 
   1401  bool before;
   1402  frame_T *curfrp;
   1403 
   1404  // Reorganise the tree of frames to insert the new window.
   1405  if (toplevel) {
   1406    if ((topframe->fr_layout == FR_COL && !vertical)
   1407        || (topframe->fr_layout == FR_ROW && vertical)) {
   1408      curfrp = topframe->fr_child;
   1409      if (flags & WSP_BOT) {
   1410        while (curfrp->fr_next != NULL) {
   1411          curfrp = curfrp->fr_next;
   1412        }
   1413      }
   1414    } else {
   1415      curfrp = topframe;
   1416    }
   1417    before = (flags & WSP_TOP);
   1418  } else {
   1419    curfrp = oldwin->w_frame;
   1420    if (flags & WSP_BELOW) {
   1421      before = false;
   1422    } else if (flags & WSP_ABOVE) {
   1423      before = true;
   1424    } else if (vertical) {
   1425      before = !p_spr;
   1426    } else {
   1427      before = !p_sb;
   1428    }
   1429  }
   1430  if (curfrp->fr_parent == NULL || curfrp->fr_parent->fr_layout != layout) {
   1431    // Need to create a new frame in the tree to make a branch.
   1432    frame_T *frp = xcalloc(1, sizeof(frame_T));
   1433    *frp = *curfrp;
   1434    curfrp->fr_layout = (char)layout;
   1435    frp->fr_parent = curfrp;
   1436    frp->fr_next = NULL;
   1437    frp->fr_prev = NULL;
   1438    curfrp->fr_child = frp;
   1439    curfrp->fr_win = NULL;
   1440    curfrp = frp;
   1441    if (frp->fr_win != NULL) {
   1442      oldwin->w_frame = frp;
   1443    } else {
   1444      FOR_ALL_FRAMES(frp, frp->fr_child) {
   1445        frp->fr_parent = curfrp;
   1446      }
   1447    }
   1448  }
   1449 
   1450  frame_T *frp;
   1451  if (new_wp == NULL) {
   1452    frp = wp->w_frame;
   1453  } else {
   1454    frp = new_wp->w_frame;
   1455  }
   1456  frp->fr_parent = curfrp->fr_parent;
   1457 
   1458  // Insert the new frame at the right place in the frame list.
   1459  if (before) {
   1460    frame_insert(curfrp, frp);
   1461  } else {
   1462    frame_append(curfrp, frp);
   1463  }
   1464 
   1465  // Set w_fraction now so that the cursor keeps the same relative
   1466  // vertical position.
   1467  if (!did_set_fraction) {
   1468    set_fraction(oldwin);
   1469  }
   1470  wp->w_fraction = oldwin->w_fraction;
   1471 
   1472  if (vertical) {
   1473    wp->w_p_scr = curwin->w_p_scr;
   1474 
   1475    if (need_status) {
   1476      win_new_height(oldwin, oldwin->w_height - 1);
   1477      oldwin->w_status_height = need_status;
   1478    }
   1479    if (toplevel) {
   1480      // set height and row of new window to full height
   1481      wp->w_winrow = tabline_height();
   1482      win_new_height(wp, curfrp->fr_height - (p_ls == 1 || p_ls == 2));
   1483      wp->w_status_height = (p_ls == 1 || p_ls == 2);
   1484      wp->w_hsep_height = 0;
   1485    } else {
   1486      // height and row of new window is same as current window
   1487      wp->w_winrow = oldwin->w_winrow;
   1488      win_new_height(wp, oldwin->w_height);
   1489      wp->w_status_height = oldwin->w_status_height;
   1490      wp->w_hsep_height = oldwin->w_hsep_height;
   1491    }
   1492    frp->fr_height = curfrp->fr_height;
   1493 
   1494    // "new_size" of the current window goes to the new window, use
   1495    // one column for the vertical separator
   1496    win_new_width(wp, new_size);
   1497    if (before) {
   1498      wp->w_vsep_width = 1;
   1499    } else {
   1500      wp->w_vsep_width = oldwin->w_vsep_width;
   1501      oldwin->w_vsep_width = 1;
   1502    }
   1503    if (toplevel) {
   1504      if (flags & WSP_BOT) {
   1505        frame_set_vsep(curfrp, true);
   1506      }
   1507      // Set width of neighbor frame
   1508      frame_new_width(curfrp, curfrp->fr_width
   1509                      - (new_size + ((flags & WSP_TOP) != 0)), flags & WSP_TOP,
   1510                      false);
   1511    } else {
   1512      win_new_width(oldwin, oldwin->w_width - (new_size + 1));
   1513    }
   1514    if (before) {       // new window left of current one
   1515      wp->w_wincol = oldwin->w_wincol;
   1516      oldwin->w_wincol += new_size + 1;
   1517    } else {  // new window right of current one
   1518      wp->w_wincol = oldwin->w_wincol + oldwin->w_width + 1;
   1519    }
   1520    frame_fix_width(oldwin);
   1521    frame_fix_width(wp);
   1522  } else {
   1523    const bool is_stl_global = global_stl_height() > 0;
   1524    // width and column of new window is same as current window
   1525    if (toplevel) {
   1526      wp->w_wincol = 0;
   1527      win_new_width(wp, Columns);
   1528      wp->w_vsep_width = 0;
   1529    } else {
   1530      wp->w_wincol = oldwin->w_wincol;
   1531      win_new_width(wp, oldwin->w_width);
   1532      wp->w_vsep_width = oldwin->w_vsep_width;
   1533    }
   1534    frp->fr_width = curfrp->fr_width;
   1535 
   1536    // "new_size" of the current window goes to the new window, use
   1537    // one row for the status line
   1538    win_new_height(wp, new_size);
   1539    const int old_status_height = oldwin->w_status_height;
   1540    if (before) {
   1541      wp->w_hsep_height = is_stl_global ? 1 : 0;
   1542    } else {
   1543      wp->w_hsep_height = oldwin->w_hsep_height;
   1544      oldwin->w_hsep_height = is_stl_global ? 1 : 0;
   1545    }
   1546    if (toplevel) {
   1547      int new_fr_height = curfrp->fr_height - new_size;
   1548      if (is_stl_global) {
   1549        if (flags & WSP_BOT) {
   1550          frame_add_hsep(curfrp);
   1551        } else {
   1552          new_fr_height -= 1;
   1553        }
   1554      } else {
   1555        if (!((flags & WSP_BOT) && p_ls == 0)) {
   1556          new_fr_height -= STATUS_HEIGHT;
   1557        }
   1558        if (flags & WSP_BOT) {
   1559          frame_add_statusline(curfrp);
   1560        }
   1561      }
   1562      frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, false, false);
   1563    } else {
   1564      win_new_height(oldwin, oldwin_height - (new_size + STATUS_HEIGHT));
   1565    }
   1566 
   1567    if (before) {       // new window above current one
   1568      wp->w_winrow = oldwin->w_winrow;
   1569      if (is_stl_global) {
   1570        wp->w_status_height = 0;
   1571        oldwin->w_winrow += wp->w_height + 1;
   1572      } else {
   1573        wp->w_status_height = STATUS_HEIGHT;
   1574        oldwin->w_winrow += wp->w_height + STATUS_HEIGHT;
   1575      }
   1576    } else {            // new window below current one
   1577      if (is_stl_global) {
   1578        wp->w_winrow = oldwin->w_winrow + oldwin->w_height + 1;
   1579        wp->w_status_height = 0;
   1580      } else {
   1581        wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT;
   1582        wp->w_status_height = old_status_height;
   1583        if (!(flags & WSP_BOT)) {
   1584          oldwin->w_status_height = STATUS_HEIGHT;
   1585        }
   1586      }
   1587    }
   1588    frame_fix_height(wp);
   1589    frame_fix_height(oldwin);
   1590  }
   1591 
   1592  if (toplevel) {
   1593    win_comp_pos();
   1594  }
   1595 
   1596  // Both windows need redrawing.  Update all status lines, in case they
   1597  // show something related to the window count or position.
   1598  redraw_later(wp, UPD_NOT_VALID);
   1599  redraw_later(oldwin, UPD_NOT_VALID);
   1600  status_redraw_all();
   1601 
   1602  if (need_status) {
   1603    msg_row = Rows - 1;
   1604    msg_col = sc_col;
   1605    msg_clr_eos_force();        // Old command/ruler may still be there
   1606    comp_col();
   1607    msg_row = Rows - 1;
   1608    msg_col = 0;        // put position back at start of line
   1609  }
   1610 
   1611  // equalize the window sizes.
   1612  if (do_equal || dir != 0) {
   1613    win_equal(wp, true, vertical ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v'));
   1614  } else if (!is_aucmd_win(wp)) {
   1615    win_fix_scroll(false);
   1616  }
   1617 
   1618  int i;
   1619 
   1620  // Don't change the window height/width to 'winheight' / 'winwidth' if a
   1621  // size was given.
   1622  if (flags & WSP_VERT) {
   1623    i = (int)p_wiw;
   1624    if (size != 0) {
   1625      p_wiw = size;
   1626    }
   1627  } else {
   1628    i = (int)p_wh;
   1629    if (size != 0) {
   1630      p_wh = size;
   1631    }
   1632  }
   1633 
   1634  if (!(flags & WSP_NOENTER)) {
   1635    // make the new window the current window
   1636    win_enter_ext(wp, (new_wp == NULL ? WEE_TRIGGER_NEW_AUTOCMDS : 0) | WEE_TRIGGER_ENTER_AUTOCMDS
   1637                  | WEE_TRIGGER_LEAVE_AUTOCMDS);
   1638  }
   1639  if (vertical) {
   1640    p_wiw = i;
   1641  } else {
   1642    p_wh = i;
   1643  }
   1644 
   1645  if (win_valid(oldwin)) {
   1646    // Send the window positions to the UI
   1647    oldwin->w_pos_changed = true;
   1648  }
   1649 
   1650  return wp;
   1651 }
   1652 
   1653 // Initialize window "newp" from window "oldp".
   1654 // Used when splitting a window and when creating a new tab page.
   1655 // The windows will both edit the same buffer.
   1656 // WSP_NEWLOC may be specified in flags to prevent the location list from
   1657 // being copied.
   1658 void win_init(win_T *newp, win_T *oldp, int flags)
   1659 {
   1660  newp->w_buffer = oldp->w_buffer;
   1661  newp->w_s = &(oldp->w_buffer->b_s);
   1662  oldp->w_buffer->b_nwindows++;
   1663  newp->w_cursor = oldp->w_cursor;
   1664  newp->w_valid = 0;
   1665  newp->w_curswant = oldp->w_curswant;
   1666  newp->w_set_curswant = oldp->w_set_curswant;
   1667  newp->w_topline = oldp->w_topline;
   1668  newp->w_topfill = oldp->w_topfill;
   1669  newp->w_leftcol = oldp->w_leftcol;
   1670  newp->w_pcmark = oldp->w_pcmark;
   1671  newp->w_prev_pcmark = oldp->w_prev_pcmark;
   1672  newp->w_alt_fnum = oldp->w_alt_fnum;
   1673  newp->w_wrow = oldp->w_wrow;
   1674  newp->w_fraction = oldp->w_fraction;
   1675  newp->w_prev_fraction_row = oldp->w_prev_fraction_row;
   1676  copy_jumplist(oldp, newp);
   1677  if (flags & WSP_NEWLOC) {
   1678    // Don't copy the location list.
   1679    newp->w_llist = NULL;
   1680    newp->w_llist_ref = NULL;
   1681  } else {
   1682    copy_loclist_stack(oldp, newp);
   1683  }
   1684  newp->w_localdir = (oldp->w_localdir == NULL)
   1685                     ? NULL : xstrdup(oldp->w_localdir);
   1686  newp->w_prevdir = (oldp->w_prevdir == NULL)
   1687                    ? NULL : xstrdup(oldp->w_prevdir);
   1688 
   1689  if (*p_spk != 'c') {
   1690    if (*p_spk == 't') {
   1691      newp->w_skipcol = oldp->w_skipcol;
   1692    }
   1693    newp->w_botline = oldp->w_botline;
   1694    newp->w_prev_height = oldp->w_height;
   1695    newp->w_prev_winrow = oldp->w_winrow;
   1696  }
   1697 
   1698  // copy tagstack and folds
   1699  for (int i = 0; i < oldp->w_tagstacklen; i++) {
   1700    taggy_T *tag = &newp->w_tagstack[i];
   1701    *tag = oldp->w_tagstack[i];
   1702    if (tag->tagname != NULL) {
   1703      tag->tagname = xstrdup(tag->tagname);
   1704    }
   1705    if (tag->user_data != NULL) {
   1706      tag->user_data = xstrdup(tag->user_data);
   1707    }
   1708  }
   1709  newp->w_tagstackidx = oldp->w_tagstackidx;
   1710  newp->w_tagstacklen = oldp->w_tagstacklen;
   1711 
   1712  // Keep same changelist position in new window.
   1713  newp->w_changelistidx = oldp->w_changelistidx;
   1714 
   1715  copyFoldingState(oldp, newp);
   1716 
   1717  win_init_some(newp, oldp);
   1718 
   1719  newp->w_winbar_height = oldp->w_winbar_height;
   1720 }
   1721 
   1722 // Initialize window "newp" from window "old".
   1723 // Only the essential things are copied.
   1724 static void win_init_some(win_T *newp, win_T *oldp)
   1725 {
   1726  // Use the same argument list.
   1727  newp->w_alist = oldp->w_alist;
   1728  newp->w_alist->al_refcount++;
   1729  newp->w_arg_idx = oldp->w_arg_idx;
   1730 
   1731  // copy options from existing window
   1732  win_copy_options(oldp, newp);
   1733 }
   1734 
   1735 /// Check if "win" is a pointer to an existing window in the current tabpage.
   1736 ///
   1737 /// @param  win  window to check
   1738 bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   1739 {
   1740  return tabpage_win_valid(curtab, win);
   1741 }
   1742 
   1743 /// Check if "win" is a pointer to an existing window in tabpage "tp".
   1744 ///
   1745 /// @param  win  window to check
   1746 bool tabpage_win_valid(const tabpage_T *tp, const win_T *win)
   1747  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   1748 {
   1749  if (win == NULL) {
   1750    return false;
   1751  }
   1752 
   1753  FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
   1754    if (wp == win) {
   1755      return true;
   1756    }
   1757  }
   1758  return false;
   1759 }
   1760 
   1761 // Find window "handle" in the current tab page.
   1762 // Return NULL if not found.
   1763 win_T *win_find_by_handle(handle_T handle)
   1764  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   1765 {
   1766  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   1767    if (wp->handle == handle) {
   1768      return wp;
   1769    }
   1770  }
   1771  return NULL;
   1772 }
   1773 
   1774 /// Check if "win" is a pointer to an existing window in any tabpage.
   1775 ///
   1776 /// @param  win  window to check
   1777 bool win_valid_any_tab(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   1778 {
   1779  if (win == NULL) {
   1780    return false;
   1781  }
   1782 
   1783  FOR_ALL_TAB_WINDOWS(tp, wp) {
   1784    if (wp == win) {
   1785      return true;
   1786    }
   1787  }
   1788  return false;
   1789 }
   1790 
   1791 // Return the number of windows.
   1792 int win_count(void)
   1793 {
   1794  int count = 0;
   1795 
   1796  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   1797    count++;
   1798  }
   1799  return count;
   1800 }
   1801 
   1802 /// Make "count" windows on the screen.
   1803 /// Must be called when there is just one window, filling the whole screen.
   1804 /// (excluding the command line).
   1805 ///
   1806 /// @param vertical  split windows vertically if true.
   1807 ///
   1808 /// @return actual number of windows on the screen.
   1809 int make_windows(int count, bool vertical)
   1810 {
   1811  int maxcount;
   1812 
   1813  if (vertical) {
   1814    // Each window needs at least 'winminwidth' lines and a separator column.
   1815    maxcount = (int)(curwin->w_width + curwin->w_vsep_width
   1816                     - (p_wiw - p_wmw)) / ((int)p_wmw + 1);
   1817  } else {
   1818    // Each window needs at least 'winminheight' lines.
   1819    // If statusline isn't global, each window also needs a statusline.
   1820    // If 'winbar' is set, each window also needs a winbar.
   1821    maxcount = (int)(curwin->w_height + curwin->w_hsep_height + curwin->w_status_height
   1822                     - (p_wh - p_wmh)) / ((int)p_wmh + STATUS_HEIGHT + global_winbar_height());
   1823  }
   1824 
   1825  maxcount = MAX(maxcount, 2);
   1826  count = MIN(count, maxcount);
   1827 
   1828  // add status line now, otherwise first window will be too big
   1829  if (count > 1) {
   1830    last_status(true);
   1831  }
   1832 
   1833  // Don't execute autocommands while creating the windows.  Must do that
   1834  // when putting the buffers in the windows.
   1835  block_autocmds();
   1836 
   1837  int todo;
   1838 
   1839  // todo is number of windows left to create
   1840  for (todo = count - 1; todo > 0; todo--) {
   1841    if (vertical) {
   1842      if (win_split(curwin->w_width - (curwin->w_width - todo)
   1843                    / (todo + 1) - 1, WSP_VERT | WSP_ABOVE) == FAIL) {
   1844        break;
   1845      }
   1846    } else {
   1847      if (win_split(curwin->w_height - (curwin->w_height - todo
   1848                                        * STATUS_HEIGHT) / (todo + 1)
   1849                    - STATUS_HEIGHT, WSP_ABOVE) == FAIL) {
   1850        break;
   1851      }
   1852    }
   1853  }
   1854 
   1855  unblock_autocmds();
   1856 
   1857  // return actual number of windows
   1858  return count - todo;
   1859 }
   1860 
   1861 // Exchange current and next window
   1862 static void win_exchange(int Prenum)
   1863 {
   1864  if (curwin->w_floating) {
   1865    emsg(e_floatexchange);
   1866    return;
   1867  }
   1868 
   1869  if (one_window(curwin, NULL)) {
   1870    // just one window
   1871    beep_flush();
   1872    return;
   1873  }
   1874  if (text_or_buf_locked()) {
   1875    beep_flush();
   1876    return;
   1877  }
   1878 
   1879  frame_T *frp;
   1880 
   1881  // find window to exchange with
   1882  if (Prenum) {
   1883    frp = curwin->w_frame->fr_parent->fr_child;
   1884    while (frp != NULL && --Prenum > 0) {
   1885      frp = frp->fr_next;
   1886    }
   1887  } else if (curwin->w_frame->fr_next != NULL) {  // Swap with next
   1888    frp = curwin->w_frame->fr_next;
   1889  } else {  // Swap last window in row/col with previous
   1890    frp = curwin->w_frame->fr_prev;
   1891  }
   1892 
   1893  // We can only exchange a window with another window, not with a frame
   1894  // containing windows.
   1895  if (frp == NULL || frp->fr_win == NULL || frp->fr_win == curwin) {
   1896    return;
   1897  }
   1898  win_T *wp = frp->fr_win;
   1899 
   1900  // 1. remove curwin from the list. Remember after which window it was in wp2
   1901  // 2. insert curwin before wp in the list
   1902  // if wp != wp2
   1903  //    3. remove wp from the list
   1904  //    4. insert wp after wp2
   1905  // 5. exchange the status line height, winbar height, hsep height and vsep width.
   1906  win_T *wp2 = curwin->w_prev;
   1907  frame_T *frp2 = curwin->w_frame->fr_prev;
   1908  if (wp->w_prev != curwin) {
   1909    win_remove(curwin, NULL);
   1910    frame_remove(curwin->w_frame);
   1911    win_append(wp->w_prev, curwin, NULL);
   1912    frame_insert(frp, curwin->w_frame);
   1913  }
   1914  if (wp != wp2) {
   1915    win_remove(wp, NULL);
   1916    frame_remove(wp->w_frame);
   1917    win_append(wp2, wp, NULL);
   1918    if (frp2 == NULL) {
   1919      frame_insert(wp->w_frame->fr_parent->fr_child, wp->w_frame);
   1920    } else {
   1921      frame_append(frp2, wp->w_frame);
   1922    }
   1923  }
   1924  int temp = curwin->w_status_height;
   1925  curwin->w_status_height = wp->w_status_height;
   1926  wp->w_status_height = temp;
   1927  temp = curwin->w_vsep_width;
   1928  curwin->w_vsep_width = wp->w_vsep_width;
   1929  wp->w_vsep_width = temp;
   1930  temp = curwin->w_hsep_height;
   1931  curwin->w_hsep_height = wp->w_hsep_height;
   1932  wp->w_hsep_height = temp;
   1933 
   1934  frame_fix_height(curwin);
   1935  frame_fix_height(wp);
   1936  frame_fix_width(curwin);
   1937  frame_fix_width(wp);
   1938 
   1939  win_comp_pos();                 // recompute window positions
   1940 
   1941  if (wp->w_buffer != curbuf) {
   1942    reset_VIsual_and_resel();
   1943  } else if (VIsual_active) {
   1944    wp->w_cursor = curwin->w_cursor;
   1945  }
   1946 
   1947  win_enter(wp, true);
   1948  redraw_later(curwin, UPD_NOT_VALID);
   1949  redraw_later(wp, UPD_NOT_VALID);
   1950 }
   1951 
   1952 // rotate windows: if upwards true the second window becomes the first one
   1953 //                 if upwards false the first window becomes the second one
   1954 static void win_rotate(bool upwards, int count)
   1955 {
   1956  if (curwin->w_floating) {
   1957    emsg(e_floatexchange);
   1958    return;
   1959  }
   1960 
   1961  if (count <= 0 || one_window(curwin, NULL)) {
   1962    // nothing to do
   1963    beep_flush();
   1964    return;
   1965  }
   1966 
   1967  // Check if all frames in this row/col have one window.
   1968  frame_T *frp;
   1969  FOR_ALL_FRAMES(frp, curwin->w_frame->fr_parent->fr_child) {
   1970    if (frp->fr_win == NULL) {
   1971      emsg(_("E443: Cannot rotate when another window is split"));
   1972      return;
   1973    }
   1974  }
   1975 
   1976  win_T *wp1 = NULL;
   1977  win_T *wp2 = NULL;
   1978 
   1979  while (count--) {
   1980    if (upwards) {              // first window becomes last window
   1981      // remove first window/frame from the list
   1982      frp = curwin->w_frame->fr_parent->fr_child;
   1983      assert(frp != NULL);
   1984      wp1 = frp->fr_win;
   1985      win_remove(wp1, NULL);
   1986      frame_remove(frp);
   1987      assert(frp->fr_parent->fr_child);
   1988 
   1989      // find last frame and append removed window/frame after it
   1990      for (; frp->fr_next != NULL; frp = frp->fr_next) {}
   1991      win_append(frp->fr_win, wp1, NULL);
   1992      frame_append(frp, wp1->w_frame);
   1993 
   1994      wp2 = frp->fr_win;                // previously last window
   1995    } else {                  // last window becomes first window
   1996      // find last window/frame in the list and remove it
   1997      for (frp = curwin->w_frame; frp->fr_next != NULL;
   1998           frp = frp->fr_next) {}
   1999      wp1 = frp->fr_win;
   2000      wp2 = wp1->w_prev;                    // will become last window
   2001      win_remove(wp1, NULL);
   2002      frame_remove(frp);
   2003      assert(frp->fr_parent->fr_child);
   2004 
   2005      // append the removed window/frame before the first in the list
   2006      win_append(frp->fr_parent->fr_child->fr_win->w_prev, wp1, NULL);
   2007      frame_insert(frp->fr_parent->fr_child, frp);
   2008    }
   2009 
   2010    // exchange status height, winbar height, hsep height and vsep width of old and new last window
   2011    int n = wp2->w_status_height;
   2012    wp2->w_status_height = wp1->w_status_height;
   2013    wp1->w_status_height = n;
   2014    n = wp2->w_hsep_height;
   2015    wp2->w_hsep_height = wp1->w_hsep_height;
   2016    wp1->w_hsep_height = n;
   2017    frame_fix_height(wp1);
   2018    frame_fix_height(wp2);
   2019    n = wp2->w_vsep_width;
   2020    wp2->w_vsep_width = wp1->w_vsep_width;
   2021    wp1->w_vsep_width = n;
   2022    frame_fix_width(wp1);
   2023    frame_fix_width(wp2);
   2024 
   2025    // recompute w_winrow and w_wincol for all windows
   2026    win_comp_pos();
   2027  }
   2028 
   2029  wp1->w_pos_changed = true;
   2030  wp2->w_pos_changed = true;
   2031 
   2032  redraw_all_later(UPD_NOT_VALID);
   2033 }
   2034 
   2035 /// Move "wp" into a new split in a given direction, possibly relative to the
   2036 /// current window.
   2037 /// "wp" must be valid in the current tabpage.
   2038 /// Returns FAIL for failure, OK otherwise.
   2039 int win_splitmove(win_T *wp, int size, int flags)
   2040 {
   2041  int dir = 0;
   2042  int height = wp->w_height;
   2043 
   2044  if (one_window(wp, NULL)) {
   2045    return OK;  // nothing to do
   2046  }
   2047  if (is_aucmd_win(wp) || check_split_disallowed(wp) == FAIL) {
   2048    return FAIL;
   2049  }
   2050 
   2051  frame_T *unflat_altfr = NULL;
   2052  if (wp->w_floating) {
   2053    win_remove(wp, NULL);
   2054  } else {
   2055    // Remove the window and frame from the tree of frames.  Don't flatten any
   2056    // frames yet so we can restore things if win_split_ins fails.
   2057    winframe_remove(wp, &dir, NULL, &unflat_altfr);
   2058    assert(unflat_altfr != NULL);
   2059    win_remove(wp, NULL);
   2060    last_status(false);  // may need to remove last status line
   2061    win_comp_pos();  // recompute window positions
   2062  }
   2063 
   2064  // Split a window on the desired side and put "wp" there.
   2065  if (win_split_ins(size, flags, wp, dir, unflat_altfr) == NULL) {
   2066    if (!wp->w_floating) {
   2067      assert(unflat_altfr != NULL);
   2068      // win_split_ins doesn't change sizes or layout if it fails to insert an
   2069      // existing window, so just undo winframe_remove.
   2070      winframe_restore(wp, dir, unflat_altfr);
   2071    }
   2072    win_append(wp->w_prev, wp, NULL);
   2073    return FAIL;
   2074  }
   2075 
   2076  // If splitting horizontally, try to preserve height.
   2077  // Note that win_split_ins autocommands may have immediately closed "wp", or made it floating!
   2078  if (size == 0 && !(flags & WSP_VERT) && win_valid(wp) && !wp->w_floating) {
   2079    win_setheight_win(height, wp);
   2080    if (p_ea) {
   2081      // Equalize windows.  Note that win_split_ins autocommands may have
   2082      // made a window other than "wp" current.
   2083      win_equal(curwin, curwin == wp, 'v');
   2084    }
   2085  }
   2086 
   2087  return OK;
   2088 }
   2089 
   2090 // Move window "win1" to below/right of "win2" and make "win1" the current
   2091 // window.  Only works within the same frame!
   2092 void win_move_after(win_T *win1, win_T *win2)
   2093 {
   2094  // check if the arguments are reasonable
   2095  if (win1 == win2) {
   2096    return;
   2097  }
   2098 
   2099  // check if there is something to do
   2100  if (win2->w_next != win1) {
   2101    if (win1->w_frame->fr_parent != win2->w_frame->fr_parent) {
   2102      iemsg("INTERNAL: trying to move a window into another frame");
   2103      return;
   2104    }
   2105 
   2106    // may need to move the status line, window bar, horizontal or vertical separator of the last
   2107    // window
   2108    if (win1 == lastwin) {
   2109      int height = win1->w_prev->w_status_height;
   2110      win1->w_prev->w_status_height = win1->w_status_height;
   2111      win1->w_status_height = height;
   2112 
   2113      height = win1->w_prev->w_hsep_height;
   2114      win1->w_prev->w_hsep_height = win1->w_hsep_height;
   2115      win1->w_hsep_height = height;
   2116 
   2117      if (win1->w_prev->w_vsep_width == 1) {
   2118        // Remove the vertical separator from the last-but-one window,
   2119        // add it to the last window.  Adjust the frame widths.
   2120        win1->w_prev->w_vsep_width = 0;
   2121        win1->w_prev->w_frame->fr_width -= 1;
   2122        win1->w_vsep_width = 1;
   2123        win1->w_frame->fr_width += 1;
   2124      }
   2125    } else if (win2 == lastwin) {
   2126      int height = win1->w_status_height;
   2127      win1->w_status_height = win2->w_status_height;
   2128      win2->w_status_height = height;
   2129 
   2130      height = win1->w_hsep_height;
   2131      win1->w_hsep_height = win2->w_hsep_height;
   2132      win2->w_hsep_height = height;
   2133 
   2134      if (win1->w_vsep_width == 1) {
   2135        // Remove the vertical separator from win1, add it to the last
   2136        // window, win2.  Adjust the frame widths.
   2137        win2->w_vsep_width = 1;
   2138        win2->w_frame->fr_width += 1;
   2139        win1->w_vsep_width = 0;
   2140        win1->w_frame->fr_width -= 1;
   2141      }
   2142    }
   2143    win_remove(win1, NULL);
   2144    frame_remove(win1->w_frame);
   2145    win_append(win2, win1, NULL);
   2146    frame_append(win2->w_frame, win1->w_frame);
   2147 
   2148    win_comp_pos();  // recompute w_winrow for all windows
   2149    redraw_later(curwin, UPD_NOT_VALID);
   2150  }
   2151  win1->w_pos_changed = true;
   2152  win2->w_pos_changed = true;
   2153 
   2154  win_enter(win1, false);
   2155 }
   2156 
   2157 /// Compute maximum number of windows that can fit within "height" in frame "fr".
   2158 static int get_maximum_wincount(frame_T *fr, int height)
   2159 {
   2160  if (fr->fr_layout != FR_COL) {
   2161    return (height / ((int)p_wmh + STATUS_HEIGHT + frame2win(fr)->w_winbar_height));
   2162  } else if (global_winbar_height()) {
   2163    // If winbar is globally enabled, no need to check each window for it.
   2164    return (height / ((int)p_wmh + STATUS_HEIGHT + 1));
   2165  }
   2166 
   2167  frame_T *frp;
   2168  int total_wincount = 0;
   2169 
   2170  // First, try to fit all child frames of "fr" into "height"
   2171  FOR_ALL_FRAMES(frp, fr->fr_child) {
   2172    win_T *wp = frame2win(frp);
   2173 
   2174    if (height < (p_wmh + STATUS_HEIGHT + wp->w_winbar_height)) {
   2175      break;
   2176    }
   2177    height -= (int)p_wmh + STATUS_HEIGHT + wp->w_winbar_height;
   2178    total_wincount += 1;
   2179  }
   2180 
   2181  // If we still have enough room for more windows, just use the default winbar height (which is 0)
   2182  // in order to get the amount of windows that'd fit in the remaining space
   2183  total_wincount += height / ((int)p_wmh + STATUS_HEIGHT);
   2184 
   2185  return total_wincount;
   2186 }
   2187 
   2188 /// Make all windows the same height.
   2189 /// 'next_curwin' will soon be the current window, make sure it has enough rows.
   2190 ///
   2191 /// @param next_curwin  pointer to current window to be or NULL
   2192 /// @param current  do only frame with current window
   2193 /// @param dir  'v' for vertically, 'h' for horizontally, 'b' for both, 0 for using p_ead
   2194 void win_equal(win_T *next_curwin, bool current, int dir)
   2195 {
   2196  if (dir == 0) {
   2197    dir = (unsigned char)(*p_ead);
   2198  }
   2199  win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current,
   2200                topframe, dir, 0, tabline_height(),
   2201                Columns, topframe->fr_height);
   2202  if (!is_aucmd_win(next_curwin)) {
   2203    win_fix_scroll(true);
   2204  }
   2205 }
   2206 
   2207 /// Set a frame to a new position and height, spreading the available room
   2208 /// equally over contained frames.
   2209 /// The window "next_curwin" (if not NULL) should at least get the size from
   2210 /// 'winheight' and 'winwidth' if possible.
   2211 ///
   2212 /// @param next_curwin  pointer to current window to be or NULL
   2213 /// @param current      do only frame with current window
   2214 /// @param topfr        frame to set size off
   2215 /// @param dir          'v', 'h' or 'b', see win_equal()
   2216 /// @param col          horizontal position for frame
   2217 /// @param row          vertical position for frame
   2218 /// @param width        new width of frame
   2219 /// @param height       new height of frame
   2220 static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int dir, int col,
   2221                          int row, int width, int height)
   2222 {
   2223  int extra_sep = 0;
   2224  int totwincount = 0;
   2225  int next_curwin_size = 0;
   2226  int room = 0;
   2227  bool has_next_curwin = false;
   2228 
   2229  if (topfr->fr_layout == FR_LEAF) {
   2230    // Set the width/height of this frame.
   2231    // Redraw when size or position changes
   2232    if (topfr->fr_height != height || topfr->fr_win->w_winrow != row
   2233        || topfr->fr_width != width
   2234        || topfr->fr_win->w_wincol != col) {
   2235      topfr->fr_win->w_winrow = row;
   2236      frame_new_height(topfr, height, false, false, false);
   2237      topfr->fr_win->w_wincol = col;
   2238      frame_new_width(topfr, width, false, false);
   2239      redraw_all_later(UPD_NOT_VALID);
   2240    }
   2241  } else if (topfr->fr_layout == FR_ROW) {
   2242    topfr->fr_width = width;
   2243    topfr->fr_height = height;
   2244 
   2245    if (dir != 'v') {                   // equalize frame widths
   2246      // Compute the maximum number of windows horizontally in this
   2247      // frame.
   2248      int n = frame_minwidth(topfr, NOWIN);
   2249      // add one for the rightmost window, it doesn't have a separator
   2250      if (col + width == Columns) {
   2251        extra_sep = 1;
   2252      } else {
   2253        extra_sep = 0;
   2254      }
   2255      totwincount = (n + extra_sep) / ((int)p_wmw + 1);
   2256      has_next_curwin = frame_has_win(topfr, next_curwin);
   2257 
   2258      // Compute width for "next_curwin" window and room available for
   2259      // other windows.
   2260      // "m" is the minimal width when counting p_wiw for "next_curwin".
   2261      int m = frame_minwidth(topfr, next_curwin);
   2262      room = width - m;
   2263      if (room < 0) {
   2264        next_curwin_size = (int)p_wiw + room;
   2265        room = 0;
   2266      } else {
   2267        next_curwin_size = -1;
   2268        frame_T *fr;
   2269        FOR_ALL_FRAMES(fr, topfr->fr_child) {
   2270          if (!frame_fixed_width(fr)) {
   2271            continue;
   2272          }
   2273          // If 'winfixwidth' set keep the window width if possible.
   2274          // Watch out for this window being the next_curwin.
   2275          n = frame_minwidth(fr, NOWIN);
   2276          int new_size = fr->fr_width;
   2277          if (frame_has_win(fr, next_curwin)) {
   2278            room += (int)p_wiw - (int)p_wmw;
   2279            next_curwin_size = 0;
   2280            new_size = MAX(new_size, (int)p_wiw);
   2281          } else {
   2282            // These windows don't use up room.
   2283            totwincount -= (n + (fr->fr_next == NULL ? extra_sep : 0)) / ((int)p_wmw + 1);
   2284          }
   2285          room -= new_size - n;
   2286          if (room < 0) {
   2287            new_size += room;
   2288            room = 0;
   2289          }
   2290          fr->fr_newwidth = new_size;
   2291        }
   2292        if (next_curwin_size == -1) {
   2293          if (!has_next_curwin) {
   2294            next_curwin_size = 0;
   2295          } else if (totwincount > 1
   2296                     && (room + (totwincount - 2))
   2297                     / (totwincount - 1) > p_wiw) {
   2298            // Can make all windows wider than 'winwidth', spread
   2299            // the room equally.
   2300            next_curwin_size = (int)(room + p_wiw
   2301                                     + (totwincount - 1) * p_wmw
   2302                                     + (totwincount - 1)) / totwincount;
   2303            room -= next_curwin_size - (int)p_wiw;
   2304          } else {
   2305            next_curwin_size = (int)p_wiw;
   2306          }
   2307        }
   2308      }
   2309 
   2310      if (has_next_curwin) {
   2311        totwincount--;                  // don't count curwin
   2312      }
   2313    }
   2314 
   2315    frame_T *fr;
   2316    FOR_ALL_FRAMES(fr, topfr->fr_child) {
   2317      int wincount = 1;
   2318      int new_size;
   2319      if (fr->fr_next == NULL) {
   2320        // last frame gets all that remains (avoid roundoff error)
   2321        new_size = width;
   2322      } else if (dir == 'v') {
   2323        new_size = fr->fr_width;
   2324      } else if (frame_fixed_width(fr)) {
   2325        new_size = fr->fr_newwidth;
   2326        wincount = 0;               // doesn't count as a sizeable window
   2327      } else {
   2328        // Compute the maximum number of windows horiz. in "fr".
   2329        int n = frame_minwidth(fr, NOWIN);
   2330        wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) / ((int)p_wmw + 1);
   2331        int m = frame_minwidth(fr, next_curwin);
   2332        bool hnc = has_next_curwin && frame_has_win(fr, next_curwin);
   2333        if (hnc) {                    // don't count next_curwin
   2334          wincount--;
   2335        }
   2336        if (totwincount == 0) {
   2337          new_size = room;
   2338        } else {
   2339          new_size = (wincount * room + (totwincount / 2)) / totwincount;
   2340        }
   2341        if (hnc) {                  // add next_curwin size
   2342          next_curwin_size -= (int)p_wiw - (m - n);
   2343          next_curwin_size = MAX(next_curwin_size, 0);
   2344          new_size += next_curwin_size;
   2345          room -= new_size - next_curwin_size;
   2346        } else {
   2347          room -= new_size;
   2348        }
   2349        new_size += n;
   2350      }
   2351 
   2352      // Skip frame that is full width when splitting or closing a
   2353      // window, unless equalizing all frames.
   2354      if (!current || dir != 'v' || topfr->fr_parent != NULL
   2355          || (new_size != fr->fr_width)
   2356          || frame_has_win(fr, next_curwin)) {
   2357        win_equal_rec(next_curwin, current, fr, dir, col, row,
   2358                      new_size, height);
   2359      }
   2360      col += new_size;
   2361      width -= new_size;
   2362      totwincount -= wincount;
   2363    }
   2364  } else {  // topfr->fr_layout == FR_COL
   2365    topfr->fr_width = width;
   2366    topfr->fr_height = height;
   2367 
   2368    if (dir != 'h') {                   // equalize frame heights
   2369      // Compute maximum number of windows vertically in this frame.
   2370      int n = frame_minheight(topfr, NOWIN);
   2371      // add one for the bottom window if it doesn't have a statusline or separator
   2372      if (row + height >= cmdline_row && p_ls == 0) {
   2373        extra_sep = STATUS_HEIGHT;
   2374      } else if (global_stl_height() > 0) {
   2375        extra_sep = 1;
   2376      } else {
   2377        extra_sep = 0;
   2378      }
   2379      totwincount = get_maximum_wincount(topfr, n + extra_sep);
   2380      has_next_curwin = frame_has_win(topfr, next_curwin);
   2381 
   2382      // Compute height for "next_curwin" window and room available for
   2383      // other windows.
   2384      // "m" is the minimal height when counting p_wh for "next_curwin".
   2385      int m = frame_minheight(topfr, next_curwin);
   2386      room = height - m;
   2387      if (room < 0) {
   2388        // The room is less than 'winheight', use all space for the
   2389        // current window.
   2390        next_curwin_size = (int)p_wh + room;
   2391        room = 0;
   2392      } else {
   2393        next_curwin_size = -1;
   2394        frame_T *fr;
   2395        FOR_ALL_FRAMES(fr, topfr->fr_child) {
   2396          if (!frame_fixed_height(fr)) {
   2397            continue;
   2398          }
   2399          // If 'winfixheight' set keep the window height if possible.
   2400          // Watch out for this window being the next_curwin.
   2401          n = frame_minheight(fr, NOWIN);
   2402          int new_size = fr->fr_height;
   2403          if (frame_has_win(fr, next_curwin)) {
   2404            room += (int)p_wh - (int)p_wmh;
   2405            next_curwin_size = 0;
   2406            new_size = MAX(new_size, (int)p_wh);
   2407          } else {
   2408            // These windows don't use up room.
   2409            totwincount -= get_maximum_wincount(fr, (n + (fr->fr_next == NULL ? extra_sep : 0)));
   2410          }
   2411          room -= new_size - n;
   2412          if (room < 0) {
   2413            new_size += room;
   2414            room = 0;
   2415          }
   2416          fr->fr_newheight = new_size;
   2417        }
   2418        if (next_curwin_size == -1) {
   2419          if (!has_next_curwin) {
   2420            next_curwin_size = 0;
   2421          } else if (totwincount > 1
   2422                     && (room + (totwincount - 2))
   2423                     / (totwincount - 1) > p_wh) {
   2424            // can make all windows higher than 'winheight',
   2425            // spread the room equally.
   2426            next_curwin_size = (int)(room + p_wh
   2427                                     + (totwincount - 1) * p_wmh
   2428                                     + (totwincount - 1)) / totwincount;
   2429            room -= next_curwin_size - (int)p_wh;
   2430          } else {
   2431            next_curwin_size = (int)p_wh;
   2432          }
   2433        }
   2434      }
   2435 
   2436      if (has_next_curwin) {
   2437        totwincount--;                  // don't count curwin
   2438      }
   2439    }
   2440 
   2441    frame_T *fr;
   2442    FOR_ALL_FRAMES(fr, topfr->fr_child) {
   2443      int new_size;
   2444      int wincount = 1;
   2445      if (fr->fr_next == NULL) {
   2446        // last frame gets all that remains (avoid roundoff error)
   2447        new_size = height;
   2448      } else if (dir == 'h') {
   2449        new_size = fr->fr_height;
   2450      } else if (frame_fixed_height(fr)) {
   2451        new_size = fr->fr_newheight;
   2452        wincount = 0;               // doesn't count as a sizeable window
   2453      } else {
   2454        // Compute the maximum number of windows vert. in "fr".
   2455        int n = frame_minheight(fr, NOWIN);
   2456        wincount = get_maximum_wincount(fr, (n + (fr->fr_next == NULL ? extra_sep : 0)));
   2457        int m = frame_minheight(fr, next_curwin);
   2458        bool hnc = has_next_curwin && frame_has_win(fr, next_curwin);
   2459        if (hnc) {                    // don't count next_curwin
   2460          wincount--;
   2461        }
   2462        if (totwincount == 0) {
   2463          new_size = room;
   2464        } else {
   2465          new_size = (wincount * room + (totwincount / 2)) / totwincount;
   2466        }
   2467        if (hnc) {                  // add next_curwin size
   2468          next_curwin_size -= (int)p_wh - (m - n);
   2469          new_size += next_curwin_size;
   2470          room -= new_size - next_curwin_size;
   2471        } else {
   2472          room -= new_size;
   2473        }
   2474        new_size += n;
   2475      }
   2476      // Skip frame that is full width when splitting or closing a
   2477      // window, unless equalizing all frames.
   2478      if (!current || dir != 'h' || topfr->fr_parent != NULL
   2479          || (new_size != fr->fr_height)
   2480          || frame_has_win(fr, next_curwin)) {
   2481        win_equal_rec(next_curwin, current, fr, dir, col, row,
   2482                      width, new_size);
   2483      }
   2484      row += new_size;
   2485      height -= new_size;
   2486      totwincount -= wincount;
   2487    }
   2488  }
   2489 }
   2490 
   2491 void leaving_window(win_T *const win)
   2492  FUNC_ATTR_NONNULL_ALL
   2493 {
   2494  // Only matters for a prompt window.
   2495  // Don't do mode changes for a prompt buffer in an autocommand window, as
   2496  // it's only used temporarily during an autocommand.
   2497  if (!bt_prompt(win->w_buffer) || is_aucmd_win(win)) {
   2498    return;
   2499  }
   2500 
   2501  // When leaving a prompt window stop Insert mode and perhaps restart
   2502  // it when entering that window again.
   2503  win->w_buffer->b_prompt_insert = restart_edit;
   2504  if (restart_edit != NUL && mode_displayed) {
   2505    clear_cmdline = true;  // unshow mode later
   2506  }
   2507  restart_edit = NUL;
   2508 
   2509  // When leaving the window (or closing the window) was done from a
   2510  // callback we need to break out of the Insert mode loop and restart Insert
   2511  // mode when entering the window again.
   2512  if ((State & MODE_INSERT) && !stop_insert_mode) {
   2513    stop_insert_mode = true;
   2514    if (win->w_buffer->b_prompt_insert == NUL) {
   2515      win->w_buffer->b_prompt_insert = 'A';
   2516    }
   2517  }
   2518 }
   2519 
   2520 void entering_window(win_T *const win)
   2521  FUNC_ATTR_NONNULL_ALL
   2522 {
   2523  // Only matters for a prompt window.
   2524  // Don't do mode changes for a prompt buffer in an autocommand window, as
   2525  // it's only used temporarily during an autocommand.
   2526  if (!bt_prompt(win->w_buffer) || is_aucmd_win(win)) {
   2527    return;
   2528  }
   2529 
   2530  // When switching to a prompt buffer that was in Insert mode, don't stop
   2531  // Insert mode, it may have been set in leaving_window().
   2532  if (win->w_buffer->b_prompt_insert != NUL) {
   2533    stop_insert_mode = false;
   2534  }
   2535 
   2536  // When entering the prompt window restart Insert mode if we were in Insert
   2537  // mode when we left it and not already in Insert mode.
   2538  if ((State & MODE_INSERT) == 0) {
   2539    restart_edit = win->w_buffer->b_prompt_insert;
   2540  }
   2541 }
   2542 
   2543 void win_init_empty(win_T *wp)
   2544 {
   2545  redraw_later(wp, UPD_NOT_VALID);
   2546  wp->w_lines_valid = 0;
   2547  wp->w_cursor.lnum = 1;
   2548  wp->w_curswant = wp->w_cursor.col = 0;
   2549  wp->w_cursor.coladd = 0;
   2550  wp->w_pcmark.lnum = 1;        // pcmark not cleared but set to line 1
   2551  wp->w_pcmark.col = 0;
   2552  wp->w_prev_pcmark.lnum = 0;
   2553  wp->w_prev_pcmark.col = 0;
   2554  wp->w_topline = 1;
   2555  wp->w_topfill = 0;
   2556  wp->w_botline = 2;
   2557  wp->w_valid = 0;
   2558  wp->w_s = &wp->w_buffer->b_s;
   2559 }
   2560 
   2561 /// Init the current window "curwin".
   2562 /// Called when a new file is being edited.
   2563 void curwin_init(void)
   2564 {
   2565  win_init_empty(curwin);
   2566 }
   2567 
   2568 /// Closes all windows for buffer `buf` unless there is only one non-floating window.
   2569 ///
   2570 /// @param keep_curwin  don't close `curwin`
   2571 void close_windows(buf_T *buf, bool keep_curwin)
   2572 {
   2573  RedrawingDisabled++;
   2574 
   2575  // Start from lastwin to close floating windows with the same buffer first.
   2576  // When the autocommand window is involved win_close() may need to print an error message.
   2577  for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !one_window(wp, NULL));) {
   2578    if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
   2579        && !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
   2580      if (window_layout_locked(CMD_SIZE)) {
   2581        goto theend;  // Only give one error message.
   2582      }
   2583      if (win_close(wp, false, false) == FAIL) {
   2584        // If closing the window fails give up, to avoid looping forever.
   2585        break;
   2586      }
   2587 
   2588      // Start all over, autocommands may change the window layout.
   2589      wp = lastwin;
   2590    } else {
   2591      wp = wp->w_prev;
   2592    }
   2593  }
   2594 
   2595  tabpage_T *nexttp;
   2596 
   2597  // Also check windows in other tab pages.
   2598  for (tabpage_T *tp = first_tabpage; tp != NULL; tp = nexttp) {
   2599    nexttp = tp->tp_next;
   2600    if (tp != curtab) {
   2601      // Start from tp_lastwin to close floating windows with the same buffer first.
   2602      for (win_T *wp = tp->tp_lastwin; wp != NULL; wp = wp->w_prev) {
   2603        if (wp->w_buffer == buf
   2604            && !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
   2605          if (window_layout_locked(CMD_SIZE)) {
   2606            goto theend;  // Only give one error message.
   2607          }
   2608          if (!win_close_othertab(wp, false, tp, false)) {
   2609            // If closing the window fails give up, to avoid looping forever.
   2610            break;
   2611          }
   2612 
   2613          // Start all over, the tab page may be closed and
   2614          // autocommands may change the window layout.
   2615          nexttp = first_tabpage;
   2616          break;
   2617        }
   2618      }
   2619    }
   2620  }
   2621 
   2622 theend:
   2623  RedrawingDisabled--;
   2624 }
   2625 
   2626 /// Check if "win" is the last non-floating window that exists.
   2627 bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   2628 {
   2629  return one_window(win, NULL) && first_tabpage->tp_next == NULL;
   2630 }
   2631 
   2632 /// Check if "win" is the only non-floating window in tabpage "tp", or NULL for current tabpage.
   2633 ///
   2634 /// This should be used in place of ONE_WINDOW when necessary,
   2635 /// with "firstwin" or the affected window as argument depending on the situation.
   2636 bool one_window(win_T *win, tabpage_T *tp)
   2637  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
   2638 {
   2639  win_T *first = tp ? tp->tp_firstwin : firstwin;
   2640  assert((!tp || tp != curtab) && !first->w_floating);
   2641  return first == win && (win->w_next == NULL || win->w_next->w_floating);
   2642 }
   2643 
   2644 /// Check if floating windows in tabpage `tp` can be closed.
   2645 /// Do not call this when the autocommand window is in use!
   2646 ///
   2647 /// @param tp tabpage to check. Must be NULL for the current tabpage.
   2648 /// @return true if all floating windows can be closed
   2649 static bool can_close_floating_windows(tabpage_T *tp)
   2650 {
   2651  assert(tp != curtab && (tp || !is_aucmd_win(lastwin)));
   2652  for (win_T *wp = tp ? tp->tp_lastwin : lastwin; wp->w_floating; wp = wp->w_prev) {
   2653    buf_T *buf = wp->w_buffer;
   2654    int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
   2655 
   2656    if (need_hide && !buf_hide(buf)) {
   2657      return false;
   2658    }
   2659  }
   2660  return true;
   2661 }
   2662 
   2663 /// @return true if, considering the cmdwin, `win` is safe to close.
   2664 /// If false and `win` is the cmdwin, it is closed; otherwise, `err` is set.
   2665 bool can_close_in_cmdwin(win_T *win, Error *err)
   2666  FUNC_ATTR_NONNULL_ALL
   2667 {
   2668  if (cmdwin_type != 0) {
   2669    if (win == cmdwin_win) {
   2670      cmdwin_result = Ctrl_C;
   2671      return false;
   2672    } else if (win == cmdwin_old_curwin) {
   2673      api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
   2674      return false;
   2675    }
   2676  }
   2677  return true;
   2678 }
   2679 
   2680 /// Close the possibly last window in a tab page.
   2681 ///
   2682 /// @param  win          window to close
   2683 /// @param  free_buf     whether to free the window's current buffer
   2684 /// @param  prev_curtab  previous tabpage that will be closed if "win" is the
   2685 ///                      last window in the tabpage
   2686 ///
   2687 /// @return false if there are other windows and nothing is done, true otherwise.
   2688 static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev_curtab)
   2689  FUNC_ATTR_NONNULL_ARG(1)
   2690 {
   2691  if (!ONE_WINDOW) {
   2692    return false;
   2693  }
   2694 
   2695  buf_T *old_curbuf = curbuf;
   2696 
   2697  Terminal *term = win->w_buffer ? win->w_buffer->terminal : NULL;
   2698  if (term) {
   2699    // Don't free terminal buffers
   2700    free_buf = false;
   2701  }
   2702 
   2703  // Closing the last window in a tab page.  First go to another tab
   2704  // page and then close the window and the tab page.  This avoids that
   2705  // curwin and curtab are invalid while we are freeing memory, they may
   2706  // be used in GUI events.
   2707  // Don't trigger *Enter autocommands yet, they may use wrong values, so do
   2708  // that below.
   2709  // Do trigger *Leave autocommands, unless win->w_buffer is NULL, in which
   2710  // case they have already been triggered.
   2711  goto_tabpage_tp(alt_tabpage(), false, win->w_buffer != NULL);
   2712 
   2713  // Safety check: Autocommands may have switched back to the old tab page
   2714  // or closed the window when jumping to the other tab page.
   2715  if (curtab != prev_curtab && valid_tabpage(prev_curtab) && prev_curtab->tp_firstwin == win) {
   2716    win_close_othertab(win, free_buf, prev_curtab, false);
   2717  }
   2718  entering_window(curwin);
   2719 
   2720  // Since goto_tabpage_tp above did not trigger *Enter autocommands, do
   2721  // that now.
   2722  apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf);
   2723  apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf);
   2724  if (old_curbuf != curbuf) {
   2725    apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
   2726  }
   2727  return true;
   2728 }
   2729 
   2730 /// Close the buffer of "win" and unload it if "action" is DOBUF_UNLOAD.
   2731 /// "action" can also be zero (do nothing).
   2732 /// "abort_if_last" is passed to close_buffer(): abort closing if all other
   2733 /// windows are closed.
   2734 ///
   2735 /// @return  whether close_buffer() decremented b_nwindows
   2736 static bool win_close_buffer(win_T *win, int action, bool abort_if_last)
   2737  FUNC_ATTR_NONNULL_ALL
   2738 {
   2739  // Free independent synblock before the buffer is freed.
   2740  if (win->w_buffer != NULL) {
   2741    reset_synblock(win);
   2742  }
   2743 
   2744  // When a quickfix/location list window is closed and the buffer is
   2745  // displayed in only one window, then unlist the buffer.
   2746  if (win->w_buffer != NULL && bt_quickfix(win->w_buffer)
   2747      && win->w_buffer->b_nwindows == 1) {
   2748    win->w_buffer->b_p_bl = false;
   2749  }
   2750 
   2751  bool retval = false;
   2752  // Close the link to the buffer.
   2753  if (win->w_buffer != NULL) {
   2754    bufref_T bufref;
   2755    set_bufref(&bufref, curbuf);
   2756    win->w_locked = true;
   2757    retval = close_buffer(win, win->w_buffer, action, abort_if_last, true);
   2758    if (win_valid_any_tab(win)) {
   2759      win->w_locked = false;
   2760    }
   2761 
   2762    // Make sure curbuf is valid. It can become invalid if 'bufhidden' is
   2763    // "wipe".
   2764    if (!bufref_valid(&bufref)) {
   2765      curbuf = firstbuf;
   2766    }
   2767  }
   2768 
   2769  return retval;
   2770 }
   2771 
   2772 /// When failing to close a window after already calling close_buffer() on it,
   2773 /// call this to make the window have a buffer again.
   2774 ///
   2775 /// @param bufref         reference to win->w_buffer before calling close_buffer()
   2776 /// @param did_decrement  whether close_buffer() decremented b_nwindows
   2777 static void win_unclose_buffer(win_T *win, bufref_T *bufref, bool did_decrement)
   2778 {
   2779  if (win->w_buffer == NULL) {
   2780    // If the buffer was removed from the window we have to give it any buffer.
   2781    win->w_buffer = firstbuf;
   2782    firstbuf->b_nwindows++;
   2783    if (win == curwin) {
   2784      curbuf = curwin->w_buffer;
   2785    }
   2786    win_init_empty(win);
   2787  } else if (did_decrement && win->w_buffer == bufref->br_buf && bufref_valid(bufref)) {
   2788    // close_buffer() decremented the window count, but we're keeping the window.
   2789    // As the window is still viewing the buffer, increment the count.
   2790    win->w_buffer->b_nwindows++;
   2791  }
   2792 }
   2793 
   2794 // Close window "win".  Only works for the current tab page.
   2795 // If "free_buf" is true related buffer may be unloaded.
   2796 //
   2797 // Called by :quit, :close, :xit, :wq and findtag().
   2798 // Returns FAIL when the window was not closed.
   2799 int win_close(win_T *win, bool free_buf, bool force)
   2800  FUNC_ATTR_NONNULL_ALL
   2801 {
   2802  tabpage_T *prev_curtab = curtab;
   2803  frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent;
   2804  const bool had_diffmode = win->w_p_diff;
   2805 
   2806  if (last_window(win)) {
   2807    emsg(_(e_cannot_close_last_window));
   2808    return FAIL;
   2809  }
   2810  if (!win->w_floating && window_layout_locked(CMD_close)) {
   2811    return FAIL;
   2812  }
   2813 
   2814  if (win_locked(win)
   2815      || (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) {
   2816    return FAIL;     // window is already being closed
   2817  }
   2818  if (is_aucmd_win(win)) {
   2819    emsg(_(e_autocmd_close));
   2820    return FAIL;
   2821  }
   2822  if (lastwin->w_floating && one_window(win, NULL)) {
   2823    if (is_aucmd_win(lastwin)) {
   2824      emsg(_("E814: Cannot close window, only autocmd window would remain"));
   2825      return FAIL;
   2826    }
   2827    if (force || can_close_floating_windows(NULL)) {
   2828      // close the last window until the there are no floating windows
   2829      while (lastwin->w_floating) {
   2830        // `force` flag isn't actually used when closing a floating window.
   2831        if (win_close(lastwin, free_buf, true) == FAIL) {
   2832          // If closing the window fails give up, to avoid looping forever.
   2833          return FAIL;
   2834        }
   2835      }
   2836      if (!win_valid_any_tab(win)) {
   2837        return FAIL;  // window already closed by autocommands
   2838      }
   2839      // Autocommands may have closed all other tabpages; check again.
   2840      if (last_window(win)) {
   2841        emsg(_(e_cannot_close_last_window));
   2842        return FAIL;
   2843      }
   2844    } else {
   2845      emsg(e_floatonly);
   2846      return FAIL;
   2847    }
   2848  }
   2849 
   2850  // When closing the last window in a tab page first go to another tab page
   2851  // and then close the window and the tab page to avoid that curwin and
   2852  // curtab are invalid while we are freeing memory.
   2853  if (close_last_window_tabpage(win, free_buf, prev_curtab)) {
   2854    return FAIL;
   2855  }
   2856 
   2857  bool help_window = false;
   2858  bool quickfix_window = false;
   2859 
   2860  // When closing the help window, try restoring a snapshot after closing
   2861  // the window.  Otherwise clear the snapshot, it's now invalid.
   2862  if (bt_help(win->w_buffer)) {
   2863    help_window = true;
   2864  } else {
   2865    clear_snapshot(curtab, SNAP_HELP_IDX);
   2866  }
   2867 
   2868  if (bt_quickfix(win->w_buffer)) {
   2869    quickfix_window = true;
   2870  } else {
   2871    clear_snapshot(curtab, SNAP_QUICKFIX_IDX);
   2872  }
   2873 
   2874  bool other_buffer = false;
   2875 
   2876  if (win == curwin) {
   2877    leaving_window(curwin);
   2878 
   2879    // Guess which window is going to be the new current window.
   2880    // This may change because of the autocommands (sigh).
   2881    win_T *wp = win->w_floating ? win_float_find_altwin(win, NULL)
   2882                                : frame2win(win_altframe(win, NULL));
   2883 
   2884    // Be careful: If autocommands delete the window or cause this window
   2885    // to be the last one left, return now.
   2886    if (wp->w_buffer != curbuf) {
   2887      reset_VIsual_and_resel();  // stop Visual mode
   2888 
   2889      other_buffer = true;
   2890      if (!win_valid(win)) {
   2891        return FAIL;
   2892      }
   2893      win->w_locked = true;
   2894      apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
   2895      if (!win_valid(win)) {
   2896        return FAIL;
   2897      }
   2898      win->w_locked = false;
   2899      if (last_window(win)) {
   2900        return FAIL;
   2901      }
   2902    }
   2903    win->w_locked = true;
   2904    apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf);
   2905    if (!win_valid(win)) {
   2906      return FAIL;
   2907    }
   2908    win->w_locked = false;
   2909    if (last_window(win)) {
   2910      return FAIL;
   2911    }
   2912    // autocmds may abort script processing
   2913    if (aborting()) {
   2914      return FAIL;
   2915    }
   2916  }
   2917 
   2918  // Fire WinClosed just before starting to free window-related resources.
   2919  do_autocmd_winclosed(win);
   2920  // autocmd may have freed the window already.
   2921  if (!win_valid_any_tab(win)) {
   2922    return OK;
   2923  }
   2924 
   2925  bufref_T bufref;
   2926  set_bufref(&bufref, win->w_buffer);
   2927 
   2928  bool did_decrement = win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, true);
   2929 
   2930  if (win_valid(win) && win->w_buffer == NULL
   2931      && !win->w_floating && last_window(win)) {
   2932    // Autocommands have closed all windows, quit now.  Restore
   2933    // curwin->w_buffer, otherwise writing ShaDa file may fail.
   2934    if (curwin->w_buffer == NULL) {
   2935      curwin->w_buffer = curbuf;
   2936    }
   2937    getout(0);
   2938  }
   2939  // Autocommands may have moved to another tab page.
   2940  if (curtab != prev_curtab && win_valid_any_tab(win)
   2941      && win->w_buffer == NULL) {
   2942    // Need to close the window anyway, since the buffer is NULL.
   2943    win_close_othertab(win, false, prev_curtab, force);
   2944    return FAIL;
   2945  }
   2946 
   2947  // Autocommands may have closed the window already, or closed the only
   2948  // other window or moved to another tab page.
   2949  if (!win_valid(win)) {
   2950    return FAIL;
   2951  }
   2952  if (one_window(win, NULL) && (first_tabpage->tp_next == NULL || lastwin->w_floating)) {
   2953    if (first_tabpage->tp_next != NULL) {
   2954      emsg(e_floatonly);
   2955    }
   2956    win_unclose_buffer(win, &bufref, did_decrement);
   2957    return FAIL;
   2958  }
   2959  if (close_last_window_tabpage(win, free_buf, prev_curtab)) {
   2960    return FAIL;
   2961  }
   2962 
   2963  // Now we are really going to close the window.  Disallow any autocommand
   2964  // to split a window to avoid trouble.
   2965  split_disallowed++;
   2966 
   2967  bool was_floating = win->w_floating;
   2968  if (ui_has(kUIMultigrid)) {
   2969    ui_call_win_close(win->w_grid_alloc.handle);
   2970  }
   2971 
   2972  if (win->w_floating) {
   2973    ui_comp_remove_grid(&win->w_grid_alloc);
   2974    assert(first_tabpage != NULL);  // suppress clang "Dereference of NULL pointer"
   2975    if (win->w_config.external) {
   2976      FOR_ALL_TABS(tp) {
   2977        if (tp != curtab && tp->tp_curwin == win) {
   2978          // NB: an autocmd can still abort the closing of this window,
   2979          // but carrying out this change anyway shouldn't be a catastrophe.
   2980          tp->tp_curwin = tp->tp_firstwin;
   2981        }
   2982      }
   2983    }
   2984  }
   2985 
   2986  // About to free the window. Remember its final buffer for terminal_check_size,
   2987  // which may have changed since the last set_bufref. (e.g: close_buffer autocmds)
   2988  set_bufref(&bufref, win->w_buffer);
   2989 
   2990  // Free the memory used for the window and get the window that received
   2991  // the screen space.
   2992  int dir;
   2993  win_T *wp = win_free_mem(win, &dir, NULL);
   2994 
   2995  if (help_window || quickfix_window) {
   2996    // Closing the help window moves the cursor back to the current window
   2997    // of the snapshot.
   2998    win_T *prev_win = get_snapshot_curwin(help_window ? SNAP_HELP_IDX : SNAP_QUICKFIX_IDX);
   2999    if (win_valid(prev_win)) {
   3000      wp = prev_win;
   3001    }
   3002  }
   3003 
   3004  bool close_curwin = false;
   3005 
   3006  // Make sure curwin isn't invalid.  It can cause severe trouble when
   3007  // printing an error message.  For win_equal() curbuf needs to be valid
   3008  // too.
   3009  if (win == curwin) {
   3010    curwin = wp;
   3011    if (wp->w_p_pvw || bt_quickfix(wp->w_buffer)) {
   3012      // If the cursor goes to the preview or the quickfix window, try
   3013      // finding another window to go to.
   3014      while (true) {
   3015        if (wp->w_next == NULL) {
   3016          wp = firstwin;
   3017        } else {
   3018          wp = wp->w_next;
   3019        }
   3020        if (wp == curwin) {
   3021          break;
   3022        }
   3023        if (!wp->w_p_pvw && !bt_quickfix(wp->w_buffer)
   3024            && !(wp->w_floating && (wp->w_config.hide || !wp->w_config.focusable))) {
   3025          curwin = wp;
   3026          break;
   3027        }
   3028      }
   3029    }
   3030    curbuf = curwin->w_buffer;
   3031    close_curwin = true;
   3032 
   3033    // The cursor position may be invalid if the buffer changed after last
   3034    // using the window.
   3035    check_cursor(curwin);
   3036  }
   3037 
   3038  if (!was_floating) {
   3039    // If last window has a status line now and we don't want one,
   3040    // remove the status line. Do this before win_equal(), because
   3041    // it may change the height of a window.
   3042    last_status(false);
   3043 
   3044    if (!curwin->w_floating && p_ea && (*p_ead == 'b' || *p_ead == dir)) {
   3045      // If the frame of the closed window contains the new current window,
   3046      // only resize that frame.  Otherwise resize all windows.
   3047      win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);
   3048    } else {
   3049      win_comp_pos();
   3050      win_fix_scroll(false);
   3051    }
   3052  }
   3053  if (bufref.br_buf && bufref_valid(&bufref) && bufref.br_buf->terminal) {
   3054    terminal_check_size(bufref.br_buf->terminal);
   3055  }
   3056 
   3057  if (close_curwin) {
   3058    win_enter_ext(wp, WEE_CURWIN_INVALID | WEE_TRIGGER_ENTER_AUTOCMDS
   3059                  | WEE_TRIGGER_LEAVE_AUTOCMDS);
   3060    if (other_buffer) {
   3061      // careful: after this wp and win may be invalid!
   3062      apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
   3063    }
   3064  }
   3065 
   3066  if (ONE_WINDOW && curwin->w_locked && curbuf->b_locked_split
   3067      && first_tabpage->tp_next != NULL) {
   3068    // The new curwin is the last window in the current tab page, and it is
   3069    // already being closed.  Trigger TabLeave now, as after its buffer is
   3070    // removed it's no longer safe to do that.
   3071    apply_autocmds(EVENT_TABLEAVE, NULL, NULL, false, curbuf);
   3072  }
   3073 
   3074  split_disallowed--;
   3075 
   3076  // After closing the help or quickfix window, try restoring the window
   3077  // layout from before it was opened.
   3078  if (help_window || quickfix_window) {
   3079    restore_snapshot(help_window ? SNAP_HELP_IDX : SNAP_QUICKFIX_IDX, close_curwin);
   3080  }
   3081 
   3082  // If the window had 'diff' set and now there is only one window left in
   3083  // the tab page with 'diff' set, and "closeoff" is in 'diffopt', then
   3084  // execute ":diffoff!".
   3085  if (diffopt_closeoff() && had_diffmode && curtab == prev_curtab) {
   3086    int diffcount = 0;
   3087 
   3088    FOR_ALL_WINDOWS_IN_TAB(dwin, curtab) {
   3089      if (dwin->w_p_diff) {
   3090        diffcount++;
   3091      }
   3092    }
   3093    if (diffcount == 1) {
   3094      do_cmdline_cmd("diffoff!");
   3095    }
   3096  }
   3097 
   3098  curwin->w_pos_changed = true;
   3099  if (!was_floating) {
   3100    // TODO(bfredl): how about no?
   3101    redraw_all_later(UPD_NOT_VALID);
   3102  }
   3103  return OK;
   3104 }
   3105 
   3106 static void trigger_winnewpre(void)
   3107 {
   3108  window_layout_lock();
   3109  apply_autocmds(EVENT_WINNEWPRE, NULL, NULL, false, NULL);
   3110  window_layout_unlock();
   3111 }
   3112 
   3113 static void do_autocmd_winclosed(win_T *win)
   3114  FUNC_ATTR_NONNULL_ALL
   3115 {
   3116  static bool recursive = false;
   3117  if (recursive || !has_event(EVENT_WINCLOSED)) {
   3118    return;
   3119  }
   3120  recursive = true;
   3121  char winid[NUMBUFLEN];
   3122  vim_snprintf(winid, sizeof(winid), "%d", win->handle);
   3123  apply_autocmds(EVENT_WINCLOSED, winid, winid, false, win->w_buffer);
   3124  recursive = false;
   3125 }
   3126 
   3127 void trigger_tabclosedpre(tabpage_T *tp)
   3128 {
   3129  static bool recursive = false;
   3130  tabpage_T *ptp = curtab;
   3131 
   3132  // Quickly return when no TabClosedPre autocommands to be executed or
   3133  // already executing
   3134  if (!has_event(EVENT_TABCLOSEDPRE) || recursive) {
   3135    return;
   3136  }
   3137 
   3138  if (valid_tabpage(tp)) {
   3139    goto_tabpage_tp(tp, false, false);
   3140  }
   3141  recursive = true;
   3142  window_layout_lock();
   3143  apply_autocmds(EVENT_TABCLOSEDPRE, NULL, NULL, false, NULL);
   3144  window_layout_unlock();
   3145  recursive = false;
   3146  // tabpage may have been modified or deleted by autocmds
   3147  if (valid_tabpage(ptp)) {
   3148    // try to recover the tabpage first
   3149    goto_tabpage_tp(ptp, false, false);
   3150  } else {
   3151    // fall back to the first tabpage
   3152    goto_tabpage_tp(first_tabpage, false, false);
   3153  }
   3154 }
   3155 
   3156 // Close window "win" in tab page "tp", which is not the current tab page.
   3157 // This may be the last window in that tab page and result in closing the tab,
   3158 // thus "tp" may become invalid!
   3159 // Caller must check if buffer is hidden and whether the tabline needs to be
   3160 // updated.
   3161 // @return false when the window was not closed as a direct result of this call
   3162 //         (e.g: not via autocmds).
   3163 bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
   3164  FUNC_ATTR_NONNULL_ALL
   3165 {
   3166  assert(tp != curtab);
   3167  bool did_decrement = false;
   3168 
   3169  // Commands that may call win_close_othertab() already check this, but
   3170  // check here again just in case.
   3171  if (window_layout_locked(CMD_SIZE)) {
   3172    return false;
   3173  }
   3174  // Get here with win->w_buffer == NULL when win_close() detects the tab page
   3175  // changed.
   3176  if (win_locked(win)
   3177      || (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) {
   3178    return false;  // window is already being closed
   3179  }
   3180  if (is_aucmd_win(win)) {
   3181    emsg(_(e_autocmd_close));
   3182    return false;
   3183  }
   3184 
   3185  // Check if closing this window would leave only floating windows.
   3186  if (tp->tp_lastwin->w_floating && one_window(win, tp)) {
   3187    if (force || can_close_floating_windows(tp)) {
   3188      // close the last window until the there are no floating windows
   3189      while (tp->tp_lastwin->w_floating) {
   3190        // `force` flag isn't actually used when closing a floating window.
   3191        if (!win_close_othertab(tp->tp_lastwin, free_buf, tp, true)) {
   3192          // If closing the window fails give up, to avoid looping forever.
   3193          goto leave_open;
   3194        }
   3195      }
   3196      if (!win_valid_any_tab(win)) {
   3197        return false;  // window already closed by autocommands
   3198      }
   3199    } else {
   3200      emsg(e_floatonly);
   3201      goto leave_open;
   3202    }
   3203  }
   3204 
   3205  // Fire WinClosed just before starting to free window-related resources.
   3206  // If the buffer is NULL, it isn't safe to trigger autocommands,
   3207  // and win_close() should have already triggered WinClosed.
   3208  if (win->w_buffer != NULL) {
   3209    do_autocmd_winclosed(win);
   3210    // autocmd may have freed the window already.
   3211    if (!win_valid_any_tab(win)) {
   3212      return false;
   3213    }
   3214  }
   3215 
   3216  if (tp->tp_firstwin == tp->tp_lastwin && !tp->tp_did_tabclosedpre) {
   3217    trigger_tabclosedpre(tp);
   3218    // autocmd may have freed the window already.
   3219    if (!win_valid_any_tab(win)) {
   3220      return false;
   3221    }
   3222  }
   3223 
   3224  bufref_T bufref;
   3225  set_bufref(&bufref, win->w_buffer);
   3226 
   3227  if (win->w_buffer != NULL) {
   3228    // Close the link to the buffer.
   3229    did_decrement = close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true);
   3230  }
   3231 
   3232  // Careful: Autocommands may have closed the tab page or made it the
   3233  // current tab page.
   3234  if (!valid_tabpage(tp) || tp == curtab) {
   3235    goto leave_open;
   3236  }
   3237  // Autocommands may have closed the window already, or nvim_win_set_config
   3238  // moved it to a different tab page.
   3239  if (!tabpage_win_valid(tp, win)) {
   3240    goto leave_open;
   3241  }
   3242  // Autocommands may again cause closing this window to leave only floats.
   3243  // Check again; we'll not bother closing floating windows this time.
   3244  if (tp->tp_lastwin->w_floating && one_window(win, tp)) {
   3245    emsg(e_floatonly);
   3246    goto leave_open;
   3247  }
   3248 
   3249  int free_tp_idx = 0;
   3250 
   3251  // When closing the last window in a tab page remove the tab page.
   3252  if (tp->tp_firstwin == tp->tp_lastwin) {
   3253    free_tp_idx = tabpage_index(tp);
   3254    int h = tabline_height();
   3255 
   3256    if (tp == first_tabpage) {
   3257      first_tabpage = tp->tp_next;
   3258    } else {
   3259      tabpage_T *ptp;
   3260      for (ptp = first_tabpage; ptp != NULL && ptp->tp_next != tp;
   3261           ptp = ptp->tp_next) {
   3262        // loop
   3263      }
   3264      if (ptp == NULL) {
   3265        internal_error("win_close_othertab()");
   3266        return false;
   3267      }
   3268      ptp->tp_next = tp->tp_next;
   3269    }
   3270    redraw_tabline = true;
   3271    if (h != tabline_height()) {
   3272      win_new_screen_rows();
   3273    }
   3274  }
   3275 
   3276  // About to free the window. Remember its final buffer for terminal_check_size/TabClosed,
   3277  // which may have changed since the last set_bufref. (e.g: close_buffer autocmds)
   3278  set_bufref(&bufref, win->w_buffer);
   3279 
   3280  // Free the memory used for the window.
   3281  int dir;
   3282  win_free_mem(win, &dir, tp);
   3283 
   3284  if (bufref.br_buf && bufref_valid(&bufref) && bufref.br_buf->terminal) {
   3285    terminal_check_size(bufref.br_buf->terminal);
   3286  }
   3287  if (free_tp_idx > 0) {
   3288    free_tabpage(tp);
   3289 
   3290    if (has_event(EVENT_TABCLOSED)) {
   3291      char prev_idx[NUMBUFLEN];
   3292      vim_snprintf(prev_idx, NUMBUFLEN, "%i", free_tp_idx);
   3293      apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false,
   3294                     bufref.br_buf && bufref_valid(&bufref) ? bufref.br_buf : curbuf);
   3295    }
   3296  }
   3297  return true;
   3298 
   3299 leave_open:
   3300  if (win_valid_any_tab(win)) {
   3301    win_unclose_buffer(win, &bufref, did_decrement);
   3302  }
   3303  return false;
   3304 }
   3305 
   3306 /// Free the memory used for a window.
   3307 ///
   3308 /// @param dirp  set to 'v' or 'h' for direction if 'ea'
   3309 /// @param tp    tab page "win" is in, NULL for current
   3310 ///
   3311 /// @return      a pointer to the window that got the freed up space.
   3312 static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp)
   3313  FUNC_ATTR_NONNULL_ARG(1)
   3314 {
   3315  win_T *wp;
   3316  tabpage_T *win_tp = tp == NULL ? curtab : tp;
   3317 
   3318  if (!win->w_floating) {
   3319    // Remove the window and its frame from the tree of frames.
   3320    frame_T *frp = win->w_frame;
   3321    wp = winframe_remove(win, dirp, tp, NULL);
   3322    xfree(frp);
   3323  } else {
   3324    *dirp = 'h';  // Dummy value.
   3325    wp = win_float_find_altwin(win, tp);
   3326  }
   3327  win_free(win, tp);
   3328 
   3329  // When deleting the current window in the tab, select a new current
   3330  // window.
   3331  if (win == win_tp->tp_curwin) {
   3332    win_tp->tp_curwin = wp;
   3333  }
   3334  // Avoid executing cmdline_win logic after it is closed.
   3335  if (win == cmdline_win) {
   3336    cmdline_win = NULL;
   3337  }
   3338 
   3339  return wp;
   3340 }
   3341 
   3342 #if defined(EXITFREE)
   3343 void win_free_all(void)
   3344 {
   3345  // avoid an error for switching tabpage with the cmdline window open
   3346  cmdwin_type = 0;
   3347  cmdwin_buf = NULL;
   3348  cmdwin_win = NULL;
   3349  cmdwin_old_curwin = NULL;
   3350 
   3351  while (first_tabpage->tp_next != NULL) {
   3352    tabpage_close(true);
   3353  }
   3354 
   3355  while (lastwin != NULL && lastwin->w_floating) {
   3356    win_T *wp = lastwin;
   3357    win_remove(lastwin, NULL);
   3358    int dummy;
   3359    win_free_mem(wp, &dummy, NULL);
   3360    for (int i = 0; i < AUCMD_WIN_COUNT; i++) {
   3361      if (aucmd_win[i].auc_win == wp) {
   3362        aucmd_win[i].auc_win = NULL;
   3363      }
   3364    }
   3365  }
   3366 
   3367  for (int i = 0; i < AUCMD_WIN_COUNT; i++) {
   3368    if (aucmd_win[i].auc_win != NULL) {
   3369      int dummy;
   3370      win_free_mem(aucmd_win[i].auc_win, &dummy, NULL);
   3371      aucmd_win[i].auc_win = NULL;
   3372    }
   3373  }
   3374 
   3375  kv_destroy(aucmd_win_vec);
   3376 
   3377  while (firstwin != NULL) {
   3378    int dummy;
   3379    win_free_mem(firstwin, &dummy, NULL);
   3380  }
   3381 
   3382  // No window should be used after this. Set curwin to NULL to crash
   3383  // instead of using freed memory.
   3384  curwin = NULL;
   3385 }
   3386 
   3387 #endif
   3388 
   3389 /// Remove a window and its frame from the tree of frames.
   3390 ///
   3391 /// @param dirp  set to 'v' or 'h' for direction if 'ea'
   3392 /// @param tp    tab page "win" is in, NULL for current
   3393 /// @param unflat_altfr if not NULL, set to pointer of frame that got
   3394 ///                     the space, and it is not flattened
   3395 ///
   3396 /// @return      a pointer to the window that got the freed up space.
   3397 win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_altfr)
   3398  FUNC_ATTR_NONNULL_ARG(1, 2)
   3399 {
   3400  frame_T *altfr;
   3401  win_T *wp = winframe_find_altwin(win, dirp, tp, &altfr);
   3402  if (wp == NULL) {
   3403    return NULL;
   3404  }
   3405 
   3406  frame_T *frp_close = win->w_frame;
   3407 
   3408  frame_locked++;
   3409 
   3410  // Save the position of the containing frame (which will also contain the
   3411  // altframe) before we remove anything, to recompute window positions later.
   3412  const win_T *const topleft = frame2win(frp_close->fr_parent);
   3413  int row = topleft->w_winrow;
   3414  int col = topleft->w_wincol;
   3415 
   3416  // If this is a rightmost window, remove vertical separators to the left.
   3417  if (win->w_vsep_width == 0 && frp_close->fr_parent->fr_layout == FR_ROW
   3418      && frp_close->fr_prev != NULL) {
   3419    frame_set_vsep(frp_close->fr_prev, false);
   3420  }
   3421 
   3422  // Remove this frame from the list of frames.
   3423  frame_remove(frp_close);
   3424 
   3425  if (*dirp == 'v') {
   3426    frame_new_height(altfr, altfr->fr_height + frp_close->fr_height,
   3427                     altfr == frp_close->fr_next, false, false);
   3428  } else {
   3429    assert(*dirp == 'h');
   3430    frame_new_width(altfr, altfr->fr_width + frp_close->fr_width,
   3431                    altfr == frp_close->fr_next, false);
   3432  }
   3433 
   3434  // If the altframe wasn't adjacent and left/above, resizing it will have
   3435  // changed window positions within the parent frame.  Recompute them.
   3436  if (altfr != frp_close->fr_prev) {
   3437    frame_comp_pos(frp_close->fr_parent, &row, &col);
   3438  }
   3439 
   3440  if (unflat_altfr == NULL) {
   3441    frame_flatten(altfr);
   3442  } else {
   3443    *unflat_altfr = altfr;
   3444  }
   3445 
   3446  frame_locked--;
   3447 
   3448  return wp;
   3449 }
   3450 
   3451 /// Find the window that will get the freed space from a call to `winframe_remove`.
   3452 /// Makes no changes to the window layout.
   3453 ///
   3454 /// @param dirp  set to 'v' or 'h' for the direction where "altfr" will be resized
   3455 ///              to fill the space
   3456 /// @param tp    tab page "win" is in, NULL for current
   3457 /// @param altfr if not NULL, set to pointer of frame that will get the space
   3458 ///
   3459 /// @return      a pointer to the window that will get the freed up space, or NULL
   3460 ///              if there is no other non-float to receive the space.
   3461 win_T *winframe_find_altwin(win_T *win, int *dirp, tabpage_T *tp, frame_T **altfr)
   3462  FUNC_ATTR_NONNULL_ARG(1, 2)
   3463 {
   3464  assert(tp == NULL || tp != curtab);
   3465 
   3466  // If there is only one non-floating window there is nothing to remove.
   3467  if (one_window(win, tp)) {
   3468    return NULL;
   3469  }
   3470 
   3471  frame_T *frp_close = win->w_frame;
   3472 
   3473  // Find the window and frame that gets the space.
   3474  frame_T *frp2 = win_altframe(win, tp);
   3475  win_T *wp = frame2win(frp2);
   3476 
   3477  if (frp_close->fr_parent->fr_layout == FR_COL) {
   3478    // When 'winfixheight' is set, try to find another frame in the column
   3479    // (as close to the closed frame as possible) to distribute the height
   3480    // to.
   3481    if (frp2->fr_win != NULL && frp2->fr_win->w_p_wfh) {
   3482      frame_T *frp = frp_close->fr_prev;
   3483      frame_T *frp3 = frp_close->fr_next;
   3484      while (frp != NULL || frp3 != NULL) {
   3485        if (frp != NULL) {
   3486          if (!frame_fixed_height(frp)) {
   3487            frp2 = frp;
   3488            wp = frame2win(frp2);
   3489            break;
   3490          }
   3491          frp = frp->fr_prev;
   3492        }
   3493        if (frp3 != NULL) {
   3494          if (frp3->fr_win != NULL && !frp3->fr_win->w_p_wfh) {
   3495            frp2 = frp3;
   3496            wp = frp3->fr_win;
   3497            break;
   3498          }
   3499          frp3 = frp3->fr_next;
   3500        }
   3501      }
   3502    }
   3503    *dirp = 'v';
   3504  } else {
   3505    // When 'winfixwidth' is set, try to find another frame in the column
   3506    // (as close to the closed frame as possible) to distribute the width
   3507    // to.
   3508    if (frp2->fr_win != NULL && frp2->fr_win->w_p_wfw) {
   3509      frame_T *frp = frp_close->fr_prev;
   3510      frame_T *frp3 = frp_close->fr_next;
   3511      while (frp != NULL || frp3 != NULL) {
   3512        if (frp != NULL) {
   3513          if (!frame_fixed_width(frp)) {
   3514            frp2 = frp;
   3515            wp = frame2win(frp2);
   3516            break;
   3517          }
   3518          frp = frp->fr_prev;
   3519        }
   3520        if (frp3 != NULL) {
   3521          if (frp3->fr_win != NULL && !frp3->fr_win->w_p_wfw) {
   3522            frp2 = frp3;
   3523            wp = frp3->fr_win;
   3524            break;
   3525          }
   3526          frp3 = frp3->fr_next;
   3527        }
   3528      }
   3529    }
   3530    *dirp = 'h';
   3531  }
   3532 
   3533  assert(wp != win && frp2 != frp_close);
   3534  if (altfr != NULL) {
   3535    *altfr = frp2;
   3536  }
   3537 
   3538  return wp;
   3539 }
   3540 
   3541 /// Flatten "frp" into its parent frame if it's the only child, also merging its
   3542 /// list with the grandparent if they share the same layout.
   3543 /// Frees "frp" if flattened; also "frp->fr_parent" if it has the same layout.
   3544 static void frame_flatten(frame_T *frp)
   3545  FUNC_ATTR_NONNULL_ALL
   3546 {
   3547  if (frp->fr_next != NULL || frp->fr_prev != NULL) {
   3548    return;
   3549  }
   3550 
   3551  // There is no other frame in this list, move its info to the parent
   3552  // and remove it.
   3553  frp->fr_parent->fr_layout = frp->fr_layout;
   3554  frp->fr_parent->fr_child = frp->fr_child;
   3555  frame_T *frp2;
   3556  FOR_ALL_FRAMES(frp2, frp->fr_child) {
   3557    frp2->fr_parent = frp->fr_parent;
   3558  }
   3559  frp->fr_parent->fr_win = frp->fr_win;
   3560  if (frp->fr_win != NULL) {
   3561    frp->fr_win->w_frame = frp->fr_parent;
   3562  }
   3563  frp2 = frp->fr_parent;
   3564  if (topframe->fr_child == frp) {
   3565    topframe->fr_child = frp2;
   3566  }
   3567  xfree(frp);
   3568 
   3569  frp = frp2->fr_parent;
   3570  if (frp != NULL && frp->fr_layout == frp2->fr_layout) {
   3571    // The frame above the parent has the same layout, have to merge
   3572    // the frames into this list.
   3573    if (frp->fr_child == frp2) {
   3574      frp->fr_child = frp2->fr_child;
   3575    }
   3576    assert(frp2->fr_child);
   3577    frp2->fr_child->fr_prev = frp2->fr_prev;
   3578    if (frp2->fr_prev != NULL) {
   3579      frp2->fr_prev->fr_next = frp2->fr_child;
   3580    }
   3581    for (frame_T *frp3 = frp2->fr_child;; frp3 = frp3->fr_next) {
   3582      frp3->fr_parent = frp;
   3583      if (frp3->fr_next == NULL) {
   3584        frp3->fr_next = frp2->fr_next;
   3585        if (frp2->fr_next != NULL) {
   3586          frp2->fr_next->fr_prev = frp3;
   3587        }
   3588        break;
   3589      }
   3590    }
   3591    if (topframe->fr_child == frp2) {
   3592      topframe->fr_child = frp;
   3593    }
   3594    xfree(frp2);
   3595  }
   3596 }
   3597 
   3598 /// Undo changes from a prior call to winframe_remove, also restoring lost
   3599 /// vertical separators and statuslines, and changed window positions for
   3600 /// windows within "unflat_altfr".
   3601 /// Caller must ensure no other changes were made to the layout or window sizes!
   3602 void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr)
   3603  FUNC_ATTR_NONNULL_ALL
   3604 {
   3605  frame_T *frp = wp->w_frame;
   3606 
   3607  // Put "wp"'s frame back where it was.
   3608  if (frp->fr_prev != NULL) {
   3609    frame_append(frp->fr_prev, frp);
   3610  } else {
   3611    frame_insert(frp->fr_next, frp);
   3612  }
   3613 
   3614  // Vertical separators to the left may have been lost.  Restore them.
   3615  if (wp->w_vsep_width == 0 && frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) {
   3616    frame_set_vsep(frp->fr_prev, true);
   3617  }
   3618 
   3619  // Statuslines or horizontal separators above may have been lost.  Restore them.
   3620  if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) {
   3621    if (global_stl_height() == 0 && wp->w_status_height == 0) {
   3622      frame_add_statusline(frp->fr_prev);
   3623    } else if (global_stl_height() > 0 && wp->w_hsep_height == 0) {
   3624      frame_add_hsep(frp->fr_prev);
   3625    }
   3626  }
   3627 
   3628  // Restore the lost room that was redistributed to the altframe.  Also
   3629  // adjusts window sizes to fit restored statuslines/separators, if needed.
   3630  if (dir == 'v') {
   3631    frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height,
   3632                     unflat_altfr == frp->fr_next, false, false);
   3633  } else if (dir == 'h') {
   3634    frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width,
   3635                    unflat_altfr == frp->fr_next, false);
   3636  }
   3637 
   3638  // Recompute window positions within the parent frame to restore them.
   3639  // Positions were unchanged if the altframe was adjacent and left/above.
   3640  if (unflat_altfr != frp->fr_prev) {
   3641    const win_T *const topleft = frame2win(frp->fr_parent);
   3642    int row = topleft->w_winrow;
   3643    int col = topleft->w_wincol;
   3644 
   3645    frame_comp_pos(frp->fr_parent, &row, &col);
   3646  }
   3647 }
   3648 
   3649 /// If 'splitbelow' or 'splitright' is set, the space goes above or to the left
   3650 /// by default.  Otherwise, the free space goes below or to the right.  The
   3651 /// result is that opening a window and then immediately closing it will
   3652 /// preserve the initial window layout.  The 'wfh' and 'wfw' settings are
   3653 /// respected when possible.
   3654 ///
   3655 /// @param  tp  tab page "win" is in, NULL for current
   3656 ///
   3657 /// @return a pointer to the frame that will receive the empty screen space that
   3658 /// is left over after "win" is closed.
   3659 static frame_T *win_altframe(win_T *win, tabpage_T *tp)
   3660  FUNC_ATTR_NONNULL_ARG(1)
   3661 {
   3662  assert(tp == NULL || tp != curtab);
   3663 
   3664  if (one_window(win, tp)) {
   3665    return alt_tabpage()->tp_curwin->w_frame;
   3666  }
   3667 
   3668  frame_T *frp = win->w_frame;
   3669 
   3670  if (frp->fr_prev == NULL) {
   3671    return frp->fr_next;
   3672  }
   3673  if (frp->fr_next == NULL) {
   3674    return frp->fr_prev;
   3675  }
   3676 
   3677  // By default the next window will get the space that was abandoned by this
   3678  // window
   3679  frame_T *target_fr = frp->fr_next;
   3680  frame_T *other_fr = frp->fr_prev;
   3681 
   3682  // If this is part of a column of windows and 'splitbelow' is true then the
   3683  // previous window will get the space.
   3684  if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_COL && p_sb) {
   3685    target_fr = frp->fr_prev;
   3686    other_fr = frp->fr_next;
   3687  }
   3688 
   3689  // If this is part of a row of windows, and 'splitright' is true then the
   3690  // previous window will get the space.
   3691  if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW && p_spr) {
   3692    target_fr = frp->fr_prev;
   3693    other_fr = frp->fr_next;
   3694  }
   3695 
   3696  // If 'wfh' or 'wfw' is set for the target and not for the alternate
   3697  // window, reverse the selection.
   3698  if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW) {
   3699    if (frame_fixed_width(target_fr) && !frame_fixed_width(other_fr)) {
   3700      target_fr = other_fr;
   3701    }
   3702  } else {
   3703    if (frame_fixed_height(target_fr) && !frame_fixed_height(other_fr)) {
   3704      target_fr = other_fr;
   3705    }
   3706  }
   3707 
   3708  return target_fr;
   3709 }
   3710 
   3711 // Return the tabpage that will be used if the current one is closed.
   3712 static tabpage_T *alt_tabpage(void)
   3713 {
   3714  // Use the last accessed tab page, if possible.
   3715  if ((tcl_flags & kOptTclFlagUselast) && valid_tabpage(lastused_tabpage)) {
   3716    return lastused_tabpage;
   3717  }
   3718 
   3719  // Use the next tab page, if possible.
   3720  bool forward = curtab->tp_next != NULL
   3721                 && ((tcl_flags & kOptTclFlagLeft) == 0 || curtab == first_tabpage);
   3722 
   3723  tabpage_T *tp;
   3724  if (forward) {
   3725    tp = curtab->tp_next;
   3726  } else {
   3727    // Use the previous tab page.
   3728    for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next) {}
   3729  }
   3730 
   3731  return tp;
   3732 }
   3733 
   3734 // Find the left-upper window in frame "frp".
   3735 win_T *frame2win(frame_T *frp)
   3736  FUNC_ATTR_NONNULL_ALL
   3737 {
   3738  while (frp->fr_win == NULL) {
   3739    frp = frp->fr_child;
   3740  }
   3741  return frp->fr_win;
   3742 }
   3743 
   3744 /// Check that the frame "frp" contains the window "wp".
   3745 ///
   3746 /// @param  frp  frame
   3747 /// @param  wp   window
   3748 static bool frame_has_win(const frame_T *frp, const win_T *wp)
   3749  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
   3750 {
   3751  if (frp->fr_layout == FR_LEAF) {
   3752    return frp->fr_win == wp;
   3753  }
   3754  const frame_T *p;
   3755  FOR_ALL_FRAMES(p, frp->fr_child) {
   3756    if (frame_has_win(p, wp)) {
   3757      return true;
   3758    }
   3759  }
   3760  return false;
   3761 }
   3762 
   3763 /// Check if current window is at the bottom
   3764 /// Returns true if there are no windows below current window
   3765 static bool is_bottom_win(win_T *wp)
   3766  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
   3767 {
   3768  for (frame_T *frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) {
   3769    if (frp->fr_parent->fr_layout == FR_COL && frp->fr_next != NULL) {
   3770      return false;
   3771    }
   3772  }
   3773  return true;
   3774 }
   3775 
   3776 /// Set a new height for a frame.  Recursively sets the height for contained
   3777 /// frames and windows.  Caller must take care of positions.
   3778 ///
   3779 /// @param topfirst  resize topmost contained frame first.
   3780 /// @param wfh       obey 'winfixheight' when there is a choice;
   3781 ///                  may cause the height not to be set.
   3782 /// @param set_ch    set 'cmdheight' to resize topframe.
   3783 void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh, bool set_ch)
   3784  FUNC_ATTR_NONNULL_ALL
   3785 {
   3786  if (topfrp->fr_parent == NULL && set_ch) {
   3787    // topframe: update the command line height, with side effects.
   3788    OptInt new_ch = MAX(min_set_ch, p_ch + topfrp->fr_height - height);
   3789    if (new_ch != p_ch) {
   3790      const OptInt save_ch = min_set_ch;
   3791      set_option_value(kOptCmdheight, NUMBER_OPTVAL(new_ch), 0);
   3792      min_set_ch = save_ch;
   3793    }
   3794    height = (int)MIN(ROWS_AVAIL, height);
   3795  }
   3796  if (topfrp->fr_win != NULL) {
   3797    // Simple case: just one window.
   3798    win_T *wp = topfrp->fr_win;
   3799    if (is_bottom_win(wp)) {
   3800      wp->w_hsep_height = 0;
   3801    }
   3802    win_new_height(wp, height - wp->w_hsep_height - wp->w_status_height);
   3803  } else if (topfrp->fr_layout == FR_ROW) {
   3804    frame_T *frp;
   3805    do {
   3806      // All frames in this row get the same new height.
   3807      FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   3808        frame_new_height(frp, height, topfirst, wfh, set_ch);
   3809        if (frp->fr_height > height) {
   3810          // Could not fit the windows, make the whole row higher.
   3811          height = frp->fr_height;
   3812          break;
   3813        }
   3814      }
   3815    } while (frp != NULL);
   3816  } else {  // fr_layout == FR_COL
   3817    // Complicated case: Resize a column of frames.  Resize the bottom
   3818    // frame first, frames above that when needed.
   3819 
   3820    frame_T *frp = topfrp->fr_child;
   3821    if (wfh) {
   3822      // Advance past frames with one window with 'wfh' set.
   3823      while (frame_fixed_height(frp)) {
   3824        frp = frp->fr_next;
   3825        if (frp == NULL) {
   3826          return;                   // no frame without 'wfh', give up
   3827        }
   3828      }
   3829    }
   3830    if (!topfirst) {
   3831      // Find the bottom frame of this column
   3832      while (frp->fr_next != NULL) {
   3833        frp = frp->fr_next;
   3834      }
   3835      if (wfh) {
   3836        // Advance back for frames with one window with 'wfh' set.
   3837        while (frame_fixed_height(frp)) {
   3838          frp = frp->fr_prev;
   3839        }
   3840      }
   3841    }
   3842 
   3843    int extra_lines = height - topfrp->fr_height;
   3844    if (extra_lines < 0) {
   3845      // reduce height of contained frames, bottom or top frame first
   3846      while (frp != NULL) {
   3847        int h = frame_minheight(frp, NULL);
   3848        if (frp->fr_height + extra_lines < h) {
   3849          extra_lines += frp->fr_height - h;
   3850          frame_new_height(frp, h, topfirst, wfh, set_ch);
   3851        } else {
   3852          frame_new_height(frp, frp->fr_height + extra_lines, topfirst, wfh, set_ch);
   3853          break;
   3854        }
   3855        if (topfirst) {
   3856          do {
   3857            frp = frp->fr_next;
   3858          } while (wfh && frp != NULL && frame_fixed_height(frp));
   3859        } else {
   3860          do {
   3861            frp = frp->fr_prev;
   3862          } while (wfh && frp != NULL && frame_fixed_height(frp));
   3863        }
   3864        // Increase "height" if we could not reduce enough frames.
   3865        if (frp == NULL) {
   3866          height -= extra_lines;
   3867        }
   3868      }
   3869    } else if (extra_lines > 0) {
   3870      // increase height of bottom or top frame
   3871      frame_new_height(frp, frp->fr_height + extra_lines, topfirst, wfh, set_ch);
   3872    }
   3873  }
   3874  topfrp->fr_height = height;
   3875 }
   3876 
   3877 /// Return true if height of frame "frp" should not be changed because of
   3878 /// the 'winfixheight' option.
   3879 ///
   3880 /// @param  frp  frame
   3881 ///
   3882 /// @return true if the frame has a fixed height
   3883 static bool frame_fixed_height(frame_T *frp)
   3884  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
   3885 {
   3886  // frame with one window: fixed height if 'winfixheight' set.
   3887  if (frp->fr_win != NULL) {
   3888    return frp->fr_win->w_p_wfh;
   3889  }
   3890  if (frp->fr_layout == FR_ROW) {
   3891    // The frame is fixed height if one of the frames in the row is fixed
   3892    // height.
   3893    FOR_ALL_FRAMES(frp, frp->fr_child) {
   3894      if (frame_fixed_height(frp)) {
   3895        return true;
   3896      }
   3897    }
   3898    return false;
   3899  }
   3900 
   3901  // frp->fr_layout == FR_COL: The frame is fixed height if all of the
   3902  // frames in the row are fixed height.
   3903  FOR_ALL_FRAMES(frp, frp->fr_child) {
   3904    if (!frame_fixed_height(frp)) {
   3905      return false;
   3906    }
   3907  }
   3908  return true;
   3909 }
   3910 
   3911 /// Return true if width of frame "frp" should not be changed because of
   3912 /// the 'winfixwidth' option.
   3913 ///
   3914 /// @param  frp  frame
   3915 ///
   3916 /// @return true if the frame has a fixed width
   3917 static bool frame_fixed_width(frame_T *frp)
   3918  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
   3919 {
   3920  // frame with one window: fixed width if 'winfixwidth' set.
   3921  if (frp->fr_win != NULL) {
   3922    return frp->fr_win->w_p_wfw;
   3923  }
   3924  if (frp->fr_layout == FR_COL) {
   3925    // The frame is fixed width if one of the frames in the row is fixed
   3926    // width.
   3927    FOR_ALL_FRAMES(frp, frp->fr_child) {
   3928      if (frame_fixed_width(frp)) {
   3929        return true;
   3930      }
   3931    }
   3932    return false;
   3933  }
   3934 
   3935  // frp->fr_layout == FR_ROW: The frame is fixed width if all of the
   3936  // frames in the row are fixed width.
   3937  FOR_ALL_FRAMES(frp, frp->fr_child) {
   3938    if (!frame_fixed_width(frp)) {
   3939      return false;
   3940    }
   3941  }
   3942  return true;
   3943 }
   3944 
   3945 // Add a status line to windows at the bottom of "frp".
   3946 // Note: Does not check if there is room!
   3947 static void frame_add_statusline(frame_T *frp)
   3948 {
   3949  if (frp->fr_layout == FR_LEAF) {
   3950    win_T *wp = frp->fr_win;
   3951    wp->w_status_height = STATUS_HEIGHT;
   3952  } else if (frp->fr_layout == FR_ROW) {
   3953    // Handle all the frames in the row.
   3954    FOR_ALL_FRAMES(frp, frp->fr_child) {
   3955      frame_add_statusline(frp);
   3956    }
   3957  } else {
   3958    assert(frp->fr_layout == FR_COL);
   3959    // Only need to handle the last frame in the column.
   3960    for (frp = frp->fr_child; frp->fr_next != NULL; frp = frp->fr_next) {}
   3961    frame_add_statusline(frp);
   3962  }
   3963 }
   3964 
   3965 /// Set width of a frame.  Handles recursively going through contained frames.
   3966 /// May remove separator line for windows at the right side (for win_close()).
   3967 ///
   3968 /// @param leftfirst  resize leftmost contained frame first.
   3969 /// @param wfw        obey 'winfixwidth' when there is a choice;
   3970 ///                   may cause the width not to be set.
   3971 static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw)
   3972 {
   3973  if (topfrp->fr_layout == FR_LEAF) {
   3974    // Simple case: just one window.
   3975    win_T *wp = topfrp->fr_win;
   3976    // Find out if there are any windows right of this one.
   3977    frame_T *frp;
   3978    for (frp = topfrp; frp->fr_parent != NULL; frp = frp->fr_parent) {
   3979      if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_next != NULL) {
   3980        break;
   3981      }
   3982    }
   3983    if (frp->fr_parent == NULL) {
   3984      wp->w_vsep_width = 0;
   3985    }
   3986    win_new_width(wp, width - wp->w_vsep_width);
   3987  } else if (topfrp->fr_layout == FR_COL) {
   3988    frame_T *frp;
   3989    do {
   3990      // All frames in this column get the same new width.
   3991      FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   3992        frame_new_width(frp, width, leftfirst, wfw);
   3993        if (frp->fr_width > width) {
   3994          // Could not fit the windows, make whole column wider.
   3995          width = frp->fr_width;
   3996          break;
   3997        }
   3998      }
   3999    } while (frp != NULL);
   4000  } else {  // fr_layout == FR_ROW
   4001    // Complicated case: Resize a row of frames.  Resize the rightmost
   4002    // frame first, frames left of it when needed.
   4003 
   4004    frame_T *frp = topfrp->fr_child;
   4005    if (wfw) {
   4006      // Advance past frames with one window with 'wfw' set.
   4007      while (frame_fixed_width(frp)) {
   4008        frp = frp->fr_next;
   4009        if (frp == NULL) {
   4010          return;                   // no frame without 'wfw', give up
   4011        }
   4012      }
   4013    }
   4014    if (!leftfirst) {
   4015      // Find the rightmost frame of this row
   4016      while (frp->fr_next != NULL) {
   4017        frp = frp->fr_next;
   4018      }
   4019      if (wfw) {
   4020        // Advance back for frames with one window with 'wfw' set.
   4021        while (frame_fixed_width(frp)) {
   4022          frp = frp->fr_prev;
   4023        }
   4024      }
   4025    }
   4026 
   4027    int extra_cols = width - topfrp->fr_width;
   4028    if (extra_cols < 0) {
   4029      // reduce frame width, rightmost frame first
   4030      while (frp != NULL) {
   4031        int w = frame_minwidth(frp, NULL);
   4032        if (frp->fr_width + extra_cols < w) {
   4033          extra_cols += frp->fr_width - w;
   4034          frame_new_width(frp, w, leftfirst, wfw);
   4035        } else {
   4036          frame_new_width(frp, frp->fr_width + extra_cols,
   4037                          leftfirst, wfw);
   4038          break;
   4039        }
   4040        if (leftfirst) {
   4041          do {
   4042            frp = frp->fr_next;
   4043          } while (wfw && frp != NULL && frame_fixed_width(frp));
   4044        } else {
   4045          do {
   4046            frp = frp->fr_prev;
   4047          } while (wfw && frp != NULL && frame_fixed_width(frp));
   4048        }
   4049        // Increase "width" if we could not reduce enough frames.
   4050        if (frp == NULL) {
   4051          width -= extra_cols;
   4052        }
   4053      }
   4054    } else if (extra_cols > 0) {
   4055      // increase width of rightmost frame
   4056      frame_new_width(frp, frp->fr_width + extra_cols, leftfirst, wfw);
   4057    }
   4058  }
   4059  topfrp->fr_width = width;
   4060 }
   4061 
   4062 /// Add or remove the vertical separator of windows to the right side of "frp".
   4063 /// Note: Does not check if there is room!
   4064 static void frame_set_vsep(const frame_T *frp, bool add)
   4065  FUNC_ATTR_NONNULL_ARG(1)
   4066 {
   4067  if (frp->fr_layout == FR_LEAF) {
   4068    win_T *wp = frp->fr_win;
   4069    if (add && wp->w_vsep_width == 0) {
   4070      if (wp->w_width > 0) {            // don't make it negative
   4071        win_new_width(wp, wp->w_width - 1);
   4072      }
   4073      wp->w_vsep_width = 1;
   4074    } else if (!add && wp->w_vsep_width == 1) {
   4075      win_new_width(wp, wp->w_width + 1);
   4076      wp->w_vsep_width = 0;
   4077    }
   4078  } else if (frp->fr_layout == FR_COL) {
   4079    // Handle all the frames in the column.
   4080    FOR_ALL_FRAMES(frp, frp->fr_child) {
   4081      frame_set_vsep(frp, add);
   4082    }
   4083  } else {
   4084    assert(frp->fr_layout == FR_ROW);
   4085    // Only need to handle the last frame in the row.
   4086    frp = frp->fr_child;
   4087    while (frp->fr_next != NULL) {
   4088      frp = frp->fr_next;
   4089    }
   4090    frame_set_vsep(frp, add);
   4091  }
   4092 }
   4093 
   4094 /// Add the horizontal separator to windows at the bottom of "frp".
   4095 /// Note: Does not check if there is room or whether the windows have a statusline!
   4096 static void frame_add_hsep(const frame_T *frp)
   4097  FUNC_ATTR_NONNULL_ARG(1)
   4098 {
   4099  if (frp->fr_layout == FR_LEAF) {
   4100    win_T *wp = frp->fr_win;
   4101    wp->w_hsep_height = 1;
   4102  } else if (frp->fr_layout == FR_ROW) {
   4103    // Handle all the frames in the row.
   4104    FOR_ALL_FRAMES(frp, frp->fr_child) {
   4105      frame_add_hsep(frp);
   4106    }
   4107  } else {
   4108    assert(frp->fr_layout == FR_COL);
   4109    // Only need to handle the last frame in the column.
   4110    frp = frp->fr_child;
   4111    while (frp->fr_next != NULL) {
   4112      frp = frp->fr_next;
   4113    }
   4114    frame_add_hsep(frp);
   4115  }
   4116 }
   4117 
   4118 // Set frame width from the window it contains.
   4119 static void frame_fix_width(win_T *wp)
   4120 {
   4121  wp->w_frame->fr_width = wp->w_width + wp->w_vsep_width;
   4122 }
   4123 
   4124 // Set frame height from the window it contains.
   4125 static void frame_fix_height(win_T *wp)
   4126  FUNC_ATTR_NONNULL_ALL
   4127 {
   4128  wp->w_frame->fr_height = wp->w_height + wp->w_hsep_height + wp->w_status_height;
   4129 }
   4130 
   4131 /// Compute the minimal height for frame "topfrp". Uses the 'winminheight' option.
   4132 /// When "next_curwin" isn't NULL, use p_wh for this window.
   4133 /// When "next_curwin" is NOWIN, don't use at least one line for the current window.
   4134 static int frame_minheight(frame_T *topfrp, win_T *next_curwin)
   4135 {
   4136  int m;
   4137 
   4138  if (topfrp->fr_win != NULL) {
   4139    // Combined height of window bar and separator column or status line.
   4140    int extra_height = topfrp->fr_win->w_winbar_height + topfrp->fr_win->w_hsep_height
   4141                       + topfrp->fr_win->w_status_height;
   4142 
   4143    if (topfrp->fr_win == next_curwin) {
   4144      m = (int)p_wh + extra_height;
   4145    } else {
   4146      m = (int)p_wmh + extra_height;
   4147      if (topfrp->fr_win == curwin && next_curwin == NULL) {
   4148        // Current window is minimal one line high.
   4149        if (p_wmh == 0) {
   4150          m++;
   4151        }
   4152      }
   4153    }
   4154  } else if (topfrp->fr_layout == FR_ROW) {
   4155    // get the minimal height from each frame in this row
   4156    m = 0;
   4157    frame_T *frp;
   4158    FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   4159      int n = frame_minheight(frp, next_curwin);
   4160      if (n > m) {
   4161        m = n;
   4162      }
   4163    }
   4164  } else {
   4165    // Add up the minimal heights for all frames in this column.
   4166    m = 0;
   4167    frame_T *frp;
   4168    FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   4169      m += frame_minheight(frp, next_curwin);
   4170    }
   4171  }
   4172 
   4173  return m;
   4174 }
   4175 
   4176 /// Compute the minimal width for frame "topfrp".
   4177 /// When "next_curwin" isn't NULL, use p_wiw for this window.
   4178 /// When "next_curwin" is NOWIN, don't use at least one column for the current
   4179 /// window.
   4180 ///
   4181 /// @param next_curwin  use p_wh and p_wiw for next_curwin
   4182 static int frame_minwidth(frame_T *topfrp, win_T *next_curwin)
   4183 {
   4184  int m;
   4185 
   4186  if (topfrp->fr_win != NULL) {
   4187    if (topfrp->fr_win == next_curwin) {
   4188      m = (int)p_wiw + topfrp->fr_win->w_vsep_width;
   4189    } else {
   4190      // window: minimal width of the window plus separator column
   4191      m = (int)p_wmw + topfrp->fr_win->w_vsep_width;
   4192      // Current window is minimal one column wide
   4193      if (p_wmw == 0 && topfrp->fr_win == curwin && next_curwin == NULL) {
   4194        m++;
   4195      }
   4196    }
   4197  } else if (topfrp->fr_layout == FR_COL) {
   4198    // get the minimal width from each frame in this column
   4199    m = 0;
   4200    frame_T *frp;
   4201    FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   4202      int n = frame_minwidth(frp, next_curwin);
   4203      m = MAX(m, n);
   4204    }
   4205  } else {
   4206    // Add up the minimal widths for all frames in this row.
   4207    m = 0;
   4208    frame_T *frp;
   4209    FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   4210      m += frame_minwidth(frp, next_curwin);
   4211    }
   4212  }
   4213 
   4214  return m;
   4215 }
   4216 
   4217 /// Try to close all windows except current one.
   4218 /// Buffers in the other windows become hidden if 'hidden' is set, or '!' is
   4219 /// used and the buffer was modified.
   4220 ///
   4221 /// Used by ":bdel" and ":only".
   4222 ///
   4223 /// @param forceit  always hide all other windows
   4224 void close_others(int message, int forceit)
   4225 {
   4226  win_T *const old_curwin = curwin;
   4227 
   4228  if (curwin->w_floating) {
   4229    if (message && !autocmd_busy) {
   4230      emsg(e_floatonly);
   4231    }
   4232    return;
   4233  }
   4234 
   4235  if (one_window(firstwin, NULL) && !lastwin->w_floating) {
   4236    if (message && !autocmd_busy) {
   4237      msg(_(m_onlyone), 0);
   4238    }
   4239    return;
   4240  }
   4241 
   4242  // Be very careful here: autocommands may change the window layout.
   4243  win_T *nextwp;
   4244  for (win_T *wp = firstwin; win_valid(wp); wp = nextwp) {
   4245    nextwp = wp->w_next;
   4246 
   4247    // autocommands messed this one up
   4248    if (old_curwin != curwin && win_valid(old_curwin)) {
   4249      curwin = old_curwin;
   4250      curbuf = curwin->w_buffer;
   4251    }
   4252 
   4253    if (wp == curwin) {                 // don't close current window
   4254      continue;
   4255    }
   4256 
   4257    // autoccommands messed this one up
   4258    if (!buf_valid(wp->w_buffer) && win_valid(wp)) {
   4259      wp->w_buffer = NULL;
   4260      win_close(wp, false, false);
   4261      continue;
   4262    }
   4263    // Check if it's allowed to abandon this window
   4264    int r = can_abandon(wp->w_buffer, forceit);
   4265    if (!win_valid(wp)) {             // autocommands messed wp up
   4266      nextwp = firstwin;
   4267      continue;
   4268    }
   4269    if (!r) {
   4270      if (message && (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) {
   4271        dialog_changed(wp->w_buffer, false);
   4272        if (!win_valid(wp)) {                 // autocommands messed wp up
   4273          nextwp = firstwin;
   4274          continue;
   4275        }
   4276      }
   4277      if (bufIsChanged(wp->w_buffer)) {
   4278        continue;
   4279      }
   4280    }
   4281    win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer), false);
   4282  }
   4283 
   4284  if (message && !ONE_WINDOW) {
   4285    emsg(_("E445: Other window contains changes"));
   4286  }
   4287 }
   4288 
   4289 /// Store the relevant window pointers for tab page "tp".  To be used before
   4290 /// use_tabpage().
   4291 void unuse_tabpage(tabpage_T *tp)
   4292 {
   4293  tp->tp_topframe = topframe;
   4294  tp->tp_firstwin = firstwin;
   4295  tp->tp_lastwin = lastwin;
   4296  tp->tp_curwin = curwin;
   4297 }
   4298 
   4299 // When switching tabpage, handle other side-effects in command_height(), but
   4300 // avoid setting frame sizes which are still correct.
   4301 static bool command_frame_height = true;
   4302 
   4303 /// Set the relevant pointers to use tab page "tp".  May want to call
   4304 /// unuse_tabpage() first.
   4305 void use_tabpage(tabpage_T *tp)
   4306 {
   4307  curtab = tp;
   4308  topframe = curtab->tp_topframe;
   4309  firstwin = curtab->tp_firstwin;
   4310  lastwin = curtab->tp_lastwin;
   4311  curwin = curtab->tp_curwin;
   4312 }
   4313 
   4314 // Allocate the first window and put an empty buffer in it.
   4315 // Only called from main().
   4316 void win_alloc_first(void)
   4317 {
   4318  if (win_alloc_firstwin(NULL) == FAIL) {
   4319    // allocating first buffer before any autocmds should not fail.
   4320    abort();
   4321  }
   4322 
   4323  first_tabpage = alloc_tabpage();
   4324  curtab = first_tabpage;
   4325  unuse_tabpage(first_tabpage);
   4326 }
   4327 
   4328 // Init `aucmd_win[idx]`. This can only be done after the first window
   4329 // is fully initialized, thus it can't be in win_alloc_first().
   4330 void win_alloc_aucmd_win(int idx)
   4331 {
   4332  Error err = ERROR_INIT;
   4333  WinConfig fconfig = WIN_CONFIG_INIT;
   4334  fconfig.width = Columns;
   4335  fconfig.height = 5;
   4336  fconfig.focusable = false;
   4337  fconfig.mouse = false;
   4338  aucmd_win[idx].auc_win = win_new_float(NULL, true, fconfig, &err);
   4339  aucmd_win[idx].auc_win->w_buffer->b_nwindows--;
   4340  RESET_BINDING(aucmd_win[idx].auc_win);
   4341 }
   4342 
   4343 // Allocate the first window or the first window in a new tab page.
   4344 // When "oldwin" is NULL create an empty buffer for it.
   4345 // When "oldwin" is not NULL copy info from it to the new window.
   4346 // Return FAIL when something goes wrong (out of memory).
   4347 static int win_alloc_firstwin(win_T *oldwin)
   4348 {
   4349  curwin = win_alloc(NULL, false);
   4350  if (oldwin == NULL) {
   4351    // Very first window, need to create an empty buffer for it and
   4352    // initialize from scratch.
   4353    curbuf = buflist_new(NULL, NULL, 1, BLN_LISTED);
   4354    if (curbuf == NULL) {
   4355      return FAIL;
   4356    }
   4357    curwin->w_buffer = curbuf;
   4358    curwin->w_s = &(curbuf->b_s);
   4359    curbuf->b_nwindows = 1;     // there is one window
   4360    curwin->w_alist = &global_alist;
   4361    curwin_init();              // init current window
   4362  } else {
   4363    // First window in new tab page, initialize it from "oldwin".
   4364    win_init(curwin, oldwin, 0);
   4365 
   4366    // We don't want cursor- and scroll-binding in the first window.
   4367    RESET_BINDING(curwin);
   4368  }
   4369 
   4370  new_frame(curwin);
   4371  topframe = curwin->w_frame;
   4372  topframe->fr_width = Columns;
   4373  topframe->fr_height = Rows - (int)p_ch - global_stl_height();
   4374 
   4375  return OK;
   4376 }
   4377 
   4378 // Create a frame for window "wp".
   4379 static void new_frame(win_T *wp)
   4380 {
   4381  frame_T *frp = xcalloc(1, sizeof(frame_T));
   4382 
   4383  wp->w_frame = frp;
   4384  frp->fr_layout = FR_LEAF;
   4385  frp->fr_win = wp;
   4386 }
   4387 
   4388 // Initialize the window and frame size to the maximum.
   4389 void win_init_size(void)
   4390 {
   4391  firstwin->w_height = (int)ROWS_AVAIL;
   4392  firstwin->w_prev_height = (int)ROWS_AVAIL;
   4393  firstwin->w_view_height = firstwin->w_height - firstwin->w_winbar_height;
   4394  firstwin->w_height_outer = firstwin->w_height;
   4395  firstwin->w_winrow_off = firstwin->w_winbar_height;
   4396  topframe->fr_height = (int)ROWS_AVAIL;
   4397  firstwin->w_width = Columns;
   4398  firstwin->w_view_width = firstwin->w_width;
   4399  firstwin->w_width_outer = firstwin->w_width;
   4400  topframe->fr_width = Columns;
   4401 }
   4402 
   4403 // Allocate a new tabpage_T and init the values.
   4404 static tabpage_T *alloc_tabpage(void)
   4405 {
   4406  static int last_tp_handle = 0;
   4407  tabpage_T *tp = xcalloc(1, sizeof(tabpage_T));
   4408  tp->handle = ++last_tp_handle;
   4409  pmap_put(int)(&tabpage_handles, tp->handle, tp);
   4410 
   4411  // Init t: variables.
   4412  tp->tp_vars = tv_dict_alloc();
   4413  init_var_dict(tp->tp_vars, &tp->tp_winvar, VAR_SCOPE);
   4414  tp->tp_diff_invalid = true;
   4415  tp->tp_ch_used = p_ch;
   4416 
   4417  return tp;
   4418 }
   4419 
   4420 void free_tabpage(tabpage_T *tp)
   4421 {
   4422  pmap_del(int)(&tabpage_handles, tp->handle, NULL);
   4423  diff_clear(tp);
   4424  for (int idx = 0; idx < SNAP_COUNT; idx++) {
   4425    clear_snapshot(tp, idx);
   4426  }
   4427  vars_clear(&tp->tp_vars->dv_hashtab);         // free all t: variables
   4428  hash_init(&tp->tp_vars->dv_hashtab);
   4429  unref_var_dict(tp->tp_vars);
   4430 
   4431  if (tp == lastused_tabpage) {
   4432    lastused_tabpage = NULL;
   4433  }
   4434 
   4435  xfree(tp->tp_localdir);
   4436  xfree(tp->tp_prevdir);
   4437  xfree(tp);
   4438 }
   4439 
   4440 /// Create a new tabpage with one window.
   4441 ///
   4442 /// It will edit the current buffer, like after :split.
   4443 ///
   4444 /// Does not trigger WinNewPre, since the window structures
   4445 /// are not completely setup yet and could cause dereferencing
   4446 /// NULL pointers
   4447 ///
   4448 /// @param after Put new tabpage after tabpage "after", or after the current
   4449 ///              tabpage in case of 0.
   4450 /// @param filename Will be passed to apply_autocmds().
   4451 /// @return Was the new tabpage created successfully? FAIL or OK.
   4452 int win_new_tabpage(int after, char *filename)
   4453 {
   4454  tabpage_T *old_curtab = curtab;
   4455 
   4456  if (cmdwin_type != 0) {
   4457    emsg(_(e_cmdwin));
   4458    return FAIL;
   4459  }
   4460  if (window_layout_locked(CMD_tabnew)) {
   4461    return FAIL;
   4462  }
   4463 
   4464  tabpage_T *newtp = alloc_tabpage();
   4465 
   4466  // Remember the current windows in this Tab page.
   4467  if (leave_tabpage(curbuf, true) == FAIL) {
   4468    xfree(newtp);
   4469    return FAIL;
   4470  }
   4471 
   4472  newtp->tp_localdir = old_curtab->tp_localdir
   4473                       ? xstrdup(old_curtab->tp_localdir) : NULL;
   4474 
   4475  curtab = newtp;
   4476 
   4477  // Create a new empty window.
   4478  if (win_alloc_firstwin(old_curtab->tp_curwin) == OK) {
   4479    // Make the new Tab page the new topframe.
   4480    if (after == 1) {
   4481      // New tab page becomes the first one.
   4482      newtp->tp_next = first_tabpage;
   4483      first_tabpage = newtp;
   4484    } else {
   4485      tabpage_T *tp = old_curtab;
   4486 
   4487      if (after > 0) {
   4488        // Put new tab page before tab page "after".
   4489        int n = 2;
   4490        for (tp = first_tabpage; tp->tp_next != NULL
   4491             && n < after; tp = tp->tp_next) {
   4492          n++;
   4493        }
   4494      }
   4495      newtp->tp_next = tp->tp_next;
   4496      tp->tp_next = newtp;
   4497    }
   4498    newtp->tp_firstwin = newtp->tp_lastwin = newtp->tp_curwin = curwin;
   4499 
   4500    win_init_size();
   4501    firstwin->w_winrow = tabline_height();
   4502    firstwin->w_prev_winrow = firstwin->w_winrow;
   4503    win_comp_scroll(curwin);
   4504 
   4505    newtp->tp_topframe = topframe;
   4506    last_status(false);
   4507 
   4508    if (curbuf->terminal) {
   4509      terminal_check_size(curbuf->terminal);
   4510    }
   4511 
   4512    redraw_all_later(UPD_NOT_VALID);
   4513 
   4514    tabpage_check_windows(old_curtab);
   4515 
   4516    lastused_tabpage = old_curtab;
   4517 
   4518    entering_window(curwin);
   4519 
   4520    apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
   4521    apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf);
   4522    apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf);
   4523    apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf);
   4524 
   4525    return OK;
   4526  }
   4527 
   4528  // Failed, get back the previous Tab page
   4529  enter_tabpage(curtab, curbuf, true, true);
   4530  return FAIL;
   4531 }
   4532 
   4533 // Open a new tab page if ":tab cmd" was used.  It will edit the same buffer,
   4534 // like with ":split".
   4535 // Returns OK if a new tab page was created, FAIL otherwise.
   4536 static int may_open_tabpage(void)
   4537 {
   4538  int n = (cmdmod.cmod_tab == 0) ? postponed_split_tab : cmdmod.cmod_tab;
   4539 
   4540  if (n == 0) {
   4541    return FAIL;
   4542  }
   4543 
   4544  cmdmod.cmod_tab = 0;         // reset it to avoid doing it twice
   4545  postponed_split_tab = 0;
   4546  int status = win_new_tabpage(n, NULL);
   4547  if (status == OK) {
   4548    apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf);
   4549  }
   4550  return status;
   4551 }
   4552 
   4553 // Create up to "maxcount" tabpages with empty windows.
   4554 // Returns the number of resulting tab pages.
   4555 int make_tabpages(int maxcount)
   4556 {
   4557  int count = maxcount;
   4558 
   4559  // Limit to 'tabpagemax' tabs.
   4560  count = MIN(count, (int)p_tpm);
   4561 
   4562  // Don't execute autocommands while creating the tab pages.  Must do that
   4563  // when putting the buffers in the windows.
   4564  block_autocmds();
   4565 
   4566  int todo;
   4567  for (todo = count - 1; todo > 0; todo--) {
   4568    if (win_new_tabpage(0, NULL) == FAIL) {
   4569      break;
   4570    }
   4571  }
   4572 
   4573  unblock_autocmds();
   4574 
   4575  // return actual number of tab pages
   4576  return count - todo;
   4577 }
   4578 
   4579 /// Check that tpc points to a valid tab page.
   4580 ///
   4581 /// @param[in]  tpc  Tabpage to check.
   4582 bool valid_tabpage(tabpage_T *tpc) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   4583 {
   4584  FOR_ALL_TABS(tp) {
   4585    if (tp == tpc) {
   4586      return true;
   4587    }
   4588  }
   4589  return false;
   4590 }
   4591 
   4592 /// Returns true when `tpc` is valid and at least one window is valid.
   4593 int valid_tabpage_win(tabpage_T *tpc)
   4594 {
   4595  FOR_ALL_TABS(tp) {
   4596    if (tp == tpc) {
   4597      FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
   4598        if (win_valid_any_tab(wp)) {
   4599          return true;
   4600        }
   4601      }
   4602      return false;
   4603    }
   4604  }
   4605  // shouldn't happen
   4606  return false;
   4607 }
   4608 
   4609 /// Close tabpage `tab`, assuming it has no windows in it.
   4610 /// There must be another tabpage or this will crash.
   4611 void close_tabpage(tabpage_T *tab)
   4612 {
   4613  tabpage_T *ptp;
   4614 
   4615  if (tab == first_tabpage) {
   4616    first_tabpage = tab->tp_next;
   4617    ptp = first_tabpage;
   4618  } else {
   4619    for (ptp = first_tabpage; ptp != NULL && ptp->tp_next != tab;
   4620         ptp = ptp->tp_next) {
   4621      // do nothing
   4622    }
   4623    assert(ptp != NULL);
   4624    ptp->tp_next = tab->tp_next;
   4625  }
   4626 
   4627  goto_tabpage_tp(ptp, false, false);
   4628  free_tabpage(tab);
   4629 }
   4630 
   4631 // Find tab page "n" (first one is 1).  Returns NULL when not found.
   4632 tabpage_T *find_tabpage(int n)
   4633 {
   4634  tabpage_T *tp;
   4635  int i = 1;
   4636 
   4637  if (n == 0) {
   4638    return curtab;
   4639  }
   4640 
   4641  for (tp = first_tabpage; tp != NULL && i != n; tp = tp->tp_next) {
   4642    i++;
   4643  }
   4644  return tp;
   4645 }
   4646 
   4647 // Get index of tab page "tp".  First one has index 1.
   4648 // When not found returns number of tab pages plus one.
   4649 int tabpage_index(tabpage_T *ftp)
   4650 {
   4651  int i = 1;
   4652  tabpage_T *tp;
   4653 
   4654  for (tp = first_tabpage; tp != NULL && tp != ftp; tp = tp->tp_next) {
   4655    i++;
   4656  }
   4657  return i;
   4658 }
   4659 
   4660 /// Prepare for leaving the current tab page.
   4661 /// When autocommands change "curtab" we don't leave the tab page and return
   4662 /// FAIL.
   4663 /// Careful: When OK is returned need to get a new tab page very very soon!
   4664 ///
   4665 /// @param new_curbuf              what is going to be the new curbuf,
   4666 ///                                NULL if unknown.
   4667 /// @param trigger_leave_autocmds  when true trigger *Leave autocommands.
   4668 static int leave_tabpage(buf_T *new_curbuf, bool trigger_leave_autocmds)
   4669 {
   4670  tabpage_T *tp = curtab;
   4671 
   4672  leaving_window(curwin);
   4673  reset_VIsual_and_resel();     // stop Visual mode
   4674  if (trigger_leave_autocmds) {
   4675    if (new_curbuf != curbuf) {
   4676      apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
   4677      if (curtab != tp) {
   4678        return FAIL;
   4679      }
   4680    }
   4681    apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf);
   4682    if (curtab != tp) {
   4683      return FAIL;
   4684    }
   4685    apply_autocmds(EVENT_TABLEAVE, NULL, NULL, false, curbuf);
   4686    if (curtab != tp) {
   4687      return FAIL;
   4688    }
   4689  }
   4690 
   4691  reset_dragwin();
   4692  tp->tp_curwin = curwin;
   4693  tp->tp_prevwin = prevwin;
   4694  tp->tp_firstwin = firstwin;
   4695  tp->tp_lastwin = lastwin;
   4696  tp->tp_old_Rows_avail = ROWS_AVAIL;
   4697  if (tp->tp_old_Columns != -1) {
   4698    tp->tp_old_Columns = Columns;
   4699  }
   4700  firstwin = NULL;
   4701  lastwin = NULL;
   4702  return OK;
   4703 }
   4704 
   4705 /// Start using tab page "tp".
   4706 /// Only to be used after leave_tabpage() or freeing the current tab page.
   4707 ///
   4708 /// @param trigger_enter_autocmds  when true trigger *Enter autocommands.
   4709 /// @param trigger_leave_autocmds  when true trigger *Leave autocommands.
   4710 static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_autocmds,
   4711                          bool trigger_leave_autocmds)
   4712 {
   4713  int old_off = tp->tp_firstwin->w_winrow;
   4714  win_T *next_prevwin = tp->tp_prevwin;
   4715  tabpage_T *old_curtab = curtab;
   4716 
   4717  use_tabpage(tp);
   4718 
   4719  if (old_curtab != curtab) {
   4720    tabpage_check_windows(old_curtab);
   4721    if (p_ch != curtab->tp_ch_used) {
   4722      // Use the stored value of p_ch, so that it can be different for each tab page.
   4723      // Handle other side-effects but avoid setting frame sizes, which are still correct.
   4724      OptInt new_ch = curtab->tp_ch_used;
   4725      curtab->tp_ch_used = p_ch;
   4726      command_frame_height = false;
   4727      set_option_value(kOptCmdheight, NUMBER_OPTVAL(new_ch), 0);
   4728      command_frame_height = true;
   4729    }
   4730  }
   4731 
   4732  // We would like doing the TabEnter event first, but we don't have a
   4733  // valid current window yet, which may break some commands.
   4734  // This triggers autocommands, thus may make "tp" invalid.
   4735  win_enter_ext(tp->tp_curwin, WEE_CURWIN_INVALID
   4736                | (trigger_enter_autocmds ? WEE_TRIGGER_ENTER_AUTOCMDS : 0)
   4737                | (trigger_leave_autocmds ? WEE_TRIGGER_LEAVE_AUTOCMDS : 0));
   4738  prevwin = next_prevwin;
   4739 
   4740  last_status(false);  // status line may appear or disappear
   4741  win_float_update_statusline();
   4742  win_comp_pos();      // recompute w_winrow for all windows
   4743  diff_need_scrollbind = true;
   4744 
   4745  // If there was a click in a window, it won't be usable for a following
   4746  // drag.
   4747  reset_dragwin();
   4748 
   4749  // The tabpage line may have appeared or disappeared, may need to resize the frames for that.
   4750  // When the Vim window was resized or ROWS_AVAIL changed need to update frame sizes too.
   4751  if (curtab->tp_old_Rows_avail != ROWS_AVAIL || (old_off != firstwin->w_winrow)) {
   4752    win_new_screen_rows();
   4753  }
   4754  if (curtab->tp_old_Columns != Columns) {
   4755    if (starting == 0) {
   4756      win_new_screen_cols();  // update window widths
   4757      curtab->tp_old_Columns = Columns;
   4758    } else {
   4759      curtab->tp_old_Columns = -1;  // update window widths later
   4760    }
   4761  }
   4762 
   4763  lastused_tabpage = old_curtab;
   4764 
   4765  // Apply autocommands after updating the display, when 'rows' and
   4766  // 'columns' have been set correctly.
   4767  if (trigger_enter_autocmds) {
   4768    apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf);
   4769    if (old_curbuf != curbuf) {
   4770      apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
   4771    }
   4772  }
   4773 
   4774  redraw_all_later(UPD_NOT_VALID);
   4775 }
   4776 
   4777 /// tells external UI that windows and inline floats in old_curtab are invisible
   4778 /// and that floats in curtab is now visible.
   4779 ///
   4780 /// External floats are considered independent of tabpages. This is
   4781 /// implemented by always moving them to curtab.
   4782 static void tabpage_check_windows(tabpage_T *old_curtab)
   4783 {
   4784  win_T *next_wp;
   4785  for (win_T *wp = old_curtab->tp_firstwin; wp; wp = next_wp) {
   4786    next_wp = wp->w_next;
   4787    if (wp->w_floating) {
   4788      if (wp->w_config.external) {
   4789        win_remove(wp, old_curtab);
   4790        win_append(lastwin_nofloating(), wp, NULL);
   4791      } else {
   4792        ui_comp_remove_grid(&wp->w_grid_alloc);
   4793      }
   4794    }
   4795    wp->w_pos_changed = true;
   4796  }
   4797 
   4798  for (win_T *wp = firstwin; wp; wp = wp->w_next) {
   4799    if (wp->w_floating && !wp->w_config.external) {
   4800      win_config_float(wp, wp->w_config);
   4801    }
   4802    wp->w_pos_changed = true;
   4803  }
   4804 }
   4805 
   4806 // Go to tab page "n".  For ":tab N" and "Ngt".
   4807 // When "n" is 9999 go to the last tab page.
   4808 void goto_tabpage(int n)
   4809 {
   4810  if (text_locked()) {
   4811    // Not allowed when editing the command line.
   4812    text_locked_msg();
   4813    return;
   4814  }
   4815 
   4816  // If there is only one it can't work.
   4817  if (first_tabpage->tp_next == NULL) {
   4818    if (n > 1) {
   4819      beep_flush();
   4820    }
   4821    return;
   4822  }
   4823 
   4824  tabpage_T *tp = NULL;  // shut up compiler
   4825 
   4826  if (n == 0) {
   4827    // No count, go to next tab page, wrap around end.
   4828    if (curtab->tp_next == NULL) {
   4829      tp = first_tabpage;
   4830    } else {
   4831      tp = curtab->tp_next;
   4832    }
   4833  } else if (n < 0) {
   4834    // "gT": go to previous tab page, wrap around end.  "N gT" repeats
   4835    // this N times.
   4836    tabpage_T *ttp = curtab;
   4837    for (int i = n; i < 0; i++) {
   4838      for (tp = first_tabpage; tp->tp_next != ttp && tp->tp_next != NULL;
   4839           tp = tp->tp_next) {}
   4840      ttp = tp;
   4841    }
   4842  } else if (n == 9999) {
   4843    // Go to last tab page.
   4844    for (tp = first_tabpage; tp->tp_next != NULL; tp = tp->tp_next) {}
   4845  } else {
   4846    // Go to tab page "n".
   4847    tp = find_tabpage(n);
   4848    if (tp == NULL) {
   4849      beep_flush();
   4850      return;
   4851    }
   4852  }
   4853 
   4854  goto_tabpage_tp(tp, true, true);
   4855 }
   4856 
   4857 /// Go to tabpage "tp".
   4858 /// Note: doesn't update the GUI tab.
   4859 ///
   4860 /// @param trigger_enter_autocmds  when true trigger *Enter autocommands.
   4861 /// @param trigger_leave_autocmds  when true trigger *Leave autocommands.
   4862 void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_leave_autocmds)
   4863 {
   4864  if (trigger_enter_autocmds || trigger_leave_autocmds) {
   4865    CHECK_CMDWIN;
   4866  }
   4867 
   4868  // Don't repeat a message in another tab page.
   4869  set_keep_msg(NULL, 0);
   4870 
   4871  skip_win_fix_scroll = true;
   4872  if (tp != curtab && leave_tabpage(tp->tp_curwin->w_buffer,
   4873                                    trigger_leave_autocmds) == OK) {
   4874    if (valid_tabpage(tp)) {
   4875      enter_tabpage(tp, curbuf, trigger_enter_autocmds,
   4876                    trigger_leave_autocmds);
   4877    } else {
   4878      enter_tabpage(curtab, curbuf, trigger_enter_autocmds,
   4879                    trigger_leave_autocmds);
   4880    }
   4881  }
   4882  skip_win_fix_scroll = false;
   4883 }
   4884 
   4885 /// Go to the last accessed tab page, if there is one.
   4886 /// @return true if the tab page is valid, false otherwise.
   4887 bool goto_tabpage_lastused(void)
   4888 {
   4889  if (!valid_tabpage(lastused_tabpage)) {
   4890    return false;
   4891  }
   4892 
   4893  goto_tabpage_tp(lastused_tabpage, true, true);
   4894  return true;
   4895 }
   4896 
   4897 // Enter window "wp" in tab page "tp".
   4898 // Also updates the GUI tab.
   4899 void goto_tabpage_win(tabpage_T *tp, win_T *wp)
   4900 {
   4901  goto_tabpage_tp(tp, true, true);
   4902  if (curtab == tp && win_valid(wp)) {
   4903    win_enter(wp, true);
   4904  }
   4905 }
   4906 
   4907 // Move the current tab page to after tab page "nr".
   4908 void tabpage_move(int nr)
   4909 {
   4910  assert(curtab != NULL);
   4911 
   4912  if (first_tabpage->tp_next == NULL) {
   4913    return;
   4914  }
   4915 
   4916  if (tabpage_move_disallowed) {
   4917    return;
   4918  }
   4919 
   4920  int n = 1;
   4921  tabpage_T *tp;
   4922 
   4923  for (tp = first_tabpage; tp->tp_next != NULL && n < nr; tp = tp->tp_next) {
   4924    n++;
   4925  }
   4926 
   4927  if (tp == curtab || (nr > 0 && tp->tp_next != NULL
   4928                       && tp->tp_next == curtab)) {
   4929    return;
   4930  }
   4931 
   4932  tabpage_T *tp_dst = tp;
   4933 
   4934  // Remove the current tab page from the list of tab pages.
   4935  if (curtab == first_tabpage) {
   4936    first_tabpage = curtab->tp_next;
   4937  } else {
   4938    tp = NULL;
   4939    FOR_ALL_TABS(tp2) {
   4940      if (tp2->tp_next == curtab) {
   4941        tp = tp2;
   4942        break;
   4943      }
   4944    }
   4945    if (tp == NULL) {   // "cannot happen"
   4946      return;
   4947    }
   4948    tp->tp_next = curtab->tp_next;
   4949  }
   4950 
   4951  // Re-insert it at the specified position.
   4952  if (nr <= 0) {
   4953    curtab->tp_next = first_tabpage;
   4954    first_tabpage = curtab;
   4955  } else {
   4956    curtab->tp_next = tp_dst->tp_next;
   4957    tp_dst->tp_next = curtab;
   4958  }
   4959 
   4960  // Need to redraw the tabline.  Tab page contents doesn't change.
   4961  redraw_tabline = true;
   4962 }
   4963 
   4964 /// Go to another window.
   4965 /// When jumping to another buffer, stop Visual mode.  Do this before
   4966 /// changing windows so we can yank the selection into the '*' register.
   4967 /// (note: this may trigger ModeChanged autocommand!)
   4968 /// When jumping to another window on the same buffer, adjust its cursor
   4969 /// position to keep the same Visual area.
   4970 void win_goto(win_T *wp)
   4971 {
   4972  win_T *owp = curwin;
   4973 
   4974  if (text_or_buf_locked()) {
   4975    beep_flush();
   4976    return;
   4977  }
   4978 
   4979  if (wp->w_buffer != curbuf) {
   4980    // careful: triggers ModeChanged autocommand
   4981    reset_VIsual_and_resel();
   4982  } else if (VIsual_active) {
   4983    wp->w_cursor = curwin->w_cursor;
   4984  }
   4985 
   4986  // autocommand may have made wp invalid
   4987  if (!win_valid(wp)) {
   4988    return;
   4989  }
   4990 
   4991  win_enter(wp, true);
   4992 
   4993  // Conceal cursor line in previous window, unconceal in current window.
   4994  if (win_valid(owp) && owp->w_p_cole > 0 && !msg_scrolled) {
   4995    redrawWinline(owp, owp->w_cursor.lnum);
   4996  }
   4997  if (curwin->w_p_cole > 0 && !msg_scrolled) {
   4998    redrawWinline(curwin, curwin->w_cursor.lnum);
   4999  }
   5000 }
   5001 
   5002 // Find the tabpage for window "win".
   5003 tabpage_T *win_find_tabpage(win_T *win)
   5004 {
   5005  FOR_ALL_TAB_WINDOWS(tp, wp) {
   5006    if (wp == win) {
   5007      return tp;
   5008    }
   5009  }
   5010  return NULL;
   5011 }
   5012 
   5013 /// Get the above or below neighbor window of the specified window.
   5014 ///
   5015 /// Returns the specified window if the neighbor is not found.
   5016 /// Returns the previous window if the specifiecied window is a floating window.
   5017 ///
   5018 /// @param up     true for the above neighbor
   5019 /// @param count  nth neighbor window
   5020 ///
   5021 /// @return       found window
   5022 win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, int count)
   5023 {
   5024  frame_T *foundfr = wp->w_frame;
   5025 
   5026  if (wp->w_floating) {
   5027    return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin;
   5028  }
   5029 
   5030  while (count--) {
   5031    frame_T *nfr;
   5032    // First go upwards in the tree of frames until we find an upwards or
   5033    // downwards neighbor.
   5034    frame_T *fr = foundfr;
   5035    while (true) {
   5036      if (fr == tp->tp_topframe) {
   5037        goto end;
   5038      }
   5039      if (up) {
   5040        nfr = fr->fr_prev;
   5041      } else {
   5042        nfr = fr->fr_next;
   5043      }
   5044      if (fr->fr_parent->fr_layout == FR_COL && nfr != NULL) {
   5045        break;
   5046      }
   5047      fr = fr->fr_parent;
   5048    }
   5049 
   5050    // Now go downwards to find the bottom or top frame in it.
   5051    while (true) {
   5052      if (nfr->fr_layout == FR_LEAF) {
   5053        foundfr = nfr;
   5054        break;
   5055      }
   5056      fr = nfr->fr_child;
   5057      if (nfr->fr_layout == FR_ROW) {
   5058        // Find the frame at the cursor row.
   5059        while (fr->fr_next != NULL
   5060               && frame2win(fr)->w_wincol + fr->fr_width
   5061               <= wp->w_wincol + wp->w_wcol) {
   5062          fr = fr->fr_next;
   5063        }
   5064      }
   5065      if (nfr->fr_layout == FR_COL && up) {
   5066        while (fr->fr_next != NULL) {
   5067          fr = fr->fr_next;
   5068        }
   5069      }
   5070      nfr = fr;
   5071    }
   5072  }
   5073 end:
   5074  return foundfr != NULL ? foundfr->fr_win : NULL;
   5075 }
   5076 
   5077 /// Move to window above or below "count" times.
   5078 ///
   5079 /// @param up     true to go to win above
   5080 /// @param count  go count times into direction
   5081 static void win_goto_ver(bool up, int count)
   5082 {
   5083  win_T *win = win_vert_neighbor(curtab, curwin, up, count);
   5084  if (win != NULL) {
   5085    win_goto(win);
   5086  }
   5087 }
   5088 
   5089 /// Get the left or right neighbor window of the specified window.
   5090 ///
   5091 /// Returns the specified window if the neighbor is not found.
   5092 /// Returns the previous window if the specifiecied window is a floating window.
   5093 ///
   5094 /// @param left  true for the left neighbor
   5095 /// @param count nth neighbor window
   5096 ///
   5097 /// @return      found window
   5098 win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, int count)
   5099 {
   5100  frame_T *foundfr = wp->w_frame;
   5101 
   5102  if (wp->w_floating) {
   5103    return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin;
   5104  }
   5105 
   5106  while (count--) {
   5107    frame_T *nfr;
   5108    // First go upwards in the tree of frames until we find a left or
   5109    // right neighbor.
   5110    frame_T *fr = foundfr;
   5111    while (true) {
   5112      if (fr == tp->tp_topframe) {
   5113        goto end;
   5114      }
   5115      if (left) {
   5116        nfr = fr->fr_prev;
   5117      } else {
   5118        nfr = fr->fr_next;
   5119      }
   5120      if (fr->fr_parent->fr_layout == FR_ROW && nfr != NULL) {
   5121        break;
   5122      }
   5123      fr = fr->fr_parent;
   5124    }
   5125 
   5126    // Now go downwards to find the leftmost or rightmost frame in it.
   5127    while (true) {
   5128      if (nfr->fr_layout == FR_LEAF) {
   5129        foundfr = nfr;
   5130        break;
   5131      }
   5132      fr = nfr->fr_child;
   5133      if (nfr->fr_layout == FR_COL) {
   5134        // Find the frame at the cursor row.
   5135        while (fr->fr_next != NULL
   5136               && frame2win(fr)->w_winrow + fr->fr_height
   5137               <= wp->w_winrow + wp->w_wrow) {
   5138          fr = fr->fr_next;
   5139        }
   5140      }
   5141      if (nfr->fr_layout == FR_ROW && left) {
   5142        while (fr->fr_next != NULL) {
   5143          fr = fr->fr_next;
   5144        }
   5145      }
   5146      nfr = fr;
   5147    }
   5148  }
   5149 end:
   5150  return foundfr != NULL ? foundfr->fr_win : NULL;
   5151 }
   5152 
   5153 /// Move to left or right window.
   5154 ///
   5155 /// @param left   true to go to left window
   5156 /// @param count  go count times into direction
   5157 static void win_goto_hor(bool left, int count)
   5158 {
   5159  win_T *win = win_horz_neighbor(curtab, curwin, left, count);
   5160  if (win != NULL) {
   5161    win_goto(win);
   5162  }
   5163 }
   5164 
   5165 /// Make window `wp` the current window.
   5166 ///
   5167 /// @warning Autocmds may close the window immediately, so caller must check
   5168 ///          win_valid(wp).
   5169 void win_enter(win_T *wp, bool undo_sync)
   5170 {
   5171  win_enter_ext(wp, (undo_sync ? WEE_UNDO_SYNC : 0)
   5172                | WEE_TRIGGER_ENTER_AUTOCMDS | WEE_TRIGGER_LEAVE_AUTOCMDS);
   5173 }
   5174 
   5175 /// Make window "wp" the current window.
   5176 ///
   5177 /// @param flags  if contains WEE_CURWIN_INVALID, it means curwin has just been
   5178 ///               closed and isn't valid.
   5179 static void win_enter_ext(win_T *const wp, const int flags)
   5180 {
   5181  bool other_buffer = false;
   5182  const bool curwin_invalid = (flags & WEE_CURWIN_INVALID);
   5183 
   5184  if (wp == curwin && !curwin_invalid) {        // nothing to do
   5185    return;
   5186  }
   5187 
   5188  if (!curwin_invalid) {
   5189    leaving_window(curwin);
   5190  }
   5191 
   5192  if (!curwin_invalid && (flags & WEE_TRIGGER_LEAVE_AUTOCMDS)) {
   5193    // Be careful: If autocommands delete the window, return now.
   5194    if (wp->w_buffer != curbuf) {
   5195      apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
   5196      other_buffer = true;
   5197      if (!win_valid(wp)) {
   5198        return;
   5199      }
   5200    }
   5201    apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf);
   5202    if (!win_valid(wp)) {
   5203      return;
   5204    }
   5205    // autocmds may abort script processing
   5206    if (aborting()) {
   5207      return;
   5208    }
   5209  }
   5210 
   5211  // sync undo before leaving the current buffer
   5212  if ((flags & WEE_UNDO_SYNC) && curbuf != wp->w_buffer) {
   5213    u_sync(false);
   5214  }
   5215 
   5216  // Might need to scroll the old window before switching, e.g., when the
   5217  // cursor was moved.
   5218  if (*p_spk == 'c' && !curwin_invalid) {
   5219    update_topline(curwin);
   5220  }
   5221 
   5222  // may have to copy the buffer options when 'cpo' contains 'S'
   5223  if (wp->w_buffer != curbuf) {
   5224    buf_copy_options(wp->w_buffer, BCO_ENTER | BCO_NOHELP);
   5225  }
   5226  if (!curwin_invalid) {
   5227    prevwin = curwin;           // remember for CTRL-W p
   5228    curwin->w_redr_status = true;
   5229  }
   5230  curwin = wp;
   5231  curbuf = wp->w_buffer;
   5232 
   5233  check_cursor(curwin);
   5234  if (!virtual_active(curwin)) {
   5235    curwin->w_cursor.coladd = 0;
   5236  }
   5237  if (*p_spk == 'c') {
   5238    changed_line_abv_curs();      // assume cursor position needs updating
   5239  } else {
   5240    // Make sure the cursor position is valid, either by moving the cursor
   5241    // or by scrolling the text.
   5242    win_fix_cursor(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL));
   5243  }
   5244 
   5245  win_fix_current_dir();
   5246 
   5247  entering_window(curwin);
   5248  // Careful: autocommands may close the window and make "wp" invalid
   5249  if (flags & WEE_TRIGGER_NEW_AUTOCMDS) {
   5250    apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
   5251  }
   5252  if (flags & WEE_TRIGGER_ENTER_AUTOCMDS) {
   5253    apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf);
   5254    if (other_buffer) {
   5255      apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
   5256    }
   5257  }
   5258 
   5259  maketitle();
   5260  curwin->w_redr_status = true;
   5261  redraw_tabline = true;
   5262  if (restart_edit) {
   5263    redraw_later(curwin, UPD_VALID);  // causes status line redraw
   5264  }
   5265 
   5266  // change background color according to NormalNC,
   5267  // but only if actually defined (otherwise no extra redraw)
   5268  if (curwin->w_hl_attr_normal != curwin->w_hl_attr_normalnc) {
   5269    // TODO(bfredl): eventually we should be smart enough
   5270    // to only recompose the window, not redraw it.
   5271    redraw_later(curwin, UPD_NOT_VALID);
   5272  }
   5273  if (prevwin) {
   5274    if (prevwin->w_hl_attr_normal != prevwin->w_hl_attr_normalnc) {
   5275      redraw_later(prevwin, UPD_NOT_VALID);
   5276    }
   5277  }
   5278 
   5279  // set window height to desired minimal value
   5280  if (curwin->w_height < p_wh && !curwin->w_p_wfh && !curwin->w_floating) {
   5281    win_setheight((int)p_wh);
   5282  } else if (curwin->w_height == 0) {
   5283    win_setheight(1);
   5284  }
   5285 
   5286  // set window width to desired minimal value
   5287  if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !curwin->w_floating) {
   5288    win_setwidth((int)p_wiw);
   5289  }
   5290 
   5291  setmouse();                   // in case jumped to/from help buffer
   5292 
   5293  // Change directories when the 'acd' option is set.
   5294  do_autochdir();
   5295 }
   5296 
   5297 /// Used after making another window the current one: change directory if needed.
   5298 void win_fix_current_dir(void)
   5299 {
   5300  // New directory is either the local directory of the window, tab or NULL.
   5301  char *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->tp_localdir;
   5302  char cwd[MAXPATHL];
   5303  if (os_dirname(cwd, MAXPATHL) != OK) {
   5304    cwd[0] = NUL;
   5305  }
   5306 
   5307  if (new_dir) {
   5308    // Window/tab has a local directory: Save current directory as global
   5309    // (unless that was done already) and change to the local directory.
   5310    if (globaldir == NULL) {
   5311      if (cwd[0] != NUL) {
   5312        globaldir = xstrdup(cwd);
   5313      }
   5314    }
   5315    bool dir_differs = pathcmp(new_dir, cwd, -1) != 0;
   5316    if (!p_acd && dir_differs) {
   5317      do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
   5318                            kCdCauseWindow, true);
   5319    }
   5320    if (os_chdir(new_dir) == 0) {
   5321      if (!p_acd && dir_differs) {
   5322        do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
   5323                              kCdCauseWindow, false);
   5324      }
   5325    }
   5326    last_chdir_reason = NULL;
   5327    shorten_fnames(true);
   5328  } else if (globaldir != NULL) {
   5329    // Window doesn't have a local directory and we are not in the global
   5330    // directory: Change to the global directory.
   5331    bool dir_differs = pathcmp(globaldir, cwd, -1) != 0;
   5332    if (!p_acd && dir_differs) {
   5333      do_autocmd_dirchanged(globaldir, kCdScopeGlobal, kCdCauseWindow, true);
   5334    }
   5335    if (os_chdir(globaldir) == 0) {
   5336      if (!p_acd && dir_differs) {
   5337        do_autocmd_dirchanged(globaldir, kCdScopeGlobal, kCdCauseWindow, false);
   5338      }
   5339    }
   5340    XFREE_CLEAR(globaldir);
   5341    last_chdir_reason = NULL;
   5342    shorten_fnames(true);
   5343  }
   5344 }
   5345 
   5346 /// Jump to the first open window that contains buffer "buf", if one exists.
   5347 /// Returns a pointer to the window found, otherwise NULL.
   5348 win_T *buf_jump_open_win(buf_T *buf)
   5349 {
   5350  if (curwin->w_buffer == buf) {
   5351    win_enter(curwin, false);
   5352    return curwin;
   5353  }
   5354  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   5355    if (wp->w_buffer == buf) {
   5356      win_enter(wp, false);
   5357      return wp;
   5358    }
   5359  }
   5360 
   5361  return NULL;
   5362 }
   5363 
   5364 /// Jump to the first open window in any tab page that contains buffer "buf",
   5365 /// if one exists. First search in the windows present in the current tab page.
   5366 /// @return the found window, or NULL.
   5367 win_T *buf_jump_open_tab(buf_T *buf)
   5368 {
   5369  // First try the current tab page.
   5370  {
   5371    win_T *wp = buf_jump_open_win(buf);
   5372    if (wp != NULL) {
   5373      return wp;
   5374    }
   5375  }
   5376 
   5377  FOR_ALL_TABS(tp) {
   5378    // Skip the current tab since we already checked it.
   5379    if (tp == curtab) {
   5380      continue;
   5381    }
   5382    FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
   5383      if (wp->w_buffer == buf) {
   5384        goto_tabpage_win(tp, wp);
   5385 
   5386        // If we the current window didn't switch,
   5387        // something went wrong.
   5388        if (curwin != wp) {
   5389          wp = NULL;
   5390        }
   5391 
   5392        // Return the window we switched to.
   5393        return wp;
   5394      }
   5395    }
   5396  }
   5397 
   5398  // If we made it this far, we didn't find the buffer.
   5399  return NULL;
   5400 }
   5401 
   5402 static int last_win_id = LOWEST_WIN_ID - 1;
   5403 
   5404 /// @param hidden  allocate a window structure and link it in the window if
   5405 //                 false.
   5406 win_T *win_alloc(win_T *after, bool hidden)
   5407 {
   5408  // allocate window structure and linesizes arrays
   5409  win_T *new_wp = xcalloc(1, sizeof(win_T));
   5410 
   5411  new_wp->handle = ++last_win_id;
   5412  pmap_put(int)(&window_handles, new_wp->handle, new_wp);
   5413 
   5414  new_wp->w_grid_alloc.mouse_enabled = true;
   5415 
   5416  grid_assign_handle(&new_wp->w_grid_alloc);
   5417 
   5418  // Init w: variables.
   5419  new_wp->w_vars = tv_dict_alloc();
   5420  init_var_dict(new_wp->w_vars, &new_wp->w_winvar, VAR_SCOPE);
   5421 
   5422  // Don't execute autocommands while the window is not properly
   5423  // initialized yet.  gui_create_scrollbar() may trigger a FocusGained
   5424  // event.
   5425  block_autocmds();
   5426  // link the window in the window list
   5427  if (!hidden) {
   5428    tabpage_T *tp = NULL;
   5429    if (after) {
   5430      tp = win_find_tabpage(after);
   5431      if (tp == curtab) {
   5432        tp = NULL;
   5433      }
   5434    }
   5435    win_append(after, new_wp, tp);
   5436  }
   5437 
   5438  new_wp->w_wincol = 0;
   5439  new_wp->w_width = Columns;
   5440 
   5441  // position the display and the cursor at the top of the file.
   5442  new_wp->w_topline = 1;
   5443  new_wp->w_topfill = 0;
   5444  new_wp->w_botline = 2;
   5445  new_wp->w_cursor.lnum = 1;
   5446  new_wp->w_scbind_pos = 1;
   5447  new_wp->w_floating = 0;
   5448  new_wp->w_config = WIN_CONFIG_INIT;
   5449  new_wp->w_viewport_invalid = true;
   5450  new_wp->w_viewport_last_topline = 1;
   5451 
   5452  new_wp->w_ns_hl = -1;
   5453 
   5454  Set(uint32_t) ns_set = SET_INIT;
   5455  new_wp->w_ns_set = ns_set;
   5456 
   5457  // use global option for global-local options
   5458  new_wp->w_allbuf_opt.wo_so = new_wp->w_p_so = -1;
   5459  new_wp->w_allbuf_opt.wo_siso = new_wp->w_p_siso = -1;
   5460 
   5461  // We won't calculate w_fraction until resizing the window
   5462  new_wp->w_fraction = 0;
   5463  new_wp->w_prev_fraction_row = -1;
   5464 
   5465  foldInitWin(new_wp);
   5466  unblock_autocmds();
   5467  new_wp->w_next_match_id = 1000;  // up to 1000 can be picked by the user
   5468  return new_wp;
   5469 }
   5470 
   5471 // Free one WinInfo.
   5472 void free_wininfo(WinInfo *wip, buf_T *bp)
   5473 {
   5474  if (wip->wi_optset) {
   5475    clear_winopt(&wip->wi_opt);
   5476    deleteFoldRecurse(bp, &wip->wi_folds);
   5477  }
   5478  xfree(wip);
   5479 }
   5480 
   5481 /// Remove window 'wp' from the window list and free the structure.
   5482 ///
   5483 /// @param tp  tab page "win" is in, NULL for current
   5484 void win_free(win_T *wp, tabpage_T *tp)
   5485 {
   5486  pmap_del(int)(&window_handles, wp->handle, NULL);
   5487  clearFolding(wp);
   5488 
   5489  // reduce the reference count to the argument list.
   5490  alist_unlink(wp->w_alist);
   5491 
   5492  // Don't execute autocommands while the window is halfway being deleted.
   5493  block_autocmds();
   5494 
   5495  set_destroy(uint32_t, &wp->w_ns_set);
   5496 
   5497  clear_winopt(&wp->w_onebuf_opt);
   5498  clear_winopt(&wp->w_allbuf_opt);
   5499 
   5500  xfree(wp->w_p_lcs_chars.multispace);
   5501  xfree(wp->w_p_lcs_chars.leadmultispace);
   5502 
   5503  vars_clear(&wp->w_vars->dv_hashtab);          // free all w: variables
   5504  hash_init(&wp->w_vars->dv_hashtab);
   5505  unref_var_dict(wp->w_vars);
   5506 
   5507  if (prevwin == wp) {
   5508    prevwin = NULL;
   5509  }
   5510  FOR_ALL_TABS(ttp) {
   5511    if (ttp->tp_prevwin == wp) {
   5512      ttp->tp_prevwin = NULL;
   5513    }
   5514  }
   5515 
   5516  xfree(wp->w_lines);
   5517 
   5518  for (int i = 0; i < wp->w_tagstacklen; i++) {
   5519    tagstack_clear_entry(&wp->w_tagstack[i]);
   5520  }
   5521 
   5522  xfree(wp->w_localdir);
   5523  xfree(wp->w_prevdir);
   5524 
   5525  stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
   5526  xfree(wp->w_status_click_defs);
   5527 
   5528  stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size);
   5529  xfree(wp->w_winbar_click_defs);
   5530 
   5531  stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size);
   5532  xfree(wp->w_statuscol_click_defs);
   5533 
   5534  // Remove the window from the b_wininfo lists, it may happen that the
   5535  // freed memory is re-used for another window.
   5536  FOR_ALL_BUFFERS(buf) {
   5537    WinInfo *wip_wp = NULL;
   5538    size_t pos_wip = kv_size(buf->b_wininfo);
   5539    size_t pos_null = kv_size(buf->b_wininfo);
   5540    for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
   5541      WinInfo *wip = kv_A(buf->b_wininfo, i);
   5542      if (wip->wi_win == wp) {
   5543        wip_wp = wip;
   5544        pos_wip = i;
   5545      } else if (wip->wi_win == NULL) {
   5546        pos_null = i;
   5547      }
   5548    }
   5549 
   5550    if (wip_wp) {
   5551      wip_wp->wi_win = NULL;
   5552      // If there already is an entry with "wi_win" set to NULL, only
   5553      // the first entry with NULL will ever be used, delete the other one.
   5554      if (pos_null < kv_size(buf->b_wininfo)) {
   5555        size_t pos_delete = MAX(pos_null, pos_wip);
   5556        free_wininfo(kv_A(buf->b_wininfo, pos_delete), buf);
   5557        kv_shift(buf->b_wininfo, pos_delete, 1);
   5558      }
   5559    }
   5560  }
   5561 
   5562  // free the border text
   5563  clear_virttext(&wp->w_config.title_chunks);
   5564  clear_virttext(&wp->w_config.footer_chunks);
   5565 
   5566  clear_matches(wp);
   5567 
   5568  free_jumplist(wp);
   5569 
   5570  qf_free_all(wp);
   5571 
   5572  xfree(wp->w_p_cc_cols);
   5573 
   5574  win_free_grid(wp, false);
   5575 
   5576  if (win_valid_any_tab(wp)) {
   5577    win_remove(wp, tp);
   5578  }
   5579  if (autocmd_busy) {
   5580    wp->w_next = au_pending_free_win;
   5581    au_pending_free_win = wp;
   5582  } else {
   5583    xfree(wp);
   5584  }
   5585 
   5586  unblock_autocmds();
   5587 }
   5588 
   5589 void win_free_grid(win_T *wp, bool reinit)
   5590 {
   5591  if (wp->w_grid_alloc.handle != 0 && ui_has(kUIMultigrid)) {
   5592    ui_call_grid_destroy(wp->w_grid_alloc.handle);
   5593  }
   5594  grid_free(&wp->w_grid_alloc);
   5595  if (reinit) {
   5596    // if a float is turned into a split, the grid data structure will be reused
   5597    CLEAR_FIELD(wp->w_grid_alloc);
   5598  }
   5599 }
   5600 
   5601 /// Append window "wp" in the window list after window "after".
   5602 ///
   5603 /// @param tp  tab page "win" (and "after", if not NULL) is in, NULL for current
   5604 void win_append(win_T *after, win_T *wp, tabpage_T *tp)
   5605  FUNC_ATTR_NONNULL_ARG(2)
   5606 {
   5607  assert(tp == NULL || tp != curtab);
   5608 
   5609  win_T **first = tp == NULL ? &firstwin : &tp->tp_firstwin;
   5610  win_T **last = tp == NULL ? &lastwin : &tp->tp_lastwin;
   5611 
   5612  // after NULL is in front of the first
   5613  win_T *before = after == NULL ? *first : after->w_next;
   5614 
   5615  wp->w_next = before;
   5616  wp->w_prev = after;
   5617  if (after == NULL) {
   5618    *first = wp;
   5619  } else {
   5620    after->w_next = wp;
   5621  }
   5622  if (before == NULL) {
   5623    *last = wp;
   5624  } else {
   5625    before->w_prev = wp;
   5626  }
   5627 }
   5628 
   5629 /// Remove a window from the window list.
   5630 ///
   5631 /// @param tp  tab page "win" is in, NULL for current
   5632 void win_remove(win_T *wp, tabpage_T *tp)
   5633  FUNC_ATTR_NONNULL_ARG(1)
   5634 {
   5635  assert(tp == NULL || tp != curtab);
   5636 
   5637  if (wp->w_prev != NULL) {
   5638    wp->w_prev->w_next = wp->w_next;
   5639  } else if (tp == NULL) {
   5640    firstwin = curtab->tp_firstwin = wp->w_next;
   5641  } else {
   5642    tp->tp_firstwin = wp->w_next;
   5643  }
   5644  if (wp->w_next != NULL) {
   5645    wp->w_next->w_prev = wp->w_prev;
   5646  } else if (tp == NULL) {
   5647    lastwin = curtab->tp_lastwin = wp->w_prev;
   5648  } else {
   5649    tp->tp_lastwin = wp->w_prev;
   5650  }
   5651 }
   5652 
   5653 // Append frame "frp" in a frame list after frame "after".
   5654 static void frame_append(frame_T *after, frame_T *frp)
   5655 {
   5656  frp->fr_next = after->fr_next;
   5657  after->fr_next = frp;
   5658  if (frp->fr_next != NULL) {
   5659    frp->fr_next->fr_prev = frp;
   5660  }
   5661  frp->fr_prev = after;
   5662 }
   5663 
   5664 // Insert frame "frp" in a frame list before frame "before".
   5665 static void frame_insert(frame_T *before, frame_T *frp)
   5666 {
   5667  frp->fr_next = before;
   5668  frp->fr_prev = before->fr_prev;
   5669  before->fr_prev = frp;
   5670  if (frp->fr_prev != NULL) {
   5671    frp->fr_prev->fr_next = frp;
   5672  } else {
   5673    frp->fr_parent->fr_child = frp;
   5674  }
   5675 }
   5676 
   5677 // Remove a frame from a frame list.
   5678 static void frame_remove(frame_T *frp)
   5679 {
   5680  if (frp->fr_prev != NULL) {
   5681    frp->fr_prev->fr_next = frp->fr_next;
   5682  } else {
   5683    frp->fr_parent->fr_child = frp->fr_next;
   5684  }
   5685  if (frp->fr_next != NULL) {
   5686    frp->fr_next->fr_prev = frp->fr_prev;
   5687  }
   5688 }
   5689 
   5690 void win_new_screensize(void)
   5691 {
   5692  static int old_Rows = 0;
   5693  static int old_Columns = 0;
   5694 
   5695  if (old_Rows != Rows) {
   5696    // If 'window' uses the whole screen, keep it using that.
   5697    // Don't change it when set with "-w size" on the command line.
   5698    if (p_window == old_Rows - 1 || (old_Rows == 0 && !option_was_set(kOptWindow))) {
   5699      p_window = Rows - 1;
   5700    }
   5701    old_Rows = Rows;
   5702    win_new_screen_rows();  // update window sizes
   5703  }
   5704  if (old_Columns != Columns) {
   5705    old_Columns = Columns;
   5706    win_new_screen_cols();  // update window sizes
   5707  }
   5708 }
   5709 /// Called from win_new_screensize() after Rows changed.
   5710 ///
   5711 /// This only does the current tab page, others must be done when made active.
   5712 void win_new_screen_rows(void)
   5713 {
   5714  if (firstwin == NULL) {       // not initialized yet
   5715    return;
   5716  }
   5717  int h = MAX((int)ROWS_AVAIL, frame_minheight(topframe, NULL));
   5718 
   5719  // First try setting the heights of windows with 'winfixheight'.  If
   5720  // that doesn't result in the right height, forget about that option.
   5721  frame_new_height(topframe, h, false, true, false);
   5722  if (!frame_check_height(topframe, h)) {
   5723    frame_new_height(topframe, h, false, false, false);
   5724  }
   5725 
   5726  win_comp_pos();  // recompute w_winrow and w_wincol
   5727  win_reconfig_floats();  // The size of floats might change
   5728  compute_cmdrow();
   5729  curtab->tp_ch_used = p_ch;
   5730 
   5731  if (!skip_win_fix_scroll) {
   5732    win_fix_scroll(true);
   5733  }
   5734 }
   5735 
   5736 /// Called from win_new_screensize() after Columns changed.
   5737 void win_new_screen_cols(void)
   5738 {
   5739  if (firstwin == NULL) {       // not initialized yet
   5740    return;
   5741  }
   5742 
   5743  // First try setting the widths of windows with 'winfixwidth'.  If that
   5744  // doesn't result in the right width, forget about that option.
   5745  frame_new_width(topframe, Columns, false, true);
   5746  if (!frame_check_width(topframe, Columns)) {
   5747    frame_new_width(topframe, Columns, false, false);
   5748  }
   5749 
   5750  win_comp_pos();  // recompute w_winrow and w_wincol
   5751  win_reconfig_floats();  // The size of floats might change
   5752 }
   5753 
   5754 /// Make a snapshot of all the window scroll positions and sizes of the current
   5755 /// tab page.
   5756 void snapshot_windows_scroll_size(void)
   5757 {
   5758  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   5759    wp->w_last_topline = wp->w_topline;
   5760    wp->w_last_topfill = wp->w_topfill;
   5761    wp->w_last_leftcol = wp->w_leftcol;
   5762    wp->w_last_skipcol = wp->w_skipcol;
   5763    wp->w_last_width = wp->w_width;
   5764    wp->w_last_height = wp->w_height;
   5765  }
   5766 }
   5767 
   5768 static bool did_initial_scroll_size_snapshot = false;
   5769 
   5770 void may_make_initial_scroll_size_snapshot(void)
   5771 {
   5772  if (!did_initial_scroll_size_snapshot) {
   5773    did_initial_scroll_size_snapshot = true;
   5774    snapshot_windows_scroll_size();
   5775  }
   5776 }
   5777 
   5778 /// Create a dictionary with information about size and scroll changes in a
   5779 /// window.
   5780 /// Returns the dictionary with refcount set to one.
   5781 /// Returns NULL on internal error.
   5782 static dict_T *make_win_info_dict(int width, int height, int topline, int topfill, int leftcol,
   5783                                  int skipcol)
   5784 {
   5785  dict_T *const d = tv_dict_alloc();
   5786  d->dv_refcount = 1;
   5787 
   5788  // not actually looping, for breaking out on error
   5789  while (true) {
   5790    typval_T tv = {
   5791      .v_lock = VAR_UNLOCKED,
   5792      .v_type = VAR_NUMBER,
   5793    };
   5794 
   5795    tv.vval.v_number = width;
   5796    if (tv_dict_add_tv(d, S_LEN("width"), &tv) == FAIL) {
   5797      break;
   5798    }
   5799    tv.vval.v_number = height;
   5800    if (tv_dict_add_tv(d, S_LEN("height"), &tv) == FAIL) {
   5801      break;
   5802    }
   5803    tv.vval.v_number = topline;
   5804    if (tv_dict_add_tv(d, S_LEN("topline"), &tv) == FAIL) {
   5805      break;
   5806    }
   5807    tv.vval.v_number = topfill;
   5808    if (tv_dict_add_tv(d, S_LEN("topfill"), &tv) == FAIL) {
   5809      break;
   5810    }
   5811    tv.vval.v_number = leftcol;
   5812    if (tv_dict_add_tv(d, S_LEN("leftcol"), &tv) == FAIL) {
   5813      break;
   5814    }
   5815    tv.vval.v_number = skipcol;
   5816    if (tv_dict_add_tv(d, S_LEN("skipcol"), &tv) == FAIL) {
   5817      break;
   5818    }
   5819    return d;
   5820  }
   5821  tv_dict_unref(d);
   5822  return NULL;
   5823 }
   5824 
   5825 /// This function is used for three purposes:
   5826 /// 1. Goes over all windows in the current tab page and sets:
   5827 ///    "size_count" to the nr of windows with size changes.
   5828 ///    "first_scroll_win" to the first window with any relevant changes.
   5829 ///    "first_size_win" to the first window with size changes.
   5830 ///
   5831 /// 2. When the first three arguments are NULL and "winlist" is not NULL,
   5832 ///    "winlist" is set to the list of window IDs with size changes.
   5833 ///
   5834 /// 3. When the first three arguments are NULL and "v_event" is not NULL,
   5835 ///    information about changed windows is added to "v_event".
   5836 static void check_window_scroll_resize(int *size_count, win_T **first_scroll_win,
   5837                                       win_T **first_size_win, list_T *winlist, dict_T *v_event)
   5838 {
   5839  // int listidx = 0;
   5840  int tot_width = 0;
   5841  int tot_height = 0;
   5842  int tot_topline = 0;
   5843  int tot_topfill = 0;
   5844  int tot_leftcol = 0;
   5845  int tot_skipcol = 0;
   5846 
   5847  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   5848    // Skip floating windows that do not have a snapshot (usually because they are newly-created),
   5849    // as unlike split windows, creating floating windows doesn't cause other windows to resize.
   5850    if (wp->w_floating && wp->w_last_topline == 0) {
   5851      wp->w_last_topline = wp->w_topline;
   5852      wp->w_last_topfill = wp->w_topfill;
   5853      wp->w_last_leftcol = wp->w_leftcol;
   5854      wp->w_last_skipcol = wp->w_skipcol;
   5855      wp->w_last_width = wp->w_width;
   5856      wp->w_last_height = wp->w_height;
   5857      continue;
   5858    }
   5859 
   5860    const bool ignore_scroll = event_ignored(EVENT_WINSCROLLED, wp->w_p_eiw);
   5861    const bool size_changed = !event_ignored(EVENT_WINRESIZED, wp->w_p_eiw)
   5862                              && (wp->w_last_width != wp->w_width
   5863                                  || wp->w_last_height != wp->w_height);
   5864    if (size_changed) {
   5865      if (winlist != NULL) {
   5866        // Add this window to the list of changed windows.
   5867        typval_T tv = {
   5868          .v_lock = VAR_UNLOCKED,
   5869          .v_type = VAR_NUMBER,
   5870          .vval.v_number = wp->handle,
   5871        };
   5872        // tv_list_set_item(winlist, listidx++, &tv);
   5873        tv_list_append_owned_tv(winlist, tv);
   5874      } else if (size_count != NULL) {
   5875        assert(first_size_win != NULL && first_scroll_win != NULL);
   5876        (*size_count)++;
   5877        if (*first_size_win == NULL) {
   5878          *first_size_win = wp;
   5879        }
   5880        // For WinScrolled the first window with a size change is used
   5881        // even when it didn't scroll.
   5882        if (*first_scroll_win == NULL && !ignore_scroll) {
   5883          *first_scroll_win = wp;
   5884        }
   5885      }
   5886    }
   5887 
   5888    const bool scroll_changed = !ignore_scroll
   5889                                && (wp->w_last_topline != wp->w_topline
   5890                                    || wp->w_last_topfill != wp->w_topfill
   5891                                    || wp->w_last_leftcol != wp->w_leftcol
   5892                                    || wp->w_last_skipcol != wp->w_skipcol);
   5893    if (scroll_changed && first_scroll_win != NULL && *first_scroll_win == NULL) {
   5894      *first_scroll_win = wp;
   5895    }
   5896 
   5897    if ((size_changed || scroll_changed) && v_event != NULL) {
   5898      // Add info about this window to the v:event dictionary.
   5899      int width = wp->w_width - wp->w_last_width;
   5900      int height = wp->w_height - wp->w_last_height;
   5901      int topline = wp->w_topline - wp->w_last_topline;
   5902      int topfill = wp->w_topfill - wp->w_last_topfill;
   5903      int leftcol = wp->w_leftcol - wp->w_last_leftcol;
   5904      int skipcol = wp->w_skipcol - wp->w_last_skipcol;
   5905      dict_T *d = make_win_info_dict(width, height, topline,
   5906                                     topfill, leftcol, skipcol);
   5907      if (d == NULL) {
   5908        break;
   5909      }
   5910      char winid[NUMBUFLEN];
   5911      int key_len = vim_snprintf(winid, sizeof(winid), "%d", wp->handle);
   5912      if (tv_dict_add_dict(v_event, winid, (size_t)key_len, d) == FAIL) {
   5913        tv_dict_unref(d);
   5914        break;
   5915      }
   5916      d->dv_refcount--;
   5917 
   5918      tot_width += abs(width);
   5919      tot_height += abs(height);
   5920      tot_topline += abs(topline);
   5921      tot_topfill += abs(topfill);
   5922      tot_leftcol += abs(leftcol);
   5923      tot_skipcol += abs(skipcol);
   5924    }
   5925  }
   5926 
   5927  if (v_event != NULL) {
   5928    dict_T *alldict = make_win_info_dict(tot_width, tot_height, tot_topline,
   5929                                         tot_topfill, tot_leftcol, tot_skipcol);
   5930    if (alldict != NULL) {
   5931      if (tv_dict_add_dict(v_event, S_LEN("all"), alldict) == FAIL) {
   5932        tv_dict_unref(alldict);
   5933      } else {
   5934        alldict->dv_refcount--;
   5935      }
   5936    }
   5937  }
   5938 }
   5939 
   5940 /// Trigger WinScrolled and/or WinResized if any window in the current tab page
   5941 /// scrolled or changed size.
   5942 void may_trigger_win_scrolled_resized(void)
   5943 {
   5944  static bool recursive = false;
   5945  const bool do_resize = has_event(EVENT_WINRESIZED);
   5946  const bool do_scroll = has_event(EVENT_WINSCROLLED);
   5947 
   5948  if (recursive
   5949      || !(do_scroll || do_resize)
   5950      || !did_initial_scroll_size_snapshot) {
   5951    return;
   5952  }
   5953 
   5954  int size_count = 0;
   5955  win_T *first_scroll_win = NULL;
   5956  win_T *first_size_win = NULL;
   5957  check_window_scroll_resize(&size_count, &first_scroll_win, &first_size_win, NULL, NULL);
   5958  bool trigger_resize = do_resize && size_count > 0;
   5959  bool trigger_scroll = do_scroll && first_scroll_win != NULL;
   5960  if (!trigger_resize && !trigger_scroll) {
   5961    return;  // no relevant changes
   5962  }
   5963 
   5964  list_T *windows_list = NULL;
   5965  if (trigger_resize) {
   5966    // Create the list for v:event.windows before making the snapshot.
   5967    // windows_list = tv_list_alloc_with_items(size_count);
   5968    windows_list = tv_list_alloc(size_count);
   5969    check_window_scroll_resize(NULL, NULL, NULL, windows_list, NULL);
   5970  }
   5971 
   5972  dict_T *scroll_dict = NULL;
   5973  if (trigger_scroll) {
   5974    // Create the dict with entries for v:event before making the snapshot.
   5975    scroll_dict = tv_dict_alloc();
   5976    scroll_dict->dv_refcount = 1;
   5977    check_window_scroll_resize(NULL, NULL, NULL, NULL, scroll_dict);
   5978  }
   5979 
   5980  // WinScrolled/WinResized are triggered only once, even when multiple
   5981  // windows scrolled or changed size.  Store the current values before
   5982  // triggering the event, if a scroll or resize happens as a side effect
   5983  // then WinScrolled/WinResized is triggered for that later.
   5984  snapshot_windows_scroll_size();
   5985 
   5986  recursive = true;
   5987 
   5988  // Save window info before autocmds since they can free windows
   5989  char resize_winid[NUMBUFLEN];
   5990  bufref_T resize_bufref;
   5991  if (trigger_resize) {
   5992    vim_snprintf(resize_winid, sizeof(resize_winid), "%d", first_size_win->handle);
   5993    set_bufref(&resize_bufref, first_size_win->w_buffer);
   5994  }
   5995 
   5996  char scroll_winid[NUMBUFLEN];
   5997  bufref_T scroll_bufref;
   5998  if (trigger_scroll) {
   5999    vim_snprintf(scroll_winid, sizeof(scroll_winid), "%d", first_scroll_win->handle);
   6000    set_bufref(&scroll_bufref, first_scroll_win->w_buffer);
   6001  }
   6002 
   6003  // If both are to be triggered do WinResized first.
   6004  if (trigger_resize) {
   6005    save_v_event_T save_v_event;
   6006    dict_T *v_event = get_v_event(&save_v_event);
   6007 
   6008    if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) {
   6009      tv_dict_set_keys_readonly(v_event);
   6010      buf_T *buf = bufref_valid(&resize_bufref) ? resize_bufref.br_buf : curbuf;
   6011      apply_autocmds(EVENT_WINRESIZED, resize_winid, resize_winid, false, buf);
   6012    }
   6013    restore_v_event(v_event, &save_v_event);
   6014  }
   6015 
   6016  if (trigger_scroll) {
   6017    save_v_event_T save_v_event;
   6018    dict_T *v_event = get_v_event(&save_v_event);
   6019 
   6020    // Move the entries from scroll_dict to v_event.
   6021    tv_dict_extend(v_event, scroll_dict, "move");
   6022    tv_dict_set_keys_readonly(v_event);
   6023    tv_dict_unref(scroll_dict);
   6024 
   6025    buf_T *buf = bufref_valid(&scroll_bufref) ? scroll_bufref.br_buf : curbuf;
   6026    apply_autocmds(EVENT_WINSCROLLED, scroll_winid, scroll_winid, false, buf);
   6027 
   6028    restore_v_event(v_event, &save_v_event);
   6029  }
   6030 
   6031  recursive = false;
   6032 }
   6033 
   6034 // Save the size of all windows in "gap".
   6035 void win_size_save(garray_T *gap)
   6036 {
   6037  ga_init(gap, (int)sizeof(int), 1);
   6038  ga_grow(gap, win_count() * 2 + 1);
   6039  // first entry is the total lines available for windows
   6040  ((int *)gap->ga_data)[gap->ga_len++] =
   6041    (int)ROWS_AVAIL + global_stl_height() - last_stl_height(false);
   6042 
   6043  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   6044    ((int *)gap->ga_data)[gap->ga_len++] =
   6045      wp->w_width + wp->w_vsep_width;
   6046    ((int *)gap->ga_data)[gap->ga_len++] = wp->w_height;
   6047  }
   6048 }
   6049 
   6050 // Restore window sizes, but only if the number of windows is still the same
   6051 // and total lines available for windows didn't change.
   6052 // Does not free the growarray.
   6053 void win_size_restore(garray_T *gap)
   6054  FUNC_ATTR_NONNULL_ALL
   6055 {
   6056  if (win_count() * 2 + 1 == gap->ga_len
   6057      && ((int *)gap->ga_data)[0] ==
   6058      ROWS_AVAIL + global_stl_height() - last_stl_height(false)) {
   6059    // The order matters, because frames contain other frames, but it's
   6060    // difficult to get right. The easy way out is to do it twice.
   6061    for (int j = 0; j < 2; j++) {
   6062      int i = 1;
   6063      FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   6064        int width = ((int *)gap->ga_data)[i++];
   6065        int height = ((int *)gap->ga_data)[i++];
   6066        if (!wp->w_floating) {
   6067          frame_setwidth(wp->w_frame, width);
   6068          win_setheight_win(height, wp);
   6069        }
   6070      }
   6071    }
   6072    // recompute the window positions
   6073    win_comp_pos();
   6074  }
   6075 }
   6076 
   6077 // Update the position for all windows, using the width and height of the frames.
   6078 // Returns the row just after the last window and global statusline (if there is one).
   6079 int win_comp_pos(void)
   6080 {
   6081  int row = tabline_height();
   6082  int col = 0;
   6083 
   6084  frame_comp_pos(topframe, &row, &col);
   6085 
   6086  for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
   6087    // float might be anchored to moved window
   6088    if (wp->w_config.relative == kFloatRelativeWindow) {
   6089      wp->w_pos_changed = true;
   6090    }
   6091  }
   6092 
   6093  return row + global_stl_height();
   6094 }
   6095 
   6096 // Update the position of the windows in frame "topfrp", using the width and
   6097 // height of the frames.
   6098 // "*row" and "*col" are the top-left position of the frame.  They are updated
   6099 // to the bottom-right position plus one.
   6100 static void frame_comp_pos(frame_T *topfrp, int *row, int *col)
   6101 {
   6102  win_T *wp = topfrp->fr_win;
   6103  if (wp != NULL) {
   6104    if (wp->w_winrow != *row
   6105        || wp->w_wincol != *col) {
   6106      // position changed, redraw
   6107      wp->w_winrow = *row;
   6108      wp->w_wincol = *col;
   6109      redraw_later(wp, UPD_NOT_VALID);
   6110      wp->w_redr_status = true;
   6111      wp->w_pos_changed = true;
   6112    }
   6113    const int h = wp->w_height + wp->w_hsep_height + wp->w_status_height;
   6114    *row += h > topfrp->fr_height ? topfrp->fr_height : h;
   6115    *col += wp->w_width + wp->w_vsep_width;
   6116  } else {
   6117    int startrow = *row;
   6118    int startcol = *col;
   6119    frame_T *frp;
   6120    FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   6121      if (topfrp->fr_layout == FR_ROW) {
   6122        *row = startrow;  // all frames are at the same row
   6123      } else {
   6124        *col = startcol;  // all frames are at the same col
   6125      }
   6126      frame_comp_pos(frp, row, col);
   6127    }
   6128  }
   6129 }
   6130 
   6131 // Set current window height and take care of repositioning other windows to
   6132 // fit around it.
   6133 void win_setheight(int height)
   6134 {
   6135  win_setheight_win(height, curwin);
   6136 }
   6137 
   6138 // Set the window height of window "win" and take care of repositioning other
   6139 // windows to fit around it.
   6140 void win_setheight_win(int height, win_T *win)
   6141 {
   6142  // Always keep current window at least one line high, even when 'winminheight' is zero.
   6143  // Keep window at least two lines high if 'winbar' is enabled.
   6144  height = MAX(height, (int)(win == curwin ? MAX(p_wmh, 1) : p_wmh) + win->w_winbar_height);
   6145 
   6146  if (win->w_floating) {
   6147    win->w_config.height = MAX(height, 1);
   6148    win_config_float(win, win->w_config);
   6149    redraw_later(win, UPD_VALID);
   6150  } else {
   6151    frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height);
   6152 
   6153    // recompute the window positions
   6154    win_comp_pos();
   6155    win_fix_scroll(true);
   6156 
   6157    redraw_all_later(UPD_NOT_VALID);
   6158    redraw_cmdline = true;
   6159  }
   6160 }
   6161 
   6162 // Set the height of a frame to "height" and take care that all frames and
   6163 // windows inside it are resized.  Also resize frames on the left and right if
   6164 // the are in the same FR_ROW frame.
   6165 //
   6166 // Strategy:
   6167 // If the frame is part of a FR_COL frame, try fitting the frame in that
   6168 // frame.  If that doesn't work (the FR_COL frame is too small), recursively
   6169 // go to containing frames to resize them and make room.
   6170 // If the frame is part of a FR_ROW frame, all frames must be resized as well.
   6171 // Check for the minimal height of the FR_ROW frame.
   6172 // At the top level we can also use change the command line height.
   6173 static void frame_setheight(frame_T *curfrp, int height)
   6174 {
   6175  // If the height already is the desired value, nothing to do.
   6176  if (curfrp->fr_height == height) {
   6177    return;
   6178  }
   6179 
   6180  if (curfrp->fr_parent == NULL) {
   6181    // topframe: can only change the command line height
   6182    if (height > 0) {
   6183      frame_new_height(curfrp, height, false, false, true);
   6184    }
   6185  } else if (curfrp->fr_parent->fr_layout == FR_ROW) {
   6186    // Row of frames: Also need to resize frames left and right of this
   6187    // one.  First check for the minimal height of these.
   6188    int h = frame_minheight(curfrp->fr_parent, NULL);
   6189    height = MAX(height, h);
   6190    frame_setheight(curfrp->fr_parent, height);
   6191  } else {
   6192    // Column of frames: try to change only frames in this column.
   6193 
   6194    int room;                     // total number of lines available
   6195    int room_cmdline;             // lines available from cmdline
   6196    int room_reserved;
   6197 
   6198    // Do this twice:
   6199    // 1: compute room available, if it's not enough try resizing the
   6200    //    containing frame.
   6201    // 2: compute the room available and adjust the height to it.
   6202    // Try not to reduce the height of a window with 'winfixheight' set.
   6203    for (int run = 1; run <= 2; run++) {
   6204      room = 0;
   6205      room_reserved = 0;
   6206      frame_T *frp;
   6207      FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) {
   6208        if (frp != curfrp
   6209            && frp->fr_win != NULL
   6210            && frp->fr_win->w_p_wfh) {
   6211          room_reserved += frp->fr_height;
   6212        }
   6213        room += frp->fr_height;
   6214        if (frp != curfrp) {
   6215          room -= frame_minheight(frp, NULL);
   6216        }
   6217      }
   6218      if (curfrp->fr_width != Columns) {
   6219        room_cmdline = 0;
   6220      } else {
   6221        win_T *wp = lastwin_nofloating();
   6222        room_cmdline = Rows - (int)p_ch - global_stl_height()
   6223                       - (wp->w_winrow + wp->w_height + wp->w_hsep_height + wp->w_status_height);
   6224        room_cmdline = MAX(room_cmdline, 0);
   6225      }
   6226 
   6227      if (height <= room + room_cmdline) {
   6228        break;
   6229      }
   6230      if (run == 2 || curfrp->fr_width == Columns) {
   6231        height = room + room_cmdline;
   6232        break;
   6233      }
   6234      frame_setheight(curfrp->fr_parent, height
   6235                      + frame_minheight(curfrp->fr_parent, NOWIN) - (int)p_wmh - 1);
   6236      // NOTREACHED
   6237    }
   6238 
   6239    // Compute the number of lines we will take from others frames (can be
   6240    // negative!).
   6241    int take = height - curfrp->fr_height;
   6242 
   6243    // If there is not enough room, also reduce the height of a window
   6244    // with 'winfixheight' set.
   6245    if (height > room + room_cmdline - room_reserved) {
   6246      room_reserved = room + room_cmdline - height;
   6247    }
   6248    // If there is only a 'winfixheight' window and making the
   6249    // window smaller, need to make the other window taller.
   6250    if (take < 0 && room - curfrp->fr_height < room_reserved) {
   6251      room_reserved = 0;
   6252    }
   6253 
   6254    if (take > 0 && room_cmdline > 0) {
   6255      // use lines from cmdline first
   6256      room_cmdline = MIN(room_cmdline, take),
   6257      take -= room_cmdline;
   6258      topframe->fr_height += room_cmdline;
   6259    }
   6260 
   6261    // set the current frame to the new height
   6262    frame_new_height(curfrp, height, false, false, true);
   6263 
   6264    // First take lines from the frames after the current frame.  If
   6265    // that is not enough, takes lines from frames above the current
   6266    // frame.
   6267    for (int run = 0; run < 2; run++) {
   6268      // 1st run: start with next window
   6269      // 2nd run: start with prev window
   6270      frame_T *frp = run == 0 ? curfrp->fr_next : curfrp->fr_prev;
   6271 
   6272      while (frp != NULL && take != 0) {
   6273        int h = frame_minheight(frp, NULL);
   6274        if (room_reserved > 0
   6275            && frp->fr_win != NULL
   6276            && frp->fr_win->w_p_wfh) {
   6277          if (room_reserved >= frp->fr_height) {
   6278            room_reserved -= frp->fr_height;
   6279          } else {
   6280            if (frp->fr_height - room_reserved > take) {
   6281              room_reserved = frp->fr_height - take;
   6282            }
   6283            take -= frp->fr_height - room_reserved;
   6284            frame_new_height(frp, room_reserved, false, false, true);
   6285            room_reserved = 0;
   6286          }
   6287        } else {
   6288          if (frp->fr_height - take < h) {
   6289            take -= frp->fr_height - h;
   6290            frame_new_height(frp, h, false, false, true);
   6291          } else {
   6292            frame_new_height(frp, frp->fr_height - take, false, false, true);
   6293            take = 0;
   6294          }
   6295        }
   6296        if (run == 0) {
   6297          frp = frp->fr_next;
   6298        } else {
   6299          frp = frp->fr_prev;
   6300        }
   6301      }
   6302    }
   6303  }
   6304 }
   6305 
   6306 // Set current window width and take care of repositioning other windows to
   6307 // fit around it.
   6308 void win_setwidth(int width)
   6309 {
   6310  win_setwidth_win(width, curwin);
   6311 }
   6312 
   6313 void win_setwidth_win(int width, win_T *wp)
   6314 {
   6315  // Always keep current window at least one column wide, even when
   6316  // 'winminwidth' is zero.
   6317  if (wp == curwin) {
   6318    width = MAX(MAX(width, (int)p_wmw), 1);
   6319  } else if (width < 0) {
   6320    width = 0;
   6321  }
   6322  if (wp->w_floating) {
   6323    wp->w_config.width = width;
   6324    win_config_float(wp, wp->w_config);
   6325    redraw_later(wp, UPD_NOT_VALID);
   6326  } else {
   6327    frame_setwidth(wp->w_frame, width + wp->w_vsep_width);
   6328 
   6329    // recompute the window positions
   6330    win_comp_pos();
   6331    redraw_all_later(UPD_NOT_VALID);
   6332  }
   6333 }
   6334 
   6335 // Set the width of a frame to "width" and take care that all frames and
   6336 // windows inside it are resized.  Also resize frames above and below if the
   6337 // are in the same FR_ROW frame.
   6338 //
   6339 // Strategy is similar to frame_setheight().
   6340 static void frame_setwidth(frame_T *curfrp, int width)
   6341 {
   6342  // If the width already is the desired value, nothing to do.
   6343  if (curfrp->fr_width == width) {
   6344    return;
   6345  }
   6346 
   6347  if (curfrp->fr_parent == NULL) {
   6348    // topframe: can't change width
   6349    return;
   6350  }
   6351 
   6352  if (curfrp->fr_parent->fr_layout == FR_COL) {
   6353    // Column of frames: Also need to resize frames above and below of
   6354    // this one.  First check for the minimal width of these.
   6355    int w = frame_minwidth(curfrp->fr_parent, NULL);
   6356    width = MAX(width, w);
   6357    frame_setwidth(curfrp->fr_parent, width);
   6358  } else {
   6359    // Row of frames: try to change only frames in this row.
   6360    //
   6361    // Do this twice:
   6362    // 1: compute room available, if it's not enough try resizing the
   6363    //    containing frame.
   6364    // 2: compute the room available and adjust the width to it.
   6365 
   6366    int room;  // total number of lines available
   6367    int room_reserved;
   6368    for (int run = 1; run <= 2; run++) {
   6369      room = 0;
   6370      room_reserved = 0;
   6371      frame_T *frp;
   6372      FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) {
   6373        if (frp != curfrp
   6374            && frp->fr_win != NULL
   6375            && frp->fr_win->w_p_wfw) {
   6376          room_reserved += frp->fr_width;
   6377        }
   6378        room += frp->fr_width;
   6379        if (frp != curfrp) {
   6380          room -= frame_minwidth(frp, NULL);
   6381        }
   6382      }
   6383 
   6384      if (width <= room) {
   6385        break;
   6386      }
   6387      if (run == 2 || curfrp->fr_height >= ROWS_AVAIL) {
   6388        width = room;
   6389        break;
   6390      }
   6391      frame_setwidth(curfrp->fr_parent, width
   6392                     + frame_minwidth(curfrp->fr_parent, NOWIN) - (int)p_wmw - 1);
   6393    }
   6394 
   6395    // Compute the number of lines we will take from others frames (can be
   6396    // negative!).
   6397    int take = width - curfrp->fr_width;
   6398 
   6399    // If there is not enough room, also reduce the width of a window
   6400    // with 'winfixwidth' set.
   6401    if (width > room - room_reserved) {
   6402      room_reserved = room - width;
   6403    }
   6404    // If there is only a 'winfixwidth' window and making the
   6405    // window smaller, need to make the other window narrower.
   6406    if (take < 0 && room - curfrp->fr_width < room_reserved) {
   6407      room_reserved = 0;
   6408    }
   6409 
   6410    // set the current frame to the new width
   6411    frame_new_width(curfrp, width, false, false);
   6412 
   6413    // First take lines from the frames right of the current frame.  If
   6414    // that is not enough, takes lines from frames left of the current
   6415    // frame.
   6416    for (int run = 0; run < 2; run++) {
   6417      // 1st run: start with next window
   6418      // 2nd run: start with prev window
   6419      frame_T *frp = run == 0 ? curfrp->fr_next : curfrp->fr_prev;
   6420 
   6421      while (frp != NULL && take != 0) {
   6422        int w = frame_minwidth(frp, NULL);
   6423        if (room_reserved > 0
   6424            && frp->fr_win != NULL
   6425            && frp->fr_win->w_p_wfw) {
   6426          if (room_reserved >= frp->fr_width) {
   6427            room_reserved -= frp->fr_width;
   6428          } else {
   6429            if (frp->fr_width - room_reserved > take) {
   6430              room_reserved = frp->fr_width - take;
   6431            }
   6432            take -= frp->fr_width - room_reserved;
   6433            frame_new_width(frp, room_reserved, false, false);
   6434            room_reserved = 0;
   6435          }
   6436        } else {
   6437          if (frp->fr_width - take < w) {
   6438            take -= frp->fr_width - w;
   6439            frame_new_width(frp, w, false, false);
   6440          } else {
   6441            frame_new_width(frp, frp->fr_width - take, false, false);
   6442            take = 0;
   6443          }
   6444        }
   6445        if (run == 0) {
   6446          frp = frp->fr_next;
   6447        } else {
   6448          frp = frp->fr_prev;
   6449        }
   6450      }
   6451    }
   6452  }
   6453 }
   6454 
   6455 // Check 'winminheight' for a valid value and reduce it if needed.
   6456 const char *did_set_winminheight(optset_T *args FUNC_ATTR_UNUSED)
   6457 {
   6458  bool first = true;
   6459 
   6460  // loop until there is a 'winminheight' that is possible
   6461  while (p_wmh > 0) {
   6462    const int room = Rows - (int)p_ch;
   6463    const int needed = min_rows_for_all_tabpages();
   6464    if (room >= needed) {
   6465      break;
   6466    }
   6467    p_wmh--;
   6468    if (first) {
   6469      emsg(_(e_noroom));
   6470      first = false;
   6471    }
   6472  }
   6473  return NULL;
   6474 }
   6475 
   6476 // Check 'winminwidth' for a valid value and reduce it if needed.
   6477 const char *did_set_winminwidth(optset_T *args FUNC_ATTR_UNUSED)
   6478 {
   6479  bool first = true;
   6480 
   6481  // loop until there is a 'winminheight' that is possible
   6482  while (p_wmw > 0) {
   6483    const int room = Columns;
   6484    const int needed = frame_minwidth(topframe, NULL);
   6485    if (room >= needed) {
   6486      break;
   6487    }
   6488    p_wmw--;
   6489    if (first) {
   6490      emsg(_(e_noroom));
   6491      first = false;
   6492    }
   6493  }
   6494  return NULL;
   6495 }
   6496 
   6497 /// Status line of dragwin is dragged "offset" lines down (negative is up).
   6498 void win_drag_status_line(win_T *dragwin, int offset)
   6499 {
   6500  frame_T *fr = dragwin->w_frame;
   6501  frame_T *curfr = fr;
   6502  if (fr != topframe) {         // more than one window
   6503    fr = fr->fr_parent;
   6504    // When the parent frame is not a column of frames, its parent should
   6505    // be.
   6506    if (fr->fr_layout != FR_COL) {
   6507      curfr = fr;
   6508      if (fr != topframe) {     // only a row of windows, may drag statusline
   6509        fr = fr->fr_parent;
   6510      }
   6511    }
   6512  }
   6513 
   6514  // If this is the last frame in a column, may want to resize the parent
   6515  // frame instead (go two up to skip a row of frames).
   6516  while (curfr != topframe && curfr->fr_next == NULL) {
   6517    if (fr != topframe) {
   6518      fr = fr->fr_parent;
   6519    }
   6520    curfr = fr;
   6521    if (fr != topframe) {
   6522      fr = fr->fr_parent;
   6523    }
   6524  }
   6525 
   6526  int room;
   6527  const bool up = offset < 0;  // if true, drag status line up, otherwise down
   6528 
   6529  if (up) {  // drag up
   6530    offset = -offset;
   6531    // sum up the room of the current frame and above it
   6532    if (fr == curfr) {
   6533      // only one window
   6534      room = fr->fr_height - frame_minheight(fr, NULL);
   6535    } else {
   6536      room = 0;
   6537      for (fr = fr->fr_child;; fr = fr->fr_next) {
   6538        room += fr->fr_height - frame_minheight(fr, NULL);
   6539        if (fr == curfr) {
   6540          break;
   6541        }
   6542      }
   6543    }
   6544    fr = curfr->fr_next;                // put fr at frame that grows
   6545  } else {  // drag down
   6546    // Only dragging the last status line can reduce p_ch.
   6547    room = Rows - cmdline_row;
   6548    if (curfr->fr_next != NULL) {
   6549      room -= (int)p_ch + global_stl_height();
   6550    } else if (min_set_ch > 0) {
   6551      room--;
   6552    }
   6553    room = MAX(room, 0);
   6554    // sum up the room of frames below of the current one
   6555    FOR_ALL_FRAMES(fr, curfr->fr_next) {
   6556      room += fr->fr_height - frame_minheight(fr, NULL);
   6557    }
   6558    fr = curfr;  // put fr at window that grows
   6559  }
   6560 
   6561  // If not enough room then move as far as we can
   6562  offset = MIN(offset, room);
   6563  if (offset <= 0) {
   6564    return;
   6565  }
   6566 
   6567  // Grow frame fr by "offset" lines.
   6568  // Doesn't happen when dragging the last status line up.
   6569  if (fr != NULL) {
   6570    frame_new_height(fr, fr->fr_height + offset, up, false, true);
   6571  }
   6572 
   6573  if (up) {
   6574    fr = curfr;                 // current frame gets smaller
   6575  } else {
   6576    fr = curfr->fr_next;        // next frame gets smaller
   6577  }
   6578  // Now make the other frames smaller.
   6579  while (fr != NULL && offset > 0) {
   6580    int n = frame_minheight(fr, NULL);
   6581    if (fr->fr_height - offset <= n) {
   6582      offset -= fr->fr_height - n;
   6583      frame_new_height(fr, n, !up, false, true);
   6584    } else {
   6585      frame_new_height(fr, fr->fr_height - offset, !up, false, true);
   6586      break;
   6587    }
   6588    if (up) {
   6589      fr = fr->fr_prev;
   6590    } else {
   6591      fr = fr->fr_next;
   6592    }
   6593  }
   6594  win_comp_pos();
   6595  win_fix_scroll(true);
   6596 
   6597  redraw_all_later(UPD_SOME_VALID);
   6598  showmode();
   6599 }
   6600 
   6601 // Separator line of dragwin is dragged "offset" lines right (negative is left).
   6602 void win_drag_vsep_line(win_T *dragwin, int offset)
   6603 {
   6604  frame_T *fr = dragwin->w_frame;
   6605  if (fr == topframe) {         // only one window (cannot happen?)
   6606    return;
   6607  }
   6608  frame_T *curfr = fr;
   6609  fr = fr->fr_parent;
   6610  // When the parent frame is not a row of frames, its parent should be.
   6611  if (fr->fr_layout != FR_ROW) {
   6612    if (fr == topframe) {       // only a column of windows (cannot happen?)
   6613      return;
   6614    }
   6615    curfr = fr;
   6616    fr = fr->fr_parent;
   6617  }
   6618 
   6619  // If this is the last frame in a row, may want to resize a parent
   6620  // frame instead.
   6621  while (curfr->fr_next == NULL) {
   6622    if (fr == topframe) {
   6623      break;
   6624    }
   6625    curfr = fr;
   6626    fr = fr->fr_parent;
   6627    if (fr != topframe) {
   6628      curfr = fr;
   6629      fr = fr->fr_parent;
   6630    }
   6631  }
   6632 
   6633  int room;
   6634  const bool left = offset < 0;  // if true, drag separator line left, otherwise right
   6635 
   6636  if (left) {  // drag left
   6637    offset = -offset;
   6638    // sum up the room of the current frame and left of it
   6639    room = 0;
   6640    for (fr = fr->fr_child;; fr = fr->fr_next) {
   6641      room += fr->fr_width - frame_minwidth(fr, NULL);
   6642      if (fr == curfr) {
   6643        break;
   6644      }
   6645    }
   6646    fr = curfr->fr_next;                // put fr at frame that grows
   6647  } else {  // drag right
   6648    // sum up the room of frames right of the current one
   6649    room = 0;
   6650    FOR_ALL_FRAMES(fr, curfr->fr_next) {
   6651      room += fr->fr_width - frame_minwidth(fr, NULL);
   6652    }
   6653    fr = curfr;  // put fr at window that grows
   6654  }
   6655 
   6656  // If not enough room then move as far as we can
   6657  offset = MIN(offset, room);
   6658 
   6659  // No room at all, quit.
   6660  if (offset <= 0) {
   6661    return;
   6662  }
   6663 
   6664  if (fr == NULL) {
   6665    // This can happen when calling win_move_separator() on the rightmost
   6666    // window.  Just don't do anything.
   6667    return;
   6668  }
   6669 
   6670  // grow frame fr by offset lines
   6671  frame_new_width(fr, fr->fr_width + offset, left, false);
   6672 
   6673  // shrink other frames: current and at the left or at the right
   6674  if (left) {
   6675    fr = curfr;                 // current frame gets smaller
   6676  } else {
   6677    fr = curfr->fr_next;        // next frame gets smaller
   6678  }
   6679  while (fr != NULL && offset > 0) {
   6680    int n = frame_minwidth(fr, NULL);
   6681    if (fr->fr_width - offset <= n) {
   6682      offset -= fr->fr_width - n;
   6683      frame_new_width(fr, n, !left, false);
   6684    } else {
   6685      frame_new_width(fr, fr->fr_width - offset, !left, false);
   6686      break;
   6687    }
   6688    if (left) {
   6689      fr = fr->fr_prev;
   6690    } else {
   6691      fr = fr->fr_next;
   6692    }
   6693  }
   6694  win_comp_pos();
   6695  redraw_all_later(UPD_NOT_VALID);
   6696 }
   6697 
   6698 #define FRACTION_MULT   16384
   6699 
   6700 // Set wp->w_fraction for the current w_wrow and w_height.
   6701 // Has no effect when the window is less than two lines.
   6702 void set_fraction(win_T *wp)
   6703 {
   6704  if (wp->w_view_height > 1) {
   6705    // When cursor is in the first line the percentage is computed as if
   6706    // it's halfway that line.  Thus with two lines it is 25%, with three
   6707    // lines 17%, etc.  Similarly for the last line: 75%, 83%, etc.
   6708    wp->w_fraction = (wp->w_wrow * FRACTION_MULT + FRACTION_MULT / 2) / wp->w_view_height;
   6709  }
   6710 }
   6711 
   6712 /// Handle scroll position, depending on 'splitkeep'.  Replaces the
   6713 /// scroll_to_fraction() call from win_new_height() if 'splitkeep' is "screen"
   6714 /// or "topline".  Instead we iterate over all windows in a tabpage and
   6715 /// calculate the new scroll position.
   6716 /// TODO(vim): Ensure this also works with wrapped lines.
   6717 /// Requires a not fully visible cursor line to be allowed at the bottom of
   6718 /// a window ("zb"), probably only when 'smoothscroll' is also set.
   6719 void win_fix_scroll(bool resize)
   6720 {
   6721  if (*p_spk == 'c') {
   6722    return;  // 'splitkeep' is "cursor"
   6723  }
   6724 
   6725  skip_update_topline = true;
   6726  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   6727    // Skip when window height has not changed or when floating.
   6728    if (!wp->w_floating && wp->w_height != wp->w_prev_height) {
   6729      // Cursor position in this window may now be invalid.  It is kept
   6730      // potentially invalid until the window is made the current window.
   6731      wp->w_do_win_fix_cursor = true;
   6732 
   6733      // If window has moved update botline to keep the same screenlines.
   6734      if (*p_spk == 's' && wp->w_winrow != wp->w_prev_winrow
   6735          && wp->w_botline - 1 <= wp->w_buffer->b_ml.ml_line_count) {
   6736        int diff = (wp->w_winrow - wp->w_prev_winrow)
   6737                   + (wp->w_height - wp->w_prev_height);
   6738        pos_T cursor = wp->w_cursor;
   6739        wp->w_cursor.lnum = wp->w_botline - 1;
   6740 
   6741        // Add difference in height and row to botline.
   6742        if (diff > 0) {
   6743          cursor_down_inner(wp, diff, false);
   6744        } else {
   6745          cursor_up_inner(wp, -diff, false);
   6746        }
   6747 
   6748        // Scroll to put the new cursor position at the bottom of the
   6749        // screen.
   6750        wp->w_fraction = FRACTION_MULT;
   6751        scroll_to_fraction(wp, wp->w_prev_height);
   6752        wp->w_cursor = cursor;
   6753        wp->w_valid &= ~VALID_WCOL;
   6754      } else if (wp == curwin) {
   6755        wp->w_valid &= ~VALID_CROW;
   6756      }
   6757 
   6758      invalidate_botline_win(wp);
   6759      validate_botline_win(wp);
   6760    }
   6761    wp->w_prev_height = wp->w_height;
   6762    wp->w_prev_winrow = wp->w_winrow;
   6763  }
   6764  skip_update_topline = false;
   6765  // Ensure cursor is valid when not in normal mode or when resized.
   6766  if (!(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL))) {
   6767    win_fix_cursor(false);
   6768  } else if (resize) {
   6769    win_fix_cursor(true);
   6770  }
   6771 }
   6772 
   6773 /// Make sure the cursor position is valid for 'splitkeep'.
   6774 /// If it is not, put the cursor position in the jumplist and move it.
   6775 /// If we are not in normal mode ("normal" is false), make it valid by scrolling
   6776 /// instead.
   6777 static void win_fix_cursor(bool normal)
   6778 {
   6779  win_T *wp = curwin;
   6780 
   6781  if (skip_win_fix_cursor
   6782      || !wp->w_do_win_fix_cursor
   6783      || wp->w_buffer->b_ml.ml_line_count < wp->w_view_height) {
   6784    return;
   6785  }
   6786 
   6787  wp->w_do_win_fix_cursor = false;
   6788  // Determine valid cursor range.
   6789  int so = MIN(wp->w_view_height / 2, get_scrolloff_value(wp));
   6790  linenr_T lnum = wp->w_cursor.lnum;
   6791 
   6792  wp->w_cursor.lnum = wp->w_topline;
   6793  cursor_down_inner(wp, so, false);
   6794  linenr_T top = wp->w_cursor.lnum;
   6795 
   6796  wp->w_cursor.lnum = wp->w_botline - 1;
   6797  cursor_up_inner(wp, so, false);
   6798  linenr_T bot = wp->w_cursor.lnum;
   6799 
   6800  wp->w_cursor.lnum = lnum;
   6801  // Check if cursor position is above or below valid cursor range.
   6802  linenr_T nlnum = 0;
   6803  if (lnum > bot && (wp->w_botline - wp->w_buffer->b_ml.ml_line_count) != 1) {
   6804    nlnum = bot;
   6805  } else if (lnum < top && wp->w_topline != 1) {
   6806    nlnum = (so == wp->w_view_height / 2) ? bot : top;
   6807  }
   6808 
   6809  if (nlnum != 0) {  // Cursor is invalid for current scroll position.
   6810    if (normal) {    // Save to jumplist and set cursor to avoid scrolling.
   6811      setmark('\'');
   6812      wp->w_cursor.lnum = nlnum;
   6813    } else {         // Scroll instead when not in normal mode.
   6814      wp->w_fraction = (nlnum == bot) ? FRACTION_MULT : 0;
   6815      scroll_to_fraction(wp, wp->w_prev_height);
   6816      validate_botline_win(curwin);
   6817    }
   6818  }
   6819 }
   6820 
   6821 // Set the height of a window.
   6822 // "height" excludes any window toolbar.
   6823 // This takes care of the things inside the window, not what happens to the
   6824 // window position, the frame or to other windows.
   6825 void win_new_height(win_T *wp, int height)
   6826 {
   6827  // Don't want a negative height.  Happens when splitting a tiny window.
   6828  // Will equalize heights soon to fix it.
   6829  height = MAX(height, 0);
   6830  if (wp->w_height == height) {
   6831    return;  // nothing to do
   6832  }
   6833 
   6834  wp->w_height = height;
   6835  wp->w_pos_changed = true;
   6836  win_set_inner_size(wp, true);
   6837 }
   6838 
   6839 void scroll_to_fraction(win_T *wp, int prev_height)
   6840 {
   6841  int height = wp->w_view_height;
   6842 
   6843  // Don't change w_topline in any of these cases:
   6844  // - window height is 0
   6845  // - 'scrollbind' is set and this isn't the current window
   6846  // - window height is sufficient to display the whole buffer and first line
   6847  //   is visible.
   6848  if (height > 0
   6849      && (!wp->w_p_scb || wp == curwin)
   6850      && (height < wp->w_buffer->b_ml.ml_line_count
   6851          || wp->w_topline > 1)) {
   6852    // Find a value for w_topline that shows the cursor at the same
   6853    // relative position in the window as before (more or less).
   6854    linenr_T lnum = wp->w_cursor.lnum;
   6855    // can happen when starting up
   6856    lnum = MAX(lnum, 1);
   6857    wp->w_wrow = (wp->w_fraction * height - 1) / FRACTION_MULT;
   6858    int line_size = plines_win_col(wp, lnum, wp->w_cursor.col) - 1;
   6859    int sline = wp->w_wrow - line_size;
   6860 
   6861    if (sline >= 0) {
   6862      // Make sure the whole cursor line is visible, if possible.
   6863      const int rows = plines_win(wp, lnum, false);
   6864 
   6865      if (sline > wp->w_view_height - rows) {
   6866        sline = wp->w_view_height - rows;
   6867        wp->w_wrow -= rows - line_size;
   6868      }
   6869    }
   6870 
   6871    if (sline < 0) {
   6872      // Cursor line would go off top of screen if w_wrow was this high.
   6873      // Make cursor line the first line in the window.  If not enough
   6874      // room use w_skipcol;
   6875      wp->w_wrow = line_size;
   6876      if (wp->w_wrow >= wp->w_view_height
   6877          && (wp->w_view_width - win_col_off(wp)) > 0) {
   6878        wp->w_skipcol += wp->w_view_width - win_col_off(wp);
   6879        wp->w_wrow--;
   6880        while (wp->w_wrow >= wp->w_view_height) {
   6881          wp->w_skipcol += wp->w_view_width - win_col_off(wp)
   6882                           + win_col_off2(wp);
   6883          wp->w_wrow--;
   6884        }
   6885      }
   6886    } else if (sline > 0) {
   6887      while (sline > 0 && lnum > 1) {
   6888        hasFolding(wp, lnum, &lnum, NULL);
   6889        if (lnum == 1) {
   6890          // first line in buffer is folded
   6891          line_size = !decor_conceal_line(wp, lnum - 1, false);
   6892          sline--;
   6893          break;
   6894        }
   6895        lnum--;
   6896        if (lnum == wp->w_topline) {
   6897          line_size = plines_win_nofill(wp, lnum, true)
   6898                      + wp->w_topfill;
   6899        } else {
   6900          line_size = plines_win(wp, lnum, true);
   6901        }
   6902        sline -= line_size;
   6903      }
   6904 
   6905      if (sline < 0) {
   6906        // Line we want at top would go off top of screen.  Use next
   6907        // line instead.
   6908        hasFolding(wp, lnum, NULL, &lnum);
   6909        lnum++;
   6910        wp->w_wrow -= line_size + sline;
   6911      } else if (sline > 0) {
   6912        // First line of file reached, use that as topline.
   6913        lnum = 1;
   6914        wp->w_wrow -= sline;
   6915      }
   6916    }
   6917    set_topline(wp, lnum);
   6918  }
   6919 
   6920  if (wp == curwin) {
   6921    curs_columns(wp, false);        // validate w_wrow
   6922  }
   6923  if (prev_height > 0) {
   6924    wp->w_prev_fraction_row = wp->w_wrow;
   6925  }
   6926 
   6927  redraw_later(wp, UPD_SOME_VALID);
   6928  invalidate_botline_win(wp);
   6929 }
   6930 
   6931 void win_set_inner_size(win_T *wp, bool valid_cursor)
   6932 {
   6933  int width = wp->w_width_request;
   6934  if (width == 0) {
   6935    width = wp->w_width;
   6936  }
   6937 
   6938  int prev_height = wp->w_view_height;
   6939  int height = wp->w_height_request;
   6940  if (height == 0) {
   6941    height = MAX(0, wp->w_height - wp->w_winbar_height);
   6942  }
   6943 
   6944  if (height != prev_height) {
   6945    if (height > 0 && valid_cursor) {
   6946      if (wp == curwin && (*p_spk == 'c' || wp->w_floating)) {
   6947        // w_wrow needs to be valid. When setting 'laststatus' this may
   6948        // call win_new_height() recursively.
   6949        validate_cursor(curwin);
   6950      }
   6951      if (wp->w_view_height != prev_height) {
   6952        return;  // Recursive call already changed the size, bail out.
   6953      }
   6954      if (wp->w_wrow != wp->w_prev_fraction_row) {
   6955        set_fraction(wp);
   6956      }
   6957    }
   6958    wp->w_view_height = height;
   6959    win_comp_scroll(wp);
   6960 
   6961    // There is no point in adjusting the scroll position when exiting.  Some
   6962    // values might be invalid.
   6963    if (valid_cursor && !exiting && (*p_spk == 'c' || wp->w_floating)) {
   6964      wp->w_skipcol = 0;
   6965      scroll_to_fraction(wp, prev_height);
   6966    }
   6967    redraw_later(wp, UPD_SOME_VALID);
   6968  }
   6969 
   6970  if (width != wp->w_view_width) {
   6971    wp->w_view_width = width;
   6972    wp->w_lines_valid = 0;
   6973    if (valid_cursor) {
   6974      changed_line_abv_curs_win(wp);
   6975      invalidate_botline_win(wp);
   6976      if (wp == curwin && (*p_spk == 'c' || wp->w_floating)) {
   6977        curs_columns(wp, true);  // validate w_wrow
   6978      }
   6979    }
   6980    redraw_later(wp, UPD_NOT_VALID);
   6981  }
   6982 
   6983  if (wp->w_buffer->terminal) {
   6984    terminal_check_size(wp->w_buffer->terminal);
   6985  }
   6986 
   6987  int float_stl_height = wp->w_floating && wp->w_status_height ? STATUS_HEIGHT : 0;
   6988  wp->w_height_outer = (wp->w_view_height + win_border_height(wp) + wp->w_winbar_height +
   6989                        float_stl_height);
   6990  wp->w_width_outer = (wp->w_view_width + win_border_width(wp));
   6991  wp->w_winrow_off = wp->w_border_adj[0] + wp->w_winbar_height;
   6992  wp->w_wincol_off = wp->w_border_adj[3];
   6993 
   6994  if (ui_has(kUIMultigrid)) {
   6995    ui_call_win_viewport_margins(wp->w_grid_alloc.handle, wp->handle,
   6996                                 wp->w_winrow_off, wp->w_border_adj[2],
   6997                                 wp->w_wincol_off, wp->w_border_adj[1]);
   6998  }
   6999 
   7000  wp->w_redr_status = true;
   7001 }
   7002 
   7003 /// Set the width of a window.
   7004 void win_new_width(win_T *wp, int width)
   7005 {
   7006  // Should we give an error if width < 0?
   7007  wp->w_width = width < 0 ? 0 : width;
   7008  wp->w_pos_changed = true;
   7009  win_set_inner_size(wp, true);
   7010 }
   7011 
   7012 OptInt win_default_scroll(win_T *wp)
   7013 {
   7014  return MAX(wp->w_view_height / 2, 1);
   7015 }
   7016 
   7017 void win_comp_scroll(win_T *wp)
   7018 {
   7019  const OptInt old_w_p_scr = wp->w_p_scr;
   7020  wp->w_p_scr = win_default_scroll(wp);
   7021 
   7022  if (wp->w_p_scr != old_w_p_scr) {
   7023    // Used by "verbose set scroll".
   7024    wp->w_p_script_ctx[kWinOptScroll].sc_sid = SID_WINLAYOUT;
   7025    wp->w_p_script_ctx[kWinOptScroll].sc_lnum = 0;
   7026  }
   7027 }
   7028 
   7029 /// command_height: called whenever p_ch has been changed.
   7030 void command_height(void)
   7031 {
   7032  int old_p_ch = (int)curtab->tp_ch_used;
   7033 
   7034  // Find bottom frame with width of screen.
   7035  frame_T *frp = lastwin_nofloating()->w_frame;
   7036  while (frp->fr_width != Columns && frp->fr_parent != NULL) {
   7037    frp = frp->fr_parent;
   7038  }
   7039 
   7040  // Avoid changing the height of a window with 'winfixheight' set.
   7041  while (frp->fr_prev != NULL && frp->fr_layout == FR_LEAF && frp->fr_win->w_p_wfh) {
   7042    frp = frp->fr_prev;
   7043  }
   7044 
   7045  while (p_ch > old_p_ch && command_frame_height) {
   7046    if (frp == NULL) {
   7047      emsg(_(e_noroom));
   7048      p_ch = old_p_ch;
   7049      break;
   7050    }
   7051    int h = MIN((int)(p_ch - old_p_ch), frp->fr_height - frame_minheight(frp, NULL));
   7052    frame_add_height(frp, -h);
   7053    old_p_ch += h;
   7054    frp = frp->fr_prev;
   7055  }
   7056  if (p_ch < old_p_ch && command_frame_height && frp != NULL) {
   7057    frame_add_height(frp, (int)(old_p_ch - p_ch));
   7058  }
   7059 
   7060  // Recompute window positions.
   7061  win_comp_pos();
   7062  cmdline_row = Rows - (int)p_ch;
   7063  redraw_cmdline = true;
   7064 
   7065  // Clear the cmdheight area.
   7066  if (msg_scrolled == 0 && full_screen) {
   7067    GridView *grid = &default_gridview;
   7068    if (!ui_has(kUIMessages)) {
   7069      msg_grid_validate();
   7070      grid = &msg_grid_adj;
   7071    }
   7072    grid_clear(grid, cmdline_row, Rows, 0, Columns, 0);
   7073    msg_row = cmdline_row;
   7074  }
   7075 
   7076  // Use the value of p_ch that we remembered.  This is needed for when the
   7077  // GUI starts up, we can't be sure in what order things happen.  And when
   7078  // p_ch was changed in another tab page.
   7079  curtab->tp_ch_used = p_ch;
   7080  min_set_ch = p_ch;
   7081 }
   7082 
   7083 // Resize frame "frp" to be "n" lines higher (negative for less high).
   7084 // Also resize the frames it is contained in.
   7085 static void frame_add_height(frame_T *frp, int n)
   7086 {
   7087  frame_new_height(frp, frp->fr_height + n, false, false, false);
   7088  while (true) {
   7089    frp = frp->fr_parent;
   7090    if (frp == NULL) {
   7091      break;
   7092    }
   7093    frp->fr_height += n;
   7094  }
   7095 }
   7096 
   7097 /// Add or remove a status line from window(s), according to the
   7098 /// value of 'laststatus'.
   7099 ///
   7100 /// @param morewin  pretend there are two or more windows if true.
   7101 void last_status(bool morewin)
   7102 {
   7103  // Don't make a difference between horizontal or vertical split.
   7104  last_status_rec(topframe, last_stl_height(morewin) > 0, global_stl_height() > 0);
   7105  win_float_anchor_laststatus();
   7106 }
   7107 
   7108 // Remove status line from window, replacing it with a horizontal separator if needed.
   7109 void win_remove_status_line(win_T *wp, bool add_hsep)
   7110 {
   7111  wp->w_status_height = 0;
   7112  if (add_hsep) {
   7113    wp->w_hsep_height = 1;
   7114  } else {
   7115    win_new_height(wp, (wp->w_floating ? wp->w_view_height : wp->w_height) + STATUS_HEIGHT);
   7116  }
   7117  comp_col();
   7118 
   7119  stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
   7120  xfree(wp->w_status_click_defs);
   7121  wp->w_status_click_defs_size = 0;
   7122  wp->w_status_click_defs = NULL;
   7123 }
   7124 
   7125 // Look for a horizontally resizable frame, starting with frame "fr".
   7126 // Returns NULL if there are no resizable frames.
   7127 static frame_T *find_horizontally_resizable_frame(frame_T *fr)
   7128 {
   7129  frame_T *fp = fr;
   7130 
   7131  while (fp->fr_height <= frame_minheight(fp, NULL)) {
   7132    if (fp == topframe) {
   7133      return NULL;
   7134    }
   7135    // In a column of frames: go to frame above.  If already at
   7136    // the top or in a row of frames: go to parent.
   7137    if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) {
   7138      fp = fp->fr_prev;
   7139    } else {
   7140      fp = fp->fr_parent;
   7141    }
   7142  }
   7143 
   7144  return fp;
   7145 }
   7146 
   7147 // Look for resizable frames and take lines from them to make room for the statusline.
   7148 // @return Success or failure.
   7149 static bool resize_frame_for_status(frame_T *fr)
   7150 {
   7151  win_T *wp = fr->fr_win;
   7152  frame_T *fp = find_horizontally_resizable_frame(fr);
   7153 
   7154  if (fp == NULL) {
   7155    emsg(_(e_noroom));
   7156    return false;
   7157  } else if (fp != fr) {
   7158    frame_new_height(fp, fp->fr_height - 1, false, false, false);
   7159    frame_fix_height(wp);
   7160    win_comp_pos();
   7161  } else {
   7162    win_new_height(wp, wp->w_height - 1);
   7163  }
   7164 
   7165  return true;
   7166 }
   7167 
   7168 // Look for resizable frames and take lines from them to make room for the winbar.
   7169 // @return Success or failure.
   7170 static bool resize_frame_for_winbar(frame_T *fr)
   7171 {
   7172  win_T *wp = fr->fr_win;
   7173  frame_T *fp = find_horizontally_resizable_frame(fr);
   7174 
   7175  if (fp == NULL || fp == fr) {
   7176    emsg(_(e_noroom));
   7177    return false;
   7178  }
   7179  frame_new_height(fp, fp->fr_height - 1, false, false, false);
   7180  win_new_height(wp, wp->w_height + 1);
   7181  frame_fix_height(wp);
   7182  win_comp_pos();
   7183 
   7184  return true;
   7185 }
   7186 
   7187 static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
   7188 {
   7189  if (fr->fr_layout == FR_LEAF) {
   7190    win_T *wp = fr->fr_win;
   7191    bool is_last = is_bottom_win(wp);
   7192 
   7193    if (is_last) {
   7194      if (wp->w_status_height != 0 && (!statusline || is_stl_global)) {
   7195        win_remove_status_line(wp, false);
   7196      } else if (wp->w_status_height == 0 && !is_stl_global && statusline) {
   7197        // Add statusline to window if needed
   7198        wp->w_status_height = STATUS_HEIGHT;
   7199        if (!resize_frame_for_status(fr)) {
   7200          return;
   7201        }
   7202        comp_col();
   7203      }
   7204      // Set prev_height when difference is due to 'laststatus'.
   7205      if (abs(wp->w_height - wp->w_prev_height) == 1) {
   7206        wp->w_prev_height = wp->w_height;
   7207      }
   7208    } else if (wp->w_status_height != 0 && is_stl_global) {
   7209      // If statusline is global and the window has a statusline, replace it with a horizontal
   7210      // separator
   7211      win_remove_status_line(wp, true);
   7212    } else if (wp->w_status_height == 0 && !is_stl_global) {
   7213      // If statusline isn't global and the window doesn't have a statusline, re-add it
   7214      wp->w_status_height = STATUS_HEIGHT;
   7215      wp->w_hsep_height = 0;
   7216      comp_col();
   7217    }
   7218  } else {
   7219    // For a column or row frame, recursively call this function for all child frames
   7220    frame_T *fp;
   7221    FOR_ALL_FRAMES(fp, fr->fr_child) {
   7222      last_status_rec(fp, statusline, is_stl_global);
   7223    }
   7224  }
   7225 }
   7226 
   7227 /// Add or remove window bar from window "wp".
   7228 ///
   7229 /// @param make_room Whether to resize frames to make room for winbar.
   7230 /// @param valid_cursor Whether the cursor is valid and should be used while
   7231 ///                     resizing.
   7232 ///
   7233 /// @return Success status.
   7234 int set_winbar_win(win_T *wp, bool make_room, bool valid_cursor)
   7235 {
   7236  // Require the local value to be set in order to show winbar on a floating window.
   7237  int winbar_height = wp->w_floating ? ((*wp->w_p_wbr != NUL) ? 1 : 0)
   7238                                     : ((*p_wbr != NUL || *wp->w_p_wbr != NUL) ? 1 : 0);
   7239 
   7240  if (wp->w_winbar_height != winbar_height) {
   7241    if (winbar_height == 1 && wp->w_view_height <= 1) {
   7242      if (wp->w_floating) {
   7243        emsg(_(e_noroom));
   7244        return NOTDONE;
   7245      } else if (!make_room || !resize_frame_for_winbar(wp->w_frame)) {
   7246        return FAIL;
   7247      }
   7248    }
   7249    wp->w_winbar_height = winbar_height;
   7250    win_set_inner_size(wp, valid_cursor);
   7251 
   7252    if (winbar_height == 0) {
   7253      // When removing winbar, deallocate the w_winbar_click_defs array
   7254      stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size);
   7255      xfree(wp->w_winbar_click_defs);
   7256      wp->w_winbar_click_defs_size = 0;
   7257      wp->w_winbar_click_defs = NULL;
   7258    }
   7259  }
   7260 
   7261  return OK;
   7262 }
   7263 
   7264 /// Add or remove window bars from all windows in tab depending on the value of 'winbar'.
   7265 ///
   7266 /// @param make_room Whether to resize frames to make room for winbar.
   7267 void set_winbar(bool make_room)
   7268 {
   7269  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   7270    if (set_winbar_win(wp, make_room, true) == FAIL) {
   7271      break;
   7272    }
   7273  }
   7274 }
   7275 
   7276 /// Return the number of lines used by the tab page line.
   7277 int tabline_height(void)
   7278 {
   7279  if (ui_has(kUITabline)) {
   7280    return 0;
   7281  }
   7282  assert(first_tabpage);
   7283  switch (p_stal) {
   7284  case 0:
   7285    return 0;
   7286  case 1:
   7287    return (first_tabpage->tp_next == NULL) ? 0 : 1;
   7288  }
   7289  return 1;
   7290 }
   7291 
   7292 /// Return the number of lines used by default by the window bar.
   7293 int global_winbar_height(void)
   7294 {
   7295  return *p_wbr != NUL ? 1 : 0;
   7296 }
   7297 
   7298 /// Return the number of lines used by the global statusline
   7299 int global_stl_height(void)
   7300 {
   7301  return (p_ls == 3) ? STATUS_HEIGHT : 0;
   7302 }
   7303 
   7304 /// Return the height of the last window's statusline, or the global statusline if set.
   7305 ///
   7306 /// @param morewin  pretend there are two or more windows if true.
   7307 int last_stl_height(bool morewin)
   7308 {
   7309  return (p_ls > 1 || (p_ls == 1 && (morewin || !one_window(firstwin, NULL)))) ? STATUS_HEIGHT : 0;
   7310 }
   7311 
   7312 /// Return the minimal number of rows that is needed on the screen to display
   7313 /// the current number of windows for the given tab page.
   7314 int min_rows(tabpage_T *tp) FUNC_ATTR_NONNULL_ALL
   7315 {
   7316  if (firstwin == NULL) {       // not initialized yet
   7317    return MIN_LINES;
   7318  }
   7319 
   7320  int total = frame_minheight(tp->tp_topframe, NULL);
   7321  total += tabline_height() + global_stl_height();
   7322  if ((tp == curtab ? p_ch : tp->tp_ch_used) > 0) {
   7323    total++;  // count the room for the command line
   7324  }
   7325  return total;
   7326 }
   7327 
   7328 /// Return the minimal number of rows that is needed on the screen to display
   7329 /// the current number of windows for all tab pages.
   7330 int min_rows_for_all_tabpages(void)
   7331 {
   7332  if (firstwin == NULL) {       // not initialized yet
   7333    return MIN_LINES;
   7334  }
   7335 
   7336  int total = 0;
   7337  FOR_ALL_TABS(tp) {
   7338    int n = frame_minheight(tp->tp_topframe, NULL);
   7339    if ((tp == curtab ? p_ch : tp->tp_ch_used) > 0) {
   7340      n++;  // count the room for the command line
   7341    }
   7342    total = MAX(total, n);
   7343  }
   7344  total += tabline_height() + global_stl_height();
   7345  return total;
   7346 }
   7347 
   7348 /// Check that there is only one window (and only one tab page), not counting a
   7349 /// help or preview window, unless it is the current window. Does not count
   7350 /// "aucmd_win". Does not count floats unless it is current.
   7351 bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   7352 {
   7353  // If there is another tab page there always is another window.
   7354  if (first_tabpage->tp_next != NULL) {
   7355    return false;
   7356  }
   7357 
   7358  int count = 0;
   7359  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   7360    if (wp->w_buffer != NULL
   7361        && (!((bt_help(wp->w_buffer) && !bt_help(curbuf)) || wp->w_floating
   7362              || wp->w_p_pvw) || wp == curwin) && !is_aucmd_win(wp)) {
   7363      count++;
   7364    }
   7365  }
   7366  return count <= 1;
   7367 }
   7368 
   7369 /// Implementation of check_lnums() and check_lnums_nested().
   7370 static void check_lnums_both(bool do_curwin, bool nested)
   7371 {
   7372  FOR_ALL_TAB_WINDOWS(tp, wp) {
   7373    if ((do_curwin || wp != curwin) && wp->w_buffer == curbuf) {
   7374      if (!nested) {
   7375        // save the original cursor position and topline
   7376        wp->w_save_cursor.w_cursor_save = wp->w_cursor;
   7377        wp->w_save_cursor.w_topline_save = wp->w_topline;
   7378      }
   7379 
   7380      bool need_adjust = wp->w_cursor.lnum > curbuf->b_ml.ml_line_count;
   7381      if (need_adjust) {
   7382        wp->w_cursor.lnum = curbuf->b_ml.ml_line_count;
   7383      }
   7384      if (need_adjust || !nested) {
   7385        // save the (corrected) cursor position
   7386        wp->w_save_cursor.w_cursor_corr = wp->w_cursor;
   7387      }
   7388 
   7389      need_adjust = wp->w_topline > curbuf->b_ml.ml_line_count;
   7390      if (need_adjust) {
   7391        wp->w_topline = curbuf->b_ml.ml_line_count;
   7392      }
   7393      if (need_adjust || !nested) {
   7394        // save the (corrected) topline
   7395        wp->w_save_cursor.w_topline_corr = wp->w_topline;
   7396      }
   7397    }
   7398  }
   7399 }
   7400 
   7401 /// Correct the cursor line number in other windows.  Used after changing the
   7402 /// current buffer, and before applying autocommands.
   7403 ///
   7404 /// @param do_curwin  when true, also check current window.
   7405 void check_lnums(bool do_curwin)
   7406 {
   7407  check_lnums_both(do_curwin, false);
   7408 }
   7409 
   7410 /// Like check_lnums() but for when check_lnums() was already called.
   7411 void check_lnums_nested(bool do_curwin)
   7412 {
   7413  check_lnums_both(do_curwin, true);
   7414 }
   7415 
   7416 /// Reset cursor and topline to its stored values from check_lnums().
   7417 /// check_lnums() must have been called first!
   7418 void reset_lnums(void)
   7419 {
   7420  FOR_ALL_TAB_WINDOWS(tp, wp) {
   7421    if (wp->w_buffer == curbuf) {
   7422      // Restore the value if the autocommand didn't change it and it was set.
   7423      // Note: This triggers e.g. on BufReadPre, when the buffer is not yet
   7424      //       loaded, so cannot validate the buffer line
   7425      if (equalpos(wp->w_save_cursor.w_cursor_corr, wp->w_cursor)
   7426          && wp->w_save_cursor.w_cursor_save.lnum != 0) {
   7427        wp->w_cursor = wp->w_save_cursor.w_cursor_save;
   7428      }
   7429      if (wp->w_save_cursor.w_topline_corr == wp->w_topline
   7430          && wp->w_save_cursor.w_topline_save != 0) {
   7431        wp->w_topline = wp->w_save_cursor.w_topline_save;
   7432      }
   7433      if (wp->w_save_cursor.w_topline_save > wp->w_buffer->b_ml.ml_line_count) {
   7434        wp->w_valid &= ~VALID_TOPLINE;
   7435      }
   7436    }
   7437  }
   7438 }
   7439 
   7440 // A snapshot of the window sizes, to restore them after closing the help
   7441 // window.
   7442 // Only these fields are used:
   7443 // fr_layout
   7444 // fr_width
   7445 // fr_height
   7446 // fr_next
   7447 // fr_child
   7448 // fr_win (only valid for the old curwin, NULL otherwise)
   7449 
   7450 // Create a snapshot of the current frame sizes.
   7451 void make_snapshot(int idx)
   7452 {
   7453  clear_snapshot(curtab, idx);
   7454  make_snapshot_rec(topframe, &curtab->tp_snapshot[idx]);
   7455 }
   7456 
   7457 static void make_snapshot_rec(frame_T *fr, frame_T **frp)
   7458 {
   7459  *frp = xcalloc(1, sizeof(frame_T));
   7460  (*frp)->fr_layout = fr->fr_layout;
   7461  (*frp)->fr_width = fr->fr_width;
   7462  (*frp)->fr_height = fr->fr_height;
   7463  if (fr->fr_next != NULL) {
   7464    make_snapshot_rec(fr->fr_next, &((*frp)->fr_next));
   7465  }
   7466  if (fr->fr_child != NULL) {
   7467    make_snapshot_rec(fr->fr_child, &((*frp)->fr_child));
   7468  }
   7469  if (fr->fr_layout == FR_LEAF && fr->fr_win == curwin) {
   7470    (*frp)->fr_win = curwin;
   7471  }
   7472 }
   7473 
   7474 // Remove any existing snapshot.
   7475 static void clear_snapshot(tabpage_T *tp, int idx)
   7476 {
   7477  clear_snapshot_rec(tp->tp_snapshot[idx]);
   7478  tp->tp_snapshot[idx] = NULL;
   7479 }
   7480 
   7481 static void clear_snapshot_rec(frame_T *fr)
   7482 {
   7483  if (fr == NULL) {
   7484    return;
   7485  }
   7486  clear_snapshot_rec(fr->fr_next);
   7487  clear_snapshot_rec(fr->fr_child);
   7488  xfree(fr);
   7489 }
   7490 
   7491 /// Traverse a snapshot to find the previous curwin.
   7492 static win_T *get_snapshot_curwin_rec(frame_T *ft)
   7493 {
   7494  win_T *wp;
   7495 
   7496  if (ft->fr_next != NULL) {
   7497    if ((wp = get_snapshot_curwin_rec(ft->fr_next)) != NULL) {
   7498      return wp;
   7499    }
   7500  }
   7501  if (ft->fr_child != NULL) {
   7502    if ((wp = get_snapshot_curwin_rec(ft->fr_child)) != NULL) {
   7503      return wp;
   7504    }
   7505  }
   7506 
   7507  return ft->fr_win;
   7508 }
   7509 
   7510 /// @return  the current window stored in the snapshot or NULL.
   7511 static win_T *get_snapshot_curwin(int idx)
   7512 {
   7513  if (curtab->tp_snapshot[idx] == NULL) {
   7514    return NULL;
   7515  }
   7516 
   7517  return get_snapshot_curwin_rec(curtab->tp_snapshot[idx]);
   7518 }
   7519 
   7520 /// Restore a previously created snapshot, if there is any.
   7521 /// This is only done if the screen size didn't change and the window layout is
   7522 /// still the same.
   7523 ///
   7524 /// @param close_curwin  closing current window
   7525 void restore_snapshot(int idx, int close_curwin)
   7526 {
   7527  if (curtab->tp_snapshot[idx] != NULL
   7528      && curtab->tp_snapshot[idx]->fr_width == topframe->fr_width
   7529      && curtab->tp_snapshot[idx]->fr_height == topframe->fr_height
   7530      && check_snapshot_rec(curtab->tp_snapshot[idx], topframe) == OK) {
   7531    win_T *wp = restore_snapshot_rec(curtab->tp_snapshot[idx], topframe);
   7532    win_comp_pos();
   7533    if (wp != NULL && close_curwin) {
   7534      win_goto(wp);
   7535    }
   7536    redraw_all_later(UPD_NOT_VALID);
   7537  }
   7538  clear_snapshot(curtab, idx);
   7539 }
   7540 
   7541 /// Check if frames "sn" and "fr" have the same layout, same following frames
   7542 /// and same children.  And the window pointer is valid.
   7543 static int check_snapshot_rec(frame_T *sn, frame_T *fr)
   7544 {
   7545  if (sn->fr_layout != fr->fr_layout
   7546      || (sn->fr_next == NULL) != (fr->fr_next == NULL)
   7547      || (sn->fr_child == NULL) != (fr->fr_child == NULL)
   7548      || (sn->fr_next != NULL
   7549          && check_snapshot_rec(sn->fr_next, fr->fr_next) == FAIL)
   7550      || (sn->fr_child != NULL
   7551          && check_snapshot_rec(sn->fr_child, fr->fr_child) == FAIL)
   7552      || (sn->fr_win != NULL && !win_valid(sn->fr_win))) {
   7553    return FAIL;
   7554  }
   7555  return OK;
   7556 }
   7557 
   7558 // Copy the size of snapshot frame "sn" to frame "fr".  Do the same for all
   7559 // following frames and children.
   7560 // Returns a pointer to the old current window, or NULL.
   7561 static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr)
   7562 {
   7563  win_T *wp = NULL;
   7564 
   7565  fr->fr_height = sn->fr_height;
   7566  fr->fr_width = sn->fr_width;
   7567  if (fr->fr_layout == FR_LEAF) {
   7568    frame_new_height(fr, fr->fr_height, false, false, false);
   7569    frame_new_width(fr, fr->fr_width, false, false);
   7570    wp = sn->fr_win;
   7571  }
   7572  if (sn->fr_next != NULL) {
   7573    win_T *wp2 = restore_snapshot_rec(sn->fr_next, fr->fr_next);
   7574    if (wp2 != NULL) {
   7575      wp = wp2;
   7576    }
   7577  }
   7578  if (sn->fr_child != NULL) {
   7579    win_T *wp2 = restore_snapshot_rec(sn->fr_child, fr->fr_child);
   7580    if (wp2 != NULL) {
   7581      wp = wp2;
   7582    }
   7583  }
   7584  return wp;
   7585 }
   7586 
   7587 /// Check that "topfrp" and its children are at the right height.
   7588 ///
   7589 /// @param  topfrp  top frame pointer
   7590 /// @param  height  expected height
   7591 static bool frame_check_height(const frame_T *topfrp, int height)
   7592  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
   7593 {
   7594  if (topfrp->fr_height != height) {
   7595    return false;
   7596  }
   7597  if (topfrp->fr_layout == FR_ROW) {
   7598    const frame_T *frp;
   7599    FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   7600      if (frp->fr_height != height) {
   7601        return false;
   7602      }
   7603    }
   7604  }
   7605  return true;
   7606 }
   7607 
   7608 /// Check that "topfrp" and its children are at the right width.
   7609 ///
   7610 /// @param  topfrp  top frame pointer
   7611 /// @param  width   expected width
   7612 static bool frame_check_width(const frame_T *topfrp, int width)
   7613  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
   7614 {
   7615  if (topfrp->fr_width != width) {
   7616    return false;
   7617  }
   7618  if (topfrp->fr_layout == FR_COL) {
   7619    const frame_T *frp;
   7620    FOR_ALL_FRAMES(frp, topfrp->fr_child) {
   7621      if (frp->fr_width != width) {
   7622        return false;
   7623      }
   7624    }
   7625  }
   7626  return true;
   7627 }
   7628 
   7629 /// Simple int comparison function for use with qsort()
   7630 static int int_cmp(const void *pa, const void *pb)
   7631 {
   7632  const int a = *(const int *)pa;
   7633  const int b = *(const int *)pb;
   7634  return a == b ? 0 : a < b ? -1 : 1;
   7635 }
   7636 
   7637 /// Check "cc" as 'colorcolumn' and update the members of "wp".
   7638 /// This is called when 'colorcolumn' or 'textwidth' is changed.
   7639 ///
   7640 /// @param cc  when NULL: use "wp->w_p_cc"
   7641 /// @param wp  when NULL: only parse "cc"
   7642 ///
   7643 /// @return error message, NULL if it's OK.
   7644 const char *check_colorcolumn(char *cc, win_T *wp)
   7645 {
   7646  if (wp != NULL && wp->w_buffer == NULL) {
   7647    return NULL;      // buffer was closed
   7648  }
   7649 
   7650  char *s = empty_string_option;
   7651  if (cc != NULL) {
   7652    s = cc;
   7653  } else if (wp != NULL) {
   7654    s = wp->w_p_cc;
   7655  }
   7656 
   7657  OptInt tw;
   7658  if (wp != NULL) {
   7659    tw = wp->w_buffer->b_p_tw;
   7660  } else {
   7661    // buffer-local value not set, assume zero
   7662    tw = 0;
   7663  }
   7664 
   7665  unsigned count = 0;
   7666  int color_cols[256];
   7667  while (*s != NUL && count < 255) {
   7668    int col;
   7669    if (*s == '-' || *s == '+') {
   7670      // -N and +N: add to 'textwidth'
   7671      col = (*s == '-') ? -1 : 1;
   7672      s++;
   7673      if (!ascii_isdigit(*s)) {
   7674        return e_invarg;
   7675      }
   7676      col = col * getdigits_int(&s, true, 0);
   7677      if (tw == 0) {
   7678        goto skip;          // 'textwidth' not set, skip this item
   7679      }
   7680      assert((col >= 0 && tw <= INT_MAX - col && tw + col >= INT_MIN)
   7681             || (col < 0 && tw >= INT_MIN - col && tw + col <= INT_MAX));
   7682      col += (int)tw;
   7683      if (col < 0) {
   7684        goto skip;
   7685      }
   7686    } else if (ascii_isdigit(*s)) {
   7687      col = getdigits_int(&s, true, 0);
   7688    } else {
   7689      return e_invarg;
   7690    }
   7691    color_cols[count++] = col - 1;      // 1-based to 0-based
   7692 skip:
   7693    if (*s == NUL) {
   7694      break;
   7695    }
   7696    if (*s != ',') {
   7697      return e_invarg;
   7698    }
   7699    if (*++s == NUL) {
   7700      return e_invarg;        // illegal trailing comma as in "set cc=80,"
   7701    }
   7702  }
   7703 
   7704  if (wp == NULL) {
   7705    return NULL;  // only parse "cc"
   7706  }
   7707 
   7708  xfree(wp->w_p_cc_cols);
   7709  if (count == 0) {
   7710    wp->w_p_cc_cols = NULL;
   7711  } else {
   7712    wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1));
   7713    // sort the columns for faster usage on screen redraw inside
   7714    // win_line()
   7715    qsort(color_cols, count, sizeof(int), int_cmp);
   7716 
   7717    int j = 0;
   7718    for (unsigned i = 0; i < count; i++) {
   7719      // skip duplicates
   7720      if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) {
   7721        wp->w_p_cc_cols[j++] = color_cols[i];
   7722      }
   7723    }
   7724    wp->w_p_cc_cols[j] = -1;        // end marker
   7725  }
   7726 
   7727  return NULL;    // no error
   7728 }
   7729 
   7730 int get_last_winid(void)
   7731 {
   7732  return last_win_id;
   7733 }
   7734 
   7735 /// Don't let autocommands close the given window
   7736 int win_locked(win_T *wp)
   7737 {
   7738  return wp->w_locked;
   7739 }
   7740 
   7741 void win_get_tabwin(handle_T id, int *tabnr, int *winnr)
   7742 {
   7743  *tabnr = 0;
   7744  *winnr = 0;
   7745 
   7746  int tnum = 1;
   7747  int wnum = 1;
   7748  FOR_ALL_TABS(tp) {
   7749    FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
   7750      if (wp->handle == id) {
   7751        if (win_has_winnr(wp, tp)) {
   7752          *winnr = wnum;
   7753          *tabnr = tnum;
   7754        }
   7755        return;
   7756      }
   7757      wnum += win_has_winnr(wp, tp);
   7758    }
   7759    tnum++;
   7760    wnum = 1;
   7761  }
   7762 }
   7763 
   7764 void win_ui_flush(bool validate)
   7765 {
   7766  FOR_ALL_TAB_WINDOWS(tp, wp) {
   7767    if ((wp->w_pos_changed || wp->w_grid_alloc.pending_comp_index_update)
   7768        && wp->w_grid_alloc.chars != NULL) {
   7769      if (tp == curtab) {
   7770        ui_ext_win_position(wp, validate);
   7771      } else {
   7772        ui_call_win_hide(wp->w_grid_alloc.handle);
   7773        wp->w_pos_changed = false;
   7774      }
   7775      wp->w_grid_alloc.pending_comp_index_update = false;
   7776    }
   7777    if (tp == curtab) {
   7778      ui_ext_win_viewport(wp);
   7779    }
   7780  }
   7781  // The popupmenu could also have moved or changed its comp_index
   7782  pum_ui_flush();
   7783 
   7784  // And the message
   7785  msg_ui_flush();
   7786 }
   7787 
   7788 win_T *lastwin_nofloating(void)
   7789 {
   7790  win_T *res = lastwin;
   7791  while (res->w_floating) {
   7792    res = res->w_prev;
   7793  }
   7794  return res;
   7795 }