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