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