drawscreen.c (95710B)
1 // drawscreen.c: Code for updating all the windows on the screen. 2 // This is the top level, drawline.c is the middle and grid.c the lower level. 3 4 // update_screen() is the function that updates all windows and status lines. 5 // It is called from the main loop when must_redraw is non-zero. It may be 6 // called from other places when an immediate screen update is needed. 7 // 8 // The part of the buffer that is displayed in a window is set with: 9 // - w_topline (first buffer line in window) 10 // - w_topfill (filler lines above the first line) 11 // - w_leftcol (leftmost window cell in window), 12 // - w_skipcol (skipped window cells of first line) 13 // 14 // Commands that only move the cursor around in a window, do not need to take 15 // action to update the display. The main loop will check if w_topline is 16 // valid and update it (scroll the window) when needed. 17 // 18 // Commands that scroll a window change w_topline and must call 19 // check_cursor() to move the cursor into the visible part of the window, and 20 // call redraw_later(wp, UPD_VALID) to have the window displayed by update_screen() 21 // later. 22 // 23 // Commands that change text in the buffer must call changed_bytes() or 24 // changed_lines() to mark the area that changed and will require updating 25 // later. The main loop will call update_screen(), which will update each 26 // window that shows the changed buffer. This assumes text above the change 27 // can remain displayed as it is. Text after the change may need updating for 28 // scrolling, folding and syntax highlighting. 29 // 30 // Commands that change how a window is displayed (e.g., setting 'list') or 31 // invalidate the contents of a window in another way (e.g., change fold 32 // settings), must call redraw_later(wp, UPD_NOT_VALID) to have the whole window 33 // redisplayed by update_screen() later. 34 // 35 // Commands that change how a buffer is displayed (e.g., setting 'tabstop') 36 // must call redraw_curbuf_later(UPD_NOT_VALID) to have all the windows for the 37 // buffer redisplayed by update_screen() later. 38 // 39 // Commands that change highlighting and possibly cause a scroll too must call 40 // redraw_later(wp, UPD_SOME_VALID) to update the whole window but still use 41 // scrolling to avoid redrawing everything. But the length of displayed lines 42 // must not change, use UPD_NOT_VALID then. 43 // 44 // Commands that move the window position must call redraw_later(wp, UPD_NOT_VALID). 45 // TODO(neovim): should minimize redrawing by scrolling when possible. 46 // 47 // Commands that change everything (e.g., resizing the screen) must call 48 // redraw_all_later(UPD_NOT_VALID) or redraw_all_later(UPD_CLEAR). 49 // 50 // Things that are handled indirectly: 51 // - When messages scroll the screen up, msg_scrolled will be set and 52 // update_screen() called to redraw. 53 54 #include <assert.h> 55 #include <inttypes.h> 56 #include <limits.h> 57 #include <stdbool.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <string.h> 61 62 #include "klib/kvec.h" 63 #include "nvim/api/private/defs.h" 64 #include "nvim/ascii_defs.h" 65 #include "nvim/autocmd.h" 66 #include "nvim/autocmd_defs.h" 67 #include "nvim/buffer.h" 68 #include "nvim/buffer_defs.h" 69 #include "nvim/charset.h" 70 #include "nvim/cmdexpand.h" 71 #include "nvim/decoration.h" 72 #include "nvim/decoration_defs.h" 73 #include "nvim/decoration_provider.h" 74 #include "nvim/diff.h" 75 #include "nvim/digraph.h" 76 #include "nvim/drawline.h" 77 #include "nvim/drawscreen.h" 78 #include "nvim/eval/vars.h" 79 #include "nvim/ex_getln.h" 80 #include "nvim/fold.h" 81 #include "nvim/fold_defs.h" 82 #include "nvim/getchar.h" 83 #include "nvim/gettext_defs.h" 84 #include "nvim/globals.h" 85 #include "nvim/grid.h" 86 #include "nvim/grid_defs.h" 87 #include "nvim/highlight.h" 88 #include "nvim/highlight_defs.h" 89 #include "nvim/highlight_group.h" 90 #include "nvim/insexpand.h" 91 #include "nvim/marktree_defs.h" 92 #include "nvim/match.h" 93 #include "nvim/mbyte.h" 94 #include "nvim/memline.h" 95 #include "nvim/message.h" 96 #include "nvim/move.h" 97 #include "nvim/normal.h" 98 #include "nvim/normal_defs.h" 99 #include "nvim/option.h" 100 #include "nvim/option_vars.h" 101 #include "nvim/os/os_defs.h" 102 #include "nvim/plines.h" 103 #include "nvim/popupmenu.h" 104 #include "nvim/pos_defs.h" 105 #include "nvim/profile.h" 106 #include "nvim/regexp.h" 107 #include "nvim/search.h" 108 #include "nvim/spell.h" 109 #include "nvim/state.h" 110 #include "nvim/state_defs.h" 111 #include "nvim/statusline.h" 112 #include "nvim/strings.h" 113 #include "nvim/syntax.h" 114 #include "nvim/syntax_defs.h" 115 #include "nvim/terminal.h" 116 #include "nvim/types_defs.h" 117 #include "nvim/ui.h" 118 #include "nvim/ui_compositor.h" 119 #include "nvim/ui_defs.h" 120 #include "nvim/version.h" 121 #include "nvim/vim_defs.h" 122 #include "nvim/window.h" 123 124 /// corner value flags for hsep_connected and vsep_connected 125 typedef enum { 126 WC_TOP_LEFT = 0, 127 WC_TOP_RIGHT, 128 WC_BOTTOM_LEFT, 129 WC_BOTTOM_RIGHT, 130 } WindowCorner; 131 132 #include "drawscreen.c.generated.h" 133 134 static bool redraw_popupmenu = false; 135 static bool msg_grid_invalid = false; 136 static bool resizing_autocmd = false; 137 static bool conceal_cursor_used = false; 138 139 /// Check if the cursor line needs to be redrawn because of 'concealcursor'. 140 /// 141 /// When cursor is moved at the same time, both lines will be redrawn regardless. 142 void conceal_check_cursor_line(void) 143 { 144 bool should_conceal = conceal_cursor_line(curwin); 145 if (curwin->w_p_cole <= 0 || conceal_cursor_used == should_conceal) { 146 return; 147 } 148 149 redrawWinline(curwin, curwin->w_cursor.lnum); 150 151 // Concealed line visibility toggled. 152 if (decor_conceal_line(curwin, curwin->w_cursor.lnum - 1, true)) { 153 changed_window_setting(curwin); 154 } 155 // Need to recompute cursor column, e.g., when starting Visual mode 156 // without concealing. 157 curs_columns(curwin, true); 158 } 159 160 /// Resize default_grid to Rows and Columns. 161 /// 162 /// Allocate default_grid.chars[] and other grid arrays. 163 /// 164 /// There may be some time between setting Rows and Columns and (re)allocating 165 /// default_grid arrays. This happens when starting up and when 166 /// (manually) changing the screen size. Always use default_grid.rows and 167 /// default_grid.cols to access items in default_grid.chars[]. Use Rows and 168 /// Columns for positioning text etc. where the final size of the screen is 169 /// needed. 170 /// 171 /// @return whether resizing has been done 172 bool default_grid_alloc(void) 173 { 174 static bool resizing = false; 175 176 // It's possible that we produce an out-of-memory message below, which 177 // will cause this function to be called again. To break the loop, just 178 // return here. 179 if (resizing) { 180 return false; 181 } 182 resizing = true; 183 184 // Allocation of the screen buffers is done only when the size changes and 185 // when Rows and Columns have been set. 186 if ((default_grid.chars != NULL 187 && Rows == default_grid.rows 188 && Columns == default_grid.cols) 189 || Rows == 0 || Columns == 0) { 190 resizing = false; 191 return false; 192 } 193 194 // We're changing the size of the screen. 195 // - Allocate new arrays for default_grid 196 // - Move lines from the old arrays into the new arrays, clear extra 197 // lines (unless the screen is going to be cleared). 198 // - Free the old arrays. 199 // 200 // If anything fails, make grid arrays NULL, so we don't do anything! 201 // Continuing with the old arrays may result in a crash, because the 202 // size is wrong. 203 204 grid_alloc(&default_grid, Rows, Columns, true, true); 205 206 stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); 207 tab_page_click_defs = stl_alloc_click_defs(tab_page_click_defs, Columns, 208 &tab_page_click_defs_size); 209 210 default_grid.comp_height = Rows; 211 default_grid.comp_width = Columns; 212 213 default_grid.handle = DEFAULT_GRID_HANDLE; 214 215 resizing = false; 216 return true; 217 } 218 219 void screenclear(void) 220 { 221 msg_check_for_delay(false); 222 223 if (starting == NO_SCREEN || default_grid.chars == NULL) { 224 return; 225 } 226 227 // blank out the default grid 228 for (int i = 0; i < default_grid.rows; i++) { 229 grid_clear_line(&default_grid, default_grid.line_offset[i], 230 default_grid.cols, true); 231 } 232 233 ui_call_grid_clear(1); // clear the display 234 ui_comp_set_screen_valid(true); 235 236 ns_hl_fast = -1; 237 238 clear_cmdline = false; 239 mode_displayed = false; 240 241 redraw_all_later(UPD_NOT_VALID); 242 cmdline_was_last_drawn = false; 243 redraw_cmdline = true; 244 redraw_tabline = true; 245 redraw_popupmenu = true; 246 pum_invalidate(); 247 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 248 if (wp->w_floating) { 249 wp->w_redr_type = UPD_CLEAR; 250 } 251 } 252 if (must_redraw == UPD_CLEAR) { 253 must_redraw = UPD_NOT_VALID; // no need to clear again 254 } 255 compute_cmdrow(); 256 msg_row = cmdline_row; // put cursor on last line for messages 257 msg_col = 0; 258 msg_reset_scroll(); // can't scroll back 259 msg_didany = false; 260 msg_didout = false; 261 if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) { 262 grid_invalidate(&msg_grid); 263 msg_grid_validate(); 264 msg_grid_invalid = false; 265 clear_cmdline = true; 266 } 267 } 268 269 /// Unlike cmdline "one_key" prompts, the message part of the prompt is not stored 270 /// to be re-emitted: avoid clearing the prompt from the message grid. 271 static bool cmdline_number_prompt(void) 272 { 273 return !ui_has(kUIMessages) && (State & MODE_CMDLINE) && get_cmdline_info()->mouse_used != NULL; 274 } 275 276 /// Set dimensions of the Nvim application "screen". 277 void screen_resize(int width, int height) 278 { 279 // Avoid recursiveness, can happen when setting the window size causes 280 // another window-changed signal. 281 if (updating_screen || resizing_screen || cmdline_number_prompt()) { 282 return; 283 } 284 285 if (width < 0 || height < 0) { // just checking... 286 return; 287 } 288 289 if (State == MODE_HITRETURN || State == MODE_SETWSIZE) { 290 // postpone the resizing 291 State = MODE_SETWSIZE; 292 return; 293 } 294 295 resizing_screen = true; 296 297 Rows = height; 298 Columns = width; 299 check_screensize(); 300 if (!ui_has(kUIMessages)) { 301 // clamp 'cmdheight' 302 int max_p_ch = Rows - min_rows(curtab) + 1; 303 if (p_ch > 0 && p_ch > max_p_ch) { 304 p_ch = MAX(max_p_ch, 1); 305 curtab->tp_ch_used = p_ch; 306 } 307 // clamp 'cmdheight' for other tab pages 308 FOR_ALL_TABS(tp) { 309 if (tp == curtab) { 310 continue; // already set above 311 } 312 int max_tp_ch = Rows - min_rows(tp) + 1; 313 if (tp->tp_ch_used > 0 && tp->tp_ch_used > max_tp_ch) { 314 tp->tp_ch_used = MAX(max_tp_ch, 1); 315 } 316 } 317 } 318 height = Rows; 319 width = Columns; 320 p_lines = Rows; 321 p_columns = Columns; 322 323 ui_call_grid_resize(1, width, height); 324 325 int retry_count = 0; 326 resizing_autocmd = true; 327 328 // In rare cases, autocommands may have altered Rows or Columns, 329 // so retry to check if we need to allocate the screen again. 330 while (default_grid_alloc()) { 331 // win_new_screensize will recompute floats position, but tell the 332 // compositor to not redraw them yet 333 ui_comp_set_screen_valid(false); 334 if (msg_grid.chars) { 335 msg_grid_invalid = true; 336 } 337 338 RedrawingDisabled++; 339 340 win_new_screensize(); // fit the windows in the new sized screen 341 342 comp_col(); // recompute columns for shown command and ruler 343 344 RedrawingDisabled--; 345 346 // Do not apply autocommands more than 3 times to avoid an endless loop 347 // in case applying autocommands always changes Rows or Columns. 348 if (++retry_count > 3) { 349 break; 350 } 351 352 apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); 353 } 354 355 resizing_autocmd = false; 356 redraw_all_later(UPD_CLEAR); 357 358 if (State != MODE_ASKMORE && State != MODE_EXTERNCMD) { 359 screenclear(); 360 } 361 362 if (starting != NO_SCREEN) { 363 maketitle(); 364 365 changed_line_abv_curs(); 366 invalidate_botline_win(curwin); 367 368 // We only redraw when it's needed: 369 // - While at the more prompt or executing an external command, don't 370 // redraw, but position the cursor. 371 // - While editing the command line, only redraw that. TODO: lies 372 // - in Ex mode, don't redraw anything. 373 // - Otherwise, redraw right now, and position the cursor. 374 if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || exmode_active 375 || ((State & MODE_CMDLINE) && get_cmdline_info()->one_key)) { 376 if (State & MODE_CMDLINE) { 377 update_screen(); 378 } 379 if (msg_grid.chars) { 380 msg_grid_validate(); 381 } 382 // TODO(bfredl): sometimes messes up the output. Implement clear+redraw 383 // also for the pager? (or: what if the pager was just a modal window?) 384 ui_comp_set_screen_valid(true); 385 repeat_message(); 386 } else { 387 if (curwin->w_p_scb) { 388 do_check_scrollbind(true); 389 } 390 if (State & MODE_CMDLINE) { 391 redraw_popupmenu = false; 392 update_screen(); 393 redrawcmdline(); 394 if (pum_drawn()) { 395 cmdline_pum_display(false); 396 } 397 } else { 398 update_topline(curwin); 399 if (pum_drawn()) { 400 // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. 401 // For now make sure the nested update_screen() won't redraw the 402 // pum at the old position. Try to untangle this later. 403 redraw_popupmenu = false; 404 ins_compl_show_pum(); 405 } 406 update_screen(); 407 if (redrawing()) { 408 setcursor(); 409 } 410 } 411 } 412 ui_flush(); 413 } 414 resizing_screen = false; 415 } 416 417 /// Check if the new Nvim application "screen" dimensions are valid. 418 /// Correct it if it's too small or way too big. 419 void check_screensize(void) 420 { 421 // Limit Rows and Columns to avoid an overflow in Rows * Columns. 422 // need room for one window and command line 423 Rows = MIN(MAX(Rows, min_rows_for_all_tabpages()), 1000); 424 Columns = MIN(MAX(Columns, MIN_COLUMNS), 10000); 425 } 426 427 /// Return true if redrawing should currently be done. 428 bool redrawing(void) 429 { 430 return !RedrawingDisabled 431 && !(p_lz && char_avail() && !KeyTyped && !do_redraw); 432 } 433 434 /// Redraw the parts of the screen that is marked for redraw. 435 /// 436 /// Most code shouldn't call this directly, rather use redraw_later() and 437 /// and redraw_all_later() to mark parts of the screen as needing a redraw. 438 int update_screen(void) 439 { 440 static bool still_may_intro = true; 441 if (still_may_intro) { 442 if (!may_show_intro()) { 443 redraw_later(firstwin, UPD_NOT_VALID); 444 still_may_intro = false; 445 } 446 } 447 448 bool is_stl_global = global_stl_height() > 0; 449 450 // Don't do anything if the screen structures are (not yet) valid. 451 // A VimResized autocmd can invoke redrawing in the middle of a resize, 452 // which would bypass the checks in screen_resize for popupmenu etc. 453 if (resizing_autocmd || !default_grid.chars) { 454 return FAIL; 455 } 456 457 // May have postponed updating diffs. 458 if (need_diff_redraw) { 459 diff_redraw(true); 460 } 461 462 // Postpone the redrawing when it's not needed and when being called 463 // recursively. 464 if (!redrawing() || updating_screen || cmdline_number_prompt()) { 465 return FAIL; 466 } 467 468 int type = must_redraw; 469 470 // must_redraw is reset here, so that when we run into some weird 471 // reason to redraw while busy redrawing (e.g., asynchronous 472 // scrolling), or update_topline() in win_update() will cause a 473 // scroll, or a decoration provider requires a redraw, the screen 474 // will be redrawn later or in win_update(). 475 must_redraw = 0; 476 477 updating_screen = true; 478 479 display_tick++; // let syntax code know we're in a next round of 480 // display updating 481 482 // glyph cache full, very rare 483 if (schar_cache_clear_if_full()) { 484 // must use CLEAR, as the contents of screen buffers cannot be 485 // compared to their previous state here. 486 // TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs) 487 // we need to revalidate these here as well! 488 type = MAX(type, UPD_CLEAR); 489 } 490 491 // Tricky: vim code can reset msg_scrolled behind our back, so need 492 // separate bookkeeping for now. 493 if (msg_did_scroll) { 494 msg_did_scroll = false; 495 msg_scrolled_at_flush = 0; 496 } 497 498 if (type >= UPD_CLEAR || !default_grid.valid) { 499 ui_comp_set_screen_valid(false); 500 } 501 502 // if the screen was scrolled up when displaying a message, scroll it down 503 if (msg_scrolled || msg_grid_invalid) { 504 clear_cmdline = true; 505 int valid = MAX(Rows - msg_scrollsize(), 0); 506 if (msg_grid.chars) { 507 // non-displayed part of msg_grid is considered invalid. 508 for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { 509 grid_clear_line(&msg_grid, msg_grid.line_offset[i], 510 msg_grid.cols, i < p_ch); 511 } 512 } 513 msg_grid.throttled = false; 514 bool was_invalidated = false; 515 516 // UPD_CLEAR is already handled 517 if (type == UPD_NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { 518 was_invalidated = ui_comp_set_screen_valid(false); 519 for (int i = valid; i < Rows - p_ch; i++) { 520 grid_clear_line(&default_grid, default_grid.line_offset[i], 521 Columns, false); 522 } 523 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 524 if (wp->w_floating) { 525 continue; 526 } 527 if (W_ENDROW(wp) > valid) { 528 // TODO(bfredl): too pessimistic. type could be UPD_NOT_VALID 529 // only because windows that are above the separator. 530 wp->w_redr_type = MAX(wp->w_redr_type, UPD_NOT_VALID); 531 } 532 if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { 533 wp->w_redr_status = true; 534 } 535 } 536 if (is_stl_global && Rows - p_ch - 1 > valid) { 537 curwin->w_redr_status = true; 538 } 539 } 540 msg_grid_set_pos(Rows - (int)p_ch, false); 541 msg_grid_invalid = false; 542 if (was_invalidated) { 543 // screen was only invalid for the msgarea part. 544 // @TODO(bfredl): using the same "valid" flag 545 // for both messages and floats moving is bit of a mess. 546 ui_comp_set_screen_valid(true); 547 } 548 msg_scrolled = 0; 549 msg_scrolled_at_flush = 0; 550 msg_grid_scroll_discount = 0; 551 need_wait_return = false; 552 } 553 554 win_ui_flush(true); 555 556 // reset cmdline_row now (may have been changed temporarily) 557 compute_cmdrow(); 558 559 bool hl_changed = false; 560 // Check for changed highlighting 561 if (need_highlight_changed) { 562 highlight_changed(); 563 hl_changed = true; 564 } 565 566 if (type == UPD_CLEAR) { // first clear screen 567 screenclear(); // will reset clear_cmdline 568 // and set UPD_NOT_VALID for each window 569 cmdline_screen_cleared(); // clear external cmdline state 570 if (ui_has(kUIMessages)) { 571 ui_call_msg_clear(); 572 } 573 type = UPD_NOT_VALID; 574 // must_redraw may be set indirectly, avoid another redraw later 575 must_redraw = 0; 576 } else if (!default_grid.valid) { 577 grid_invalidate(&default_grid); 578 default_grid.valid = true; 579 } 580 581 // might need to clear space on default_grid for the message area. 582 if (type == UPD_NOT_VALID && clear_cmdline && !ui_has(kUIMessages)) { 583 grid_clear(&default_gridview, Rows - (int)p_ch, Rows, 0, Columns, 0); 584 } 585 586 ui_comp_set_screen_valid(true); 587 588 decor_providers_start(); 589 590 // "start" callback could have changed highlights for global elements 591 if (win_check_ns_hl(NULL)) { 592 redraw_cmdline = true; 593 redraw_tabline = true; 594 } 595 596 if (clear_cmdline) { // going to clear cmdline (done below) 597 msg_check_for_delay(false); 598 } 599 600 // Force redraw when width of 'number' or 'relativenumber' column 601 // changes. 602 // TODO(bfredl): special casing curwin here is SÅ JÄVLA BULL. 603 // Either this should be done for all windows or not at all. 604 if (curwin->w_redr_type < UPD_NOT_VALID 605 && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu || *curwin->w_p_stc) 606 ? number_width(curwin) : 0)) { 607 curwin->w_redr_type = UPD_NOT_VALID; 608 } 609 610 if (curwin->w_redr_type == UPD_INVERTED) { 611 // Update w_curswant so that the end of Visual selection is correct. 612 update_curswant(); 613 } 614 615 // Redraw the tab pages line if needed. 616 if (redraw_tabline || type >= UPD_NOT_VALID) { 617 update_window_hl(curwin, type >= UPD_NOT_VALID); 618 FOR_ALL_TABS(tp) { 619 if (tp != curtab) { 620 update_window_hl(tp->tp_curwin, type >= UPD_NOT_VALID); 621 } 622 } 623 draw_tabline(); 624 } 625 626 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 627 // Correct stored syntax highlighting info for changes in each displayed 628 // buffer. Each buffer must only be done once. 629 update_window_hl(wp, type >= UPD_NOT_VALID || hl_changed); 630 631 buf_T *buf = wp->w_buffer; 632 if (buf->b_mod_set) { 633 if (buf->b_mod_tick_syn < display_tick 634 && syntax_present(wp)) { 635 syn_stack_apply_changes(buf); 636 buf->b_mod_tick_syn = display_tick; 637 } 638 639 if (buf->b_mod_tick_decor < display_tick) { 640 decor_providers_invoke_buf(buf); 641 buf->b_mod_tick_decor = display_tick; 642 } 643 } 644 } 645 646 // Go from top to bottom through the windows, redrawing the ones that need it. 647 bool did_one = false; 648 screen_search_hl.rm.regprog = NULL; 649 650 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 651 if (wp->w_redr_type == UPD_CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { 652 grid_invalidate(&wp->w_grid_alloc); 653 wp->w_redr_type = UPD_NOT_VALID; 654 } 655 656 win_check_ns_hl(wp); 657 658 // reallocate grid if needed. 659 win_grid_alloc(wp); 660 661 if (wp->w_redr_border || wp->w_redr_type >= UPD_NOT_VALID) { 662 grid_draw_border(&wp->w_grid_alloc, &wp->w_config, wp->w_border_adj, (int)wp->w_p_winbl, 663 wp->w_ns_hl_attr); 664 } 665 666 if (wp->w_redr_type != 0) { 667 if (!did_one) { 668 did_one = true; 669 start_search_hl(); 670 } 671 win_update(wp); 672 } 673 674 // redraw status line and window bar after the window to minimize cursor movement 675 if (wp->w_redr_status) { 676 win_redr_winbar(wp); 677 win_redr_status(wp); 678 } 679 } 680 681 // Draw separator connectors for all windows after all window updates, so that 682 // connectors overwrite vsep/hsep characters regardless of which windows were redrawn. 683 if (did_one) { 684 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 685 draw_sep_connectors_win(wp); 686 } 687 } 688 689 end_search_hl(); 690 691 // May need to redraw the popup menu. 692 if (pum_drawn() && must_redraw_pum) { 693 win_check_ns_hl(curwin); 694 pum_redraw(); 695 } else if (State & MODE_CMDLINE) { 696 pum_check_clear(); 697 } 698 699 win_check_ns_hl(NULL); 700 701 // Reset b_mod_set. Going through all windows is probably faster than going 702 // through all buffers (there could be many buffers). 703 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 704 wp->w_buffer->b_mod_set = false; 705 } 706 707 updating_screen = false; 708 709 if (need_maketitle) { 710 maketitle(); 711 } 712 713 // Clear or redraw the command line. Done last, because scrolling may 714 // mess up the command line. 715 if (clear_cmdline || redraw_cmdline || redraw_mode) { 716 showmode(); 717 } 718 719 // May put up an introductory message when not editing a file 720 if (still_may_intro) { 721 intro_message(false); 722 } 723 repeat_message(); 724 725 decor_providers_invoke_end(); 726 727 // Either cmdline is cleared, not drawn or mode is last drawn. 728 // This does not (necessarily) overwrite an external cmdline. 729 if (!ui_has(kUICmdline)) { 730 cmdline_was_last_drawn = false; 731 } 732 return OK; 733 } 734 735 /// Prepare for 'hlsearch' highlighting. 736 void start_search_hl(void) 737 { 738 if (!p_hls || no_hlsearch) { 739 return; 740 } 741 742 end_search_hl(); // just in case it wasn't called before 743 last_pat_prog(&screen_search_hl.rm); 744 // Set the time limit to 'redrawtime'. 745 screen_search_hl.tm = profile_setlimit(p_rdt); 746 } 747 748 /// Clean up for 'hlsearch' highlighting. 749 void end_search_hl(void) 750 { 751 if (screen_search_hl.rm.regprog == NULL) { 752 return; 753 } 754 755 vim_regfree(screen_search_hl.rm.regprog); 756 screen_search_hl.rm.regprog = NULL; 757 } 758 759 /// Set cursor to its position in the current window. 760 void setcursor(void) 761 { 762 setcursor_mayforce(curwin, false); 763 } 764 765 /// Set cursor to its position in the current window. 766 /// @param force when true, also when not redrawing. 767 void setcursor_mayforce(win_T *wp, bool force) 768 { 769 if (force || redrawing()) { 770 validate_cursor(wp); 771 772 int row = wp->w_wrow; 773 int col = wp->w_wcol; 774 if (wp->w_p_rl) { 775 // With 'rightleft' set and the cursor on a double-wide character, 776 // position it on the leftmost column. 777 char *cursor = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) + wp->w_cursor.col; 778 col = wp->w_view_width - wp->w_wcol - ((utf_ptr2cells(cursor) == 2 779 && vim_isprintc(utf_ptr2char(cursor))) ? 2 : 1); 780 } 781 782 ScreenGrid *grid = grid_adjust(&wp->w_grid, &row, &col); 783 if (grid) { 784 ui_grid_cursor_goto(grid->handle, row, col); 785 } 786 } 787 } 788 789 /// Mark the title and icon for redraw if either of them uses statusline format. 790 /// 791 /// @return whether either title or icon uses statusline format. 792 bool redraw_custom_title_later(void) 793 { 794 if ((p_icon && (stl_syntax & STL_IN_ICON)) 795 || (p_title && (stl_syntax & STL_IN_TITLE))) { 796 need_maketitle = true; 797 return true; 798 } 799 return false; 800 } 801 802 /// Show current cursor info in ruler and various other places 803 /// 804 /// @param always if false, only show ruler if position has changed. 805 void show_cursor_info_later(bool force) 806 { 807 int state = get_real_state(); 808 int empty_line = (State & MODE_INSERT) == 0 809 && *ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum) == NUL; 810 811 // Only draw when something changed. 812 validate_virtcol(curwin); 813 if (force 814 || curwin->w_cursor.lnum != curwin->w_stl_cursor.lnum 815 || curwin->w_cursor.col != curwin->w_stl_cursor.col 816 || curwin->w_virtcol != curwin->w_stl_virtcol 817 || curwin->w_cursor.coladd != curwin->w_stl_cursor.coladd 818 || curwin->w_topline != curwin->w_stl_topline 819 || curwin->w_buffer->b_ml.ml_line_count != curwin->w_stl_line_count 820 || curwin->w_topfill != curwin->w_stl_topfill 821 || empty_line != curwin->w_stl_empty 822 || reg_recording != curwin->w_stl_recording 823 || state != curwin->w_stl_state 824 || (VIsual_active && (VIsual_mode != curwin->w_stl_visual_mode 825 || VIsual.lnum != curwin->w_stl_visual_pos.lnum 826 || VIsual.col != curwin->w_stl_visual_pos.col 827 || VIsual.coladd != curwin->w_stl_visual_pos.coladd))) { 828 if (curwin->w_status_height || global_stl_height()) { 829 curwin->w_redr_status = true; 830 } else { 831 redraw_cmdline = true; 832 } 833 834 if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { 835 curwin->w_redr_status = true; 836 } 837 838 redraw_custom_title_later(); 839 } 840 841 curwin->w_stl_cursor = curwin->w_cursor; 842 curwin->w_stl_virtcol = curwin->w_virtcol; 843 curwin->w_stl_empty = (char)empty_line; 844 curwin->w_stl_topline = curwin->w_topline; 845 curwin->w_stl_line_count = curwin->w_buffer->b_ml.ml_line_count; 846 curwin->w_stl_topfill = curwin->w_topfill; 847 curwin->w_stl_recording = reg_recording; 848 curwin->w_stl_state = state; 849 if (VIsual_active) { 850 curwin->w_stl_visual_mode = VIsual_mode; 851 curwin->w_stl_visual_pos = VIsual; 852 } 853 } 854 855 /// @return true when postponing displaying the mode message: when not redrawing 856 /// or inside a mapping. 857 bool skip_showmode(void) 858 { 859 // Call char_avail() only when we are going to show something, because it 860 // takes a bit of time. redrawing() may also call char_avail(). 861 if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) { 862 redraw_mode = true; // show mode later 863 return true; 864 } 865 return false; 866 } 867 868 /// Show the current mode and ruler. 869 /// 870 /// If clear_cmdline is true, clear the rest of the cmdline. 871 /// If clear_cmdline is false there may be a message there that needs to be 872 /// cleared only if a mode is shown. 873 /// If redraw_mode is true show or clear the mode. 874 /// @return the length of the message (0 if no message). 875 int showmode(void) 876 { 877 int length = 0; 878 879 // Don't make non-flushed message part of the showmode. 880 msg_ext_ui_flush(); 881 882 msg_grid_validate(); 883 884 bool do_mode = ((p_smd && msg_silent == 0) 885 && ((State & MODE_TERMINAL) 886 || (State & MODE_INSERT) 887 || restart_edit != NUL 888 || VIsual_active)); 889 890 bool can_show_mode = (p_ch != 0 || ui_has(kUIMessages)); 891 if ((do_mode || reg_recording != 0) && can_show_mode) { 892 if (skip_showmode()) { 893 return 0; // show mode later 894 } 895 896 bool nwr_save = need_wait_return; 897 898 // wait a bit before overwriting an important message 899 msg_check_for_delay(false); 900 901 // if the cmdline is more than one line high, erase top lines 902 bool need_clear = clear_cmdline; 903 if (clear_cmdline && cmdline_row < Rows - 1) { 904 msg_clr_cmdline(); // will reset clear_cmdline 905 } 906 907 // Position on the last line in the window, column 0 908 msg_pos_mode(); 909 int hl_id = HLF_CM; // Highlight mode 910 911 // When the screen is too narrow to show the entire mode message, 912 // avoid scrolling and truncate instead. 913 msg_no_more = true; 914 int save_lines_left = lines_left; 915 lines_left = 0; 916 917 if (do_mode) { 918 msg_puts_hl("--", hl_id, false); 919 // CTRL-X in Insert mode 920 if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) { 921 // These messages can get long, avoid a wrap in a narrow window. 922 // Prefer showing edit_submode_extra. With external messages there 923 // is no imposed limit. 924 if (ui_has(kUIMessages)) { 925 length = INT_MAX; 926 } else { 927 length = (Rows - msg_row) * Columns - 3; 928 } 929 if (edit_submode_extra != NULL) { 930 length -= vim_strsize(edit_submode_extra); 931 } 932 if (length > 0) { 933 if (edit_submode_pre != NULL) { 934 length -= vim_strsize(edit_submode_pre); 935 } 936 if (length - vim_strsize(edit_submode) > 0) { 937 if (edit_submode_pre != NULL) { 938 msg_puts_hl(edit_submode_pre, hl_id, false); 939 } 940 msg_puts_hl(edit_submode, hl_id, false); 941 } 942 if (edit_submode_extra != NULL) { 943 msg_puts_hl(" ", hl_id, false); // Add a space in between. 944 int sub_id = edit_submode_highl < HLF_COUNT ? (int)edit_submode_highl : hl_id; 945 msg_puts_hl(edit_submode_extra, sub_id, false); 946 } 947 } 948 } else { 949 if (State & MODE_TERMINAL) { 950 msg_puts_hl(_(" TERMINAL"), hl_id, false); 951 } else if (State & VREPLACE_FLAG) { 952 msg_puts_hl(_(" VREPLACE"), hl_id, false); 953 } else if (State & REPLACE_FLAG) { 954 msg_puts_hl(_(" REPLACE"), hl_id, false); 955 } else if (State & MODE_INSERT) { 956 if (p_ri) { 957 msg_puts_hl(_(" REVERSE"), hl_id, false); 958 } 959 msg_puts_hl(_(" INSERT"), hl_id, false); 960 } else if (restart_edit == 'I' || restart_edit == 'i' 961 || restart_edit == 'a' || restart_edit == 'A') { 962 if (curbuf->terminal) { 963 msg_puts_hl(_(" (terminal)"), hl_id, false); 964 } else { 965 msg_puts_hl(_(" (insert)"), hl_id, false); 966 } 967 } else if (restart_edit == 'R') { 968 msg_puts_hl(_(" (replace)"), hl_id, false); 969 } else if (restart_edit == 'V') { 970 msg_puts_hl(_(" (vreplace)"), hl_id, false); 971 } 972 if (State & MODE_LANGMAP) { 973 if (curwin->w_p_arab) { 974 msg_puts_hl(_(" Arabic"), hl_id, false); 975 } else if (get_keymap_str(curwin, " (%s)", NameBuff, MAXPATHL) > 0) { 976 msg_puts_hl(NameBuff, hl_id, false); 977 } 978 } 979 if ((State & MODE_INSERT) && p_paste) { 980 msg_puts_hl(_(" (paste)"), hl_id, false); 981 } 982 983 if (VIsual_active) { 984 char *p; 985 986 // Don't concatenate separate words to avoid translation 987 // problems. 988 switch ((VIsual_select ? 4 : 0) 989 + (VIsual_mode == Ctrl_V) * 2 990 + (VIsual_mode == 'V')) { 991 case 0: 992 p = N_(" VISUAL"); break; 993 case 1: 994 p = N_(" VISUAL LINE"); break; 995 case 2: 996 p = N_(" VISUAL BLOCK"); break; 997 case 4: 998 p = N_(" SELECT"); break; 999 case 5: 1000 p = N_(" SELECT LINE"); break; 1001 default: 1002 p = N_(" SELECT BLOCK"); break; 1003 } 1004 msg_puts_hl(_(p), hl_id, false); 1005 } 1006 msg_puts_hl(" --", hl_id, false); 1007 } 1008 1009 need_clear = true; 1010 } 1011 if (reg_recording != 0 1012 && edit_submode == NULL // otherwise it gets too long 1013 ) { 1014 recording_mode(hl_id); 1015 need_clear = true; 1016 } 1017 1018 mode_displayed = true; 1019 if (need_clear || clear_cmdline || redraw_mode) { 1020 msg_clr_eos(); 1021 } 1022 msg_didout = false; // overwrite this message 1023 length = msg_col; 1024 msg_col = 0; 1025 msg_no_more = false; 1026 lines_left = save_lines_left; 1027 need_wait_return = nwr_save; // never ask for hit-return for this 1028 } else if (clear_cmdline && msg_silent == 0) { 1029 // Clear the whole command line. Will reset "clear_cmdline". 1030 msg_clr_cmdline(); 1031 } else if (redraw_mode) { 1032 msg_pos_mode(); 1033 msg_clr_eos(); 1034 } 1035 1036 // NB: also handles clearing the showmode if it was empty or disabled 1037 msg_ext_flush_showmode(); 1038 1039 // In Visual mode the size of the selected area must be redrawn. 1040 if (VIsual_active) { 1041 clear_showcmd(); 1042 } 1043 1044 redraw_ruler(); // check if ruler should be redrawn 1045 redraw_cmdline = false; 1046 redraw_mode = false; 1047 clear_cmdline = false; 1048 1049 return length; 1050 } 1051 1052 /// Position for a mode message. 1053 static void msg_pos_mode(void) 1054 { 1055 msg_col = 0; 1056 msg_row = Rows - 1; 1057 } 1058 1059 /// Delete mode message. Used when ESC is typed which is expected to end 1060 /// Insert mode (but Insert mode didn't end yet!). 1061 /// Caller should check "mode_displayed". 1062 void unshowmode(bool force) 1063 { 1064 // Don't delete it right now, when not redrawing or inside a mapping. 1065 if (!redrawing() || (!force && char_avail() && !KeyTyped)) { 1066 redraw_cmdline = true; // delete mode later 1067 } else { 1068 clearmode(); 1069 } 1070 } 1071 1072 // Clear the mode message. 1073 void clearmode(void) 1074 { 1075 const int save_msg_row = msg_row; 1076 const int save_msg_col = msg_col; 1077 1078 msg_ext_ui_flush(); 1079 msg_pos_mode(); 1080 if (reg_recording != 0) { 1081 recording_mode(HLF_CM); 1082 } 1083 msg_clr_eos(); 1084 msg_ext_flush_showmode(); 1085 1086 msg_col = save_msg_col; 1087 msg_row = save_msg_row; 1088 } 1089 1090 static void recording_mode(int hl_id) 1091 { 1092 if (shortmess(SHM_RECORDING)) { 1093 return; 1094 } 1095 1096 msg_puts_hl(_("recording"), hl_id, false); 1097 char s[4]; 1098 snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); 1099 msg_puts_hl(s, hl_id, false); 1100 } 1101 1102 #define COL_RULER 17 // columns needed by standard ruler 1103 1104 /// Compute columns for ruler and shown command. 'sc_col' is also used to 1105 /// decide what the maximum length of a message on the status line can be. 1106 /// If there is a status line for the last window, 'sc_col' is independent 1107 /// of 'ru_col'. 1108 void comp_col(void) 1109 { 1110 bool last_has_status = last_stl_height(false) > 0; 1111 1112 sc_col = 0; 1113 ru_col = 0; 1114 if (p_ru) { 1115 ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; 1116 // no last status line, adjust sc_col 1117 if (!last_has_status) { 1118 sc_col = ru_col; 1119 } 1120 } 1121 if (p_sc && *p_sloc == 'l') { 1122 sc_col += SHOWCMD_COLS; 1123 if (!p_ru || last_has_status) { // no need for separating space 1124 sc_col++; 1125 } 1126 } 1127 assert(sc_col >= 0 1128 && INT_MIN + sc_col <= Columns); 1129 sc_col = Columns - sc_col; 1130 assert(ru_col >= 0 1131 && INT_MIN + ru_col <= Columns); 1132 ru_col = Columns - ru_col; 1133 if (sc_col <= 0) { // screen too narrow, will become a mess 1134 sc_col = 1; 1135 } 1136 if (ru_col <= 0) { 1137 ru_col = 1; 1138 } 1139 set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); 1140 } 1141 1142 /// Redraw entire window "wp" if "auto" 'signcolumn' width has changed. 1143 static bool win_redraw_signcols(win_T *wp) 1144 { 1145 buf_T *buf = wp->w_buffer; 1146 1147 if (!buf->b_signcols.autom 1148 && (*wp->w_p_stc != NUL || (wp->w_maxscwidth > 1 && wp->w_minscwidth != wp->w_maxscwidth))) { 1149 buf->b_signcols.autom = true; 1150 buf_signcols_count_range(buf, 0, buf->b_ml.ml_line_count - 1, MAXLNUM, kFalse); 1151 } 1152 1153 while (buf->b_signcols.max > 0 && buf->b_signcols.count[buf->b_signcols.max - 1] == 0) { 1154 buf->b_signcols.max--; 1155 } 1156 1157 int width = MIN(wp->w_maxscwidth, buf->b_signcols.max); 1158 bool rebuild_stc = buf->b_signcols.max != buf->b_signcols.last_max && *wp->w_p_stc != NUL; 1159 1160 if (rebuild_stc) { 1161 wp->w_nrwidth_line_count = 0; 1162 } else if (wp->w_minscwidth == 0 && wp->w_maxscwidth == 1) { 1163 width = buf_meta_total(buf, kMTMetaSignText) > 0; 1164 } 1165 1166 int scwidth = wp->w_scwidth; 1167 wp->w_scwidth = MAX(MAX(0, wp->w_minscwidth), width); 1168 return (wp->w_scwidth != scwidth || rebuild_stc); 1169 } 1170 1171 /// Check if horizontal separator of window "wp" at specified window corner is connected to the 1172 /// horizontal separator of another window 1173 /// Assumes global statusline is enabled 1174 static bool hsep_connected(win_T *wp, WindowCorner corner) 1175 { 1176 bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); 1177 int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) 1178 ? wp->w_winrow - 1 : W_ENDROW(wp); 1179 frame_T *fr = wp->w_frame; 1180 1181 while (fr->fr_parent != NULL) { 1182 if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { 1183 fr = before ? fr->fr_prev : fr->fr_next; 1184 break; 1185 } 1186 fr = fr->fr_parent; 1187 } 1188 if (fr->fr_parent == NULL) { 1189 return false; 1190 } 1191 while (fr->fr_layout != FR_LEAF) { 1192 fr = fr->fr_child; 1193 if (fr->fr_parent->fr_layout == FR_ROW && before) { 1194 while (fr->fr_next != NULL) { 1195 fr = fr->fr_next; 1196 } 1197 } else { 1198 while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { 1199 fr = fr->fr_next; 1200 } 1201 } 1202 } 1203 1204 return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); 1205 } 1206 1207 /// Check if vertical separator of window "wp" at specified window corner is connected to the 1208 /// vertical separator of another window 1209 static bool vsep_connected(win_T *wp, WindowCorner corner) 1210 { 1211 bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); 1212 int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) 1213 ? wp->w_wincol - 1 : W_ENDCOL(wp); 1214 frame_T *fr = wp->w_frame; 1215 1216 while (fr->fr_parent != NULL) { 1217 if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { 1218 fr = before ? fr->fr_prev : fr->fr_next; 1219 break; 1220 } 1221 fr = fr->fr_parent; 1222 } 1223 if (fr->fr_parent == NULL) { 1224 return false; 1225 } 1226 while (fr->fr_layout != FR_LEAF) { 1227 fr = fr->fr_child; 1228 if (fr->fr_parent->fr_layout == FR_COL && before) { 1229 while (fr->fr_next != NULL) { 1230 fr = fr->fr_next; 1231 } 1232 } else { 1233 while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { 1234 fr = fr->fr_next; 1235 } 1236 } 1237 } 1238 1239 return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); 1240 } 1241 1242 /// Draw the vertical separator right of window "wp" 1243 static void draw_vsep_win(win_T *wp) 1244 { 1245 if (!wp->w_vsep_width) { 1246 return; 1247 } 1248 1249 // draw the vertical separator right of this window 1250 for (int row = wp->w_winrow; row < W_ENDROW(wp); row++) { 1251 grid_line_start(&default_gridview, row); 1252 grid_line_put_schar(W_ENDCOL(wp), wp->w_p_fcs_chars.vert, win_hl_attr(wp, HLF_C)); 1253 grid_line_flush(); 1254 } 1255 } 1256 1257 /// Draw the horizontal separator below window "wp" 1258 static void draw_hsep_win(win_T *wp) 1259 { 1260 if (!wp->w_hsep_height) { 1261 return; 1262 } 1263 1264 // draw the horizontal separator below this window 1265 grid_line_start(&default_gridview, W_ENDROW(wp)); 1266 grid_line_fill(wp->w_wincol, W_ENDCOL(wp), wp->w_p_fcs_chars.horiz, win_hl_attr(wp, HLF_C)); 1267 grid_line_flush(); 1268 } 1269 1270 /// Get the separator connector for specified window corner of window "wp" 1271 static schar_T get_corner_sep_connector(win_T *wp, WindowCorner corner) 1272 { 1273 // It's impossible for windows to be connected neither vertically nor horizontally 1274 // So if they're not vertically connected, assume they're horizontally connected 1275 if (vsep_connected(wp, corner)) { 1276 if (hsep_connected(wp, corner)) { 1277 return wp->w_p_fcs_chars.verthoriz; 1278 } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { 1279 return wp->w_p_fcs_chars.vertright; 1280 } else { 1281 return wp->w_p_fcs_chars.vertleft; 1282 } 1283 } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { 1284 return wp->w_p_fcs_chars.horizdown; 1285 } else { 1286 return wp->w_p_fcs_chars.horizup; 1287 } 1288 } 1289 1290 /// Draw separator connecting characters on the corners of window "wp" 1291 static void draw_sep_connectors_win(win_T *wp) 1292 { 1293 // Don't draw separator connectors unless global statusline is enabled and the window has 1294 // either a horizontal or vertical separator 1295 if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { 1296 return; 1297 } 1298 1299 int hl = win_hl_attr(wp, HLF_C); 1300 1301 // Determine which edges of the screen the window is located on so we can avoid drawing separators 1302 // on corners contained in those edges 1303 bool win_at_top; 1304 bool win_at_bottom = wp->w_hsep_height == 0; 1305 bool win_at_left; 1306 bool win_at_right = wp->w_vsep_width == 0; 1307 frame_T *frp; 1308 1309 for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { 1310 if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { 1311 break; 1312 } 1313 } 1314 win_at_top = frp->fr_parent == NULL; 1315 for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { 1316 if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { 1317 break; 1318 } 1319 } 1320 win_at_left = frp->fr_parent == NULL; 1321 1322 // Draw the appropriate separator connector in every corner where drawing them is necessary 1323 // Make sure not to send cursor position updates to ui. 1324 bool top_left = !(win_at_top || win_at_left); 1325 bool top_right = !(win_at_top || win_at_right); 1326 bool bot_left = !(win_at_bottom || win_at_left); 1327 bool bot_right = !(win_at_bottom || win_at_right); 1328 1329 if (top_left) { 1330 grid_line_start(&default_gridview, wp->w_winrow - 1); 1331 grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl); 1332 grid_line_flush(); 1333 } 1334 if (top_right) { 1335 grid_line_start(&default_gridview, wp->w_winrow - 1); 1336 grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl); 1337 grid_line_flush(); 1338 } 1339 if (bot_left) { 1340 grid_line_start(&default_gridview, W_ENDROW(wp)); 1341 grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl); 1342 grid_line_flush(); 1343 } 1344 if (bot_right) { 1345 grid_line_start(&default_gridview, W_ENDROW(wp)); 1346 grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl); 1347 grid_line_flush(); 1348 } 1349 } 1350 1351 /// Update a single window. 1352 /// 1353 /// This may cause the windows below it also to be redrawn (when clearing the 1354 /// screen or scrolling lines). 1355 /// 1356 /// How the window is redrawn depends on wp->w_redr_type. Each type also 1357 /// implies the one below it. 1358 /// UPD_NOT_VALID redraw the whole window 1359 /// UPD_SOME_VALID redraw the whole window but do scroll when possible 1360 /// UPD_REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like UPD_VALID 1361 /// UPD_INVERTED redraw the changed part of the Visual area 1362 /// UPD_INVERTED_ALL redraw the whole Visual area 1363 /// UPD_VALID 1. scroll up/down to adjust for a changed w_topline 1364 /// 2. update lines at the top when scrolled down 1365 /// 3. redraw changed text: 1366 /// - if wp->w_buffer->b_mod_set set, update lines between 1367 /// b_mod_top and b_mod_bot. 1368 /// - if wp->w_redraw_top non-zero, redraw lines between 1369 /// wp->w_redraw_top and wp->w_redraw_bot. 1370 /// - continue redrawing when syntax status is invalid. 1371 /// 4. if scrolled up, update lines at the bottom. 1372 /// This results in three areas that may need updating: 1373 /// top: from first row to top_end (when scrolled down) 1374 /// mid: from mid_start to mid_end (update inversion or changed text) 1375 /// bot: from bot_start to last row (when scrolled up) 1376 static void win_update(win_T *wp) 1377 { 1378 // Return early when the windows would overflow the shrunk terminal window 1379 // avoiding invalid drawing an assert failure 1380 if (wp->w_grid.target == &default_grid && wp->w_wincol >= Columns) { 1381 return; 1382 } 1383 1384 int top_end = 0; // Below last row of the top area that needs 1385 // updating. 0 when no top area updating. 1386 int mid_start = 999; // first row of the mid area that needs 1387 // updating. 999 when no mid area updating. 1388 int mid_end = 0; // Below last row of the mid area that needs 1389 // updating. 0 when no mid area updating. 1390 int bot_start = 999; // first row of the bot area that needs 1391 // updating. 999 when no bot area updating 1392 bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit 1393 bool scrolled_for_mod = false; // true after scrolling for changed lines 1394 bool top_to_mod = false; // redraw above mod_top 1395 1396 int bot_scroll_start = 999; // first line that needs to be redrawn due to 1397 // scrolling. only used for EOB 1398 1399 static bool recursive = false; // being called recursively 1400 1401 // Remember what happened to the previous line. 1402 enum { 1403 DID_NONE = 1, // didn't update a line 1404 DID_LINE = 2, // updated a normal line 1405 DID_FOLD = 3, // updated a folded line 1406 } did_update = DID_NONE; 1407 1408 linenr_T syntax_last_parsed = 0; // last parsed text line 1409 linenr_T mod_top = 0; 1410 linenr_T mod_bot = 0; 1411 1412 int type = wp->w_redr_type; 1413 1414 if (type >= UPD_NOT_VALID) { 1415 wp->w_redr_status = true; 1416 wp->w_lines_valid = 0; 1417 } 1418 1419 // Window is zero-height: Only need to draw the separator 1420 if (wp->w_view_height == 0) { 1421 // draw the horizontal separator below this window 1422 draw_hsep_win(wp); 1423 wp->w_redr_type = 0; 1424 return; 1425 } 1426 1427 // Window is zero-width: Only need to draw the separator. 1428 if (wp->w_view_width == 0) { 1429 // draw the vertical separator right of this window 1430 draw_vsep_win(wp); 1431 wp->w_redr_type = 0; 1432 return; 1433 } 1434 1435 buf_T *buf = wp->w_buffer; 1436 1437 // reset got_int, otherwise regexp won't work 1438 int save_got_int = got_int; 1439 got_int = 0; 1440 // Set the time limit to 'redrawtime'. 1441 proftime_T syntax_tm = profile_setlimit(p_rdt); 1442 syn_set_timeout(&syntax_tm); 1443 1444 win_extmark_arr.size = 0; 1445 1446 decor_redraw_reset(wp, &decor_state); 1447 1448 decor_providers_invoke_win(wp); 1449 1450 if (buf->terminal && terminal_suspended(buf->terminal)) { 1451 static VirtTextChunk chunk = { .text = "[Process suspended]", .hl_id = -1 }; 1452 static DecorVirtText virt_text = { 1453 .priority = DECOR_PRIORITY_BASE, 1454 .pos = kVPosWinCol, 1455 .data.virt_text = { .items = &chunk, .size = 1 }, 1456 }; 1457 decor_range_add_virt(&decor_state, buf->b_ml.ml_line_count - 1, 0, 1458 buf->b_ml.ml_line_count - 1, 0, &virt_text, false); 1459 } 1460 1461 FOR_ALL_WINDOWS_IN_TAB(win, curtab) { 1462 if (win->w_buffer == wp->w_buffer && win_redraw_signcols(win)) { 1463 changed_line_abv_curs_win(win); 1464 redraw_later(win, UPD_NOT_VALID); 1465 } 1466 } 1467 buf->b_signcols.last_max = buf->b_signcols.max; 1468 1469 init_search_hl(wp, &screen_search_hl); 1470 1471 // Make sure skipcol is valid, it depends on various options and the window 1472 // width. 1473 if (wp->w_skipcol > 0 && wp->w_view_width > win_col_off(wp)) { 1474 int w = 0; 1475 int width1 = wp->w_view_width - win_col_off(wp); 1476 int width2 = width1 + win_col_off2(wp); 1477 int add = width1; 1478 1479 while (w < wp->w_skipcol) { 1480 if (w > 0) { 1481 add = width2; 1482 } 1483 w += add; 1484 } 1485 if (w != wp->w_skipcol) { 1486 // always round down, the higher value may not be valid 1487 wp->w_skipcol = w - add; 1488 } 1489 } 1490 1491 const int nrwidth_before = wp->w_nrwidth; 1492 int nrwidth_new = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0; 1493 // Force redraw when width of 'number' or 'relativenumber' column changes. 1494 if (wp->w_nrwidth != nrwidth_new) { 1495 type = UPD_NOT_VALID; 1496 changed_line_abv_curs_win(wp); 1497 wp->w_nrwidth = nrwidth_new; 1498 } else { 1499 // Set mod_top to the first line that needs displaying because of 1500 // changes. Set mod_bot to the first line after the changes. 1501 mod_top = wp->w_redraw_top; 1502 if (wp->w_redraw_bot != 0) { 1503 mod_bot = wp->w_redraw_bot + 1; 1504 } else { 1505 mod_bot = 0; 1506 } 1507 if (buf->b_mod_set) { 1508 if (mod_top == 0 || mod_top > buf->b_mod_top) { 1509 mod_top = buf->b_mod_top; 1510 // Need to redraw lines above the change that may be included 1511 // in a pattern match. 1512 if (syntax_present(wp)) { 1513 mod_top -= buf->b_s.b_syn_sync_linebreaks; 1514 mod_top = MAX(mod_top, 1); 1515 } 1516 } 1517 if (mod_bot == 0 || mod_bot < buf->b_mod_bot) { 1518 mod_bot = buf->b_mod_bot; 1519 } 1520 1521 // When 'hlsearch' is on and using a multi-line search pattern, a 1522 // change in one line may make the Search highlighting in a 1523 // previous line invalid. Simple solution: redraw all visible 1524 // lines above the change. 1525 // Same for a match pattern. 1526 if (screen_search_hl.rm.regprog != NULL 1527 && re_multiline(screen_search_hl.rm.regprog)) { 1528 top_to_mod = true; 1529 } else { 1530 const matchitem_T *cur = wp->w_match_head; 1531 while (cur != NULL) { 1532 if (cur->mit_match.regprog != NULL 1533 && re_multiline(cur->mit_match.regprog)) { 1534 top_to_mod = true; 1535 break; 1536 } 1537 cur = cur->mit_next; 1538 } 1539 } 1540 } 1541 1542 if (search_hl_has_cursor_lnum > 0) { 1543 // CurSearch was used last time, need to redraw the line with it to 1544 // avoid having two matches highlighted with CurSearch. 1545 if (mod_top == 0 || mod_top > search_hl_has_cursor_lnum) { 1546 mod_top = search_hl_has_cursor_lnum; 1547 } 1548 if (mod_bot == 0 || mod_bot < search_hl_has_cursor_lnum + 1) { 1549 mod_bot = search_hl_has_cursor_lnum + 1; 1550 } 1551 } 1552 1553 if (mod_top != 0 && win_lines_concealed(wp)) { 1554 // A change in a line can cause lines above it to become folded or 1555 // unfolded. Find the top most buffer line that may be affected. 1556 // If the line was previously folded and displayed, get the first 1557 // line of that fold. If the line is folded now, get the first 1558 // folded line. Use the minimum of these two. 1559 1560 // Find last valid w_lines[] entry above mod_top. Set lnumt to 1561 // the line below it. If there is no valid entry, use w_topline. 1562 // Find the first valid w_lines[] entry below mod_bot. Set lnumb 1563 // to this line. If there is no valid entry, use MAXLNUM. 1564 linenr_T lnumt = wp->w_topline; 1565 linenr_T lnumb = MAXLNUM; 1566 for (int i = 0; i < wp->w_lines_valid; i++) { 1567 if (wp->w_lines[i].wl_valid) { 1568 if (wp->w_lines[i].wl_lastlnum < mod_top) { 1569 lnumt = wp->w_lines[i].wl_lastlnum + 1; 1570 } 1571 if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) { 1572 lnumb = wp->w_lines[i].wl_lnum; 1573 // When there is a fold column it might need updating 1574 // in the next line ("J" just above an open fold). 1575 if (compute_foldcolumn(wp, 0) > 0) { 1576 lnumb++; 1577 } 1578 } 1579 } 1580 } 1581 1582 hasFolding(wp, mod_top, &mod_top, NULL); 1583 mod_top = MIN(mod_top, lnumt); 1584 1585 // Now do the same for the bottom line (one above mod_bot). 1586 mod_bot--; 1587 hasFolding(wp, mod_bot, NULL, &mod_bot); 1588 mod_bot++; 1589 mod_bot = MAX(mod_bot, lnumb); 1590 } 1591 1592 // When a change starts above w_topline and the end is below 1593 // w_topline, start redrawing at w_topline. 1594 // If the end of the change is above w_topline: do like no change was 1595 // made, but redraw the first line to find changes in syntax. 1596 if (mod_top != 0 && mod_top < wp->w_topline) { 1597 if (mod_bot > wp->w_topline) { 1598 mod_top = wp->w_topline; 1599 } else if (syntax_present(wp)) { 1600 top_end = 1; 1601 } 1602 } 1603 } 1604 1605 wp->w_redraw_top = 0; // reset for next time 1606 wp->w_redraw_bot = 0; 1607 search_hl_has_cursor_lnum = 0; 1608 1609 // When only displaying the lines at the top, set top_end. Used when 1610 // window has scrolled down for msg_scrolled. 1611 if (type == UPD_REDRAW_TOP) { 1612 int j = 0; 1613 for (int i = 0; i < wp->w_lines_valid; i++) { 1614 j += wp->w_lines[i].wl_size; 1615 if (j >= wp->w_upd_rows) { 1616 top_end = j; 1617 break; 1618 } 1619 } 1620 if (top_end == 0) { 1621 // not found (cannot happen?): redraw everything 1622 type = UPD_NOT_VALID; 1623 } else { 1624 // top area defined, the rest is UPD_VALID 1625 type = UPD_VALID; 1626 } 1627 } 1628 1629 // Below logic compares wp->w_topline against wp->w_lines[0].wl_lnum, 1630 // which may point to a line below wp->w_topline if it is concealed; 1631 // incurring scrolling even though wp->w_topline is still the same. 1632 // Compare against an adjusted topline instead: 1633 linenr_T topline_conceal = wp->w_topline; 1634 while (topline_conceal < buf->b_ml.ml_line_count 1635 && decor_conceal_line(wp, topline_conceal - 1, false)) { 1636 topline_conceal++; 1637 hasFolding(wp, topline_conceal, NULL, &topline_conceal); 1638 } 1639 1640 // If there are no changes on the screen that require a complete redraw, 1641 // handle three cases: 1642 // 1: we are off the top of the screen by a few lines: scroll down 1643 // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up 1644 // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in 1645 // w_lines[] that needs updating. 1646 if ((type == UPD_VALID || type == UPD_SOME_VALID 1647 || type == UPD_INVERTED || type == UPD_INVERTED_ALL) 1648 && !wp->w_botfill && !wp->w_old_botfill) { 1649 if (mod_top != 0 1650 && wp->w_topline == mod_top 1651 && (!wp->w_lines[0].wl_valid 1652 || topline_conceal == wp->w_lines[0].wl_lnum)) { 1653 // w_topline is the first changed line and window is not scrolled, 1654 // the scrolling from changed lines will be done further down. 1655 } else if (wp->w_lines[0].wl_valid 1656 && (topline_conceal < wp->w_lines[0].wl_lnum 1657 || (topline_conceal == wp->w_lines[0].wl_lnum 1658 && wp->w_topfill > wp->w_old_topfill))) { 1659 // New topline is above old topline: May scroll down. 1660 int j; 1661 if (win_lines_concealed(wp)) { 1662 // Count the number of lines we are off, counting a sequence 1663 // of folded lines as one, and skip concealed lines. 1664 j = 0; 1665 for (linenr_T ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { 1666 j += !decor_conceal_line(wp, ln - 1, false); 1667 if (j >= wp->w_view_height - 2) { 1668 break; 1669 } 1670 hasFolding(wp, ln, NULL, &ln); 1671 } 1672 } else { 1673 j = wp->w_lines[0].wl_lnum - wp->w_topline; 1674 } 1675 if (j < wp->w_view_height - 2) { // not too far off 1676 int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1, wp->w_view_height); 1677 // insert extra lines for previously invisible filler lines 1678 if (wp->w_lines[0].wl_lnum != wp->w_topline) { 1679 i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill; 1680 } 1681 if (i != 0 && i < wp->w_view_height - 2) { // less than a screen off 1682 // Try to insert the correct number of lines. 1683 // If not the last window, delete the lines at the bottom. 1684 // win_ins_lines may fail when the terminal can't do it. 1685 win_scroll_lines(wp, 0, i); 1686 bot_scroll_start = 0; 1687 if (wp->w_lines_valid != 0) { 1688 // Need to update rows that are new, stop at the 1689 // first one that scrolled down. 1690 top_end = i; 1691 scrolled_down = true; 1692 1693 // Move the entries that were scrolled, disable 1694 // the entries for the lines to be redrawn. 1695 if ((wp->w_lines_valid += (linenr_T)j) > wp->w_view_height) { 1696 wp->w_lines_valid = wp->w_view_height; 1697 } 1698 int idx; 1699 for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { 1700 wp->w_lines[idx] = wp->w_lines[idx - j]; 1701 } 1702 while (idx >= 0) { 1703 wp->w_lines[idx--].wl_valid = false; 1704 } 1705 } 1706 } else { 1707 mid_start = 0; // redraw all lines 1708 } 1709 } else { 1710 mid_start = 0; // redraw all lines 1711 } 1712 } else { 1713 // New topline is at or below old topline: May scroll up. 1714 // When topline didn't change, find first entry in w_lines[] that 1715 // needs updating. 1716 1717 // try to find wp->w_topline in wp->w_lines[].wl_lnum 1718 int j = -1; 1719 int row = 0; 1720 for (int i = 0; i < wp->w_lines_valid; i++) { 1721 if (wp->w_lines[i].wl_valid 1722 && wp->w_lines[i].wl_lnum == wp->w_topline) { 1723 j = i; 1724 break; 1725 } 1726 row += wp->w_lines[i].wl_size; 1727 } 1728 if (j == -1) { 1729 // if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all 1730 // lines 1731 mid_start = 0; 1732 } else { 1733 // Try to delete the correct number of lines. 1734 // wp->w_topline is at wp->w_lines[i].wl_lnum. 1735 1736 // If the topline didn't change, delete old filler lines, 1737 // otherwise delete filler lines of the new topline... 1738 if (wp->w_lines[0].wl_lnum == wp->w_topline) { 1739 row += wp->w_old_topfill; 1740 } else { 1741 row += win_get_fill(wp, wp->w_topline); 1742 } 1743 // ... but don't delete new filler lines. 1744 row -= wp->w_topfill; 1745 if (row > 0) { 1746 win_scroll_lines(wp, 0, -row); 1747 bot_start = wp->w_view_height - row; 1748 bot_scroll_start = bot_start; 1749 } 1750 if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { 1751 // Skip the lines (below the deleted lines) that are still 1752 // valid and don't need redrawing. Copy their info 1753 // upwards, to compensate for the deleted lines. Set 1754 // bot_start to the first row that needs redrawing. 1755 bot_start = 0; 1756 int idx = 0; 1757 while (true) { 1758 wp->w_lines[idx] = wp->w_lines[j]; 1759 // stop at line that didn't fit, unless it is still 1760 // valid (no lines deleted) 1761 if (row > 0 && bot_start + row 1762 + (int)wp->w_lines[j].wl_size > wp->w_view_height) { 1763 wp->w_lines_valid = idx + 1; 1764 break; 1765 } 1766 bot_start += wp->w_lines[idx++].wl_size; 1767 1768 // stop at the last valid entry in w_lines[].wl_size 1769 if (++j >= wp->w_lines_valid) { 1770 wp->w_lines_valid = idx; 1771 break; 1772 } 1773 } 1774 1775 // Correct the first entry for filler lines at the top 1776 // when it won't get updated below. 1777 if (win_may_fill(wp) && bot_start > 0) { 1778 wp->w_lines[0].wl_size 1779 = (uint16_t)plines_correct_topline(wp, wp->w_topline, NULL, true, NULL); 1780 } 1781 } 1782 } 1783 } 1784 1785 // When starting redraw in the first line, redraw all lines. 1786 if (mid_start == 0) { 1787 mid_end = wp->w_view_height; 1788 } 1789 } else { 1790 // Not UPD_VALID or UPD_INVERTED: redraw all lines. 1791 mid_start = 0; 1792 mid_end = wp->w_view_height; 1793 } 1794 1795 if (type == UPD_SOME_VALID) { 1796 // UPD_SOME_VALID: redraw all lines. 1797 mid_start = 0; 1798 mid_end = wp->w_view_height; 1799 type = UPD_NOT_VALID; 1800 } 1801 1802 // check if we are updating or removing the inverted part 1803 if ((VIsual_active && buf == curwin->w_buffer) 1804 || (wp->w_old_cursor_lnum != 0 && type != UPD_NOT_VALID)) { 1805 linenr_T from, to; 1806 1807 if (VIsual_active) { 1808 if (VIsual_mode != wp->w_old_visual_mode || type == UPD_INVERTED_ALL) { 1809 // If the type of Visual selection changed, redraw the whole 1810 // selection. Also when the ownership of the X selection is 1811 // gained or lost. 1812 if (curwin->w_cursor.lnum < VIsual.lnum) { 1813 from = curwin->w_cursor.lnum; 1814 to = VIsual.lnum; 1815 } else { 1816 from = VIsual.lnum; 1817 to = curwin->w_cursor.lnum; 1818 } 1819 // redraw more when the cursor moved as well 1820 from = MIN(MIN(from, wp->w_old_cursor_lnum), wp->w_old_visual_lnum); 1821 to = MAX(MAX(to, wp->w_old_cursor_lnum), wp->w_old_visual_lnum); 1822 } else { 1823 // Find the line numbers that need to be updated: The lines 1824 // between the old cursor position and the current cursor 1825 // position. Also check if the Visual position changed. 1826 if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) { 1827 from = curwin->w_cursor.lnum; 1828 to = wp->w_old_cursor_lnum; 1829 } else { 1830 from = wp->w_old_cursor_lnum; 1831 to = curwin->w_cursor.lnum; 1832 if (from == 0) { // Visual mode just started 1833 from = to; 1834 } 1835 } 1836 1837 if (VIsual.lnum != wp->w_old_visual_lnum 1838 || VIsual.col != wp->w_old_visual_col) { 1839 if (wp->w_old_visual_lnum < from 1840 && wp->w_old_visual_lnum != 0) { 1841 from = wp->w_old_visual_lnum; 1842 } 1843 to = MAX(MAX(to, wp->w_old_visual_lnum), VIsual.lnum); 1844 from = MIN(from, VIsual.lnum); 1845 } 1846 } 1847 1848 // If in block mode and changed column or curwin->w_curswant: 1849 // update all lines. 1850 // First compute the actual start and end column. 1851 if (VIsual_mode == Ctrl_V) { 1852 colnr_T fromc, toc; 1853 unsigned save_ve_flags = curwin->w_ve_flags; 1854 1855 if (curwin->w_p_lbr) { 1856 curwin->w_ve_flags = kOptVeFlagAll; 1857 } 1858 1859 getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); 1860 toc++; 1861 curwin->w_ve_flags = save_ve_flags; 1862 // Highlight to the end of the line, unless 'virtualedit' has 1863 // "block". 1864 if (curwin->w_curswant == MAXCOL) { 1865 if (get_ve_flags(curwin) & kOptVeFlagBlock) { 1866 pos_T pos; 1867 int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; 1868 1869 // Need to find the longest line. 1870 toc = 0; 1871 pos.coladd = 0; 1872 for (pos.lnum = curwin->w_cursor.lnum; 1873 cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; 1874 pos.lnum += cursor_above ? 1 : -1) { 1875 colnr_T t; 1876 1877 pos.col = ml_get_buf_len(wp->w_buffer, pos.lnum); 1878 getvvcol(wp, &pos, NULL, NULL, &t); 1879 toc = MAX(toc, t); 1880 } 1881 toc++; 1882 } else { 1883 toc = MAXCOL; 1884 } 1885 } 1886 1887 if (fromc != wp->w_old_cursor_fcol 1888 || toc != wp->w_old_cursor_lcol) { 1889 from = MIN(from, VIsual.lnum); 1890 to = MAX(to, VIsual.lnum); 1891 } 1892 wp->w_old_cursor_fcol = fromc; 1893 wp->w_old_cursor_lcol = toc; 1894 } 1895 } else { 1896 // Use the line numbers of the old Visual area. 1897 if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) { 1898 from = wp->w_old_cursor_lnum; 1899 to = wp->w_old_visual_lnum; 1900 } else { 1901 from = wp->w_old_visual_lnum; 1902 to = wp->w_old_cursor_lnum; 1903 } 1904 } 1905 1906 // There is no need to update lines above the top of the window. 1907 from = MAX(from, wp->w_topline); 1908 1909 // If we know the value of w_botline, use it to restrict the update to 1910 // the lines that are visible in the window. 1911 if (wp->w_valid & VALID_BOTLINE) { 1912 from = MIN(from, wp->w_botline - 1); 1913 to = MIN(to, wp->w_botline - 1); 1914 } 1915 1916 // Find the minimal part to be updated. 1917 // Watch out for scrolling that made entries in w_lines[] invalid. 1918 // E.g., CTRL-U makes the first half of w_lines[] invalid and sets 1919 // top_end; need to redraw from top_end to the "to" line. 1920 // A middle mouse click with a Visual selection may change the text 1921 // above the Visual area and reset wl_valid, do count these for 1922 // mid_end (in srow). 1923 if (mid_start > 0) { 1924 linenr_T lnum = wp->w_topline; 1925 int idx = 0; 1926 int srow = 0; 1927 if (scrolled_down) { 1928 mid_start = top_end; 1929 } else { 1930 mid_start = 0; 1931 } 1932 while (lnum < from && idx < wp->w_lines_valid) { // find start 1933 if (wp->w_lines[idx].wl_valid) { 1934 mid_start += wp->w_lines[idx].wl_size; 1935 } else if (!scrolled_down) { 1936 srow += wp->w_lines[idx].wl_size; 1937 } 1938 idx++; 1939 if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) { 1940 lnum = wp->w_lines[idx].wl_lnum; 1941 } else { 1942 lnum++; 1943 } 1944 } 1945 srow += mid_start; 1946 mid_end = wp->w_view_height; 1947 for (; idx < wp->w_lines_valid; idx++) { // find end 1948 if (wp->w_lines[idx].wl_valid 1949 && wp->w_lines[idx].wl_lnum >= to + 1) { 1950 // Only update until first row of this line 1951 mid_end = srow; 1952 break; 1953 } 1954 srow += wp->w_lines[idx].wl_size; 1955 } 1956 } 1957 } 1958 1959 if (VIsual_active && buf == curwin->w_buffer) { 1960 wp->w_old_visual_mode = (char)VIsual_mode; 1961 wp->w_old_cursor_lnum = curwin->w_cursor.lnum; 1962 wp->w_old_visual_lnum = VIsual.lnum; 1963 wp->w_old_visual_col = VIsual.col; 1964 wp->w_old_curswant = curwin->w_curswant; 1965 } else { 1966 wp->w_old_visual_mode = 0; 1967 wp->w_old_cursor_lnum = 0; 1968 wp->w_old_visual_lnum = 0; 1969 wp->w_old_visual_col = 0; 1970 } 1971 1972 foldinfo_T cursorline_fi = { 0 }; 1973 win_update_cursorline(wp, &cursorline_fi); 1974 if (wp == curwin) { 1975 conceal_cursor_used = conceal_cursor_line(curwin); 1976 } 1977 1978 win_check_ns_hl(wp); 1979 1980 spellvars_T spv = { 0 }; 1981 linenr_T lnum = wp->w_topline; // first line shown in window 1982 // Initialize spell related variables for the first drawn line. 1983 if (spell_check_window(wp)) { 1984 spv.spv_has_spell = true; 1985 spv.spv_unchanged = mod_top == 0; 1986 } 1987 1988 // Update all the window rows. 1989 int idx = 0; // first entry in w_lines[].wl_size 1990 int row = 0; // current window row to display 1991 int srow = 0; // starting row of the current line 1992 1993 bool eof = false; // if true, we hit the end of the file 1994 bool didline = false; // if true, we finished the last line 1995 while (true) { 1996 // stop updating when reached the end of the window (check for _past_ 1997 // the end of the window is at the end of the loop) 1998 if (row == wp->w_view_height) { 1999 didline = true; 2000 break; 2001 } 2002 2003 // stop updating when hit the end of the file 2004 if (lnum > buf->b_ml.ml_line_count) { 2005 eof = true; 2006 break; 2007 } 2008 2009 // Remember the starting row of the line that is going to be dealt 2010 // with. It is used further down when the line doesn't fit. 2011 srow = row; 2012 2013 // Update a line when it is in an area that needs updating, when it 2014 // has changes or w_lines[idx] is invalid. 2015 // "bot_start" may be halfway a wrapped line after using 2016 // win_scroll_lines(), check if the current line includes it. 2017 // When syntax folding is being used, the saved syntax states will 2018 // already have been updated, we can't see where the syntax state is 2019 // the same again, just update until the end of the window. 2020 if (row < top_end 2021 || (row >= mid_start && row < mid_end) 2022 || top_to_mod 2023 || idx >= wp->w_lines_valid 2024 || (row + wp->w_lines[idx].wl_size > bot_start) 2025 || (mod_top != 0 2026 && (lnum == mod_top 2027 || (lnum >= mod_top 2028 && (lnum < mod_bot 2029 || did_update == DID_FOLD 2030 || (did_update == DID_LINE 2031 && syntax_present(wp) 2032 && ((foldmethodIsSyntax(wp) 2033 && hasAnyFolding(wp)) 2034 || syntax_check_changed(lnum))) 2035 // match in fixed position might need redraw 2036 // if lines were inserted or deleted 2037 || (wp->w_match_head != NULL 2038 && buf->b_mod_set && buf->b_mod_xlines != 0))))) 2039 || lnum == wp->w_cursorline 2040 || lnum == wp->w_last_cursorline) { 2041 if (lnum == mod_top) { 2042 top_to_mod = false; 2043 } 2044 2045 // When lines are folded, display one line for all of them. 2046 // Otherwise, display normally (can be several display lines when 2047 // 'wrap' is on). 2048 foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum 2049 ? cursorline_fi : fold_info(wp, lnum); 2050 2051 // If the line is concealed and has no filler lines, go to the next line. 2052 bool concealed = decor_conceal_line(wp, lnum - 1, false); 2053 if (concealed && win_get_fill(wp, lnum) == 0) { 2054 if (lnum == mod_top && lnum < mod_bot) { 2055 mod_top += foldinfo.fi_lines ? foldinfo.fi_lines : 1; 2056 } 2057 lnum += foldinfo.fi_lines ? foldinfo.fi_lines : 1; 2058 spv.spv_capcol_lnum = 0; 2059 continue; 2060 } 2061 2062 // When at start of changed lines: May scroll following lines 2063 // up or down to minimize redrawing. 2064 // Don't do this when the change continues until the end. 2065 // Don't scroll for changed lines in the top area if that's already 2066 // done above, but do scroll for changed lines below the top area. 2067 if (!scrolled_for_mod && mod_bot != MAXLNUM 2068 && lnum >= mod_top && lnum < MAX(mod_bot, mod_top + 1) 2069 && (!scrolled_down || row >= top_end)) { 2070 scrolled_for_mod = true; 2071 2072 int old_cline_height = 0; 2073 int old_rows = 0; 2074 linenr_T l; 2075 int i; 2076 2077 // Count the old number of window rows, using w_lines[], which 2078 // should still contain the sizes for the lines as they are 2079 // currently displayed. 2080 for (i = idx; i < wp->w_lines_valid; i++) { 2081 // Only valid lines have a meaningful wl_lnum. Invalid 2082 // lines are part of the changed area. 2083 if (wp->w_lines[i].wl_valid 2084 && wp->w_lines[i].wl_lnum == mod_bot) { 2085 break; 2086 } 2087 if (wp->w_lines[i].wl_lnum == wp->w_cursor.lnum) { 2088 old_cline_height = wp->w_lines[i].wl_size; 2089 } 2090 old_rows += wp->w_lines[i].wl_size; 2091 if (wp->w_lines[i].wl_valid 2092 && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) { 2093 // Must have found the last valid entry above mod_bot. 2094 // Add following invalid entries. 2095 i++; 2096 while (i < wp->w_lines_valid 2097 && !wp->w_lines[i].wl_valid) { 2098 old_rows += wp->w_lines[i++].wl_size; 2099 } 2100 break; 2101 } 2102 } 2103 2104 if (i >= wp->w_lines_valid) { 2105 // We can't find a valid line below the changed lines, 2106 // need to redraw until the end of the window. 2107 // Inserting/deleting lines has no use. 2108 bot_start = 0; 2109 bot_scroll_start = 0; 2110 } else { 2111 int new_rows = 0; 2112 // Able to count old number of rows: Count new window 2113 // rows, and may insert/delete lines 2114 int j = idx; 2115 for (l = lnum; l < mod_bot; l++) { 2116 if (dollar_vcol >= 0 && wp == curwin 2117 && old_cline_height > 0 && l == wp->w_cursor.lnum) { 2118 // When dollar_vcol >= 0, cursor line isn't fully 2119 // redrawn, and its height remains unchanged. 2120 new_rows += old_cline_height; 2121 j++; 2122 } else { 2123 int n = plines_correct_topline(wp, l, &l, true, NULL); 2124 new_rows += n; 2125 j += n > 0; // don't count concealed lines 2126 } 2127 if (new_rows > wp->w_view_height - row - 2) { 2128 // it's getting too much, must redraw the rest 2129 new_rows = 9999; 2130 break; 2131 } 2132 } 2133 int xtra_rows = new_rows - old_rows; 2134 if (xtra_rows < 0) { 2135 // May scroll text up. If there is not enough 2136 // remaining text or scrolling fails, must redraw the 2137 // rest. If scrolling works, must redraw the text 2138 // below the scrolled text. 2139 if (row - xtra_rows >= wp->w_view_height - 2) { 2140 mod_bot = MAXLNUM; 2141 } else { 2142 win_scroll_lines(wp, row, xtra_rows); 2143 bot_start = wp->w_view_height + xtra_rows; 2144 bot_scroll_start = bot_start; 2145 } 2146 } else if (xtra_rows > 0) { 2147 // May scroll text down. If there is not enough 2148 // remaining text of scrolling fails, must redraw the 2149 // rest. 2150 if (row + xtra_rows >= wp->w_view_height - 2) { 2151 mod_bot = MAXLNUM; 2152 } else { 2153 win_scroll_lines(wp, row + old_rows, xtra_rows); 2154 bot_scroll_start = 0; 2155 if (top_end > row + old_rows) { 2156 // Scrolled the part at the top that requires 2157 // updating down. 2158 top_end += xtra_rows; 2159 } 2160 } 2161 } 2162 2163 // When not updating the rest, may need to move w_lines[] 2164 // entries. 2165 if (mod_bot != MAXLNUM && i != j) { 2166 if (j < i) { 2167 int x = row + new_rows; 2168 2169 // move entries in w_lines[] upwards 2170 while (true) { 2171 // stop at last valid entry in w_lines[] 2172 if (i >= wp->w_lines_valid) { 2173 wp->w_lines_valid = j; 2174 break; 2175 } 2176 wp->w_lines[j] = wp->w_lines[i]; 2177 // stop at a line that won't fit 2178 if (x + (int)wp->w_lines[j].wl_size 2179 > wp->w_view_height) { 2180 wp->w_lines_valid = j + 1; 2181 break; 2182 } 2183 x += wp->w_lines[j++].wl_size; 2184 i++; 2185 } 2186 bot_start = MIN(bot_start, x); 2187 } else { // j > i 2188 // move entries in w_lines[] downwards 2189 j -= i; 2190 wp->w_lines_valid += (linenr_T)j; 2191 wp->w_lines_valid = MIN(wp->w_lines_valid, wp->w_view_height); 2192 for (i = wp->w_lines_valid; i - j >= idx; i--) { 2193 wp->w_lines[i] = wp->w_lines[i - j]; 2194 } 2195 2196 // The w_lines[] entries for inserted lines are 2197 // now invalid, but wl_size may be used above. 2198 // Reset to zero. 2199 while (i >= idx) { 2200 wp->w_lines[i].wl_size = 0; 2201 wp->w_lines[i--].wl_valid = false; 2202 } 2203 } 2204 } 2205 } 2206 } 2207 2208 if (foldinfo.fi_lines == 0 2209 && idx < wp->w_lines_valid 2210 && wp->w_lines[idx].wl_valid 2211 && wp->w_lines[idx].wl_lnum == lnum 2212 && lnum > wp->w_topline 2213 && !(dy_flags & (kOptDyFlagLastline | kOptDyFlagTruncate)) 2214 && srow + wp->w_lines[idx].wl_size > wp->w_view_height 2215 && win_get_fill(wp, lnum) == 0) { 2216 // This line is not going to fit. Don't draw anything here, 2217 // will draw "@ " lines below. 2218 row = wp->w_view_height + 1; 2219 } else { 2220 prepare_search_hl(wp, &screen_search_hl, lnum); 2221 // Let the syntax stuff know we skipped a few lines. 2222 if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum 2223 && syntax_present(wp)) { 2224 syntax_end_parsing(wp, syntax_last_parsed + 1); 2225 } 2226 2227 bool display_buf_line = !concealed && (foldinfo.fi_lines == 0 || *wp->w_p_fdt == NUL); 2228 2229 // Display one line 2230 spellvars_T zero_spv = { 0 }; 2231 row = win_line(wp, lnum, srow, wp->w_view_height, 0, concealed, 2232 display_buf_line ? &spv : &zero_spv, foldinfo); 2233 2234 if (display_buf_line) { 2235 syntax_last_parsed = lnum; 2236 } else { 2237 spv.spv_capcol_lnum = 0; 2238 } 2239 2240 linenr_T lastlnum = lnum + foldinfo.fi_lines - (foldinfo.fi_lines > 0); 2241 wp->w_lines[idx].wl_folded = foldinfo.fi_lines > 0; 2242 wp->w_lines[idx].wl_foldend = lastlnum; 2243 wp->w_lines[idx].wl_lastlnum = lastlnum; 2244 did_update = foldinfo.fi_lines > 0 ? DID_FOLD : DID_LINE; 2245 2246 // Adjust "wl_lastlnum" for concealed lines below this line, unless it should 2247 // still be drawn for below virt_lines attached to the current line. Below 2248 // virt_lines attached to a second adjacent concealed line are concealed. 2249 bool virt_below = decor_virt_lines(wp, lastlnum, lastlnum + 1, NULL, NULL, true) > 0; 2250 while (!virt_below && wp->w_lines[idx].wl_lastlnum < buf->b_ml.ml_line_count 2251 && decor_conceal_line(wp, wp->w_lines[idx].wl_lastlnum, false)) { 2252 virt_below = false; 2253 wp->w_lines[idx].wl_lastlnum++; 2254 hasFolding(wp, wp->w_lines[idx].wl_lastlnum, NULL, &wp->w_lines[idx].wl_lastlnum); 2255 } 2256 } 2257 2258 wp->w_lines[idx].wl_lnum = lnum; 2259 wp->w_lines[idx].wl_valid = true; 2260 2261 bool is_curline = wp == curwin && lnum == wp->w_cursor.lnum; 2262 2263 if (row > wp->w_view_height) { // past end of grid 2264 // we may need the size of that too long line later on 2265 if (dollar_vcol == -1 || !is_curline) { 2266 wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true); 2267 } 2268 idx++; 2269 break; 2270 } 2271 if (dollar_vcol == -1 || !is_curline) { 2272 wp->w_lines[idx].wl_size = (uint16_t)(row - srow); 2273 } 2274 lnum = wp->w_lines[idx++].wl_lastlnum + 1; 2275 } else { 2276 // If: 2277 // - 'number' is set and below inserted/deleted lines, or 2278 // - 'relativenumber' is set and cursor moved vertically, 2279 // the text doesn't need to be redrawn, but the number column does. 2280 if ((wp->w_p_nu && mod_top != 0 && lnum >= mod_bot 2281 && buf->b_mod_set && buf->b_mod_xlines != 0) 2282 || (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum)) { 2283 foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum 2284 ? cursorline_fi : fold_info(wp, lnum); 2285 win_line(wp, lnum, srow, wp->w_view_height, wp->w_lines[idx].wl_size, false, &spv, info); 2286 } 2287 2288 // This line does not need to be drawn, advance to the next one. 2289 row += wp->w_lines[idx++].wl_size; 2290 if (row > wp->w_view_height) { // past end of screen 2291 break; 2292 } 2293 lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; 2294 did_update = DID_NONE; 2295 spv.spv_capcol_lnum = 0; 2296 } 2297 2298 // 'statuscolumn' width has changed or errored, start from the top. 2299 if (wp->w_redr_statuscol) { 2300 redr_statuscol: 2301 wp->w_redr_statuscol = false; 2302 idx = 0; 2303 row = 0; 2304 lnum = wp->w_topline; 2305 wp->w_lines_valid = 0; 2306 wp->w_valid &= ~VALID_WCOL; 2307 decor_redraw_reset(wp, &decor_state); 2308 decor_providers_invoke_win(wp); 2309 continue; 2310 } 2311 2312 if (lnum > buf->b_ml.ml_line_count) { 2313 eof = true; 2314 break; 2315 } 2316 } 2317 // End of loop over all window lines. 2318 2319 // Now that the window has been redrawn with the old and new cursor line, 2320 // update w_last_cursorline. 2321 wp->w_last_cursorline = wp->w_cursorline; 2322 2323 wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; 2324 2325 wp->w_lines_valid = MAX(wp->w_lines_valid, idx); 2326 2327 // Let the syntax stuff know we stop parsing here. 2328 if (syntax_last_parsed != 0 && syntax_present(wp)) { 2329 syntax_end_parsing(wp, syntax_last_parsed + 1); 2330 } 2331 2332 const linenr_T old_botline = wp->w_botline; 2333 2334 // If we didn't hit the end of the file, and we didn't finish the last 2335 // line we were working on, then the line didn't fit. 2336 wp->w_empty_rows = 0; 2337 wp->w_filler_rows = 0; 2338 if (!eof && !didline) { 2339 int at_attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, HLF_AT)); 2340 if (lnum == wp->w_topline) { 2341 // Single line that does not fit! 2342 // Don't overwrite it, it can be edited. 2343 wp->w_botline = lnum + 1; 2344 } else if (win_get_fill(wp, lnum) >= wp->w_view_height - srow) { 2345 // Window ends in filler lines. 2346 wp->w_botline = lnum; 2347 wp->w_filler_rows = wp->w_view_height - srow; 2348 } else if (dy_flags & kOptDyFlagTruncate) { // 'display' has "truncate" 2349 // Last line isn't finished: Display "@@@" in the last screen line. 2350 grid_line_start(&wp->w_grid, wp->w_view_height - 1); 2351 grid_line_fill(0, MIN(wp->w_view_width, 3), wp->w_p_fcs_chars.lastline, at_attr); 2352 grid_line_fill(3, wp->w_view_width, schar_from_ascii(' '), at_attr); 2353 grid_line_flush(); 2354 set_empty_rows(wp, srow); 2355 wp->w_botline = lnum; 2356 } else if (dy_flags & kOptDyFlagLastline) { // 'display' has "lastline" 2357 // Last line isn't finished: Display "@@@" at the end. 2358 // If this would split a doublewidth char in two, we need to display "@@@@" instead 2359 grid_line_start(&wp->w_grid, wp->w_view_height - 1); 2360 int width = grid_line_getchar(MAX(wp->w_view_width - 3, 0), NULL) == NUL ? 4 : 3; 2361 grid_line_fill(MAX(wp->w_view_width - width, 0), wp->w_view_width, 2362 wp->w_p_fcs_chars.lastline, at_attr); 2363 grid_line_flush(); 2364 set_empty_rows(wp, srow); 2365 wp->w_botline = lnum; 2366 } else { 2367 win_draw_end(wp, wp->w_p_fcs_chars.lastline, true, srow, 2368 wp->w_view_height, HLF_AT); 2369 set_empty_rows(wp, srow); 2370 wp->w_botline = lnum; 2371 } 2372 } else { 2373 if (eof) { // we hit the end of the file 2374 wp->w_botline = buf->b_ml.ml_line_count + 1; 2375 int j = win_get_fill(wp, wp->w_botline); 2376 if (j > 0 && !wp->w_botfill && row < wp->w_view_height) { 2377 // Display filler text below last line. win_line() will check 2378 // for ml_line_count+1 and only draw filler lines 2379 spellvars_T zero_spv = { 0 }; 2380 foldinfo_T zero_foldinfo = { 0 }; 2381 row = win_line(wp, wp->w_botline, row, wp->w_view_height, 0, false, &zero_spv, 2382 zero_foldinfo); 2383 if (wp->w_redr_statuscol) { 2384 eof = false; 2385 goto redr_statuscol; 2386 } 2387 } 2388 } else if (dollar_vcol == -1 || wp != curwin) { 2389 wp->w_botline = lnum; 2390 } 2391 2392 // Make sure the rest of the screen is blank. 2393 // write the "eob" character from 'fillchars' to rows that aren't part 2394 // of the file. 2395 // TODO(bfredl): just keep track of the valid EOB area from last redraw? 2396 int lastline = bot_scroll_start; 2397 if (mid_end >= row) { 2398 lastline = MIN(lastline, mid_start); 2399 } 2400 // if (mod_bot > buf->b_ml.ml_line_count + 1) { 2401 if (mod_bot > buf->b_ml.ml_line_count) { 2402 lastline = 0; 2403 } 2404 2405 win_draw_end(wp, wp->w_p_fcs_chars.eob, false, MAX(lastline, row), 2406 wp->w_view_height, 2407 HLF_EOB); 2408 set_empty_rows(wp, row); 2409 } 2410 2411 if (wp->w_redr_type >= UPD_REDRAW_TOP) { 2412 draw_vsep_win(wp); 2413 draw_hsep_win(wp); 2414 } 2415 syn_set_timeout(NULL); 2416 2417 // Reset the type of redrawing required, the window has been updated. 2418 wp->w_redr_type = 0; 2419 wp->w_old_topfill = wp->w_topfill; 2420 wp->w_old_botfill = wp->w_botfill; 2421 2422 // Send win_extmarks if needed 2423 for (size_t n = 0; n < kv_size(win_extmark_arr); n++) { 2424 ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle, 2425 kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id, 2426 kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col); 2427 } 2428 2429 if (dollar_vcol == -1 || wp != curwin) { 2430 // There is a trick with w_botline. If we invalidate it on each 2431 // change that might modify it, this will cause a lot of expensive 2432 // calls to plines_win() in update_topline() each time. Therefore the 2433 // value of w_botline is often approximated, and this value is used to 2434 // compute the value of w_topline. If the value of w_botline was 2435 // wrong, check that the value of w_topline is correct (cursor is on 2436 // the visible part of the text). If it's not, we need to redraw 2437 // again. Mostly this just means scrolling up a few lines, so it 2438 // doesn't look too bad. Only do this for the current window (where 2439 // changes are relevant). 2440 wp->w_valid |= VALID_BOTLINE; 2441 wp->w_viewport_invalid = true; 2442 if (wp == curwin && wp->w_botline != old_botline && !recursive) { 2443 recursive = true; 2444 curwin->w_valid &= ~VALID_TOPLINE; 2445 update_topline(curwin); // may invalidate w_botline again 2446 // New redraw either due to updated topline or reset skipcol. 2447 if (must_redraw != 0) { 2448 // Don't update for changes in buffer again. 2449 int mod_set = curbuf->b_mod_set; 2450 curbuf->b_mod_set = false; 2451 curs_columns(curwin, true); 2452 win_update(curwin); 2453 must_redraw = 0; 2454 curbuf->b_mod_set = mod_set; 2455 } 2456 recursive = false; 2457 } 2458 } 2459 2460 if (nrwidth_before != wp->w_nrwidth && buf->terminal) { 2461 terminal_check_size(buf->terminal); 2462 } 2463 2464 // restore got_int, unless CTRL-C was hit while redrawing 2465 if (!got_int) { 2466 got_int = save_got_int; 2467 } 2468 } 2469 2470 /// Scroll `line_count` lines at 'row' in window 'wp'. 2471 /// 2472 /// Positive `line_count` means scrolling down, so that more space is available 2473 /// at 'row'. Negative `line_count` implies deleting lines at `row`. 2474 void win_scroll_lines(win_T *wp, int row, int line_count) 2475 { 2476 if (!redrawing() || line_count == 0) { 2477 return; 2478 } 2479 2480 int col = 0; 2481 int row_off = 0; 2482 ScreenGrid *grid = grid_adjust(&wp->w_grid, &row_off, &col); 2483 2484 // TODO(bfredl): this is due to the call in curs_columns(). We really don't want to 2485 // fiddle with the screen outside of update_screen() like this. 2486 int checked_width = MIN(grid->cols - col, wp->w_view_width); 2487 int checked_height = MIN(grid->rows - row_off, wp->w_view_height); 2488 2489 // No lines are being moved, just draw over the entire area 2490 if (row + abs(line_count) >= checked_height) { 2491 return; 2492 } 2493 2494 if (line_count < 0) { 2495 grid_del_lines(grid, row + row_off, -line_count, 2496 checked_height + row_off, col, checked_width); 2497 } else { 2498 grid_ins_lines(grid, row + row_off, line_count, 2499 checked_height + row_off, col, checked_width); 2500 } 2501 } 2502 2503 /// Clear lines near the end of the window and mark the unused lines with "c1". 2504 /// When "draw_margin" is true, then draw the sign/fold/number columns. 2505 void win_draw_end(win_T *wp, schar_T c1, bool draw_margin, int startrow, int endrow, hlf_T hl) 2506 { 2507 assert(hl >= 0 && hl < HLF_COUNT); 2508 const int view_width = wp->w_view_width; 2509 const int fdc = compute_foldcolumn(wp, 0); 2510 const int scwidth = wp->w_scwidth; 2511 2512 for (int row = startrow; row < endrow; row++) { 2513 grid_line_start(&wp->w_grid, row); 2514 2515 int n = 0; 2516 if (draw_margin) { 2517 // draw the fold column 2518 if (fdc > 0) { 2519 n = grid_line_fill(n, MIN(view_width, n + fdc), 2520 schar_from_ascii(' '), win_hl_attr(wp, HLF_FC)); 2521 } 2522 2523 // draw the sign column 2524 if (scwidth > 0) { 2525 n = grid_line_fill(n, MIN(view_width, n + scwidth * SIGN_WIDTH), 2526 schar_from_ascii(' '), win_hl_attr(wp, HLF_SC)); 2527 } 2528 2529 // draw the number column 2530 if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) { 2531 int width = number_width(wp) + 1; 2532 n = grid_line_fill(n, MIN(view_width, n + width), 2533 schar_from_ascii(' '), win_hl_attr(wp, HLF_N)); 2534 } 2535 } 2536 2537 int attr = win_hl_attr(wp, (int)hl); 2538 2539 if (n < view_width) { 2540 grid_line_put_schar(n, c1, attr); 2541 n++; 2542 } 2543 2544 grid_line_clear_end(n, view_width, win_bg_attr(wp), attr); 2545 2546 if (wp->w_p_rl) { 2547 grid_line_mirror(view_width); 2548 } 2549 grid_line_flush(); 2550 } 2551 } 2552 2553 /// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much 2554 /// space is available for window "wp", minus "col". 2555 int compute_foldcolumn(win_T *wp, int col) 2556 { 2557 int fdc = win_fdccol_count(wp); 2558 int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw; 2559 int n = wp->w_view_width - (col + wmw); 2560 2561 return MIN(fdc, n); 2562 } 2563 2564 /// Return the width of the 'number' and 'relativenumber' column. 2565 /// Caller may need to check if 'number' or 'relativenumber' is set. 2566 /// Otherwise it depends on 'numberwidth' and the line count. 2567 int number_width(win_T *wp) 2568 { 2569 linenr_T lnum; 2570 2571 if (wp->w_p_rnu && !wp->w_p_nu) { 2572 // cursor line shows "0" 2573 lnum = wp->w_view_height; 2574 } else { 2575 // cursor line shows absolute line number 2576 lnum = wp->w_buffer->b_ml.ml_line_count; 2577 } 2578 2579 if (lnum == wp->w_nrwidth_line_count) { 2580 return wp->w_nrwidth_width; 2581 } 2582 wp->w_nrwidth_line_count = lnum; 2583 2584 // reset for 'statuscolumn' 2585 if (*wp->w_p_stc != NUL) { 2586 wp->w_statuscol_line_count = 0; // make sure width is re-estimated 2587 wp->w_nrwidth_width = (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw; 2588 return wp->w_nrwidth_width; 2589 } 2590 2591 int n = 0; 2592 do { 2593 lnum /= 10; 2594 n++; 2595 } while (lnum > 0); 2596 2597 // 'numberwidth' gives the minimal width plus one 2598 n = MAX(n, (int)wp->w_p_nuw - 1); 2599 2600 // If 'signcolumn' is set to 'number' and there is a sign to display, then 2601 // the minimal width for the number column is 2. 2602 if (n < 2 && buf_meta_total(wp->w_buffer, kMTMetaSignText) && wp->w_minscwidth == SCL_NUM) { 2603 n = 2; 2604 } 2605 2606 wp->w_nrwidth_width = n; 2607 return n; 2608 } 2609 2610 /// Redraw a window later, with wp->w_redr_type >= type. 2611 /// 2612 /// Set must_redraw only if not already set to a higher value. 2613 /// e.g. if must_redraw is UPD_CLEAR, type UPD_NOT_VALID will do nothing. 2614 void redraw_later(win_T *wp, int type) 2615 { 2616 // curwin may have been set to NULL when exiting 2617 assert(wp != NULL || exiting); 2618 if (!exiting && !redraw_not_allowed && wp->w_redr_type < type) { 2619 wp->w_redr_type = type; 2620 if (type >= UPD_NOT_VALID) { 2621 wp->w_lines_valid = 0; 2622 } 2623 must_redraw = MAX(must_redraw, type); // must_redraw is the maximum of all windows 2624 } 2625 } 2626 2627 /// Mark all windows to be redrawn later. 2628 void redraw_all_later(int type) 2629 { 2630 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2631 redraw_later(wp, type); 2632 } 2633 // This may be needed when switching tabs. 2634 set_must_redraw(type); 2635 } 2636 2637 /// Set "must_redraw" to "type" unless it already has a higher value 2638 /// or it is currently not allowed. 2639 void set_must_redraw(int type) 2640 { 2641 if (!redraw_not_allowed) { 2642 must_redraw = MAX(must_redraw, type); 2643 } 2644 } 2645 2646 void screen_invalidate_highlights(void) 2647 { 2648 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2649 redraw_later(wp, UPD_NOT_VALID); 2650 wp->w_grid_alloc.valid = false; 2651 } 2652 } 2653 2654 /// Mark all windows that are editing the current buffer to be updated later. 2655 void redraw_curbuf_later(int type) 2656 { 2657 redraw_buf_later(curbuf, type); 2658 } 2659 2660 void redraw_buf_later(buf_T *buf, int type) 2661 { 2662 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2663 if (wp->w_buffer == buf) { 2664 redraw_later(wp, type); 2665 } 2666 } 2667 } 2668 2669 void redraw_buf_line_later(buf_T *buf, linenr_T line, bool force) 2670 { 2671 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2672 if (wp->w_buffer == buf) { 2673 redrawWinline(wp, MIN(line, buf->b_ml.ml_line_count)); 2674 if (force && line > buf->b_ml.ml_line_count) { 2675 wp->w_redraw_bot = line; 2676 } 2677 } 2678 } 2679 } 2680 2681 void redraw_win_range_later(win_T *wp, linenr_T first, linenr_T last) 2682 { 2683 if (last >= wp->w_topline && first < wp->w_botline) { 2684 if (wp->w_redraw_top == 0 || wp->w_redraw_top > first) { 2685 wp->w_redraw_top = first; 2686 } 2687 if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < last) { 2688 wp->w_redraw_bot = last; 2689 } 2690 redraw_later(wp, UPD_VALID); 2691 } 2692 } 2693 2694 /// Changed something in the current window, at buffer line "lnum", that 2695 /// requires that line and possibly other lines to be redrawn. 2696 /// Used when entering/leaving Insert mode with the cursor on a folded line. 2697 /// Used to remove the "$" from a change command. 2698 /// Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot 2699 /// may become invalid and the whole window will have to be redrawn. 2700 void redrawWinline(win_T *wp, linenr_T lnum) 2701 FUNC_ATTR_NONNULL_ALL 2702 { 2703 redraw_win_range_later(wp, lnum, lnum); 2704 } 2705 2706 void redraw_buf_range_later(buf_T *buf, linenr_T first, linenr_T last) 2707 { 2708 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2709 if (wp->w_buffer == buf) { 2710 redraw_win_range_later(wp, first, last); 2711 } 2712 } 2713 } 2714 2715 /// called when the status bars for the buffer 'buf' need to be updated 2716 void redraw_buf_status_later(buf_T *buf) 2717 { 2718 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2719 if (wp->w_buffer == buf 2720 && (wp->w_status_height 2721 || (wp == curwin && global_stl_height()) 2722 || wp->w_winbar_height)) { 2723 wp->w_redr_status = true; 2724 set_must_redraw(UPD_VALID); 2725 } 2726 } 2727 } 2728 2729 /// Mark all status lines and window bars for redraw; used after first :cd 2730 void status_redraw_all(void) 2731 { 2732 bool is_stl_global = global_stl_height() != 0; 2733 2734 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2735 if ((!is_stl_global && wp->w_status_height) || wp == curwin 2736 || wp->w_winbar_height) { 2737 wp->w_redr_status = true; 2738 redraw_later(wp, UPD_VALID); 2739 } 2740 } 2741 } 2742 2743 /// Marks all status lines and window bars of the current buffer for redraw. 2744 void status_redraw_curbuf(void) 2745 { 2746 status_redraw_buf(curbuf); 2747 } 2748 2749 /// Marks all status lines and window bars of the given buffer for redraw. 2750 void status_redraw_buf(buf_T *buf) 2751 { 2752 bool is_stl_global = global_stl_height() != 0; 2753 2754 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2755 if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height) 2756 || (is_stl_global && wp == curwin) || wp->w_winbar_height)) { 2757 wp->w_redr_status = true; 2758 redraw_later(wp, UPD_VALID); 2759 } 2760 } 2761 // Redraw the ruler if it is in the command line and was not marked for redraw above 2762 if (p_ru && !curwin->w_status_height && !curwin->w_redr_status) { 2763 redraw_cmdline = true; 2764 redraw_later(curwin, UPD_VALID); 2765 } 2766 } 2767 2768 /// Redraw all status lines that need to be redrawn. 2769 void redraw_statuslines(void) 2770 { 2771 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2772 if (wp->w_redr_status) { 2773 win_check_ns_hl(wp); 2774 win_redr_winbar(wp); 2775 win_redr_status(wp); 2776 } 2777 } 2778 2779 win_check_ns_hl(NULL); 2780 if (redraw_tabline) { 2781 draw_tabline(); 2782 } 2783 2784 if (need_maketitle) { 2785 maketitle(); 2786 } 2787 } 2788 2789 /// Redraw all status lines at the bottom of frame "frp". 2790 void win_redraw_last_status(const frame_T *frp) 2791 FUNC_ATTR_NONNULL_ARG(1) 2792 { 2793 if (frp->fr_layout == FR_LEAF) { 2794 frp->fr_win->w_redr_status = true; 2795 } else if (frp->fr_layout == FR_ROW) { 2796 FOR_ALL_FRAMES(frp, frp->fr_child) { 2797 win_redraw_last_status(frp); 2798 } 2799 } else { 2800 assert(frp->fr_layout == FR_COL); 2801 frp = frp->fr_child; 2802 while (frp->fr_next != NULL) { 2803 frp = frp->fr_next; 2804 } 2805 win_redraw_last_status(frp); 2806 } 2807 } 2808 2809 /// Return true if the cursor line in window "wp" may be concealed, according 2810 /// to the 'concealcursor' option. 2811 bool conceal_cursor_line(const win_T *wp) 2812 FUNC_ATTR_NONNULL_ALL 2813 { 2814 int c; 2815 2816 if (*wp->w_p_cocu == NUL) { 2817 return false; 2818 } 2819 if (get_real_state() & MODE_VISUAL) { 2820 c = 'v'; 2821 } else if (State & MODE_INSERT) { 2822 c = 'i'; 2823 } else if (State & MODE_NORMAL) { 2824 c = 'n'; 2825 } else if (State & MODE_CMDLINE) { 2826 c = 'c'; 2827 } else { 2828 return false; 2829 } 2830 return vim_strchr(wp->w_p_cocu, c) != NULL; 2831 } 2832 2833 /// Whether cursorline is drawn in a special way 2834 /// 2835 /// If true, both old and new cursorline will need to be redrawn when moving cursor within windows. 2836 bool win_cursorline_standout(const win_T *wp) 2837 FUNC_ATTR_NONNULL_ALL 2838 { 2839 return wp->w_p_cul || (wp == curwin && wp->w_p_cole > 0 && !conceal_cursor_line(wp)); 2840 } 2841 2842 /// Update w_cursorline, taking care to set it to the to the start of a closed fold. 2843 /// 2844 /// @param[out] foldinfo foldinfo for the cursor line 2845 void win_update_cursorline(win_T *wp, foldinfo_T *foldinfo) 2846 { 2847 wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0; 2848 if (wp->w_p_cul) { 2849 // Make sure that the cursorline on a closed fold is redrawn 2850 *foldinfo = fold_info(wp, wp->w_cursor.lnum); 2851 if (foldinfo->fi_level != 0 && foldinfo->fi_lines > 0) { 2852 wp->w_cursorline = foldinfo->fi_lnum; 2853 } 2854 } 2855 }