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