neovim

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

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 }