text: // This is an open source non-commercial project. Dear PVS-Studio, please check
  // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
  
  // screen.c: code for displaying on the screen
  //
  // Output to the screen (console, terminal emulator or GUI window) is minimized
  // by remembering what is already on the screen, and only updating the parts
  // that changed.
  //
  // The grid_*() functions write to the screen and handle updating grid->lines[].
  //
  // update_screen() is the function that updates all windows and status lines.
  // It is called from the main loop when must_redraw is non-zero.  It may be
  // called from other places when an immediate screen update is needed.
  //
  // The part of the buffer that is displayed in a window is set with:
  // - w_topline (first buffer line in window)
  // - w_topfill (filler lines above the first line)
  // - w_leftcol (leftmost window cell in window),
  // - w_skipcol (skipped window cells of first line)
  //
  // Commands that only move the cursor around in a window, do not need to take
  // action to update the display.  The main loop will check if w_topline is
  // valid and update it (scroll the window) when needed.
  //
  // Commands that scroll a window change w_topline and must call
  // check_cursor() to move the cursor into the visible part of the window, and
  // call redraw_later(VALID) to have the window displayed by update_screen()
  // later.
  //
  // Commands that change text in the buffer must call changed_bytes() or
  // changed_lines() to mark the area that changed and will require updating
  // later.  The main loop will call update_screen(), which will update each
  // window that shows the changed buffer.  This assumes text above the change
  // can remain displayed as it is.  Text after the change may need updating for
  // scrolling, folding and syntax highlighting.
  //
  // Commands that change how a window is displayed (e.g., setting 'list') or
  // invalidate the contents of a window in another way (e.g., change fold
  // settings), must call redraw_later(NOT_VALID) to have the whole window
  // redisplayed by update_screen() later.
  //
  // Commands that change how a buffer is displayed (e.g., setting 'tabstop')
  // must call redraw_curbuf_later(NOT_VALID) to have all the windows for the
  // buffer redisplayed by update_screen() later.
  //
  // Commands that change highlighting and possibly cause a scroll too must call
  // redraw_later(SOME_VALID) to update the whole window but still use scrolling
  // to avoid redrawing everything.  But the length of displayed lines must not
  // change, use NOT_VALID then.
  //
  // Commands that move the window position must call redraw_later(NOT_VALID).
  // TODO(neovim): should minimize redrawing by scrolling when possible.
  //
  // Commands that change everything (e.g., resizing the screen) must call
  // redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR).
  //
  // Things that are handled indirectly:
  // - When messages scroll the screen up, msg_scrolled will be set and
  //   update_screen() called to redraw.
  ///
  
  #include <assert.h>
  #include <inttypes.h>
  #include <stdbool.h>
  #include <string.h>
  
  #include "nvim/log.h"
  #include "nvim/vim.h"
  #include "nvim/ascii.h"
  #include "nvim/arabic.h"
  #include "nvim/screen.h"
  #include "nvim/buffer.h"
  #include "nvim/charset.h"
  #include "nvim/cursor.h"
  #include "nvim/cursor_shape.h"
  #include "nvim/diff.h"
  #include "nvim/eval.h"
  #include "nvim/ex_cmds.h"
  #include "nvim/ex_cmds2.h"
  #include "nvim/ex_getln.h"
  #include "nvim/edit.h"
  #include "nvim/fileio.h"
  #include "nvim/fold.h"
  #include "nvim/indent.h"
  #include "nvim/getchar.h"
  #include "nvim/highlight.h"
  #include "nvim/main.h"
  #include "nvim/mark.h"
  #include "nvim/mbyte.h"
  #include "nvim/memline.h"
  #include "nvim/memory.h"
  #include "nvim/menu.h"
  #include "nvim/message.h"
  #include "nvim/misc1.h"
  #include "nvim/garray.h"
  #include "nvim/move.h"
  #include "nvim/normal.h"
  #include "nvim/option.h"
  #include "nvim/os_unix.h"
  #include "nvim/path.h"
  #include "nvim/popupmnu.h"
  #include "nvim/quickfix.h"
  #include "nvim/regexp.h"
  #include "nvim/search.h"
  #include "nvim/sign.h"
  #include "nvim/spell.h"
  #include "nvim/state.h"
  #include "nvim/strings.h"
  #include "nvim/syntax.h"
  #include "nvim/terminal.h"
  #include "nvim/ui.h"
  #include "nvim/ui_compositor.h"
  #include "nvim/undo.h"
  #include "nvim/version.h"
  #include "nvim/window.h"
  #include "nvim/os/time.h"
  #include "nvim/api/private/helpers.h"
  #include "nvim/api/vim.h"
  #include "nvim/lua/executor.h"
  
  #define MB_FILLER_CHAR '<'  /* character used when a double-width character
                               * doesn't fit. */
  
  
  // temporary buffer for rendering a single screenline, so it can be
  // compared with previous contents to calculate smallest delta.
  // Per-cell attributes
  static size_t linebuf_size = 0;
  static schar_T *linebuf_char = NULL;
  static sattr_T *linebuf_attr = NULL;
  
  static match_T search_hl;       /* used for 'hlsearch' highlight matching */
  
  static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */
  
  StlClickDefinition *tab_page_click_defs = NULL;
  
  long tab_page_click_defs_size = 0;
  
  // for line_putchar. Contains the state that needs to be remembered from
  // putting one character to the next.
  typedef struct {
    const char_u *p;
    int prev_c;  // previous Arabic character
    int prev_c1;  // first composing char for prev_c
  } LineState;
  #define LINE_STATE(p) { p, 0, 0 }
  
  /// Whether to call "ui_call_grid_resize" in win_grid_alloc
  static bool send_grid_resize = false;
  
  static bool conceal_cursor_used = false;
  
  static bool redraw_popupmenu = false;
  static bool msg_grid_invalid = false;
  
  static bool resizing = false;
  
  #ifdef INCLUDE_GENERATED_DECLARATIONS
  # include "screen.c.generated.h"
  #endif
  #define SEARCH_HL_PRIORITY 0
  
  /*
   * Redraw the current window later, with update_screen(type).
   * Set must_redraw only if not already set to a higher value.
   * e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing.
   */
  void redraw_later(int type)
  {
    redraw_win_later(curwin, type);
  }
  
  void redraw_win_later(win_T *wp, int type)
    FUNC_ATTR_NONNULL_ALL
  {
    if (!exiting && wp->w_redr_type < type) {
      wp->w_redr_type = type;
      if (type >= NOT_VALID)
        wp->w_lines_valid = 0;
      if (must_redraw < type)     /* must_redraw is the maximum of all windows */
        must_redraw = type;
    }
  }
  
  /*
   * Mark all windows to be redrawn later.
   */
  void redraw_all_later(int type)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      redraw_win_later(wp, type);
    }
    // This may be needed when switching tabs.
    if (must_redraw < type) {
      must_redraw = type;
    }
  }
  
  void screen_invalidate_highlights(void)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      redraw_win_later(wp, NOT_VALID);
      wp->w_grid.valid = false;
    }
  }
  
  /*
   * Mark all windows that are editing the current buffer to be updated later.
   */
  void redraw_curbuf_later(int type)
  {
    redraw_buf_later(curbuf, type);
  }
  
  void redraw_buf_later(buf_T *buf, int type)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_buffer == buf) {
        redraw_win_later(wp, type);
      }
    }
  }
  
  void redraw_buf_line_later(buf_T *buf,  linenr_T line)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_buffer == buf
          && line >= wp->w_topline && line < wp->w_botline) {
        redrawWinline(wp, line);
      }
    }
  }
  
  void redraw_buf_range_later(buf_T *buf,  linenr_T firstline, linenr_T lastline)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_buffer == buf
          && lastline >= wp->w_topline && firstline < wp->w_botline) {
        if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) {
            wp->w_redraw_top = firstline;
        }
        if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) {
            wp->w_redraw_bot = lastline;
        }
        redraw_win_later(wp, VALID);
      }
    }
  }
  
  /*
   * Changed something in the current window, at buffer line "lnum", that
   * requires that line and possibly other lines to be redrawn.
   * Used when entering/leaving Insert mode with the cursor on a folded line.
   * Used to remove the "$" from a change command.
   * Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot
   * may become invalid and the whole window will have to be redrawn.
   */
  void
  redrawWinline(
      win_T *wp,
      linenr_T lnum
  )
    FUNC_ATTR_NONNULL_ALL
  {
    if (lnum >= wp->w_topline
        && lnum < wp->w_botline) {
      if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) {
          wp->w_redraw_top = lnum;
      }
      if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) {
          wp->w_redraw_bot = lnum;
      }
      redraw_win_later(wp, VALID);
    }
  }
  
  /*
   * update all windows that are editing the current buffer
   */
  void update_curbuf(int type)
  {
    redraw_curbuf_later(type);
    update_screen(type);
  }
  
  /// Redraw the parts of the screen that is marked for redraw.
  ///
  /// Most code shouldn't call this directly, rather use redraw_later() and
  /// and redraw_all_later() to mark parts of the screen as needing a redraw.
  ///
  /// @param type set to a NOT_VALID to force redraw of entire screen
  int update_screen(int type)
  {
    static int did_intro = FALSE;
    int did_one;
  
    // Don't do anything if the screen structures are (not yet) valid.
    // A VimResized autocmd can invoke redrawing in the middle of a resize,
    // which would bypass the checks in screen_resize for popupmenu etc.
    if (!default_grid.chars || resizing) {
      return FAIL;
    }
  
    // May have postponed updating diffs.
    if (need_diff_redraw) {
      diff_redraw(true);
    }
  
    if (must_redraw) {
      if (type < must_redraw)         /* use maximal type */
        type = must_redraw;
  
      /* must_redraw is reset here, so that when we run into some weird
      * reason to redraw while busy redrawing (e.g., asynchronous
      * scrolling), or update_topline() in win_update() will cause a
      * scroll, the screen will be redrawn later or in win_update(). */
      must_redraw = 0;
    }
  
    /* Need to update w_lines[]. */
    if (curwin->w_lines_valid == 0 && type < NOT_VALID)
      type = NOT_VALID;
  
    /* Postpone the redrawing when it's not needed and when being called
     * recursively. */
    if (!redrawing() || updating_screen) {
      redraw_later(type);                 /* remember type for next time */
      must_redraw = type;
      if (type > INVERTED_ALL) {
        curwin->w_lines_valid = 0;  // don't use w_lines[].wl_size now
      }
      return FAIL;
    }
  
    updating_screen = TRUE;
    ++display_tick;           /* let syntax code know we're in a next round of
                               * display updating */
  
    // Tricky: vim code can reset msg_scrolled behind our back, so need
    // separate bookkeeping for now.
    if (msg_did_scroll) {
      msg_did_scroll = false;
      msg_scrolled_at_flush = 0;
    }
  
    if (type >= CLEAR || !default_grid.valid) {
      ui_comp_set_screen_valid(false);
    }
  
    // if the screen was scrolled up when displaying a message, scroll it down
    if (msg_scrolled || msg_grid_invalid) {
      clear_cmdline = true;
      int valid = MAX(Rows - msg_scrollsize(), 0);
      if (msg_grid.chars) {
        // non-displayed part of msg_grid is considered invalid.
        for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.Rows); i++) {
          grid_clear_line(&msg_grid, msg_grid.line_offset[i],
                          (int)msg_grid.Columns, false);
        }
      }
      if (msg_use_msgsep()) {
        msg_grid.throttled = false;
        // CLEAR is already handled
        if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) {
          ui_comp_set_screen_valid(false);
          for (int i = valid; i < Rows-p_ch; i++) {
            grid_clear_line(&default_grid, default_grid.line_offset[i],
                            Columns, false);
          }
        }
        msg_grid_set_pos(Rows-p_ch, false);
        msg_grid_invalid = false;
      } else if (msg_scrolled > Rows - 5) {  // clearing is faster
        type = CLEAR;
      } else if (type != CLEAR) {
        check_for_delay(false);
        grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns);
        FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
          if (wp->w_floating) {
            continue;
          }
          if (wp->w_winrow < msg_scrolled) {
            if (W_ENDROW(wp) > msg_scrolled
                && wp->w_redr_type < REDRAW_TOP
                && wp->w_lines_valid > 0
                && wp->w_topline == wp->w_lines[0].wl_lnum) {
              wp->w_upd_rows = msg_scrolled - wp->w_winrow;
              wp->w_redr_type = REDRAW_TOP;
            } else {
              wp->w_redr_type = NOT_VALID;
              if (W_ENDROW(wp) + wp->w_status_height
                  <= msg_scrolled) {
                wp->w_redr_status = TRUE;
              }
            }
          }
        }
        redraw_cmdline = TRUE;
        redraw_tabline = TRUE;
      }
      msg_scrolled = 0;
      msg_scrolled_at_flush = 0;
      need_wait_return = false;
    }
  
    win_ui_flush_positions();
    msg_ext_check_clear();
  
    /* reset cmdline_row now (may have been changed temporarily) */
    compute_cmdrow();
  
    /* Check for changed highlighting */
    if (need_highlight_changed)
      highlight_changed();
  
    if (type == CLEAR) {          // first clear screen
      screenclear();              // will reset clear_cmdline
      cmdline_screen_cleared();   // clear external cmdline state
      type = NOT_VALID;
      // must_redraw may be set indirectly, avoid another redraw later
      must_redraw = 0;
    } else if (!default_grid.valid) {
      grid_invalidate(&default_grid);
      default_grid.valid = true;
    }
  
    // After disabling msgsep the grid might not have been deallocated yet,
    // hence we also need to check msg_grid.chars
    if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) {
      grid_fill(&default_grid, Rows-p_ch, Rows, 0, Columns, ' ', ' ', 0);
    }
  
    ui_comp_set_screen_valid(true);
  
    if (clear_cmdline)            /* going to clear cmdline (done below) */
      check_for_delay(FALSE);
  
    /* Force redraw when width of 'number' or 'relativenumber' column
     * changes. */
    if (curwin->w_redr_type < NOT_VALID
        && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu)
                                 ? number_width(curwin) : 0))
      curwin->w_redr_type = NOT_VALID;
  
    /*
     * Only start redrawing if there is really something to do.
     */
    if (type == INVERTED)
      update_curswant();
    if (curwin->w_redr_type < type
        && !((type == VALID
              && curwin->w_lines[0].wl_valid
              && curwin->w_topfill == curwin->w_old_topfill
              && curwin->w_botfill == curwin->w_old_botfill
              && curwin->w_topline == curwin->w_lines[0].wl_lnum)
             || (type == INVERTED
                 && VIsual_active
                 && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum
                 && curwin->w_old_visual_mode == VIsual_mode
                 && (curwin->w_valid & VALID_VIRTCOL)
                 && curwin->w_old_curswant == curwin->w_curswant)
             ))
      curwin->w_redr_type = type;
  
    // Redraw the tab pages line if needed.
    if (redraw_tabline || type >= NOT_VALID) {
      update_window_hl(curwin, type >= NOT_VALID);
      FOR_ALL_TABS(tp) {
        if (tp != curtab) {
          update_window_hl(tp->tp_curwin, type >= NOT_VALID);
        }
      }
      draw_tabline();
    }
  
    /*
     * Correct stored syntax highlighting info for changes in each displayed
     * buffer.  Each buffer must only be done once.
     */
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      update_window_hl(wp, type >= NOT_VALID);
  
      if (wp->w_buffer->b_mod_set) {
        win_T       *wwp;
  
        // Check if we already did this buffer.
        for (wwp = firstwin; wwp != wp; wwp = wwp->w_next) {
          if (wwp->w_buffer == wp->w_buffer) {
            break;
          }
        }
        if (wwp == wp && syntax_present(wp)) {
          syn_stack_apply_changes(wp->w_buffer);
        }
  
        buf_T *buf = wp->w_buffer;
        if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
          Error err = ERROR_INIT;
          FIXED_TEMP_ARRAY(args, 2);
          args.items[0] = BUFFER_OBJ(buf->handle);
          args.items[1] = INTEGER_OBJ(display_tick);
          executor_exec_lua_cb(buf->b_luahl_start, "start", args, false, &err);
          if (ERROR_SET(&err)) {
            ELOG("error in luahl start: %s", err.msg);
            api_clear_error(&err);
          }
        }
      }
    }
  
    /*
     * Go from top to bottom through the windows, redrawing the ones that need
     * it.
     */
    did_one = FALSE;
    search_hl.rm.regprog = NULL;
  
  
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) {
        grid_invalidate(&wp->w_grid);
        wp->w_redr_type = NOT_VALID;
      }
  
      if (wp->w_redr_type != 0) {
        if (!did_one) {
          did_one = TRUE;
          start_search_hl();
        }
        win_update(wp);
      }
  
      /* redraw status line after the window to minimize cursor movement */
      if (wp->w_redr_status) {
        win_redr_status(wp);
      }
    }
  
    end_search_hl();
  
    // May need to redraw the popup menu.
    if (pum_drawn() && must_redraw_pum) {
      pum_redraw();
    }
  
    send_grid_resize = false;
  
    /* Reset b_mod_set flags.  Going through all windows is probably faster
     * than going through all buffers (there could be many buffers). */
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      wp->w_buffer->b_mod_set = false;
    }
  
    updating_screen = FALSE;
  
    /* Clear or redraw the command line.  Done last, because scrolling may
     * mess up the command line. */
    if (clear_cmdline || redraw_cmdline) {
      showmode();
    }
  
    /* May put up an introductory message when not editing a file */
    if (!did_intro)
      maybe_intro_message();
    did_intro = TRUE;
  
    // either cmdline is cleared, not drawn or mode is last drawn
    cmdline_was_last_drawn = false;
    return OK;
  }
  
  // Return true if the cursor line in window "wp" may be concealed, according
  // to the 'concealcursor' option.
  bool conceal_cursor_line(const win_T *wp)
    FUNC_ATTR_NONNULL_ALL
  {
    int c;
  
    if (*wp->w_p_cocu == NUL) {
      return false;
    }
    if (get_real_state() & VISUAL) {
      c = 'v';
    } else if (State & INSERT) {
      c = 'i';
    } else if (State & NORMAL) {
      c = 'n';
    } else if (State & CMDLINE) {
      c = 'c';
    } else {
      return false;
    }
    return vim_strchr(wp->w_p_cocu, c) != NULL;
  }
  
  // Check if the cursor line needs to be redrawn because of 'concealcursor'.
  //
  // When cursor is moved at the same time, both lines will be redrawn regardless.
  void conceal_check_cursor_line(void)
  {
    bool should_conceal = conceal_cursor_line(curwin);
    if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) {
      redrawWinline(curwin, curwin->w_cursor.lnum);
      // Need to recompute cursor column, e.g., when starting Visual mode
      // without concealing. */
      curs_columns(true);
    }
  }
  
  /// Whether cursorline is drawn in a special way
  ///
  /// If true, both old and new cursorline will need
  /// to be redrawn when moving cursor within windows.
  /// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch
  ///               caused by scrolling.
  bool win_cursorline_standout(const win_T *wp)
    FUNC_ATTR_NONNULL_ALL
  {
    return wp->w_p_cul
      || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp)));
  }
  
  /*
   * Update a single window.
   *
   * This may cause the windows below it also to be redrawn (when clearing the
   * screen or scrolling lines).
   *
   * How the window is redrawn depends on wp->w_redr_type.  Each type also
   * implies the one below it.
   * NOT_VALID	redraw the whole window
   * SOME_VALID	redraw the whole window but do scroll when possible
   * REDRAW_TOP	redraw the top w_upd_rows window lines, otherwise like VALID
   * INVERTED	redraw the changed part of the Visual area
   * INVERTED_ALL	redraw the whole Visual area
   * VALID	1. scroll up/down to adjust for a changed w_topline
   *		2. update lines at the top when scrolled down
   *		3. redraw changed text:
   *		   - if wp->w_buffer->b_mod_set set, update lines between
   *		     b_mod_top and b_mod_bot.
   *		   - if wp->w_redraw_top non-zero, redraw lines between
   *		     wp->w_redraw_top and wp->w_redr_bot.
   *		   - continue redrawing when syntax status is invalid.
   *		4. if scrolled up, update lines at the bottom.
   * This results in three areas that may need updating:
   * top:	from first row to top_end (when scrolled down)
   * mid: from mid_start to mid_end (update inversion or changed text)
   * bot: from bot_start to last row (when scrolled up)
   */
  static void win_update(win_T *wp)
  {
    buf_T       *buf = wp->w_buffer;
    int type;
    int top_end = 0;              /* Below last row of the top area that needs
                                     updating.  0 when no top area updating. */
    int mid_start = 999;          /* first row of the mid area that needs
                                     updating.  999 when no mid area updating. */
    int mid_end = 0;              /* Below last row of the mid area that needs
                                     updating.  0 when no mid area updating. */
    int bot_start = 999;          /* first row of the bot area that needs
                                     updating.  999 when no bot area updating */
    int scrolled_down = FALSE;            /* TRUE when scrolled down when
                                             w_topline got smaller a bit */
    bool top_to_mod = false;      // redraw above mod_top
  
    int row;                      /* current window row to display */
    linenr_T lnum;                /* current buffer lnum to display */
    int idx;                      /* current index in w_lines[] */
    int srow;                     /* starting row of the current line */
  
    int eof = FALSE;              /* if TRUE, we hit the end of the file */
    int didline = FALSE;           /* if TRUE, we finished the last line */
    int i;
    long j;
    static int recursive = FALSE;         /* being called recursively */
    int old_botline = wp->w_botline;
    long fold_count;
    // Remember what happened to the previous line.
  #define DID_NONE 1      // didn't update a line
  #define DID_LINE 2      // updated a normal line
  #define DID_FOLD 3      // updated a folded line
    int did_update = DID_NONE;
    linenr_T syntax_last_parsed = 0;              /* last parsed text line */
    linenr_T mod_top = 0;
    linenr_T mod_bot = 0;
    int save_got_int;
  
    // If we can compute a change in the automatic sizing of the sign column
    // under 'signcolumn=auto:X' and signs currently placed in the buffer, better
    // figuring it out here so we can redraw the entire screen for it.
    buf_signcols(buf);
  
    type = wp->w_redr_type;
  
    win_grid_alloc(wp);
  
    if (type >= NOT_VALID) {
      wp->w_redr_status = true;
      wp->w_lines_valid = 0;
    }
  
    // Window is zero-height: nothing to draw.
    if (wp->w_grid.Rows == 0) {
      wp->w_redr_type = 0;
      return;
    }
  
    // Window is zero-width: Only need to draw the separator.
    if (wp->w_grid.Columns == 0) {
      // draw the vertical separator right of this window
      draw_vsep_win(wp, 0);
      wp->w_redr_type = 0;
      return;
    }
  
    init_search_hl(wp);
  
    /* Force redraw when width of 'number' or 'relativenumber' column
     * changes. */
    i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0;
    if (wp->w_nrwidth != i) {
      type = NOT_VALID;
      wp->w_nrwidth = i;
  
      if (buf->terminal) {
        terminal_check_size(buf->terminal);
      }
    } else if (buf->b_mod_set
               && buf->b_mod_xlines != 0
               && wp->w_redraw_top != 0) {
      // When there are both inserted/deleted lines and specific lines to be
      // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw
      // everything (only happens when redrawing is off for while).
      type = NOT_VALID;
    } else {
      /*
       * Set mod_top to the first line that needs displaying because of
       * changes.  Set mod_bot to the first line after the changes.
       */
      mod_top = wp->w_redraw_top;
      if (wp->w_redraw_bot != 0)
        mod_bot = wp->w_redraw_bot + 1;
      else
        mod_bot = 0;
      if (buf->b_mod_set) {
        if (mod_top == 0 || mod_top > buf->b_mod_top) {
          mod_top = buf->b_mod_top;
          /* Need to redraw lines above the change that may be included
           * in a pattern match. */
          if (syntax_present(wp)) {
            mod_top -= buf->b_s.b_syn_sync_linebreaks;
            if (mod_top < 1)
              mod_top = 1;
          }
        }
        if (mod_bot == 0 || mod_bot < buf->b_mod_bot)
          mod_bot = buf->b_mod_bot;
  
        // When 'hlsearch' is on and using a multi-line search pattern, a
        // change in one line may make the Search highlighting in a
        // previous line invalid.  Simple solution: redraw all visible
        // lines above the change.
        // Same for a match pattern.
        if (search_hl.rm.regprog != NULL
            && re_multiline(search_hl.rm.regprog)) {
          top_to_mod = true;
        } else {
          const matchitem_T *cur = wp->w_match_head;
          while (cur != NULL) {
            if (cur->match.regprog != NULL
                && re_multiline(cur->match.regprog)) {
              top_to_mod = true;
              break;
            }
            cur = cur->next;
          }
        }
      }
      if (mod_top != 0 && hasAnyFolding(wp)) {
        linenr_T lnumt, lnumb;
  
        /*
         * A change in a line can cause lines above it to become folded or
         * unfolded.  Find the top most buffer line that may be affected.
         * If the line was previously folded and displayed, get the first
         * line of that fold.  If the line is folded now, get the first
         * folded line.  Use the minimum of these two.
         */
  
        /* Find last valid w_lines[] entry above mod_top.  Set lnumt to
         * the line below it.  If there is no valid entry, use w_topline.
         * Find the first valid w_lines[] entry below mod_bot.  Set lnumb
         * to this line.  If there is no valid entry, use MAXLNUM. */
        lnumt = wp->w_topline;
        lnumb = MAXLNUM;
        for (i = 0; i < wp->w_lines_valid; ++i)
          if (wp->w_lines[i].wl_valid) {
            if (wp->w_lines[i].wl_lastlnum < mod_top)
              lnumt = wp->w_lines[i].wl_lastlnum + 1;
            if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) {
              lnumb = wp->w_lines[i].wl_lnum;
              // When there is a fold column it might need updating
              // in the next line ("J" just above an open fold).
              if (compute_foldcolumn(wp, 0) > 0) {
                lnumb++;
              }
            }
          }
  
        (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL);
        if (mod_top > lnumt) {
          mod_top = lnumt;
        }
  
        // Now do the same for the bottom line (one above mod_bot).
        mod_bot--;
        (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL);
        mod_bot++;
        if (mod_bot < lnumb) {
          mod_bot = lnumb;
        }
      }
  
      /* When a change starts above w_topline and the end is below
       * w_topline, start redrawing at w_topline.
       * If the end of the change is above w_topline: do like no change was
       * made, but redraw the first line to find changes in syntax. */
      if (mod_top != 0 && mod_top < wp->w_topline) {
        if (mod_bot > wp->w_topline)
          mod_top = wp->w_topline;
        else if (syntax_present(wp))
          top_end = 1;
      }
  
      /* When line numbers are displayed need to redraw all lines below
       * inserted/deleted lines. */
      if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu)
        mod_bot = MAXLNUM;
    }
    wp->w_redraw_top = 0;  // reset for next time
    wp->w_redraw_bot = 0;
  
    /*
     * When only displaying the lines at the top, set top_end.  Used when
     * window has scrolled down for msg_scrolled.
     */
    if (type == REDRAW_TOP) {
      j = 0;
      for (i = 0; i < wp->w_lines_valid; ++i) {
        j += wp->w_lines[i].wl_size;
        if (j >= wp->w_upd_rows) {
          top_end = j;
          break;
        }
      }
      if (top_end == 0)
        /* not found (cannot happen?): redraw everything */
        type = NOT_VALID;
      else
        /* top area defined, the rest is VALID */
        type = VALID;
    }
  
    /*
     * If there are no changes on the screen that require a complete redraw,
     * handle three cases:
     * 1: we are off the top of the screen by a few lines: scroll down
     * 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up
     * 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in
     *    w_lines[] that needs updating.
     */
    if ((type == VALID || type == SOME_VALID
         || type == INVERTED || type == INVERTED_ALL)
        && !wp->w_botfill && !wp->w_old_botfill
        ) {
      if (mod_top != 0 && wp->w_topline == mod_top) {
        /*
         * w_topline is the first changed line, the scrolling will be done
         * further down.
         */
      } else if (wp->w_lines[0].wl_valid
                 && (wp->w_topline < wp->w_lines[0].wl_lnum
                     || (wp->w_topline == wp->w_lines[0].wl_lnum
                         && wp->w_topfill > wp->w_old_topfill)
                     )) {
        /*
         * New topline is above old topline: May scroll down.
         */
        if (hasAnyFolding(wp)) {
          linenr_T ln;
  
          /* count the number of lines we are off, counting a sequence
           * of folded lines as one */
          j = 0;
          for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) {
            j++;
            if (j >= wp->w_grid.Rows - 2) {
              break;
            }
            (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL);
          }
        } else
          j = wp->w_lines[0].wl_lnum - wp->w_topline;
        if (j < wp->w_grid.Rows - 2) {               // not too far off
          i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1);
          /* insert extra lines for previously invisible filler lines */
          if (wp->w_lines[0].wl_lnum != wp->w_topline)
            i += diff_check_fill(wp, wp->w_lines[0].wl_lnum)
                 - wp->w_old_topfill;
          if (i != 0 && i < wp->w_grid.Rows - 2) {  // less than a screen off
            // Try to insert the correct number of lines.
            // If not the last window, delete the lines at the bottom.
            // win_ins_lines may fail when the terminal can't do it.
            win_scroll_lines(wp, 0, i);
            if (wp->w_lines_valid != 0) {
              // Need to update rows that are new, stop at the
              // first one that scrolled down.
              top_end = i;
              scrolled_down = true;
  
              // Move the entries that were scrolled, disable
              // the entries for the lines to be redrawn.
              if ((wp->w_lines_valid += j) > wp->w_grid.Rows) {
                wp->w_lines_valid = wp->w_grid.Rows;
              }
              for (idx = wp->w_lines_valid; idx - j >= 0; idx--) {
                wp->w_lines[idx] = wp->w_lines[idx - j];
              }
              while (idx >= 0) {
                wp->w_lines[idx--].wl_valid = false;
              }
            }
          } else {
            mid_start = 0;  // redraw all lines
          }
        } else {
          mid_start = 0;  // redraw all lines
        }
      } else {
        /*
         * New topline is at or below old topline: May scroll up.
         * When topline didn't change, find first entry in w_lines[] that
         * needs updating.
         */
  
        /* try to find wp->w_topline in wp->w_lines[].wl_lnum */
        j = -1;
        row = 0;
        for (i = 0; i < wp->w_lines_valid; i++) {
          if (wp->w_lines[i].wl_valid
              && wp->w_lines[i].wl_lnum == wp->w_topline) {
            j = i;
            break;
          }
          row += wp->w_lines[i].wl_size;
        }
        if (j == -1) {
          /* if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all
           * lines */
          mid_start = 0;
        } else {
          /*
           * Try to delete the correct number of lines.
           * wp->w_topline is at wp->w_lines[i].wl_lnum.
           */
          /* If the topline didn't change, delete old filler lines,
           * otherwise delete filler lines of the new topline... */
          if (wp->w_lines[0].wl_lnum == wp->w_topline)
            row += wp->w_old_topfill;
          else
            row += diff_check_fill(wp, wp->w_topline);
          /* ... but don't delete new filler lines. */
          row -= wp->w_topfill;
          if (row > 0) {
            win_scroll_lines(wp, 0, -row);
            bot_start = wp->w_grid.Rows - row;
          }
          if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) {
            /*
             * Skip the lines (below the deleted lines) that are still
             * valid and don't need redrawing.	Copy their info
             * upwards, to compensate for the deleted lines.  Set
             * bot_start to the first row that needs redrawing.
             */
            bot_start = 0;
            idx = 0;
            for (;; ) {
              wp->w_lines[idx] = wp->w_lines[j];
              /* stop at line that didn't fit, unless it is still
               * valid (no lines deleted) */
              if (row > 0 && bot_start + row
                  + (int)wp->w_lines[j].wl_size > wp->w_grid.Rows) {
                wp->w_lines_valid = idx + 1;
                break;
              }
              bot_start += wp->w_lines[idx++].wl_size;
  
              /* stop at the last valid entry in w_lines[].wl_size */
              if (++j >= wp->w_lines_valid) {
                wp->w_lines_valid = idx;
                break;
              }
            }
            /* Correct the first entry for filler lines at the top
             * when it won't get updated below. */
            if (wp->w_p_diff && bot_start > 0)
              wp->w_lines[0].wl_size =
                plines_win_nofill(wp, wp->w_topline, true)
                + wp->w_topfill;
          }
        }
      }
  
      // When starting redraw in the first line, redraw all lines.
      if (mid_start == 0) {
        mid_end = wp->w_grid.Rows;
      }
    } else {
      /* Not VALID or INVERTED: redraw all lines. */
      mid_start = 0;
      mid_end = wp->w_grid.Rows;
    }
  
    if (type == SOME_VALID) {
      /* SOME_VALID: redraw all lines. */
      mid_start = 0;
      mid_end = wp->w_grid.Rows;
      type = NOT_VALID;
    }
  
    /* check if we are updating or removing the inverted part */
    if ((VIsual_active && buf == curwin->w_buffer)
        || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) {
      linenr_T from, to;
  
      if (VIsual_active) {
        if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) {
          // If the type of Visual selection changed, redraw the whole
          // selection.  Also when the ownership of the X selection is
          // gained or lost.
          if (curwin->w_cursor.lnum < VIsual.lnum) {
            from = curwin->w_cursor.lnum;
            to = VIsual.lnum;
          } else {
            from = VIsual.lnum;
            to = curwin->w_cursor.lnum;
          }
          /* redraw more when the cursor moved as well */
          if (wp->w_old_cursor_lnum < from)
            from = wp->w_old_cursor_lnum;
          if (wp->w_old_cursor_lnum > to)
            to = wp->w_old_cursor_lnum;
          if (wp->w_old_visual_lnum < from)
            from = wp->w_old_visual_lnum;
          if (wp->w_old_visual_lnum > to)
            to = wp->w_old_visual_lnum;
        } else {
          /*
           * Find the line numbers that need to be updated: The lines
           * between the old cursor position and the current cursor
           * position.  Also check if the Visual position changed.
           */
          if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) {
            from = curwin->w_cursor.lnum;
            to = wp->w_old_cursor_lnum;
          } else {
            from = wp->w_old_cursor_lnum;
            to = curwin->w_cursor.lnum;
            if (from == 0)                /* Visual mode just started */
              from = to;
          }
  
          if (VIsual.lnum != wp->w_old_visual_lnum
              || VIsual.col != wp->w_old_visual_col) {
            if (wp->w_old_visual_lnum < from
                && wp->w_old_visual_lnum != 0)
              from = wp->w_old_visual_lnum;
            if (wp->w_old_visual_lnum > to)
              to = wp->w_old_visual_lnum;
            if (VIsual.lnum < from)
              from = VIsual.lnum;
            if (VIsual.lnum > to)
              to = VIsual.lnum;
          }
        }
  
        /*
         * If in block mode and changed column or curwin->w_curswant:
         * update all lines.
         * First compute the actual start and end column.
         */
        if (VIsual_mode == Ctrl_V) {
          colnr_T fromc, toc;
          int save_ve_flags = ve_flags;
  
          if (curwin->w_p_lbr)
            ve_flags = VE_ALL;
  
          getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc);
          ve_flags = save_ve_flags;
          ++toc;
          if (curwin->w_curswant == MAXCOL)
            toc = MAXCOL;
  
          if (fromc != wp->w_old_cursor_fcol
              || toc != wp->w_old_cursor_lcol) {
            if (from > VIsual.lnum)
              from = VIsual.lnum;
            if (to < VIsual.lnum)
              to = VIsual.lnum;
          }
          wp->w_old_cursor_fcol = fromc;
          wp->w_old_cursor_lcol = toc;
        }
      } else {
        /* Use the line numbers of the old Visual area. */
        if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) {
          from = wp->w_old_cursor_lnum;
          to = wp->w_old_visual_lnum;
        } else {
          from = wp->w_old_visual_lnum;
          to = wp->w_old_cursor_lnum;
        }
      }
  
      /*
       * There is no need to update lines above the top of the window.
       */
      if (from < wp->w_topline)
        from = wp->w_topline;
  
      /*
       * If we know the value of w_botline, use it to restrict the update to
       * the lines that are visible in the window.
       */
      if (wp->w_valid & VALID_BOTLINE) {
        if (from >= wp->w_botline)
          from = wp->w_botline - 1;
        if (to >= wp->w_botline)
          to = wp->w_botline - 1;
      }
  
      /*
       * Find the minimal part to be updated.
       * Watch out for scrolling that made entries in w_lines[] invalid.
       * E.g., CTRL-U makes the first half of w_lines[] invalid and sets
       * top_end; need to redraw from top_end to the "to" line.
       * A middle mouse click with a Visual selection may change the text
       * above the Visual area and reset wl_valid, do count these for
       * mid_end (in srow).
       */
      if (mid_start > 0) {
        lnum = wp->w_topline;
        idx = 0;
        srow = 0;
        if (scrolled_down)
          mid_start = top_end;
        else
          mid_start = 0;
        while (lnum < from && idx < wp->w_lines_valid) {          /* find start */
          if (wp->w_lines[idx].wl_valid)
            mid_start += wp->w_lines[idx].wl_size;
          else if (!scrolled_down)
            srow += wp->w_lines[idx].wl_size;
          ++idx;
          if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid)
            lnum = wp->w_lines[idx].wl_lnum;
          else
            ++lnum;
        }
        srow += mid_start;
        mid_end = wp->w_grid.Rows;
        for (; idx < wp->w_lines_valid; idx++) {                  // find end
          if (wp->w_lines[idx].wl_valid
              && wp->w_lines[idx].wl_lnum >= to + 1) {
            /* Only update until first row of this line */
            mid_end = srow;
            break;
          }
          srow += wp->w_lines[idx].wl_size;
        }
      }
    }
  
    if (VIsual_active && buf == curwin->w_buffer) {
      wp->w_old_visual_mode = VIsual_mode;
      wp->w_old_cursor_lnum = curwin->w_cursor.lnum;
      wp->w_old_visual_lnum = VIsual.lnum;
      wp->w_old_visual_col = VIsual.col;
      wp->w_old_curswant = curwin->w_curswant;
    } else {
      wp->w_old_visual_mode = 0;
      wp->w_old_cursor_lnum = 0;
      wp->w_old_visual_lnum = 0;
      wp->w_old_visual_col = 0;
    }
  
    /* reset got_int, otherwise regexp won't work */
    save_got_int = got_int;
    got_int = 0;
    // Set the time limit to 'redrawtime'.
    proftime_T syntax_tm = profile_setlimit(p_rdt);
    syn_set_timeout(&syntax_tm);
    win_foldinfo.fi_level = 0;
  
    /*
     * Update all the window rows.
     */
    idx = 0;              /* first entry in w_lines[].wl_size */
    row = 0;
    srow = 0;
    lnum = wp->w_topline;  // first line shown in window
  
    if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
      Error err = ERROR_INIT;
      FIXED_TEMP_ARRAY(args, 4);
      linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
                           ? wp->w_botline
                           : (wp->w_topline + wp->w_height_inner));
      args.items[0] = WINDOW_OBJ(wp->handle);
      args.items[1] = BUFFER_OBJ(buf->handle);
      args.items[2] = INTEGER_OBJ(wp->w_topline-1);
      args.items[3] = INTEGER_OBJ(knownmax);
      // TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
      // For now the "start" callback is expected to use nvim__buf_redraw_range.
      executor_exec_lua_cb(buf->b_luahl_window, "window", args, false, &err);
      if (ERROR_SET(&err)) {
        ELOG("error in luahl window: %s", err.msg);
        api_clear_error(&err);
      }
    }
  
    for (;; ) {
      /* stop updating when reached the end of the window (check for _past_
       * the end of the window is at the end of the loop) */
      if (row == wp->w_grid.Rows) {
        didline = true;
        break;
      }
  
      /* stop updating when hit the end of the file */
      if (lnum > buf->b_ml.ml_line_count) {
        eof = TRUE;
        break;
      }
  
      /* Remember the starting row of the line that is going to be dealt
       * with.  It is used further down when the line doesn't fit. */
      srow = row;
  
      // Update a line when it is in an area that needs updating, when it
      // has changes or w_lines[idx] is invalid.
      // "bot_start" may be halfway a wrapped line after using
      // win_scroll_lines(), check if the current line includes it.
      // When syntax folding is being used, the saved syntax states will
      // already have been updated, we can't see where the syntax state is
      // the same again, just update until the end of the window.
      if (row < top_end
          || (row >= mid_start && row < mid_end)
          || top_to_mod
          || idx >= wp->w_lines_valid
          || (row + wp->w_lines[idx].wl_size > bot_start)
          || (mod_top != 0
              && (lnum == mod_top
                  || (lnum >= mod_top
                      && (lnum < mod_bot
                          || did_update == DID_FOLD
                          || (did_update == DID_LINE
                              && syntax_present(wp)
                              && ((foldmethodIsSyntax(wp)
                                   && hasAnyFolding(wp))
                                  || syntax_check_changed(lnum)))
                          // match in fixed position might need redraw
                          // if lines were inserted or deleted
                          || (wp->w_match_head != NULL
                              && buf->b_mod_xlines != 0)))))) {
        if (lnum == mod_top) {
          top_to_mod = false;
        }
  
        /*
         * When at start of changed lines: May scroll following lines
         * up or down to minimize redrawing.
         * Don't do this when the change continues until the end.
         * Don't scroll when dollar_vcol >= 0, keep the "$".
         */
        if (lnum == mod_top
            && mod_bot != MAXLNUM
            && !(dollar_vcol >= 0 && mod_bot == mod_top + 1)) {
          int old_rows = 0;
          int new_rows = 0;
          int xtra_rows;
          linenr_T l;
  
          /* Count the old number of window rows, using w_lines[], which
           * should still contain the sizes for the lines as they are
           * currently displayed. */
          for (i = idx; i < wp->w_lines_valid; ++i) {
            /* Only valid lines have a meaningful wl_lnum.  Invalid
             * lines are part of the changed area. */
            if (wp->w_lines[i].wl_valid
                && wp->w_lines[i].wl_lnum == mod_bot)
              break;
            old_rows += wp->w_lines[i].wl_size;
            if (wp->w_lines[i].wl_valid
                && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) {
              /* Must have found the last valid entry above mod_bot.
               * Add following invalid entries. */
              ++i;
              while (i < wp->w_lines_valid
                     && !wp->w_lines[i].wl_valid)
                old_rows += wp->w_lines[i++].wl_size;
              break;
            }
          }
  
          if (i >= wp->w_lines_valid) {
            /* We can't find a valid line below the changed lines,
             * need to redraw until the end of the window.
             * Inserting/deleting lines has no use. */
            bot_start = 0;
          } else {
            /* Able to count old number of rows: Count new window
             * rows, and may insert/delete lines */
            j = idx;
            for (l = lnum; l < mod_bot; l++) {
              if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) {
                new_rows++;
              } else if (l == wp->w_topline) {
                new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill;
              } else {
                new_rows += plines_win(wp, l, true);
              }
              j++;
              if (new_rows > wp->w_grid.Rows - row - 2) {
                // it's getting too much, must redraw the rest
                new_rows = 9999;
                break;
              }
            }
            xtra_rows = new_rows - old_rows;
            if (xtra_rows < 0) {
              /* May scroll text up.  If there is not enough
               * remaining text or scrolling fails, must redraw the
               * rest.  If scrolling works, must redraw the text
               * below the scrolled text. */
              if (row - xtra_rows >= wp->w_grid.Rows - 2) {
                mod_bot = MAXLNUM;
              } else {
                win_scroll_lines(wp, row, xtra_rows);
                bot_start = wp->w_grid.Rows + xtra_rows;
              }
            } else if (xtra_rows > 0) {
              /* May scroll text down.  If there is not enough
               * remaining text of scrolling fails, must redraw the
               * rest. */
              if (row + xtra_rows >= wp->w_grid.Rows - 2) {
                mod_bot = MAXLNUM;
              } else {
                win_scroll_lines(wp, row + old_rows, xtra_rows);
                if (top_end > row + old_rows) {
                  // Scrolled the part at the top that requires
                  // updating down.
                  top_end += xtra_rows;
                }
              }
            }
  
            /* When not updating the rest, may need to move w_lines[]
             * entries. */
            if (mod_bot != MAXLNUM && i != j) {
              if (j < i) {
                int x = row + new_rows;
  
                /* move entries in w_lines[] upwards */
                for (;; ) {
                  /* stop at last valid entry in w_lines[] */
                  if (i >= wp->w_lines_valid) {
                    wp->w_lines_valid = j;
                    break;
                  }
                  wp->w_lines[j] = wp->w_lines[i];
                  /* stop at a line that won't fit */
                  if (x + (int)wp->w_lines[j].wl_size
                      > wp->w_grid.Rows) {
                    wp->w_lines_valid = j + 1;
                    break;
                  }
                  x += wp->w_lines[j++].wl_size;
                  ++i;
                }
                if (bot_start > x)
                  bot_start = x;
              } else {       /* j > i */
                               /* move entries in w_lines[] downwards */
                j -= i;
                wp->w_lines_valid += j;
                if (wp->w_lines_valid > wp->w_grid.Rows) {
                  wp->w_lines_valid = wp->w_grid.Rows;
                }
                for (i = wp->w_lines_valid; i - j >= idx; i--) {
                  wp->w_lines[i] = wp->w_lines[i - j];
                }
  
                /* The w_lines[] entries for inserted lines are
                 * now invalid, but wl_size may be used above.
                 * Reset to zero. */
                while (i >= idx) {
                  wp->w_lines[i].wl_size = 0;
                  wp->w_lines[i--].wl_valid = FALSE;
                }
              }
            }
          }
        }
  
        /*
         * When lines are folded, display one line for all of them.
         * Otherwise, display normally (can be several display lines when
         * 'wrap' is on).
         */
        fold_count = foldedCount(wp, lnum, &win_foldinfo);
        if (fold_count != 0) {
          fold_line(wp, fold_count, &win_foldinfo, lnum, row);
          ++row;
          --fold_count;
          wp->w_lines[idx].wl_folded = TRUE;
          wp->w_lines[idx].wl_lastlnum = lnum + fold_count;
          did_update = DID_FOLD;
        } else if (idx < wp->w_lines_valid
                   && wp->w_lines[idx].wl_valid
                   && wp->w_lines[idx].wl_lnum == lnum
                   && lnum > wp->w_topline
                   && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
                   && srow + wp->w_lines[idx].wl_size > wp->w_grid.Rows
                   && diff_check_fill(wp, lnum) == 0
                   ) {
          /* This line is not going to fit.  Don't draw anything here,
           * will draw "@  " lines below. */
          row = wp->w_grid.Rows + 1;
        } else {
          prepare_search_hl(wp, lnum);
          /* Let the syntax stuff know we skipped a few lines. */
          if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum
              && syntax_present(wp))
            syntax_end_parsing(syntax_last_parsed + 1);
  
          /*
           * Display one line.
           */
          row = win_line(wp, lnum, srow, wp->w_grid.Rows, mod_top == 0, false);
  
          wp->w_lines[idx].wl_folded = FALSE;
          wp->w_lines[idx].wl_lastlnum = lnum;
          did_update = DID_LINE;
          syntax_last_parsed = lnum;
        }
  
        wp->w_lines[idx].wl_lnum = lnum;
        wp->w_lines[idx].wl_valid = true;
  
        if (row > wp->w_grid.Rows) {         // past end of grid
          // we may need the size of that too long line later on
          if (dollar_vcol == -1) {
            wp->w_lines[idx].wl_size = plines_win(wp, lnum, true);
          }
          idx++;
          break;
        }
        if (dollar_vcol == -1)
          wp->w_lines[idx].wl_size = row - srow;
        ++idx;
        lnum += fold_count + 1;
      } else {
        if (wp->w_p_rnu) {
          // 'relativenumber' set: The text doesn't need to be drawn, but
          // the number column nearly always does.
          fold_count = foldedCount(wp, lnum, &win_foldinfo);
          if (fold_count != 0) {
            fold_line(wp, fold_count, &win_foldinfo, lnum, row);
          } else {
            (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true);
          }
        }
  
        // This line does not need to be drawn, advance to the next one.
        row += wp->w_lines[idx++].wl_size;
        if (row > wp->w_grid.Rows) {  // past end of screen
          break;
        }
        lnum = wp->w_lines[idx - 1].wl_lastlnum + 1;
        did_update = DID_NONE;
      }
  
      if (lnum > buf->b_ml.ml_line_count) {
        eof = TRUE;
        break;
      }
    }
    /*
     * End of loop over all window lines.
     */
  
  
    if (idx > wp->w_lines_valid)
      wp->w_lines_valid = idx;
  
    /*
     * Let the syntax stuff know we stop parsing here.
     */
    if (syntax_last_parsed != 0 && syntax_present(wp))
      syntax_end_parsing(syntax_last_parsed + 1);
  
    /*
     * If we didn't hit the end of the file, and we didn't finish the last
     * line we were working on, then the line didn't fit.
     */
    wp->w_empty_rows = 0;
    wp->w_filler_rows = 0;
    if (!eof && !didline) {
      int at_attr = hl_combine_attr(wp->w_hl_attr_normal,
                                    win_hl_attr(wp, HLF_AT));
      if (lnum == wp->w_topline) {
        /*
         * Single line that does not fit!
         * Don't overwrite it, it can be edited.
         */
        wp->w_botline = lnum + 1;
      } else if (diff_check_fill(wp, lnum) >= wp->w_grid.Rows - srow) {
        // Window ends in filler lines.
        wp->w_botline = lnum;
        wp->w_filler_rows = wp->w_grid.Rows - srow;
      } else if (dy_flags & DY_TRUNCATE) {      // 'display' has "truncate"
        int scr_row = wp->w_grid.Rows - 1;
  
        // Last line isn't finished: Display "@@@" in the last screen line.
        grid_puts_len(&wp->w_grid, (char_u *)"@@", 2, scr_row, 0, at_attr);
  
        grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.Columns,
                  '@', ' ', at_attr);
        set_empty_rows(wp, srow);
        wp->w_botline = lnum;
      } else if (dy_flags & DY_LASTLINE) {      // 'display' has "lastline"
        // Last line isn't finished: Display "@@@" at the end.
        grid_fill(&wp->w_grid, wp->w_grid.Rows - 1, wp->w_grid.Rows,
                  wp->w_grid.Columns - 3, wp->w_grid.Columns, '@', '@', at_attr);
        set_empty_rows(wp, srow);
        wp->w_botline = lnum;
      } else {
        win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.Rows, HLF_AT);
        wp->w_botline = lnum;
      }
    } else {
      if (eof) {  // we hit the end of the file
        wp->w_botline = buf->b_ml.ml_line_count + 1;
        j = diff_check_fill(wp, wp->w_botline);
        if (j > 0 && !wp->w_botfill) {
          // display filler lines at the end of the file
          if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
            i = '-';
          } else {
            i = wp->w_p_fcs_chars.diff;
          }
          if (row + j > wp->w_grid.Rows) {
            j = wp->w_grid.Rows - row;
          }
          win_draw_end(wp, i, i, true, row, row + (int)j, HLF_DED);
          row += j;
        }
      } else if (dollar_vcol == -1)
        wp->w_botline = lnum;
  
      // make sure the rest of the screen is blank
      // write the 'eob' character to rows that aren't part of the file.
      win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.Rows,
                   HLF_EOB);
    }
  
    if (wp->w_redr_type >= REDRAW_TOP) {
      draw_vsep_win(wp, 0);
    }
    syn_set_timeout(NULL);
  
    /* Reset the type of redrawing required, the window has been updated. */
    wp->w_redr_type = 0;
    wp->w_old_topfill = wp->w_topfill;
    wp->w_old_botfill = wp->w_botfill;
  
    if (dollar_vcol == -1) {
      /*
       * There is a trick with w_botline.  If we invalidate it on each
       * change that might modify it, this will cause a lot of expensive
       * calls to plines() in update_topline() each time.  Therefore the
       * value of w_botline is often approximated, and this value is used to
       * compute the value of w_topline.  If the value of w_botline was
       * wrong, check that the value of w_topline is correct (cursor is on
       * the visible part of the text).  If it's not, we need to redraw
       * again.  Mostly this just means scrolling up a few lines, so it
       * doesn't look too bad.  Only do this for the current window (where
       * changes are relevant).
       */
      wp->w_valid |= VALID_BOTLINE;
      if (wp == curwin && wp->w_botline != old_botline && !recursive) {
        recursive = TRUE;
        curwin->w_valid &= ~VALID_TOPLINE;
        update_topline();         /* may invalidate w_botline again */
        if (must_redraw != 0) {
          /* Don't update for changes in buffer again. */
          i = curbuf->b_mod_set;
          curbuf->b_mod_set = false;
          win_update(curwin);
          must_redraw = 0;
          curbuf->b_mod_set = i;
        }
        recursive = FALSE;
      }
    }
  
    /* restore got_int, unless CTRL-C was hit while redrawing */
    if (!got_int)
      got_int = save_got_int;
  }
  
  /// Returns width of the signcolumn that should be used for the whole window
  ///
  /// @param wp window we want signcolumn width from
  /// @return max width of signcolumn (cell unit)
  ///
  /// @note Returns a constant for now but hopefully we can improve neovim so that
  ///       the returned value width adapts to the maximum number of marks to draw
  ///       for the window
  /// TODO(teto)
  int win_signcol_width(win_T *wp)
  {
    // 2 is vim default value
    return 2;
  }
  
  /// Call grid_fill() with columns adjusted for 'rightleft' if needed.
  /// Return the new offset.
  static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row,
                          int endrow, int attr)
  {
    int nn = off + width;
  
    if (nn > wp->w_grid.Columns) {
      nn = wp->w_grid.Columns;
    }
  
    if (wp->w_p_rl) {
      grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - nn, W_ENDCOL(wp) - off,
                c1, c2, attr);
    } else {
      grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr);
    }
  
    return nn;
  }
  
  /// Clear lines near the end of the window and mark the unused lines with "c1".
  /// Use "c2" as filler character.
  /// When "draw_margin" is true, then draw the sign/fold/number columns.
  static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row,
                           int endrow, hlf_T hl)
  {
    assert(hl >= 0 && hl < HLF_COUNT);
    int n = 0;
  
    if (draw_margin) {
      // draw the fold column
      int fdc = compute_foldcolumn(wp, 0);
      if (fdc > 0) {
        n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow,
                         win_hl_attr(wp, HLF_FC));
      }
      // draw the sign column
      int count = win_signcol_count(wp);
      if (count > 0) {
        n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row,
                         endrow, win_hl_attr(wp, HLF_SC));
      }
      // draw the number column
      if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) {
        n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow,
                         win_hl_attr(wp, HLF_N));
      }
    }
  
    int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, hl));
  
    if (wp->w_p_rl) {
      grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n,
                c2, c2, attr);
      grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - 1 - n, W_ENDCOL(wp) - n,
                c1, c2, attr);
    } else {
      grid_fill(&wp->w_grid, row, endrow, n, wp->w_grid.Columns, c1, c2, attr);
    }
  
    set_empty_rows(wp, row);
  }
  
  
  /*
   * Advance **color_cols and return TRUE when there are columns to draw.
   */
  static int advance_color_col(int vcol, int **color_cols)
  {
    while (**color_cols >= 0 && vcol > **color_cols)
      ++*color_cols;
    return **color_cols >= 0;
  }
  
  // Compute the width of the foldcolumn.  Based on 'foldcolumn' and how much
  // space is available for window "wp", minus "col".
  static int compute_foldcolumn(win_T *wp, int col)
  {
    int fdc = wp->w_p_fdc;
    int wmw = wp == curwin && p_wmw == 0 ? 1 : p_wmw;
    int wwidth = wp->w_grid.Columns;
  
    if (fdc > wwidth - (col + wmw)) {
      fdc = wwidth - (col + wmw);
    }
    return fdc;
  }
  
  /// Put a single char from an UTF-8 buffer into a line buffer.
  ///
  /// Handles composing chars and arabic shaping state.
  static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl)
  {
    const char_u *p = s->p;
    int cells = utf_ptr2cells(p);
    int c_len = utfc_ptr2len(p);
    int u8c, u8cc[MAX_MCO];
    if (cells > maxcells) {
      return -1;
    }
    u8c = utfc_ptr2char(p, u8cc);
    if (*p < 0x80 && u8cc[0] == 0) {
      schar_from_ascii(dest[0], *p);
      s->prev_c = u8c;
    } else {
      if (p_arshape && !p_tbidi && arabic_char(u8c)) {
        // Do Arabic shaping.
        int pc, pc1, nc;
        int pcc[MAX_MCO];
        int firstbyte = *p;
  
        // The idea of what is the previous and next
        // character depends on 'rightleft'.
        if (rl) {
          pc = s->prev_c;
          pc1 = s->prev_c1;
          nc = utf_ptr2char(p + c_len);
          s->prev_c1 = u8cc[0];
        } else {
          pc = utfc_ptr2char(p + c_len, pcc);
          nc = s->prev_c;
          pc1 = pcc[0];
        }
        s->prev_c = u8c;
  
        u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc);
      } else {
        s->prev_c = u8c;
      }
      schar_from_cc(dest[0], u8c, u8cc);
    }
    if (cells > 1) {
      dest[1][0] = 0;
    }
    s->p += c_len;
    return cells;
  }
  
  /*
   * Display one folded line.
   */
  static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T lnum, int row)
  {
    char_u buf[FOLD_TEXT_LEN];
    pos_T       *top, *bot;
    linenr_T lnume = lnum + fold_count - 1;
    int len;
    char_u      *text;
    int fdc;
    int col;
    int txtcol;
    int off;
  
    /* Build the fold line:
     * 1. Add the cmdwin_type for the command-line window
     * 2. Add the 'foldcolumn'
     * 3. Add the 'number' or 'relativenumber' column
     * 4. Compose the text
     * 5. Add the text
     * 6. set highlighting for the Visual area an other text
     */
    col = 0;
    off = 0;
  
    /*
     * 1. Add the cmdwin_type for the command-line window
     * Ignores 'rightleft', this window is never right-left.
     */
    if (cmdwin_type != 0 && wp == curwin) {
      schar_from_ascii(linebuf_char[off], cmdwin_type);
      linebuf_attr[off] = win_hl_attr(wp, HLF_AT);
      col++;
    }
  
  # define RL_MEMSET(p, v, l) \
    do { \
      if (wp->w_p_rl) { \
        for (int ri = 0; ri < l; ri++) { \
          linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \
        } \
      } else { \
        for (int ri = 0; ri < l; ri++) { \
          linebuf_attr[off + (p) + ri] = v; \
        } \
      } \
    } while (0)
  
    // 2. Add the 'foldcolumn'
    // Reduce the width when there is not enough space.
    fdc = compute_foldcolumn(wp, col);
    if (fdc > 0) {
      fill_foldcolumn(buf, wp, true, lnum);
      const char_u *it = &buf[0];
      for (int i = 0; i < fdc; i++) {
        int mb_c = mb_ptr2char_adv(&it);
        if (wp->w_p_rl) {
            schar_from_char(linebuf_char[off + wp->w_grid.Columns - i - 1 - col],
                            mb_c);
        } else {
          schar_from_char(linebuf_char[off + col + i], mb_c);
        }
      }
      RL_MEMSET(col, win_hl_attr(wp, HLF_FC), fdc);
      col += fdc;
    }
  
    /* Set all attributes of the 'number' or 'relativenumber' column and the
     * text */
    RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col);
  
    // If signs are being displayed, add spaces.
    if (win_signcol_count(wp) > 0) {
        len = wp->w_grid.Columns - col;
        if (len > 0) {
            int len_max = win_signcol_width(wp) * win_signcol_count(wp);
            if (len > len_max) {
                len = len_max;
            }
            char_u space_buf[18] = "                  ";
            assert((size_t)len_max <= sizeof(space_buf));
            copy_text_attr(off + col, space_buf, len,
                           win_hl_attr(wp, HLF_FL));
            col += len;
        }
    }
  
    /*
     * 3. Add the 'number' or 'relativenumber' column
     */
    if (wp->w_p_nu || wp->w_p_rnu) {
      len = wp->w_grid.Columns - col;
      if (len > 0) {
        int w = number_width(wp);
        long num;
        char *fmt = "%*ld ";
  
        if (len > w + 1)
          len = w + 1;
  
        if (wp->w_p_nu && !wp->w_p_rnu)
          /* 'number' + 'norelativenumber' */
          num = (long)lnum;
        else {
          /* 'relativenumber', don't use negative numbers */
          num = labs((long)get_cursor_rel_lnum(wp, lnum));
          if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
            /* 'number' + 'relativenumber': cursor line shows absolute
             * line number */
            num = lnum;
            fmt = "%-*ld ";
          }
        }
  
        snprintf((char *)buf, FOLD_TEXT_LEN, fmt, w, num);
        if (wp->w_p_rl) {
          // the line number isn't reversed
          copy_text_attr(off + wp->w_grid.Columns - len - col, buf, len,
                         win_hl_attr(wp, HLF_FL));
        } else {
          copy_text_attr(off + col, buf, len, win_hl_attr(wp, HLF_FL));
        }
        col += len;
      }
    }
  
    /*
     * 4. Compose the folded-line string with 'foldtext', if set.
     */
    text = get_foldtext(wp, lnum, lnume, foldinfo, buf);
  
    txtcol = col;         /* remember where text starts */
  
    // 5. move the text to linebuf_char[off].  Fill up with "fold".
    //    Right-left text is put in columns 0 - number-col, normal text is put
    //    in columns number-col - window-width.
    int idx;
  
    if (wp->w_p_rl) {
      idx = off;
    } else {
      idx = off + col;
    }
  
    LineState s = LINE_STATE(text);
  
    while (*s.p != NUL) {
      // TODO(bfredl): cargo-culted from the old Vim code:
      // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
      // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
      // in the correct condition.
      int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0);
      int cells = line_putchar(&s, &linebuf_char[idx], maxcells, wp->w_p_rl);
      if (cells == -1) {
        break;
      }
      col += cells;
      idx += cells;
    }
  
    /* Fill the rest of the line with the fold filler */
    if (wp->w_p_rl)
      col -= txtcol;
  
    schar_T sc;
    schar_from_char(sc, wp->w_p_fcs_chars.fold);
    while (col < wp->w_grid.Columns
           - (wp->w_p_rl ? txtcol : 0)
           ) {
      schar_copy(linebuf_char[off+col++], sc);
    }
  
    if (text != buf)
      xfree(text);
  
    /*
     * 6. set highlighting for the Visual area an other text.
     * If all folded lines are in the Visual area, highlight the line.
     */
    if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
      if (ltoreq(curwin->w_cursor, VIsual)) {
        /* Visual is after curwin->w_cursor */
        top = &curwin->w_cursor;
        bot = &VIsual;
      } else {
        /* Visual is before curwin->w_cursor */
        top = &VIsual;
        bot = &curwin->w_cursor;
      }
      if (lnum >= top->lnum
          && lnume <= bot->lnum
          && (VIsual_mode != 'v'
              || ((lnum > top->lnum
                   || (lnum == top->lnum
                       && top->col == 0))
                  && (lnume < bot->lnum
                      || (lnume == bot->lnum
                          && (bot->col - (*p_sel == 'e'))
                          >= (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, lnume,
                                  FALSE))))))) {
        if (VIsual_mode == Ctrl_V) {
          // Visual block mode: highlight the chars part of the block
          if (wp->w_old_cursor_fcol + txtcol < (colnr_T)wp->w_grid.Columns) {
            if (wp->w_old_cursor_lcol != MAXCOL
                && wp->w_old_cursor_lcol + txtcol
                < (colnr_T)wp->w_grid.Columns) {
              len = wp->w_old_cursor_lcol;
            } else {
              len = wp->w_grid.Columns - txtcol;
            }
            RL_MEMSET(wp->w_old_cursor_fcol + txtcol, win_hl_attr(wp, HLF_V),
                      len - (int)wp->w_old_cursor_fcol);
          }
        } else {
          // Set all attributes of the text
          RL_MEMSET(txtcol, win_hl_attr(wp, HLF_V), wp->w_grid.Columns - txtcol);
        }
      }
    }
  
    // Show colorcolumn in the fold line, but let cursorcolumn override it.
    if (wp->w_p_cc_cols) {
      int i = 0;
      int j = wp->w_p_cc_cols[i];
      int old_txtcol = txtcol;
  
      while (j > -1) {
        txtcol += j;
        if (wp->w_p_wrap) {
          txtcol -= wp->w_skipcol;
        } else {
          txtcol -= wp->w_leftcol;
        }
        if (txtcol >= 0 && txtcol < wp->w_grid.Columns) {
          linebuf_attr[off + txtcol] =
            hl_combine_attr(linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_MC));
        }
        txtcol = old_txtcol;
        j = wp->w_p_cc_cols[++i];
      }
    }
  
    /* Show 'cursorcolumn' in the fold line. */
    if (wp->w_p_cuc) {
      txtcol += wp->w_virtcol;
      if (wp->w_p_wrap)
        txtcol -= wp->w_skipcol;
      else
        txtcol -= wp->w_leftcol;
      if (txtcol >= 0 && txtcol < wp->w_grid.Columns) {
        linebuf_attr[off + txtcol] = hl_combine_attr(
            linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_CUC));
      }
    }
  
    grid_put_linebuf(&wp->w_grid, row, 0, wp->w_grid.Columns, wp->w_grid.Columns,
                     false, wp, wp->w_hl_attr_normal, false);
  
    /*
     * Update w_cline_height and w_cline_folded if the cursor line was
     * updated (saves a call to plines() later).
     */
    if (wp == curwin
        && lnum <= curwin->w_cursor.lnum
        && lnume >= curwin->w_cursor.lnum) {
      curwin->w_cline_row = row;
      curwin->w_cline_height = 1;
      curwin->w_cline_folded = true;
      curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
      conceal_cursor_used = conceal_cursor_line(curwin);
    }
  }
  
  
  /// Copy "buf[len]" to linebuf_char["off"] and set attributes to "attr".
  ///
  /// Only works for ASCII text!
  static void copy_text_attr(int off, char_u *buf, int len, int attr)
  {
    int i;
  
    for (i = 0; i < len; i++) {
      schar_from_ascii(linebuf_char[off + i], buf[i]);
      linebuf_attr[off + i] = attr;
    }
  }
  
  /// Fills the foldcolumn at "p" for window "wp".
  /// Only to be called when 'foldcolumn' > 0.
  ///
  /// @param[out] p  Char array to write into
  /// @param lnum    Absolute current line number
  /// @param closed  Whether it is in 'foldcolumn' mode
  ///
  /// Assume monocell characters
  /// @return number of chars added to \param p
  static size_t
  fill_foldcolumn(
      char_u *p,
      win_T *wp,
      int closed,
      linenr_T lnum
  )
  {
    int i = 0;
    int level;
    int first_level;
    int fdc = compute_foldcolumn(wp, 0);    // available cell width
    size_t char_counter = 0;
    int symbol = 0;
    int len = 0;
    // Init to all spaces.
    memset(p, ' ', MAX_MCO * fdc + 1);
  
    level = win_foldinfo.fi_level;
  
    // If the column is too narrow, we start at the lowest level that
    // fits and use numbers to indicated the depth.
    first_level = level - fdc - closed + 1;
    if (first_level < 1) {
      first_level = 1;
    }
  
    for (i = 0; i  < MIN(fdc, level); i++) {
      if (win_foldinfo.fi_lnum == lnum
          && first_level + i >= win_foldinfo.fi_low_level) {
        symbol = wp->w_p_fcs_chars.foldopen;
      } else if (first_level == 1) {
        symbol = wp->w_p_fcs_chars.foldsep;
      } else if (first_level + i <= 9) {
        symbol = '0' + first_level + i;
      } else {
        symbol = '>';
      }
  
      len = utf_char2bytes(symbol, &p[char_counter]);
      char_counter += len;
      if (first_level + i >= level) {
        i++;
        break;
      }
    }
  
    if (closed) {
      if (symbol != 0) {
        // rollback length
        char_counter -= len;
      }
      symbol = wp->w_p_fcs_chars.foldclosed;
      len = utf_char2bytes(symbol, &p[char_counter]);
      char_counter += len;
    }
  
    return MAX(char_counter + (fdc-i), (size_t)fdc);
  }
  
  /*
   * Display line "lnum" of window 'wp' on the screen.
   * Start at row "startrow", stop when "endrow" is reached.
   * wp->w_virtcol needs to be valid.
   *
   * Return the number of last row the line occupies.
   */
  static int
  win_line (
      win_T *wp,
      linenr_T lnum,
      int startrow,
      int endrow,
      bool nochange,                    // not updating for changed text
      bool number_only                  // only update the number column
  )
  {
    int c = 0;                          // init for GCC
    long vcol = 0;                      // virtual column (for tabs)
    long vcol_sbr = -1;                 // virtual column after showbreak
    long vcol_prev = -1;                // "vcol" of previous character
    char_u      *line;                  // current line
    char_u      *ptr;                   // current position in "line"
    int row;                            // row in the window, excl w_winrow
    ScreenGrid *grid = &wp->w_grid;     // grid specfic to the window
  
    char_u extra[57];                   // sign, line number and 'fdc' must
                                        // fit in here
    int n_extra = 0;                    // number of extra chars
    char_u      *p_extra = NULL;        // string of extra chars, plus NUL
    char_u      *p_extra_free = NULL;   // p_extra needs to be freed
    int c_extra = NUL;                  // extra chars, all the same
    int c_final = NUL;                  // final char, mandatory if set
    int extra_attr = 0;                 // attributes when n_extra != 0
    static char_u *at_end_str = (char_u *)"";  // used for p_extra when displaying
                                               // curwin->w_p_lcs_chars.eol at
                                               // end-of-line
    int lcs_eol_one = wp->w_p_lcs_chars.eol;     // 'eol'  until it's been used
    int lcs_prec_todo = wp->w_p_lcs_chars.prec;  // 'prec' until it's been used
  
    // saved "extra" items for when draw_state becomes WL_LINE (again)
    int saved_n_extra = 0;
    char_u      *saved_p_extra = NULL;
    int saved_c_extra = 0;
    int saved_c_final = 0;
    int saved_char_attr = 0;
  
    int n_attr = 0;                       /* chars with special attr */
    int saved_attr2 = 0;                  /* char_attr saved for n_attr */
    int n_attr3 = 0;                      /* chars with overruling special attr */
    int saved_attr3 = 0;                  /* char_attr saved for n_attr3 */
  
    int n_skip = 0;                       /* nr of chars to skip for 'nowrap' */
  
    int fromcol = 0, tocol = 0;           // start/end of inverting
    int fromcol_prev = -2;                // start of inverting after cursor
    int noinvcur = false;                 // don't invert the cursor
    pos_T *top, *bot;
    int lnum_in_visual_area = false;
    pos_T pos;
    long v;
  
    int char_attr = 0;                    /* attributes for next character */
    int attr_pri = FALSE;                 /* char_attr has priority */
    int area_highlighting = FALSE;           /* Visual or incsearch highlighting
                                                in this line */
    int attr = 0;                         /* attributes for area highlighting */
    int area_attr = 0;                    /* attributes desired by highlighting */
    int search_attr = 0;                  /* attributes desired by 'hlsearch' */
    int vcol_save_attr = 0;               /* saved attr for 'cursorcolumn' */
    int syntax_attr = 0;                  /* attributes desired by syntax */
    int has_syntax = FALSE;               /* this buffer has syntax highl. */
    int save_did_emsg;
    int eol_hl_off = 0;                   // 1 if highlighted char after EOL
    int draw_color_col = false;           // highlight colorcolumn
    int *color_cols = NULL;               // pointer to according columns array
    bool has_spell = false;               // this buffer has spell checking
  # define SPWORDLEN 150
    char_u nextline[SPWORDLEN * 2];       /* text with start of the next line */
    int nextlinecol = 0;                  /* column where nextline[] starts */
    int nextline_idx = 0;                 /* index in nextline[] where next line
                                             starts */
    int spell_attr = 0;                   /* attributes desired by spelling */
    int word_end = 0;                     /* last byte with same spell_attr */
    static linenr_T checked_lnum = 0;     /* line number for "checked_col" */
    static int checked_col = 0;           /* column in "checked_lnum" up to which
                                           * there are no spell errors */
    static int cap_col = -1;              // column to check for Cap word
    static linenr_T capcol_lnum = 0;      // line number where "cap_col"
    int cur_checked_col = 0;              // checked column for current line
    int extra_check = 0;                  // has syntax or linebreak
    int multi_attr = 0;                   // attributes desired by multibyte
    int mb_l = 1;                         // multi-byte byte length
    int mb_c = 0;                         // decoded multi-byte character
    bool mb_utf8 = false;                 // screen char is UTF-8 char
    int u8cc[MAX_MCO];                    // composing UTF-8 chars
    int filler_lines;                     // nr of filler lines to be drawn
    int filler_todo;                      // nr of filler lines still to do + 1
    hlf_T diff_hlf = (hlf_T)0;            // type of diff highlighting
    int change_start = MAXCOL;            // first col of changed area
    int change_end = -1;                  // last col of changed area
    colnr_T trailcol = MAXCOL;            // start of trailing spaces
    int need_showbreak = false;           // overlong line, skip first x chars
    int line_attr = 0;                    // attribute for the whole line
    int line_attr_lowprio = 0;            // low-priority attribute for the line
    matchitem_T *cur;                     // points to the match list
    match_T     *shl;                     // points to search_hl or a match
    int shl_flag;                         // flag to indicate whether search_hl
                                          // has been processed or not
    bool prevcol_hl_flag;                 // flag to indicate whether prevcol
                                          // equals startcol of search_hl or one
                                          // of the matches
    int prev_c = 0;                       // previous Arabic character
    int prev_c1 = 0;                      // first composing char for prev_c
  
    bool search_attr_from_match = false;  // if search_attr is from :match
    BufhlLineInfo bufhl_info;             // bufhl data for this line
    bool has_bufhl = false;               // this buffer has highlight matches
    bool do_virttext = false;             // draw virtual text for this line
  
    /* draw_state: items that are drawn in sequence: */
  #define WL_START        0               /* nothing done yet */
  # define WL_CMDLINE     WL_START + 1    /* cmdline window column */
  # define WL_FOLD        WL_CMDLINE + 1  /* 'foldcolumn' */
  # define WL_SIGN        WL_FOLD + 1     /* column for signs */
  #define WL_NR           WL_SIGN + 1     /* line number */
  # define WL_BRI         WL_NR + 1       /* 'breakindent' */
  # define WL_SBR         WL_BRI + 1       /* 'showbreak' or 'diff' */
  #define WL_LINE         WL_SBR + 1      /* text in the line */
    int draw_state = WL_START;            /* what to draw next */
  
    int syntax_flags    = 0;
    int syntax_seqnr    = 0;
    int prev_syntax_id  = 0;
    int conceal_attr    = win_hl_attr(wp, HLF_CONCEAL);
    int is_concealing   = false;
    int boguscols       = 0;              ///< nonexistent columns added to
                                          ///< force wrapping
    int vcol_off        = 0;              ///< offset for concealed characters
    int did_wcol        = false;
    int match_conc      = 0;              ///< cchar for match functions
    int old_boguscols = 0;
  # define VCOL_HLC (vcol - vcol_off)
  # define FIX_FOR_BOGUSCOLS \
    { \
      n_extra += vcol_off; \
      vcol -= vcol_off; \
      vcol_off = 0; \
      col -= boguscols; \
      old_boguscols = boguscols; \
      boguscols = 0; \
    }
  
    if (startrow > endrow)                /* past the end already! */
      return startrow;
  
    row = startrow;
  
    char *luatext = NULL;
  
    if (!number_only) {
      // To speed up the loop below, set extra_check when there is linebreak,
      // trailing white space and/or syntax processing to be done.
      extra_check = wp->w_p_lbr;
      if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) {
        // Prepare for syntax highlighting in this line.  When there is an
        // error, stop syntax highlighting.
        save_did_emsg = did_emsg;
        did_emsg = false;
        syntax_start(wp, lnum);
        if (did_emsg) {
          wp->w_s->b_syn_error = true;
        } else {
          did_emsg = save_did_emsg;
          if (!wp->w_s->b_syn_slow) {
            has_syntax = true;
            extra_check = true;
          }
        }
      }
  
      if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) {
        if (kv_size(bufhl_info.line->items)) {
          has_bufhl = true;
          extra_check = true;
        }
        if (kv_size(bufhl_info.line->virt_text)) {
          do_virttext = true;
        }
      }
  
      // Check for columns to display for 'colorcolumn'.
      color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
      if (color_cols != NULL) {
        draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
      }
  
      if (wp->w_p_spell
          && *wp->w_s->b_p_spl != NUL
          && !GA_EMPTY(&wp->w_s->b_langp)
          && *(char **)(wp->w_s->b_langp.ga_data) != NULL) {
        // Prepare for spell checking.
        has_spell = true;
        extra_check = true;
  
        // Get the start of the next line, so that words that wrap to the next
        // line are found too: "et<line-break>al.".
        // Trick: skip a few chars for C/shell/Vim comments
        nextline[SPWORDLEN] = NUL;
        if (lnum < wp->w_buffer->b_ml.ml_line_count) {
          line = ml_get_buf(wp->w_buffer, lnum + 1, false);
          spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
        }
  
        // When a word wrapped from the previous line the start of the current
        // line is valid.
        if (lnum == checked_lnum) {
          cur_checked_col = checked_col;
        }
        checked_lnum = 0;
  
        // When there was a sentence end in the previous line may require a
        // word starting with capital in this line.  In line 1 always check
        // the first word.
        if (lnum != capcol_lnum) {
          cap_col = -1;
        }
        if (lnum == 1) {
          cap_col = 0;
        }
        capcol_lnum = 0;
      }
  
      //
      // handle visual active in this window
      //
      fromcol = -10;
      tocol = MAXCOL;
      if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
        // Visual is after curwin->w_cursor
        if (ltoreq(curwin->w_cursor, VIsual)) {
          top = &curwin->w_cursor;
          bot = &VIsual;
        } else {                          // Visual is before curwin->w_cursor
          top = &VIsual;
          bot = &curwin->w_cursor;
        }
        lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum);
        if (VIsual_mode == Ctrl_V) {        // block mode
          if (lnum_in_visual_area) {
            fromcol = wp->w_old_cursor_fcol;
            tocol = wp->w_old_cursor_lcol;
          }
        } else {                          // non-block mode
          if (lnum > top->lnum && lnum <= bot->lnum) {
            fromcol = 0;
          } else if (lnum == top->lnum) {
            if (VIsual_mode == 'V') {       // linewise
              fromcol = 0;
            } else {
              getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL);
              if (gchar_pos(top) == NUL) {
                tocol = fromcol + 1;
              }
            }
          }
          if (VIsual_mode != 'V' && lnum == bot->lnum) {
            if (*p_sel == 'e' && bot->col == 0
                && bot->coladd == 0) {
              fromcol = -10;
              tocol = MAXCOL;
            } else if (bot->col == MAXCOL) {
              tocol = MAXCOL;
            } else {
              pos = *bot;
              if (*p_sel == 'e') {
                getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL);
              } else {
                getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol);
                tocol++;
              }
            }
          }
        }
  
        // Check if the char under the cursor should be inverted (highlighted).
        if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin
            && cursor_is_block_during_visual(*p_sel == 'e')) {
          noinvcur = true;
        }
  
        // if inverting in this line set area_highlighting
        if (fromcol >= 0) {
          area_highlighting = true;
          attr = win_hl_attr(wp, HLF_V);
        }
      // handle 'incsearch' and ":s///c" highlighting
      } else if (highlight_match
                 && wp == curwin
                 && lnum >= curwin->w_cursor.lnum
                 && lnum <= curwin->w_cursor.lnum + search_match_lines) {
        if (lnum == curwin->w_cursor.lnum) {
          getvcol(curwin, &(curwin->w_cursor),
                  (colnr_T *)&fromcol, NULL, NULL);
        } else {
          fromcol = 0;
        }
        if (lnum == curwin->w_cursor.lnum + search_match_lines) {
          pos.lnum = lnum;
          pos.col = search_match_endcol;
          getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL);
        }
        // do at least one character; happens when past end of line
        if (fromcol == tocol) {
          tocol = fromcol + 1;
        }
        area_highlighting = true;
        attr = win_hl_attr(wp, HLF_I);
      }
    }
  
    filler_lines = diff_check(wp, lnum);
    if (filler_lines < 0) {
      if (filler_lines == -1) {
        if (diff_find_change(wp, lnum, &change_start, &change_end))
          diff_hlf = HLF_ADD;             /* added line */
        else if (change_start == 0)
          diff_hlf = HLF_TXD;             /* changed text */
        else
          diff_hlf = HLF_CHD;             /* changed line */
      } else
        diff_hlf = HLF_ADD;               /* added line */
      filler_lines = 0;
      area_highlighting = TRUE;
    }
    if (lnum == wp->w_topline)
      filler_lines = wp->w_topfill;
    filler_todo = filler_lines;
  
    // Cursor line highlighting for 'cursorline' in the current window.
    if (lnum == wp->w_cursor.lnum) {
      // Do not show the cursor line when Visual mode is active, because it's
      // not clear what is selected then.
      if (wp->w_p_cul && !(wp == curwin && VIsual_active)) {
        int cul_attr = win_hl_attr(wp, HLF_CUL);
        HlAttrs ae = syn_attr2entry(cul_attr);
  
        // We make a compromise here (#7383):
        //  * low-priority CursorLine if fg is not set
        //  * high-priority ("same as Vim" priority) CursorLine if fg is set
        if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
          line_attr_lowprio = cul_attr;
        } else {
          if (!(State & INSERT) && bt_quickfix(wp->w_buffer)
              && qf_current_entry(wp) == lnum) {
            line_attr = hl_combine_attr(cul_attr, line_attr);
          } else {
            line_attr = cul_attr;
          }
        }
      }
      // Update w_last_cursorline even if Visual mode is active.
      wp->w_last_cursorline = wp->w_cursor.lnum;
    }
  
    // If this line has a sign with line highlighting set line_attr.
    v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1);
    if (v != 0) {
      line_attr = sign_get_attr((int)v, SIGN_LINEHL);
    }
  
    // Highlight the current line in the quickfix window.
    if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) {
      line_attr = win_hl_attr(wp, HLF_QFL);
    }
  
    if (line_attr_lowprio || line_attr) {
      area_highlighting = true;
    }
  
    line = ml_get_buf(wp->w_buffer, lnum, FALSE);
    ptr = line;
  
    buf_T *buf = wp->w_buffer;
    if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) {
      size_t size = STRLEN(line);
      if (lua_attr_bufsize < size) {
        xfree(lua_attr_buf);
        lua_attr_buf = xcalloc(size, sizeof(*lua_attr_buf));
        lua_attr_bufsize = size;
      } else if (lua_attr_buf) {
        memset(lua_attr_buf, 0, size * sizeof(*lua_attr_buf));
      }
      Error err = ERROR_INIT;
      // TODO(bfredl): build a macro for the "static array" pattern
      // in buf_updates_send_changes?
      FIXED_TEMP_ARRAY(args, 3);
      args.items[0] = WINDOW_OBJ(wp->handle);
      args.items[1] = BUFFER_OBJ(buf->handle);
      args.items[2] = INTEGER_OBJ(lnum-1);
      lua_attr_active = true;
      extra_check = true;
      Object o = executor_exec_lua_cb(buf->b_luahl_line, "line",
                                      args, true, &err);
      lua_attr_active = false;
      if (o.type == kObjectTypeString) {
        // TODO(bfredl): this is a bit of a hack. A final API should use an
        // "unified" interface where luahl can add both bufhl and virttext
        luatext = o.data.string.data;
        do_virttext = true;
      } else if (ERROR_SET(&err)) {
        ELOG("error in luahl line: %s", err.msg);
        luatext = err.msg;
        do_virttext = true;
        api_clear_error(&err);
      }
    }
  
    if (has_spell && !number_only) {
      // For checking first word with a capital skip white space.
      if (cap_col == 0) {
        cap_col = (int)getwhitecols(line);
      }
  
      /* To be able to spell-check over line boundaries copy the end of the
       * current line into nextline[].  Above the start of the next line was
       * copied to nextline[SPWORDLEN]. */
      if (nextline[SPWORDLEN] == NUL) {
        /* No next line or it is empty. */
        nextlinecol = MAXCOL;
        nextline_idx = 0;
      } else {
        v = (long)STRLEN(line);
        if (v < SPWORDLEN) {
          /* Short line, use it completely and append the start of the
           * next line. */
          nextlinecol = 0;
          memmove(nextline, line, (size_t)v);
          STRMOVE(nextline + v, nextline + SPWORDLEN);
          nextline_idx = v + 1;
        } else {
          /* Long line, use only the last SPWORDLEN bytes. */
          nextlinecol = v - SPWORDLEN;
          memmove(nextline, line + nextlinecol, SPWORDLEN);  // -V512
          nextline_idx = SPWORDLEN + 1;
        }
      }
    }
  
    if (wp->w_p_list) {
      if (curwin->w_p_lcs_chars.space
          || wp->w_p_lcs_chars.trail
          || wp->w_p_lcs_chars.nbsp) {
        extra_check = true;
      }
      // find start of trailing whitespace
      if (wp->w_p_lcs_chars.trail) {
        trailcol = (colnr_T)STRLEN(ptr);
        while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) {
          trailcol--;
        }
        trailcol += (colnr_T) (ptr - line);
      }
    }
  
    /*
     * 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
     * first character to be displayed.
     */
    if (wp->w_p_wrap)
      v = wp->w_skipcol;
    else
      v = wp->w_leftcol;
    if (v > 0 && !number_only) {
      char_u  *prev_ptr = ptr;
      while (vcol < v && *ptr != NUL) {
        c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL);
        vcol += c;
        prev_ptr = ptr;
        MB_PTR_ADV(ptr);
      }
  
      // When:
      // - 'cuc' is set, or
      // - 'colorcolumn' is set, or
      // - 'virtualedit' is set, or
      // - the visual mode is active,
      // the end of the line may be before the start of the displayed part.
      if (vcol < v && (wp->w_p_cuc
                       || draw_color_col
                       || virtual_active()
                       || (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
        vcol = v;
      }
  
      /* Handle a character that's not completely on the screen: Put ptr at
       * that character but skip the first few screen characters. */
      if (vcol > v) {
        vcol -= c;
        ptr = prev_ptr;
        // If the character fits on the screen, don't need to skip it.
        // Except for a TAB.
        if (utf_ptr2cells(ptr) >= c || *ptr == TAB) {
          n_skip = v - vcol;
        }
      }
  
      /*
       * Adjust for when the inverted text is before the screen,
       * and when the start of the inverted text is before the screen.
       */
      if (tocol <= vcol)
        fromcol = 0;
      else if (fromcol >= 0 && fromcol < vcol)
        fromcol = vcol;
  
      /* When w_skipcol is non-zero, first line needs 'showbreak' */
      if (wp->w_p_wrap)
        need_showbreak = TRUE;
      /* When spell checking a word we need to figure out the start of the
       * word and if it's badly spelled or not. */
      if (has_spell) {
        size_t len;
        colnr_T linecol = (colnr_T)(ptr - line);
        hlf_T spell_hlf = HLF_COUNT;
  
        pos = wp->w_cursor;
        wp->w_cursor.lnum = lnum;
        wp->w_cursor.col = linecol;
        len = spell_move_to(wp, FORWARD, TRUE, TRUE, &spell_hlf);
  
        /* spell_move_to() may call ml_get() and make "line" invalid */
        line = ml_get_buf(wp->w_buffer, lnum, FALSE);
        ptr = line + linecol;
  
        if (len == 0 || (int)wp->w_cursor.col > ptr - line) {
          /* no bad word found at line start, don't check until end of a
           * word */
          spell_hlf = HLF_COUNT;
          word_end = (int)(spell_to_word_end(ptr, wp) - line + 1);
        } else {
          /* bad word found, use attributes until end of word */
          assert(len <= INT_MAX);
          word_end = wp->w_cursor.col + (int)len + 1;
  
          /* Turn index into actual attributes. */
          if (spell_hlf != HLF_COUNT)
            spell_attr = highlight_attr[spell_hlf];
        }
        wp->w_cursor = pos;
  
        // Need to restart syntax highlighting for this line.
        if (has_syntax) {
          syntax_start(wp, lnum);
        }
      }
    }
  
    /*
     * Correct highlighting for cursor that can't be disabled.
     * Avoids having to check this for each character.
     */
    if (fromcol >= 0) {
      if (noinvcur) {
        if ((colnr_T)fromcol == wp->w_virtcol) {
          /* highlighting starts at cursor, let it start just after the
           * cursor */
          fromcol_prev = fromcol;
          fromcol = -1;
        } else if ((colnr_T)fromcol < wp->w_virtcol)
          /* restart highlighting after the cursor */
          fromcol_prev = wp->w_virtcol;
      }
      if (fromcol >= tocol)
        fromcol = -1;
    }
  
    /*
     * Handle highlighting the last used search pattern and matches.
     * Do this for both search_hl and the match list.
     */
    cur = wp->w_match_head;
    shl_flag = false;
    while ((cur != NULL || !shl_flag) && !number_only) {
      if (!shl_flag) {
        shl = &search_hl;
        shl_flag = true;
      } else {
        shl = &cur->hl;  // -V595
      }
      shl->startcol = MAXCOL;
      shl->endcol = MAXCOL;
      shl->attr_cur = 0;
      shl->is_addpos = false;
      v = (long)(ptr - line);
      if (cur != NULL) {
        cur->pos.cur = 0;
      }
      next_search_hl(wp, shl, lnum, (colnr_T)v,
                     shl == &search_hl ? NULL : cur);
      if (wp->w_s->b_syn_slow) {
        has_syntax = false;
      }
  
      // Need to get the line again, a multi-line regexp may have made it
      // invalid.
      line = ml_get_buf(wp->w_buffer, lnum, false);
      ptr = line + v;
  
      if (shl->lnum != 0 && shl->lnum <= lnum) {
        if (shl->lnum == lnum) {
          shl->startcol = shl->rm.startpos[0].col;
        } else {
          shl->startcol = 0;
        }
        if (lnum == shl->lnum + shl->rm.endpos[0].lnum
                    - shl->rm.startpos[0].lnum) {
            shl->endcol = shl->rm.endpos[0].col;
        } else {
            shl->endcol = MAXCOL;
        }
        // Highlight one character for an empty match.
        if (shl->startcol == shl->endcol) {
            if (line[shl->endcol] != NUL) {
                shl->endcol += (*mb_ptr2len)(line + shl->endcol);
            } else {
                ++shl->endcol;
            }
        }
        if ((long)shl->startcol < v) {   // match at leftcol
          shl->attr_cur = shl->attr;
          search_attr = shl->attr;
          search_attr_from_match = shl != &search_hl;
        }
        area_highlighting = true;
      }
      if (shl != &search_hl && cur != NULL)
        cur = cur->next;
    }
  
    unsigned off = 0;  // Offset relative start of line
    int col = 0;  // Visual column on screen.
    if (wp->w_p_rl) {
      // Rightleft window: process the text in the normal direction, but put
      // it in linebuf_char[off] from right to left.  Start at the
      // rightmost column of the window.
      col = grid->Columns - 1;
      off += col;
    }
  
    // wont highlight after 1024 columns
    int term_attrs[1024] = {0};
    if (wp->w_buffer->terminal) {
      terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs);
      extra_check = true;
    }
  
    int sign_idx = 0;
    // Repeat for the whole displayed line.
    for (;; ) {
      int has_match_conc = 0;  ///< match wants to conceal
      bool did_decrement_ptr = false;
      // Skip this quickly when working on the text.
      if (draw_state != WL_LINE) {
        if (draw_state == WL_CMDLINE - 1 && n_extra == 0) {
          draw_state = WL_CMDLINE;
          if (cmdwin_type != 0 && wp == curwin) {
            /* Draw the cmdline character. */
            n_extra = 1;
            c_extra = cmdwin_type;
            c_final = NUL;
            char_attr = win_hl_attr(wp, HLF_AT);
          }
        }
  
        if (draw_state == WL_FOLD - 1 && n_extra == 0) {
          int fdc = compute_foldcolumn(wp, 0);
  
          draw_state = WL_FOLD;
          if (fdc > 0) {
            // Draw the 'foldcolumn'.  Allocate a buffer, "extra" may
            // already be in use.
            xfree(p_extra_free);
            p_extra_free = xmalloc(MAX_MCO * fdc + 1);
            n_extra = fill_foldcolumn(p_extra_free, wp, false, lnum);
            p_extra_free[n_extra] = NUL;
            p_extra = p_extra_free;
            c_extra = NUL;
            c_final = NUL;
            char_attr = win_hl_attr(wp, HLF_FC);
          }
        }
  
        //sign column
        if (draw_state == WL_SIGN - 1 && n_extra == 0) {
            draw_state = WL_SIGN;
            /* Show the sign column when there are any signs in this
             * buffer or when using Netbeans. */
            int count = win_signcol_count(wp);
            if (count > 0) {
                int text_sign;
                // Draw cells with the sign value or blank.
                c_extra = ' ';
                c_final = NUL;
                char_attr = win_hl_attr(wp, HLF_SC);
                n_extra = win_signcol_width(wp);
  
                if (row == startrow + filler_lines && filler_todo <= 0) {
                    text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT,
                                                sign_idx, count);
                    if (text_sign != 0) {
                        p_extra = sign_get_text(text_sign);
                        if (p_extra != NULL) {
                            int symbol_blen = (int)STRLEN(p_extra);
  
                            c_extra = NUL;
                            c_final = NUL;
  
                            // TODO(oni-link): Is sign text already extended to
                            // full cell width?
                            assert((size_t)win_signcol_width(wp)
                                   >= mb_string2cells(p_extra));
                            // symbol(s) bytes + (filling spaces) (one byte each)
                            n_extra = symbol_blen +
                              (win_signcol_width(wp) - mb_string2cells(p_extra));
  
                            assert(sizeof(extra) > (size_t)symbol_blen);
                            memset(extra, ' ', sizeof(extra));
                            memcpy(extra, p_extra, symbol_blen);
  
                            p_extra = extra;
                            p_extra[n_extra] = NUL;
                        }
                        char_attr = sign_get_attr(text_sign, SIGN_TEXT);
                    }
                }
  
                sign_idx++;
                if (sign_idx < count) {
                    draw_state = WL_SIGN - 1;
                }
            }
        }
  
        if (draw_state == WL_NR - 1 && n_extra == 0) {
          draw_state = WL_NR;
          /* Display the absolute or relative line number. After the
           * first fill with blanks when the 'n' flag isn't in 'cpo' */
          if ((wp->w_p_nu || wp->w_p_rnu)
              && (row == startrow
                  + filler_lines
                  || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) {
            /* Draw the line number (empty space after wrapping). */
            if (row == startrow
                + filler_lines
                ) {
              long num;
              char *fmt = "%*ld ";
  
              if (wp->w_p_nu && !wp->w_p_rnu)
                /* 'number' + 'norelativenumber' */
                num = (long)lnum;
              else {
                /* 'relativenumber', don't use negative numbers */
                num = labs((long)get_cursor_rel_lnum(wp, lnum));
                if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
                  /* 'number' + 'relativenumber' */
                  num = lnum;
                  fmt = "%-*ld ";
                }
              }
  
              sprintf((char *)extra, fmt,
                  number_width(wp), num);
              if (wp->w_skipcol > 0)
                for (p_extra = extra; *p_extra == ' '; ++p_extra)
                  *p_extra = '-';
              if (wp->w_p_rl) {                       // reverse line numbers
                // like rl_mirror(), but keep the space at the end
                char_u *p2 = skiptowhite(extra) - 1;
                for (char_u *p1 = extra; p1 < p2; p1++, p2--) {
                  const int t = *p1;
                  *p1 = *p2;
                  *p2 = t;
                }
              }
              p_extra = extra;
              c_extra = NUL;
              c_final = NUL;
            } else {
              c_extra = ' ';
              c_final = NUL;
            }
            n_extra = number_width(wp) + 1;
            char_attr = win_hl_attr(wp, HLF_N);
  
            int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL,
                                           0, 1);
            if (num_sign != 0) {
              // :sign defined with "numhl" highlight.
              char_attr = sign_get_attr(num_sign, SIGN_NUMHL);
            } else if ((wp->w_p_cul || wp->w_p_rnu)
                       && lnum == wp->w_cursor.lnum) {
              // When 'cursorline' is set highlight the line number of
              // the current line differently.
              // TODO(vim): Can we use CursorLine instead of CursorLineNr
              // when CursorLineNr isn't set?
              char_attr = win_hl_attr(wp, HLF_CLN);
            }
          }
        }
  
        if (wp->w_p_brisbr && draw_state == WL_BRI - 1
            && n_extra == 0 && *p_sbr != NUL) {
          // draw indent after showbreak value
          draw_state = WL_BRI;
        } else if (wp->w_p_brisbr && draw_state == WL_SBR && n_extra == 0) {
          // after the showbreak, draw the breakindent
          draw_state = WL_BRI - 1;
        }
  
        // draw 'breakindent': indent wrapped text accordingly
        if (draw_state == WL_BRI - 1 && n_extra == 0) {
          draw_state = WL_BRI;
          // if need_showbreak is set, breakindent also applies
          if (wp->w_p_bri && (row != startrow || need_showbreak)
              && filler_lines == 0) {
            char_attr = 0;
  
            if (diff_hlf != (hlf_T)0) {
              char_attr = win_hl_attr(wp, diff_hlf);
              if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
                char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUL));
              }
            }
            p_extra = NULL;
            c_extra = ' ';
            n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, FALSE));
            /* Correct end of highlighted area for 'breakindent',
               required wen 'linebreak' is also set. */
            if (tocol == vcol)
              tocol += n_extra;
          }
        }
  
        if (draw_state == WL_SBR - 1 && n_extra == 0) {
          draw_state = WL_SBR;
          if (filler_todo > 0) {
            // draw "deleted" diff line(s)
            if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
              c_extra = '-';
              c_final = NUL;
            } else {
              c_extra = wp->w_p_fcs_chars.diff;
              c_final = NUL;
            }
            if (wp->w_p_rl) {
              n_extra = col + 1;
            } else {
              n_extra = grid->Columns - col;
            }
            char_attr = win_hl_attr(wp, HLF_DED);
          }
          if (*p_sbr != NUL && need_showbreak) {
            /* Draw 'showbreak' at the start of each broken line. */
            p_extra = p_sbr;
            c_extra = NUL;
            c_final = NUL;
            n_extra = (int)STRLEN(p_sbr);
            char_attr = win_hl_attr(wp, HLF_AT);
            need_showbreak = false;
            vcol_sbr = vcol + MB_CHARLEN(p_sbr);
            /* Correct end of highlighted area for 'showbreak',
             * required when 'linebreak' is also set. */
            if (tocol == vcol)
              tocol += n_extra;
            /* combine 'showbreak' with 'cursorline' */
            if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
              char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUL));
            }
          }
        }
  
        if (draw_state == WL_LINE - 1 && n_extra == 0) {
          sign_idx = 0;
          draw_state = WL_LINE;
          if (saved_n_extra) {
            /* Continue item from end of wrapped line. */
            n_extra = saved_n_extra;
            c_extra = saved_c_extra;
            c_final = saved_c_final;
            p_extra = saved_p_extra;
            char_attr = saved_char_attr;
          } else {
            char_attr = 0;
          }
        }
      }
  
      // When still displaying '$' of change command, stop at cursor
      if ((dollar_vcol >= 0 && wp == curwin
           && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol
           && filler_todo <= 0)
          || (number_only && draw_state > WL_NR)) {
        grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp,
                         wp->w_hl_attr_normal, false);
        // Pretend we have finished updating the window.  Except when
        // 'cursorcolumn' is set.
        if (wp->w_p_cuc) {
          row = wp->w_cline_row + wp->w_cline_height;
        } else {
          row = grid->Rows;
        }
        break;
      }
  
      if (draw_state == WL_LINE && (area_highlighting || has_spell)) {
        // handle Visual or match highlighting in this line
        if (vcol == fromcol
            || (vcol + 1 == fromcol && n_extra == 0
                && utf_ptr2cells(ptr) > 1)
            || ((int)vcol_prev == fromcol_prev
                && vcol_prev < vcol               // not at margin
                && vcol < tocol)) {
          area_attr = attr;                       // start highlighting
        } else if (area_attr != 0 && (vcol == tocol
                                      || (noinvcur
                                          && (colnr_T)vcol == wp->w_virtcol))) {
          area_attr = 0;                          // stop highlighting
       }
  
        if (!n_extra) {
          /*
           * Check for start/end of search pattern match.
           * After end, check for start/end of next match.
           * When another match, have to check for start again.
           * Watch out for matching an empty string!
           * Do this for 'search_hl' and the match list (ordered by
           * priority).
           */
          v = (long)(ptr - line);
          cur = wp->w_match_head;
          shl_flag = FALSE;
          while (cur != NULL || shl_flag == FALSE) {
            if (shl_flag == FALSE
                && ((cur != NULL
                     && cur->priority > SEARCH_HL_PRIORITY)
                    || cur == NULL)) {
              shl = &search_hl;
              shl_flag = TRUE;
            } else
              shl = &cur->hl;
            if (cur != NULL) {
              cur->pos.cur = 0;
            }
            bool pos_inprogress = true; // mark that a position match search is
                                        // in progress
            while (shl->rm.regprog != NULL
                                   || (cur != NULL && pos_inprogress)) {
              if (shl->startcol != MAXCOL
                  && v >= (long)shl->startcol
                  && v < (long)shl->endcol) {
                int tmp_col = v + utfc_ptr2len(ptr);
  
                if (shl->endcol < tmp_col) {
                  shl->endcol = tmp_col;
                }
                shl->attr_cur = shl->attr;
                // Match with the "Conceal" group results in hiding
                // the match.
                if (cur != NULL
                    && shl != &search_hl
                    && syn_name2id((char_u *)"Conceal") == cur->hlg_id) {
                  has_match_conc = v == (long)shl->startcol ? 2 : 1;
                  match_conc = cur->conceal_char;
                } else {
                  has_match_conc = match_conc = 0;
                }
              } else if (v == (long)shl->endcol) {
                shl->attr_cur = 0;
  
                next_search_hl(wp, shl, lnum, (colnr_T)v,
                               shl == &search_hl ? NULL : cur);
                pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
  
                /* Need to get the line again, a multi-line regexp
                 * may have made it invalid. */
                line = ml_get_buf(wp->w_buffer, lnum, FALSE);
                ptr = line + v;
  
                if (shl->lnum == lnum) {
                  shl->startcol = shl->rm.startpos[0].col;
                  if (shl->rm.endpos[0].lnum == 0)
                    shl->endcol = shl->rm.endpos[0].col;
                  else
                    shl->endcol = MAXCOL;
  
                  if (shl->startcol == shl->endcol) {
                    // highlight empty match, try again after it
                    shl->endcol += (*mb_ptr2len)(line + shl->endcol);
                  }
  
                  /* Loop to check if the match starts at the
                   * current position */
                  continue;
                }
              }
              break;
            }
            if (shl != &search_hl && cur != NULL)
              cur = cur->next;
          }
  
          /* Use attributes from match with highest priority among
           * 'search_hl' and the match list. */
          search_attr_from_match = false;
          search_attr = search_hl.attr_cur;
          cur = wp->w_match_head;
          shl_flag = FALSE;
          while (cur != NULL || shl_flag == FALSE) {
            if (shl_flag == FALSE
                && ((cur != NULL
                     && cur->priority > SEARCH_HL_PRIORITY)
                    || cur == NULL)) {
              shl = &search_hl;
              shl_flag = TRUE;
            } else
              shl = &cur->hl;
            if (shl->attr_cur != 0) {
              search_attr = shl->attr_cur;
              search_attr_from_match = shl != &search_hl;
            }
            if (shl != &search_hl && cur != NULL)
              cur = cur->next;
          }
          // Only highlight one character after the last column.
          if (*ptr == NUL
              && (wp->w_p_list && lcs_eol_one == -1)) {
            search_attr = 0;
          }
        }
  
        if (diff_hlf != (hlf_T)0) {
          if (diff_hlf == HLF_CHD && ptr - line >= change_start
              && n_extra == 0) {
            diff_hlf = HLF_TXD;                   // changed text
          }
          if (diff_hlf == HLF_TXD && ptr - line > change_end
              && n_extra == 0) {
            diff_hlf = HLF_CHD;                   // changed line
          }
          line_attr = win_hl_attr(wp, diff_hlf);
          // Overlay CursorLine onto diff-mode highlight.
          if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
            line_attr = 0 != line_attr_lowprio  // Low-priority CursorLine
              ? hl_combine_attr(hl_combine_attr(win_hl_attr(wp, HLF_CUL),
                                                line_attr),
                                hl_get_underline())
              : hl_combine_attr(line_attr, win_hl_attr(wp, HLF_CUL));
          }
        }
  
        // Decide which of the highlight attributes to use.
        attr_pri = true;
  
        if (area_attr != 0) {
          char_attr = hl_combine_attr(line_attr, area_attr);
        } else if (search_attr != 0) {
          char_attr = hl_combine_attr(line_attr, search_attr);
        }
        // Use line_attr when not in the Visual or 'incsearch' area
        // (area_attr may be 0 when "noinvcur" is set).
        else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL)
                                    || vcol < fromcol || vcol_prev < fromcol_prev
                                    || vcol >= tocol)) {
          char_attr = line_attr;
      } else {
          attr_pri = false;
          if (has_syntax) {
            char_attr = syntax_attr;
          } else {
            char_attr = 0;
          }
        }
      }
  
      // Get the next character to put on the screen.
      //
      // The "p_extra" points to the extra stuff that is inserted to
      // represent special characters (non-printable stuff) and other
      // things.  When all characters are the same, c_extra is used.
      // If c_final is set, it will compulsorily be used at the end.
      // "p_extra" must end in a NUL to avoid mb_ptr2len() reads past
      // "p_extra[n_extra]".
      // For the '$' of the 'list' option, n_extra == 1, p_extra == "".
      if (n_extra > 0) {
        if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) {
          c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra;
          mb_c = c;               // doesn't handle non-utf-8 multi-byte!
          if (utf_char2len(c) > 1) {
            mb_utf8 = true;
            u8cc[0] = 0;
            c = 0xc0;
          } else {
            mb_utf8 = false;
          }
        } else {
          c = *p_extra;
          mb_c = c;
          // If the UTF-8 character is more than one byte:
          // Decode it into "mb_c".
          mb_l = utfc_ptr2len(p_extra);
          mb_utf8 = false;
          if (mb_l > n_extra) {
            mb_l = 1;
          } else if (mb_l > 1) {
            mb_c = utfc_ptr2char(p_extra, u8cc);
            mb_utf8 = true;
            c = 0xc0;
          }
          if (mb_l == 0) {          // at the NUL at end-of-line
            mb_l = 1;
          }
  
          // If a double-width char doesn't fit display a '>' in the last column.
          if ((wp->w_p_rl ? (col <= 0) : (col >= grid->Columns - 1))
              && (*mb_char2cells)(mb_c) == 2) {
            c = '>';
            mb_c = c;
            mb_l = 1;
            (void)mb_l;
            multi_attr = win_hl_attr(wp, HLF_AT);
  
            // put the pointer back to output the double-width
            // character at the start of the next line.
            n_extra++;
            p_extra--;
          } else {
            n_extra -= mb_l - 1;
            p_extra += mb_l - 1;
          }
          p_extra++;
        }
        n_extra--;
      } else {
        int c0;
  
        if (p_extra_free != NULL) {
          XFREE_CLEAR(p_extra_free);
        }
  
        // Get a character from the line itself.
        c0 = c = *ptr;
        mb_c = c;
        // If the UTF-8 character is more than one byte: Decode it
        // into "mb_c".
        mb_l = utfc_ptr2len(ptr);
        mb_utf8 = false;
        if (mb_l > 1) {
          mb_c = utfc_ptr2char(ptr, u8cc);
          // Overlong encoded ASCII or ASCII with composing char
          // is displayed normally, except a NUL.
          if (mb_c < 0x80) {
            c0 = c = mb_c;
          }
          mb_utf8 = true;
  
          // At start of the line we can have a composing char.
          // Draw it as a space with a composing char.
          if (utf_iscomposing(mb_c)) {
            int i;
  
            for (i = MAX_MCO - 1; i > 0; i--) {
              u8cc[i] = u8cc[i - 1];
            }
            u8cc[0] = mb_c;
            mb_c = ' ';
          }
        }
  
        if ((mb_l == 1 && c >= 0x80)
            || (mb_l >= 1 && mb_c == 0)
            || (mb_l > 1 && (!vim_isprintc(mb_c)))) {
          // Illegal UTF-8 byte: display as <xx>.
          // Non-BMP character : display as ? or fullwidth ?.
          transchar_hex((char *)extra, mb_c);
          if (wp->w_p_rl) {  // reverse
            rl_mirror(extra);
          }
  
          p_extra = extra;
          c = *p_extra;
          mb_c = mb_ptr2char_adv((const char_u **)&p_extra);
          mb_utf8 = (c >= 0x80);
          n_extra = (int)STRLEN(p_extra);
          c_extra = NUL;
          c_final = NUL;
          if (area_attr == 0 && search_attr == 0) {
            n_attr = n_extra + 1;
            extra_attr = win_hl_attr(wp, HLF_8);
            saved_attr2 = char_attr;               // save current attr
          }
        } else if (mb_l == 0) {        // at the NUL at end-of-line
          mb_l = 1;
        } else if (p_arshape && !p_tbidi && arabic_char(mb_c)) {
          // Do Arabic shaping.
          int pc, pc1, nc;
          int pcc[MAX_MCO];
  
          // The idea of what is the previous and next
          // character depends on 'rightleft'.
          if (wp->w_p_rl) {
            pc = prev_c;
            pc1 = prev_c1;
            nc = utf_ptr2char(ptr + mb_l);
            prev_c1 = u8cc[0];
          } else {
            pc = utfc_ptr2char(ptr + mb_l, pcc);
            nc = prev_c;
            pc1 = pcc[0];
          }
          prev_c = mb_c;
  
          mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc);
        } else {
          prev_c = mb_c;
        }
        // If a double-width char doesn't fit display a '>' in the
        // last column; the character is displayed at the start of the
        // next line.
        if ((wp->w_p_rl ? (col <= 0) :
             (col >= grid->Columns - 1))
            && (*mb_char2cells)(mb_c) == 2) {
          c = '>';
          mb_c = c;
          mb_utf8 = false;
          mb_l = 1;
          multi_attr = win_hl_attr(wp, HLF_AT);
          // Put pointer back so that the character will be
          // displayed at the start of the next line.
          ptr--;
          did_decrement_ptr = true;
        } else if (*ptr != NUL) {
          ptr += mb_l - 1;
        }
  
        // If a double-width char doesn't fit at the left side display a '<' in
        // the first column.  Don't do this for unprintable characters.
        if (n_skip > 0 && mb_l > 1 && n_extra == 0) {
          n_extra = 1;
          c_extra = MB_FILLER_CHAR;
          c_final = NUL;
          c = ' ';
          if (area_attr == 0 && search_attr == 0) {
            n_attr = n_extra + 1;
            extra_attr = win_hl_attr(wp, HLF_AT);
            saved_attr2 = char_attr;             // save current attr
          }
          mb_c = c;
          mb_utf8 = false;
          mb_l = 1;
        }
        ptr++;
  
        if (extra_check) {
          bool can_spell = true;
  
          /* Get syntax attribute, unless still at the start of the line
           * (double-wide char that doesn't fit). */
          v = (long)(ptr - line);
          if (has_syntax && v > 0) {
            /* Get the syntax attribute for the character.  If there
             * is an error, disable syntax highlighting. */
            save_did_emsg = did_emsg;
            did_emsg = FALSE;
  
            syntax_attr = get_syntax_attr((colnr_T)v - 1,
                                          has_spell ? &can_spell : NULL, false);
  
            if (did_emsg) {
              wp->w_s->b_syn_error = TRUE;
              has_syntax = FALSE;
            } else
              did_emsg = save_did_emsg;
  
            /* Need to get the line again, a multi-line regexp may
             * have made it invalid. */
            line = ml_get_buf(wp->w_buffer, lnum, FALSE);
            ptr = line + v;
  
            if (!attr_pri) {
              char_attr = syntax_attr;
            } else {
              char_attr = hl_combine_attr(syntax_attr, char_attr);
            }
            // no concealing past the end of the line, it interferes
            // with line highlighting.
            if (c == NUL) {
              syntax_flags = 0;
            } else {
              syntax_flags = get_syntax_info(&syntax_seqnr);
            }
          } else if (!attr_pri) {
            char_attr = 0;
          }
  
          /* Check spelling (unless at the end of the line).
           * Only do this when there is no syntax highlighting, the
           * @Spell cluster is not used or the current syntax item
           * contains the @Spell cluster. */
          if (has_spell && v >= word_end && v > cur_checked_col) {
            spell_attr = 0;
            if (!attr_pri) {
              char_attr = syntax_attr;
            }
            if (c != 0 && (!has_syntax || can_spell)) {
              char_u *prev_ptr;
              char_u *p;
              int len;
              hlf_T spell_hlf = HLF_COUNT;
              prev_ptr = ptr - mb_l;
              v -= mb_l - 1;
  
              /* Use nextline[] if possible, it has the start of the
               * next line concatenated. */
              if ((prev_ptr - line) - nextlinecol >= 0) {
                p = nextline + ((prev_ptr - line) - nextlinecol);
              } else {
                p = prev_ptr;
              }
              cap_col -= (int)(prev_ptr - line);
              size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange);
              assert(tmplen <= INT_MAX);
              len = (int)tmplen;
              word_end = v + len;
  
              /* In Insert mode only highlight a word that
               * doesn't touch the cursor. */
              if (spell_hlf != HLF_COUNT
                  && (State & INSERT) != 0
                  && wp->w_cursor.lnum == lnum
                  && wp->w_cursor.col >=
                  (colnr_T)(prev_ptr - line)
                  && wp->w_cursor.col < (colnr_T)word_end) {
                spell_hlf = HLF_COUNT;
                spell_redraw_lnum = lnum;
              }
  
              if (spell_hlf == HLF_COUNT && p != prev_ptr
                  && (p - nextline) + len > nextline_idx) {
                /* Remember that the good word continues at the
                 * start of the next line. */
                checked_lnum = lnum + 1;
                checked_col = (int)((p - nextline) + len - nextline_idx);
              }
  
              /* Turn index into actual attributes. */
              if (spell_hlf != HLF_COUNT)
                spell_attr = highlight_attr[spell_hlf];
  
              if (cap_col > 0) {
                if (p != prev_ptr
                    && (p - nextline) + cap_col >= nextline_idx) {
                  /* Remember that the word in the next line
                   * must start with a capital. */
                  capcol_lnum = lnum + 1;
                  cap_col = (int)((p - nextline) + cap_col
                                  - nextline_idx);
                } else
                  /* Compute the actual column. */
                  cap_col += (int)(prev_ptr - line);
              }
            }
          }
          if (spell_attr != 0) {
            if (!attr_pri)
              char_attr = hl_combine_attr(char_attr, spell_attr);
            else
              char_attr = hl_combine_attr(spell_attr, char_attr);
          }
  
          if (has_bufhl && v > 0) {
            int bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v);
            if (bufhl_attr != 0) {
              if (!attr_pri) {
                char_attr = hl_combine_attr(char_attr, bufhl_attr);
              } else {
                char_attr = hl_combine_attr(bufhl_attr, char_attr);
              }
            }
          }
  
          if (buf->b_luahl && v > 0 && v < (long)lua_attr_bufsize+1) {
            char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]);
          }
  
          if (wp->w_buffer->terminal) {
            char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
          }
  
          // Found last space before word: check for line break.
          if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
              && !vim_isbreak((int)(*ptr))) {
            int mb_off = utf_head_off(line, ptr - 1);
            char_u *p = ptr - (mb_off + 1);
            // TODO: is passing p for start of the line OK?
            n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1;
            if (c == TAB && n_extra + col > grid->Columns) {
              n_extra = (int)wp->w_buffer->b_p_ts
                        - vcol % (int)wp->w_buffer->b_p_ts - 1;
            }
            c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
            c_final = NUL;
            if (ascii_iswhite(c)) {
              if (c == TAB)
                /* See "Tab alignment" below. */
                FIX_FOR_BOGUSCOLS;
              if (!wp->w_p_list) {
                c = ' ';
              }
            }
          }
  
          // 'list': change char 160 to 'nbsp' and space to 'space'.
          if (wp->w_p_list
              && (((c == 160
                    || (mb_utf8 && (mb_c == 160 || mb_c == 0x202f)))
                   && curwin->w_p_lcs_chars.nbsp)
                  || (c == ' ' && curwin->w_p_lcs_chars.space
                      && ptr - line <= trailcol))) {
            c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
            n_attr = 1;
            extra_attr = win_hl_attr(wp, HLF_0);
            saved_attr2 = char_attr;  // save current attr
            mb_c = c;
            if (utf_char2len(c) > 1) {
              mb_utf8 = true;
              u8cc[0] = 0;
              c = 0xc0;
            } else {
              mb_utf8 = false;
            }
          }
  
          if (trailcol != MAXCOL && ptr > line + trailcol && c == ' ') {
            c = wp->w_p_lcs_chars.trail;
            n_attr = 1;
            extra_attr = win_hl_attr(wp, HLF_0);
            saved_attr2 = char_attr;  // save current attr
            mb_c = c;
            if (utf_char2len(c) > 1) {
              mb_utf8 = true;
              u8cc[0] = 0;
              c = 0xc0;
            } else {
              mb_utf8 = false;
            }
          }
        }
  
        /*
         * Handling of non-printable characters.
         */
        if (!vim_isprintc(c)) {
          // when getting a character from the file, we may have to
          // turn it into something else on the way to putting it on the screen.
          if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
            int tab_len = 0;
            long vcol_adjusted = vcol;  // removed showbreak length
            // Only adjust the tab_len, when at the first column after the
            // showbreak value was drawn.
            if (*p_sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) {
              vcol_adjusted = vcol - MB_CHARLEN(p_sbr);
            }
            // tab amount depends on current column
            tab_len = (int)wp->w_buffer->b_p_ts
                      - vcol_adjusted % (int)wp->w_buffer->b_p_ts - 1;
  
            if (!wp->w_p_lbr || !wp->w_p_list) {
              n_extra = tab_len;
            } else {
              char_u *p;
              int    i;
              int    saved_nextra = n_extra;
  
              if (vcol_off > 0) {
                // there are characters to conceal
                tab_len += vcol_off;
              }
              // boguscols before FIX_FOR_BOGUSCOLS macro from above.
              if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0
                  && n_extra > tab_len) {
                tab_len += n_extra - tab_len;
              }
  
              /* if n_extra > 0, it gives the number of chars to use for
               * a tab, else we need to calculate the width for a tab */
              int len = (tab_len * mb_char2len(wp->w_p_lcs_chars.tab2));
              if (n_extra > 0) {
                len += n_extra - tab_len;
              }
              c = wp->w_p_lcs_chars.tab1;
              p = xmalloc(len + 1);
              memset(p, ' ', len);
              p[len] = NUL;
              xfree(p_extra_free);
              p_extra_free = p;
              for (i = 0; i < tab_len; i++) {
                utf_char2bytes(wp->w_p_lcs_chars.tab2, p);
                p += mb_char2len(wp->w_p_lcs_chars.tab2);
                n_extra += mb_char2len(wp->w_p_lcs_chars.tab2)
                           - (saved_nextra > 0 ? 1: 0);
              }
              p_extra = p_extra_free;
  
              // n_extra will be increased by FIX_FOX_BOGUSCOLS
              // macro below, so need to adjust for that here
              if (vcol_off > 0) {
                n_extra -= vcol_off;
              }
            }
  
            {
              int vc_saved = vcol_off;
  
              // Tab alignment should be identical regardless of
              // 'conceallevel' value. So tab compensates of all
              // previous concealed characters, and thus resets
              // vcol_off and boguscols accumulated so far in the
              // line. Note that the tab can be longer than
              // 'tabstop' when there are concealed characters.
              FIX_FOR_BOGUSCOLS;
  
              // Make sure, the highlighting for the tab char will be
              // correctly set further below (effectively reverts the
              // FIX_FOR_BOGSUCOLS macro.
              if (n_extra == tab_len + vc_saved && wp->w_p_list
                  && wp->w_p_lcs_chars.tab1) {
                tab_len += vc_saved;
              }
            }
  
            mb_utf8 = false;  // don't draw as UTF-8
            if (wp->w_p_list) {
              c = (n_extra == 0 && wp->w_p_lcs_chars.tab3)
                   ? wp->w_p_lcs_chars.tab3
                   : wp->w_p_lcs_chars.tab1;
              if (wp->w_p_lbr) {
                c_extra = NUL; /* using p_extra from above */
              } else {
                c_extra = wp->w_p_lcs_chars.tab2;
              }
              c_final = wp->w_p_lcs_chars.tab3;
              n_attr = tab_len + 1;
              extra_attr = win_hl_attr(wp, HLF_0);
              saved_attr2 = char_attr;  // save current attr
              mb_c = c;
              if (utf_char2len(c) > 1) {
                mb_utf8 = true;
                u8cc[0] = 0;
                c = 0xc0;
              }
            } else {
              c_final = NUL;
              c_extra = ' ';
              c = ' ';
            }
          } else if (c == NUL
                     && (wp->w_p_list
                         || ((fromcol >= 0 || fromcol_prev >= 0)
                             && tocol > vcol
                             && VIsual_mode != Ctrl_V
                             && (wp->w_p_rl ? (col >= 0) : (col < grid->Columns))
                             && !(noinvcur
                                  && lnum == wp->w_cursor.lnum
                                  && (colnr_T)vcol == wp->w_virtcol)))
                     && lcs_eol_one > 0) {
            // Display a '$' after the line or highlight an extra
            // character if the line break is included.
            // For a diff line the highlighting continues after the "$".
            if (diff_hlf == (hlf_T)0
                && line_attr == 0
                && line_attr_lowprio == 0) {
              // In virtualedit, visual selections may extend beyond end of line
              if (area_highlighting && virtual_active()
                  && tocol != MAXCOL && vcol < tocol) {
                n_extra = 0;
              } else {
                p_extra = at_end_str;
                n_extra = 1;
                c_extra = NUL;
                c_final = NUL;
              }
            }
            if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
              c = wp->w_p_lcs_chars.eol;
            } else {
              c = ' ';
            }
            lcs_eol_one = -1;
            ptr--;  // put it back at the NUL
            extra_attr = win_hl_attr(wp, HLF_AT);
            n_attr = 1;
            mb_c = c;
            if (utf_char2len(c) > 1) {
              mb_utf8 = true;
              u8cc[0] = 0;
              c = 0xc0;
            } else {
              mb_utf8 = false;                    // don't draw as UTF-8
            }
          } else if (c != NUL) {
            p_extra = transchar(c);
            if (n_extra == 0) {
                n_extra = byte2cells(c) - 1;
            }
            if ((dy_flags & DY_UHEX) && wp->w_p_rl)
              rl_mirror(p_extra);                 /* reverse "<12>" */
            c_extra = NUL;
            c_final = NUL;
            if (wp->w_p_lbr) {
              char_u *p;
  
              c = *p_extra;
              p = xmalloc(n_extra + 1);
              memset(p, ' ', n_extra);
              STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1);
              p[n_extra] = NUL;
              xfree(p_extra_free);
              p_extra_free = p_extra = p;
            } else {
              n_extra = byte2cells(c) - 1;
              c = *p_extra++;
            }
            n_attr = n_extra + 1;
            extra_attr = win_hl_attr(wp, HLF_8);
            saved_attr2 = char_attr;  // save current attr
            mb_utf8 = false;   // don't draw as UTF-8
          } else if (VIsual_active
                     && (VIsual_mode == Ctrl_V || VIsual_mode == 'v')
                     && virtual_active()
                     && tocol != MAXCOL
                     && vcol < tocol
                     && (wp->w_p_rl ? (col >= 0) : (col < grid->Columns))) {
            c = ' ';
            ptr--;  // put it back at the NUL
          }
        }
  
        if (wp->w_p_cole > 0
            && (wp != curwin || lnum != wp->w_cursor.lnum
                || conceal_cursor_line(wp))
            && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0)
            && !(lnum_in_visual_area
                 && vim_strchr(wp->w_p_cocu, 'v') == NULL)) {
          char_attr = conceal_attr;
          if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1)
              && (syn_get_sub_char() != NUL || match_conc
                  || wp->w_p_cole == 1)
              && wp->w_p_cole != 3) {
            // First time at this concealed item: display one
            // character.
            if (match_conc) {
              c = match_conc;
            } else if (syn_get_sub_char() != NUL) {
              c = syn_get_sub_char();
            } else if (wp->w_p_lcs_chars.conceal != NUL) {
              c = wp->w_p_lcs_chars.conceal;
            } else {
              c = ' ';
            }
  
            prev_syntax_id = syntax_seqnr;
  
            if (n_extra > 0)
              vcol_off += n_extra;
            vcol += n_extra;
            if (wp->w_p_wrap && n_extra > 0) {
              if (wp->w_p_rl) {
                col -= n_extra;
                boguscols -= n_extra;
              } else {
                boguscols += n_extra;
                col += n_extra;
              }
            }
            n_extra = 0;
            n_attr = 0;
          } else if (n_skip == 0) {
            is_concealing = TRUE;
            n_skip = 1;
          }
          mb_c = c;
          if (utf_char2len(c) > 1) {
            mb_utf8 = true;
            u8cc[0] = 0;
            c = 0xc0;
          } else {
            mb_utf8 = false;              // don't draw as UTF-8
          }
        } else {
          prev_syntax_id = 0;
          is_concealing = FALSE;
        }
  
        if (n_skip > 0 && did_decrement_ptr) {
          // not showing the '>', put pointer back to avoid getting stuck
          ptr++;
        }
      }
  
      /* In the cursor line and we may be concealing characters: correct
       * the cursor column when we reach its position. */
      if (!did_wcol && draw_state == WL_LINE
          && wp == curwin && lnum == wp->w_cursor.lnum
          && conceal_cursor_line(wp)
          && (int)wp->w_virtcol <= vcol + n_skip) {
        if (wp->w_p_rl) {
          wp->w_wcol = grid->Columns - col + boguscols - 1;
        } else {
          wp->w_wcol = col - boguscols;
        }
        wp->w_wrow = row;
        did_wcol = true;
      }
  
      // Don't override visual selection highlighting.
      if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) {
        char_attr = hl_combine_attr(char_attr, extra_attr);
      }
  
      // Handle the case where we are in column 0 but not on the first
      // character of the line and the user wants us to show us a
      // special character (via 'listchars' option "precedes:<char>".
      if (lcs_prec_todo != NUL
          && wp->w_p_list
          && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0)
          && filler_todo <= 0
          && draw_state > WL_NR
          && c != NUL) {
        c = wp->w_p_lcs_chars.prec;
        lcs_prec_todo = NUL;
        if ((*mb_char2cells)(mb_c) > 1) {
          // Double-width character being overwritten by the "precedes"
          // character, need to fill up half the character.
          c_extra = MB_FILLER_CHAR;
          c_final = NUL;
          n_extra = 1;
          n_attr = 2;
          extra_attr = win_hl_attr(wp, HLF_AT);
        }
        mb_c = c;
        if (utf_char2len(c) > 1) {
          mb_utf8 = true;
          u8cc[0] = 0;
          c = 0xc0;
        } else {
          mb_utf8 = false;  // don't draw as UTF-8
        }
        saved_attr3 = char_attr;  // save current attr
        char_attr = win_hl_attr(wp, HLF_AT);  // overwriting char_attr
        n_attr3 = 1;
      }
  
      // At end of the text line or just after the last character.
      if (c == NUL) {
        long prevcol = (long)(ptr - line) - 1;
  
        // we're not really at that column when skipping some text
        if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
          prevcol++;
        }
  
        // Invert at least one char, used for Visual and empty line or
        // highlight match at end of line. If it's beyond the last
        // char on the screen, just overwrite that one (tricky!)  Not
        // needed when a '$' was displayed for 'list'.
        prevcol_hl_flag = false;
        if (!search_hl.is_addpos && prevcol == (long)search_hl.startcol) {
          prevcol_hl_flag = true;
        } else {
          cur = wp->w_match_head;
          while (cur != NULL) {
            if (!cur->hl.is_addpos && prevcol == (long)cur->hl.startcol) {
              prevcol_hl_flag = true;
              break;
            }
            cur = cur->next;
          }
        }
        if (wp->w_p_lcs_chars.eol == lcs_eol_one
            && ((area_attr != 0 && vcol == fromcol
                 && (VIsual_mode != Ctrl_V
                     || lnum == VIsual.lnum
                     || lnum == curwin->w_cursor.lnum))
                // highlight 'hlsearch' match at end of line
                || prevcol_hl_flag)) {
          int n = 0;
  
          if (wp->w_p_rl) {
            if (col < 0)
              n = 1;
          } else {
            if (col >= grid->Columns) {
              n = -1;
            }
          }
          if (n != 0) {
            /* At the window boundary, highlight the last character
             * instead (better than nothing). */
            off += n;
            col += n;
          } else {
            // Add a blank character to highlight.
            schar_from_ascii(linebuf_char[off], ' ');
          }
          if (area_attr == 0) {
            /* Use attributes from match with highest priority among
             * 'search_hl' and the match list. */
            char_attr = search_hl.attr;
            cur = wp->w_match_head;
            shl_flag = FALSE;
            while (cur != NULL || shl_flag == FALSE) {
              if (shl_flag == FALSE
                  && ((cur != NULL
                       && cur->priority > SEARCH_HL_PRIORITY)
                      || cur == NULL)) {
                shl = &search_hl;
                shl_flag = TRUE;
              } else
                shl = &cur->hl;
              if ((ptr - line) - 1 == (long)shl->startcol
                  && (shl == &search_hl || !shl->is_addpos)) {
                char_attr = shl->attr;
              }
              if (shl != &search_hl && cur != NULL) {
                cur = cur->next;
              }
            }
          }
  
          int eol_attr = char_attr;
          if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
            eol_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUL), eol_attr);
          }
          linebuf_attr[off] = eol_attr;
          if (wp->w_p_rl) {
            --col;
            --off;
          } else {
            ++col;
            ++off;
          }
          ++vcol;
          eol_hl_off = 1;
        }
        // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
        if (wp->w_p_wrap) {
          v = wp->w_skipcol;
        } else {
          v = wp->w_leftcol;
        }
  
        /* check if line ends before left margin */
        if (vcol < v + col - win_col_off(wp))
          vcol = v + col - win_col_off(wp);
        /* Get rid of the boguscols now, we want to draw until the right
         * edge for 'cursorcolumn'. */
        col -= boguscols;
        // boguscols = 0;  // Disabled because value never read after this
  
        if (draw_color_col)
          draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
  
        if (((wp->w_p_cuc
              && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
              && (int)wp->w_virtcol <
              grid->Columns * (row - startrow + 1) + v
              && lnum != wp->w_cursor.lnum)
             || draw_color_col || line_attr_lowprio || line_attr
             || diff_hlf != (hlf_T)0 || do_virttext)) {
          int rightmost_vcol = 0;
          int i;
  
          VirtText virt_text;
          if (luatext) {
            virt_text = (VirtText)KV_INITIAL_VALUE;
            kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 }));
          } else {
            virt_text = do_virttext ? bufhl_info.line->virt_text
                                    : (VirtText)KV_INITIAL_VALUE;
          }
          size_t virt_pos = 0;
          LineState s = LINE_STATE((char_u *)"");
          int virt_attr = 0;
  
          // Make sure alignment is the same regardless
          // if listchars=eol:X is used or not.
          bool delay_virttext = wp->w_p_lcs_chars.eol == lcs_eol_one
                                && eol_hl_off == 0;
  
          if (wp->w_p_cuc) {
            rightmost_vcol = wp->w_virtcol;
          }
  
          if (draw_color_col) {
            // determine rightmost colorcolumn to possibly draw
            for (i = 0; color_cols[i] >= 0; i++) {
              if (rightmost_vcol < color_cols[i]) {
                rightmost_vcol = color_cols[i];
              }
            }
          }
  
          int cuc_attr = win_hl_attr(wp, HLF_CUC);
          int mc_attr = win_hl_attr(wp, HLF_MC);
  
          int diff_attr = 0;
          if (diff_hlf == HLF_TXD) {
            diff_hlf = HLF_CHD;
          }
          if (diff_hlf != 0) {
            diff_attr = win_hl_attr(wp, diff_hlf);
          }
  
          int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
          if (base_attr || line_attr) {
            rightmost_vcol = INT_MAX;
          }
  
          int col_stride = wp->w_p_rl ? -1 : 1;
  
          while (wp->w_p_rl ? col >= 0 : col < grid->Columns) {
            int cells = -1;
            if (do_virttext && !delay_virttext) {
              if (*s.p == NUL) {
                if (virt_pos < virt_text.size) {
                  s.p = (char_u *)kv_A(virt_text, virt_pos).text;
                  int hl_id = kv_A(virt_text, virt_pos).hl_id;
                  virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
                  virt_pos++;
                } else {
                 do_virttext = false;
                }
              }
              if (*s.p != NUL) {
                cells = line_putchar(&s, &linebuf_char[off], grid->Columns - col,
                                     false);
              }
            }
            delay_virttext = false;
  
            if (cells == -1) {
              schar_from_ascii(linebuf_char[off], ' ');
              cells = 1;
            }
            col += cells * col_stride;
            if (draw_color_col) {
              draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
            }
  
            int col_attr = base_attr;
  
            if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) {
              col_attr = cuc_attr;
            } else if (draw_color_col && VCOL_HLC == *color_cols) {
              col_attr = mc_attr;
            }
  
            if (do_virttext) {
              col_attr = hl_combine_attr(col_attr, virt_attr);
            }
  
            col_attr = hl_combine_attr(col_attr, line_attr);
  
            linebuf_attr[off] = col_attr;
            if (cells == 2) {
              linebuf_attr[off+1] = col_attr;
            }
            off += cells * col_stride;
  
            if (VCOL_HLC >= rightmost_vcol && *s.p == NUL
                && virt_pos >= virt_text.size) {
              break;
            }
  
            vcol += cells;
          }
        }
  
        // TODO(bfredl): integrate with the common beyond-the-end-loop
        if (wp->w_buffer->terminal) {
          // terminal buffers may need to highlight beyond the end of the
          // logical line
          int n = wp->w_p_rl ? -1 : 1;
          while (col >= 0 && col < grid->Columns) {
            schar_from_ascii(linebuf_char[off], ' ');
            linebuf_attr[off] = term_attrs[vcol];
            off += n;
            vcol += n;
            col += n;
          }
        }
        grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp,
                         wp->w_hl_attr_normal, false);
        row++;
  
        /*
         * Update w_cline_height and w_cline_folded if the cursor line was
         * updated (saves a call to plines() later).
         */
        if (wp == curwin && lnum == curwin->w_cursor.lnum) {
          curwin->w_cline_row = startrow;
          curwin->w_cline_height = row - startrow;
          curwin->w_cline_folded = false;
          curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
          conceal_cursor_used = conceal_cursor_line(curwin);
        }
  
        break;
      }
  
      // Show "extends" character from 'listchars' if beyond the line end and
      // 'list' is set.
      if (wp->w_p_lcs_chars.ext != NUL
          && wp->w_p_list
          && !wp->w_p_wrap
          && filler_todo <= 0
          && (wp->w_p_rl ? col == 0 : col == grid->Columns - 1)
          && (*ptr != NUL
              || lcs_eol_one > 0
              || (n_extra && (c_extra != NUL || *p_extra != NUL)))) {
        c = wp->w_p_lcs_chars.ext;
        char_attr = win_hl_attr(wp, HLF_AT);
        mb_c = c;
        if (utf_char2len(c) > 1) {
          mb_utf8 = true;
          u8cc[0] = 0;
          c = 0xc0;
        } else {
          mb_utf8 = false;
        }
      }
  
      // advance to the next 'colorcolumn'
      if (draw_color_col) {
        draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
      }
  
      // Highlight the cursor column if 'cursorcolumn' is set.  But don't
      // highlight the cursor position itself.
      // Also highlight the 'colorcolumn' if it is different than
      // 'cursorcolumn'
      vcol_save_attr = -1;
      if (draw_state == WL_LINE && !lnum_in_visual_area
          && search_attr == 0 && area_attr == 0) {
        if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol
            && lnum != wp->w_cursor.lnum) {
          vcol_save_attr = char_attr;
          char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr);
        } else if (draw_color_col && VCOL_HLC == *color_cols) {
          vcol_save_attr = char_attr;
          char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr);
        }
      }
  
      // Apply lowest-priority line attr now, so everything can override it.
      if (draw_state == WL_LINE) {
        char_attr = hl_combine_attr(line_attr_lowprio, char_attr);
      }
  
      // Store character to be displayed.
      // Skip characters that are left of the screen for 'nowrap'.
      vcol_prev = vcol;
      if (draw_state < WL_LINE || n_skip <= 0) {
        //
        // Store the character.
        //
        if (wp->w_p_rl && (*mb_char2cells)(mb_c) > 1) {
          // A double-wide character is: put first halve in left cell.
          off--;
          col--;
        }
        if (mb_utf8) {
          schar_from_cc(linebuf_char[off], mb_c, u8cc);
        } else {
          schar_from_ascii(linebuf_char[off], c);
        }
        if (multi_attr) {
          linebuf_attr[off] = multi_attr;
          multi_attr = 0;
        } else {
          linebuf_attr[off] = char_attr;
        }
  
        if ((*mb_char2cells)(mb_c) > 1) {
          // Need to fill two screen columns.
          off++;
          col++;
          // UTF-8: Put a 0 in the second screen char.
          linebuf_char[off][0] = 0;
          if (draw_state > WL_NR && filler_todo <= 0) {
            vcol++;
          }
          // When "tocol" is halfway through a character, set it to the end of
          // the character, otherwise highlighting won't stop.
          if (tocol == vcol) {
            tocol++;
          }
          if (wp->w_p_rl) {
            /* now it's time to backup one cell */
            --off;
            --col;
          }
        }
        if (wp->w_p_rl) {
          --off;
          --col;
        } else {
          ++off;
          ++col;
        }
      } else if (wp->w_p_cole > 0 && is_concealing) {
        --n_skip;
        ++vcol_off;
        if (n_extra > 0)
          vcol_off += n_extra;
        if (wp->w_p_wrap) {
          /*
           * Special voodoo required if 'wrap' is on.
           *
           * Advance the column indicator to force the line
           * drawing to wrap early. This will make the line
           * take up the same screen space when parts are concealed,
           * so that cursor line computations aren't messed up.
           *
           * To avoid the fictitious advance of 'col' causing
           * trailing junk to be written out of the screen line
           * we are building, 'boguscols' keeps track of the number
           * of bad columns we have advanced.
           */
          if (n_extra > 0) {
            vcol += n_extra;
            if (wp->w_p_rl) {
              col -= n_extra;
              boguscols -= n_extra;
            } else {
              col += n_extra;
              boguscols += n_extra;
            }
            n_extra = 0;
            n_attr = 0;
          }
  
  
          if ((*mb_char2cells)(mb_c) > 1) {
            // Need to fill two screen columns.
            if (wp->w_p_rl) {
              --boguscols;
              --col;
            } else {
              ++boguscols;
              ++col;
            }
          }
  
          if (wp->w_p_rl) {
            --boguscols;
            --col;
          } else {
            ++boguscols;
            ++col;
          }
        } else {
          if (n_extra > 0) {
            vcol += n_extra;
            n_extra = 0;
            n_attr = 0;
          }
        }
  
      } else
        --n_skip;
  
      /* Only advance the "vcol" when after the 'number' or 'relativenumber'
       * column. */
      if (draw_state > WL_NR
          && filler_todo <= 0
          )
        ++vcol;
  
      if (vcol_save_attr >= 0)
        char_attr = vcol_save_attr;
  
      /* restore attributes after "predeces" in 'listchars' */
      if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0)
        char_attr = saved_attr3;
  
      /* restore attributes after last 'listchars' or 'number' char */
      if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0)
        char_attr = saved_attr2;
  
      /*
       * At end of screen line and there is more to come: Display the line
       * so far.  If there is no more to display it is caught above.
       */
      if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns))
          && (*ptr != NUL
              || filler_todo > 0
              || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
                  && p_extra != at_end_str)
              || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL)))
          ) {
        bool wrap = wp->w_p_wrap       // Wrapping enabled.
          && filler_todo <= 0          // Not drawing diff filler lines.
          && lcs_eol_one != -1         // Haven't printed the lcs_eol character.
          && row != endrow - 1         // Not the last line being displayed.
          && (grid->Columns == Columns  // Window spans the width of the screen,
              || ui_has(kUIMultigrid))  // or has dedicated grid.
          && !wp->w_p_rl;              // Not right-to-left.
        grid_put_linebuf(grid, row, 0, col - boguscols, grid->Columns, wp->w_p_rl,
                         wp, wp->w_hl_attr_normal, wrap);
        if (wrap) {
          ScreenGrid *current_grid = grid;
          int current_row = row, dummy_col = 0;  // dummy_col unused
          screen_adjust_grid(&current_grid, &current_row, &dummy_col);
  
          // Force a redraw of the first column of the next line.
          current_grid->attrs[current_grid->line_offset[current_row+1]] = -1;
  
          // Remember that the line wraps, used for modeless copy.
          current_grid->line_wraps[current_row] = true;
        }
  
        boguscols = 0;
        row++;
  
        /* When not wrapping and finished diff lines, or when displayed
         * '$' and highlighting until last column, break here. */
        if ((!wp->w_p_wrap
             && filler_todo <= 0
             ) || lcs_eol_one == -1)
          break;
  
        // When the window is too narrow draw all "@" lines.
        if (draw_state != WL_LINE && filler_todo <= 0) {
          win_draw_end(wp, '@', ' ', true, row, wp->w_grid.Rows, HLF_AT);
          row = endrow;
        }
  
        /* When line got too long for screen break here. */
        if (row == endrow) {
          ++row;
          break;
        }
  
        col = 0;
        off = 0;
        if (wp->w_p_rl) {
          col = grid->Columns - 1;  // col is not used if breaking!
          off += col;
        }
  
        /* reset the drawing state for the start of a wrapped line */
        draw_state = WL_START;
        saved_n_extra = n_extra;
        saved_p_extra = p_extra;
        saved_c_extra = c_extra;
        saved_c_final = c_final;
        saved_char_attr = char_attr;
        n_extra = 0;
        lcs_prec_todo = wp->w_p_lcs_chars.prec;
        if (filler_todo <= 0) {
          need_showbreak = true;
        }
        filler_todo--;
        // When the filler lines are actually below the last line of the
        // file, don't draw the line itself, break here.
        if (filler_todo == 0 && wp->w_botfill) {
          break;
        }
      }
  
    }     /* for every character in the line */
  
    /* After an empty line check first word for capital. */
    if (*skipwhite(line) == NUL) {
      capcol_lnum = lnum + 1;
      cap_col = 0;
    }
  
    xfree(p_extra_free);
    xfree(luatext);
    return row;
  }
  
  /// Determine if dedicated window grid should be used or the default_grid
  ///
  /// If UI did not request multigrid support, draw all windows on the
  /// default_grid.
  ///
  /// NB: this function can only been used with window grids in a context where
  /// win_grid_alloc already has been called!
  ///
  /// If the default_grid is used, adjust window relative positions to global
  /// screen positions.
  void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off)
  {
    if (!(*grid)->chars && *grid != &default_grid) {
        *row_off += (*grid)->row_offset;
        *col_off += (*grid)->col_offset;
      if (*grid == &msg_grid_adj && msg_grid.chars) {
        *grid = &msg_grid;
      } else {
        *grid = &default_grid;
      }
    }
  }
  
  
  /*
   * Check whether the given character needs redrawing:
   * - the (first byte of the) character is different
   * - the attributes are different
   * - the character is multi-byte and the next byte is different
   * - the character is two cells wide and the second cell differs.
   */
  static int grid_char_needs_redraw(ScreenGrid *grid, int off_from, int off_to,
                                    int cols)
  {
    return (cols > 0
            && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to])
                 || linebuf_attr[off_from] != grid->attrs[off_to]
                 || (line_off2cells(linebuf_char, off_from, off_from + cols) > 1
                     && schar_cmp(linebuf_char[off_from + 1],
                                  grid->chars[off_to + 1])))
                || rdb_flags & RDB_NODELTA));
  }
  
  /// Move one buffered line to the window grid, but only the characters that
  /// have actually changed.  Handle insert/delete character.
  /// "coloff" gives the first column on the grid for this line.
  /// "endcol" gives the columns where valid characters are.
  /// "clear_width" is the width of the window.  It's > 0 if the rest of the line
  /// needs to be cleared, negative otherwise.
  /// "rlflag" is TRUE in a rightleft window:
  ///    When TRUE and "clear_width" > 0, clear columns 0 to "endcol"
  ///    When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width"
  /// If "wrap" is true, then hint to the UI that "row" contains a line
  /// which has wrapped into the next row.
  static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol,
                               int clear_width, int rlflag, win_T *wp,
                               int bg_attr, bool wrap)
  {
    unsigned off_from;
    unsigned off_to;
    unsigned max_off_from;
    unsigned max_off_to;
    int col = 0;
    bool redraw_this;                         // Does character need redraw?
    bool redraw_next;                         // redraw_this for next character
    bool clear_next = false;
    int char_cells;                           // 1: normal char
                                              // 2: occupies two display cells
    int start_dirty = -1, end_dirty = 0;
  
    // TODO(bfredl): check all callsites and eliminate
    // Check for illegal row and col, just in case
    if (row >= grid->Rows) {
      row = grid->Rows - 1;
    }
    if (endcol > grid->Columns) {
      endcol = grid->Columns;
    }
  
    screen_adjust_grid(&grid, &row, &coloff);
  
    // Safety check. Avoids clang warnings down the call stack.
    if (grid->chars == NULL || row >= grid->Rows || coloff >= grid->Columns) {
      DLOG("invalid state, skipped");
      return;
    }
  
    off_from = 0;
    off_to = grid->line_offset[row] + coloff;
    max_off_from = linebuf_size;
    max_off_to = grid->line_offset[row] + grid->Columns;
  
    if (rlflag) {
      /* Clear rest first, because it's left of the text. */
      if (clear_width > 0) {
        while (col <= endcol && grid->chars[off_to][0] == ' '
               && grid->chars[off_to][1] == NUL
               && grid->attrs[off_to] == bg_attr
               ) {
          ++off_to;
          ++col;
        }
        if (col <= endcol) {
          grid_fill(grid, row, row + 1, col + coloff, endcol + coloff + 1,
                    ' ', ' ', bg_attr);
        }
      }
      col = endcol + 1;
      off_to = grid->line_offset[row] + col + coloff;
      off_from += col;
      endcol = (clear_width > 0 ? clear_width : -clear_width);
    }
  
    if (bg_attr) {
      for (int c = col; c < endcol; c++) {
        linebuf_attr[off_from+c] =
          hl_combine_attr(bg_attr, linebuf_attr[off_from+c]);
      }
    }
  
    redraw_next = grid_char_needs_redraw(grid, off_from, off_to, endcol - col);
  
    while (col < endcol) {
      char_cells = 1;
      if (col + 1 < endcol) {
        char_cells = line_off2cells(linebuf_char, off_from, max_off_from);
      }
      redraw_this = redraw_next;
      redraw_next = grid_char_needs_redraw(grid, off_from + char_cells,
                                           off_to + char_cells,
                                           endcol - col - char_cells);
  
      if (redraw_this) {
        if (start_dirty == -1) {
          start_dirty = col;
        }
        end_dirty = col + char_cells;
        // When writing a single-width character over a double-width
        // character and at the end of the redrawn text, need to clear out
        // the right halve of the old character.
        // Also required when writing the right halve of a double-width
        // char over the left halve of an existing one
        if (col + char_cells == endcol
            && ((char_cells == 1
                 && grid_off2cells(grid, off_to, max_off_to) > 1)
                || (char_cells == 2
                    && grid_off2cells(grid, off_to, max_off_to) == 1
                    && grid_off2cells(grid, off_to + 1, max_off_to) > 1))) {
          clear_next = true;
        }
  
        schar_copy(grid->chars[off_to], linebuf_char[off_from]);
        if (char_cells == 2) {
          schar_copy(grid->chars[off_to+1], linebuf_char[off_from+1]);
        }
  
        grid->attrs[off_to] = linebuf_attr[off_from];
        // For simplicity set the attributes of second half of a
        // double-wide character equal to the first half.
        if (char_cells == 2) {
          grid->attrs[off_to + 1] = linebuf_attr[off_from];
        }
      }
  
      off_to += char_cells;
      off_from += char_cells;
      col += char_cells;
    }
  
    if (clear_next) {
      /* Clear the second half of a double-wide character of which the left
       * half was overwritten with a single-wide character. */
      schar_from_ascii(grid->chars[off_to], ' ');
      end_dirty++;
    }
  
    int clear_end = -1;
    if (clear_width > 0 && !rlflag) {
      // blank out the rest of the line
      // TODO(bfredl): we could cache winline widths
      while (col < clear_width) {
        if (grid->chars[off_to][0] != ' '
            || grid->chars[off_to][1] != NUL
            || grid->attrs[off_to] != bg_attr) {
          grid->chars[off_to][0] = ' ';
          grid->chars[off_to][1] = NUL;
          grid->attrs[off_to] = bg_attr;
          if (start_dirty == -1) {
            start_dirty = col;
            end_dirty = col;
          } else if (clear_end == -1) {
            end_dirty = endcol;
          }
          clear_end = col+1;
        }
        col++;
        off_to++;
      }
    }
  
    if (clear_width > 0 || wp->w_width != grid->Columns) {
      // If we cleared after the end of the line, it did not wrap.
      // For vsplit, line wrapping is not possible.
      grid->line_wraps[row] = false;
    }
  
    if (clear_end < end_dirty) {
      clear_end = end_dirty;
    }
    if (start_dirty == -1) {
      start_dirty = end_dirty;
    }
    if (clear_end > start_dirty) {
      ui_line(grid, row, coloff+start_dirty, coloff+end_dirty, coloff+clear_end,
              bg_attr, wrap);
    }
  }
  
  /*
   * Mirror text "str" for right-left displaying.
   * Only works for single-byte characters (e.g., numbers).
   */
  void rl_mirror(char_u *str)
  {
    char_u      *p1, *p2;
    int t;
  
    for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; ++p1, --p2) {
      t = *p1;
      *p1 = *p2;
      *p2 = t;
    }
  }
  
  /*
   * mark all status lines for redraw; used after first :cd
   */
  void status_redraw_all(void)
  {
  
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_status_height) {
        wp->w_redr_status = TRUE;
        redraw_later(VALID);
      }
    }
  }
  
  /// Marks all status lines of the current buffer for redraw.
  void status_redraw_curbuf(void)
  {
    status_redraw_buf(curbuf);
  }
  
  /// Marks all status lines of the specified buffer for redraw.
  void status_redraw_buf(buf_T *buf)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_status_height != 0 && wp->w_buffer == buf) {
        wp->w_redr_status = true;
        redraw_later(VALID);
      }
    }
  }
  
  /*
   * Redraw all status lines that need to be redrawn.
   */
  void redraw_statuslines(void)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_redr_status) {
        win_redr_status(wp);
      }
    }
    if (redraw_tabline)
      draw_tabline();
  }
  
  /*
   * Redraw all status lines at the bottom of frame "frp".
   */
  void win_redraw_last_status(const frame_T *frp)
    FUNC_ATTR_NONNULL_ARG(1)
  {
    if (frp->fr_layout == FR_LEAF) {
      frp->fr_win->w_redr_status = true;
    } else if (frp->fr_layout == FR_ROW) {
      FOR_ALL_FRAMES(frp, frp->fr_child) {
        win_redraw_last_status(frp);
      }
    } else {
      assert(frp->fr_layout == FR_COL);
      frp = frp->fr_child;
      while (frp->fr_next != NULL) {
        frp = frp->fr_next;
      }
      win_redraw_last_status(frp);
    }
  }
  
  /*
   * Draw the verticap separator right of window "wp" starting with line "row".
   */
  static void draw_vsep_win(win_T *wp, int row)
  {
    int hl;
    int c;
  
    if (wp->w_vsep_width) {
      // draw the vertical separator right of this window
      c = fillchar_vsep(wp, &hl);
      grid_fill(&default_grid, wp->w_winrow + row, W_ENDROW(wp),
                W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl);
    }
  }
  
  
  /*
   * Get the length of an item as it will be shown in the status line.
   */
  static int status_match_len(expand_T *xp, char_u *s)
  {
    int len = 0;
  
    int emenu = (xp->xp_context == EXPAND_MENUS
                 || xp->xp_context == EXPAND_MENUNAMES);
  
    /* Check for menu separators - replace with '|'. */
    if (emenu && menu_is_separator(s))
      return 1;
  
    while (*s != NUL) {
      s += skip_status_match_char(xp, s);
      len += ptr2cells(s);
      MB_PTR_ADV(s);
    }
  
    return len;
  }
  
  /*
   * Return the number of characters that should be skipped in a status match.
   * These are backslashes used for escaping.  Do show backslashes in help tags.
   */
  static int skip_status_match_char(expand_T *xp, char_u *s)
  {
    if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP)
        || ((xp->xp_context == EXPAND_MENUS
             || xp->xp_context == EXPAND_MENUNAMES)
            && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))
        ) {
  #ifndef BACKSLASH_IN_FILENAME
      if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!')
        return 2;
  #endif
      return 1;
    }
    return 0;
  }
  
  /*
   * Show wildchar matches in the status line.
   * Show at least the "match" item.
   * We start at item 'first_match' in the list and show all matches that fit.
   *
   * If inversion is possible we use it. Else '=' characters are used.
   */
  void
  win_redr_status_matches (
      expand_T *xp,
      int num_matches,
      char_u **matches,          /* list of matches */
      int match,
      int showtail
  )
  {
  #define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m])
    int row;
    char_u      *buf;
    int len;
    int clen;                     /* length in screen cells */
    int fillchar;
    int attr;
    int i;
    int highlight = TRUE;
    char_u      *selstart = NULL;
    int selstart_col = 0;
    char_u      *selend = NULL;
    static int first_match = 0;
    int add_left = FALSE;
    char_u      *s;
    int emenu;
    int l;
  
    if (matches == NULL)          /* interrupted completion? */
      return;
  
    buf = xmalloc(Columns * MB_MAXBYTES + 1);
  
    if (match == -1) {    /* don't show match but original text */
      match = 0;
      highlight = FALSE;
    }
    /* count 1 for the ending ">" */
    clen = status_match_len(xp, L_MATCH(match)) + 3;
    if (match == 0)
      first_match = 0;
    else if (match < first_match) {
      /* jumping left, as far as we can go */
      first_match = match;
      add_left = TRUE;
    } else {
      /* check if match fits on the screen */
      for (i = first_match; i < match; ++i)
        clen += status_match_len(xp, L_MATCH(i)) + 2;
      if (first_match > 0)
        clen += 2;
      // jumping right, put match at the left
      if ((long)clen > Columns) {
        first_match = match;
        /* if showing the last match, we can add some on the left */
        clen = 2;
        for (i = match; i < num_matches; ++i) {
          clen += status_match_len(xp, L_MATCH(i)) + 2;
          if ((long)clen >= Columns) {
            break;
          }
        }
        if (i == num_matches)
          add_left = TRUE;
      }
    }
    if (add_left)
      while (first_match > 0) {
        clen += status_match_len(xp, L_MATCH(first_match - 1)) + 2;
        if ((long)clen >= Columns) {
          break;
        }
        first_match--;
      }
  
    fillchar = fillchar_status(&attr, curwin);
  
    if (first_match == 0) {
      *buf = NUL;
      len = 0;
    } else {
      STRCPY(buf, "< ");
      len = 2;
    }
    clen = len;
  
    i = first_match;
    while ((long)(clen + status_match_len(xp, L_MATCH(i)) + 2) < Columns) {
      if (i == match) {
        selstart = buf + len;
        selstart_col = clen;
      }
  
      s = L_MATCH(i);
      /* Check for menu separators - replace with '|' */
      emenu = (xp->xp_context == EXPAND_MENUS
               || xp->xp_context == EXPAND_MENUNAMES);
      if (emenu && menu_is_separator(s)) {
        STRCPY(buf + len, transchar('|'));
        l = (int)STRLEN(buf + len);
        len += l;
        clen += l;
      } else
        for (; *s != NUL; ++s) {
          s += skip_status_match_char(xp, s);
          clen += ptr2cells(s);
          if ((l = (*mb_ptr2len)(s)) > 1) {
            STRNCPY(buf + len, s, l);  // NOLINT(runtime/printf)
            s += l - 1;
            len += l;
          } else {
            STRCPY(buf + len, transchar_byte(*s));
            len += (int)STRLEN(buf + len);
          }
        }
      if (i == match)
        selend = buf + len;
  
      *(buf + len++) = ' ';
      *(buf + len++) = ' ';
      clen += 2;
      if (++i == num_matches)
        break;
    }
  
    if (i != num_matches) {
      *(buf + len++) = '>';
      ++clen;
    }
  
    buf[len] = NUL;
  
    row = cmdline_row - 1;
    if (row >= 0) {
      if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) {
        if (msg_scrolled > 0) {
          /* Put the wildmenu just above the command line.  If there is
           * no room, scroll the screen one line up. */
          if (cmdline_row == Rows - 1) {
            msg_scroll_up(false);
            msg_scrolled++;
          } else {
            cmdline_row++;
            row++;
          }
          wild_menu_showing = WM_SCROLLED;
        } else {
          /* Create status line if needed by setting 'laststatus' to 2.
           * Set 'winminheight' to zero to avoid that the window is
           * resized. */
          if (lastwin->w_status_height == 0) {
            save_p_ls = p_ls;
            save_p_wmh = p_wmh;
            p_ls = 2;
            p_wmh = 0;
            last_status(FALSE);
          }
          wild_menu_showing = WM_SHOWN;
        }
      }
  
      // Tricky: wildmenu can be drawn either over a status line, or at empty
      // scrolled space in the message output
      ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED)
                          ? &msg_grid_adj : &default_grid;
  
      grid_puts(grid, buf, row, 0, attr);
      if (selstart != NULL && highlight) {
        *selend = NUL;
        grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM));
      }
  
      grid_fill(grid, row, row + 1, clen, Columns,
                fillchar, fillchar, attr);
    }
  
    win_redraw_last_status(topframe);
    xfree(buf);
  }
  
  /// Redraw the status line of window `wp`.
  ///
  /// If inversion is possible we use it. Else '=' characters are used.
  static void win_redr_status(win_T *wp)
  {
    int row;
    char_u      *p;
    int len;
    int fillchar;
    int attr;
    int this_ru_col;
    static int busy = FALSE;
  
    // May get here recursively when 'statusline' (indirectly)
    // invokes ":redrawstatus".  Simply ignore the call then.
    if (busy
        // Also ignore if wildmenu is showing.
        || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) {
      return;
    }
    busy = true;
  
    wp->w_redr_status = FALSE;
    if (wp->w_status_height == 0) {
      // no status line, can only be last window
      redraw_cmdline = true;
    } else if (!redrawing()) {
      // Don't redraw right now, do it later. Don't update status line when
      // popup menu is visible and may be drawn over it
      wp->w_redr_status = true;
    } else if (*p_stl != NUL || *wp->w_p_stl != NUL) {
      /* redraw custom status line */
      redraw_custom_statusline(wp);
    } else {
      fillchar = fillchar_status(&attr, wp);
  
      get_trans_bufname(wp->w_buffer);
      p = NameBuff;
      len = (int)STRLEN(p);
  
      if (bt_help(wp->w_buffer)
          || wp->w_p_pvw
          || bufIsChanged(wp->w_buffer)
          || wp->w_buffer->b_p_ro) {
        *(p + len++) = ' ';
      }
      if (bt_help(wp->w_buffer)) {
        STRCPY(p + len, _("[Help]"));
        len += (int)STRLEN(p + len);
      }
      if (wp->w_p_pvw) {
        STRCPY(p + len, _("[Preview]"));
        len += (int)STRLEN(p + len);
      }
      if (bufIsChanged(wp->w_buffer)) {
        STRCPY(p + len, "[+]");
        len += 3;
      }
      if (wp->w_buffer->b_p_ro) {
        STRCPY(p + len, _("[RO]"));
        // len += (int)STRLEN(p + len);  // dead assignment
      }
  
      this_ru_col = ru_col - (Columns - wp->w_width);
      if (this_ru_col < (wp->w_width + 1) / 2) {
        this_ru_col = (wp->w_width + 1) / 2;
      }
      if (this_ru_col <= 1) {
        p = (char_u *)"<";                // No room for file name!
        len = 1;
      } else {
        int clen = 0, i;
  
        // Count total number of display cells.
        clen = (int)mb_string2cells(p);
  
        // Find first character that will fit.
        // Going from start to end is much faster for DBCS.
        for (i = 0; p[i] != NUL && clen >= this_ru_col - 1;
             i += utfc_ptr2len(p + i)) {
          clen -= utf_ptr2cells(p + i);
        }
        len = clen;
        if (i > 0) {
          p = p + i - 1;
          *p = '<';
          ++len;
        }
      }
  
      row = W_ENDROW(wp);
      grid_puts(&default_grid, p, row, wp->w_wincol, attr);
      grid_fill(&default_grid, row, row + 1, len + wp->w_wincol,
                this_ru_col + wp->w_wincol, fillchar, fillchar, attr);
  
      if (get_keymap_str(wp, (char_u *)"<%s>", NameBuff, MAXPATHL)
          && this_ru_col - len > (int)(STRLEN(NameBuff) + 1))
        grid_puts(&default_grid, NameBuff, row,
                  (int)(this_ru_col - STRLEN(NameBuff) - 1), attr);
  
      win_redr_ruler(wp, TRUE);
    }
  
    /*
     * May need to draw the character below the vertical separator.
     */
    if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) {
      if (stl_connected(wp)) {
        fillchar = fillchar_status(&attr, wp);
      } else {
        fillchar = fillchar_vsep(wp, &attr);
      }
      grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr);
    }
    busy = FALSE;
  }
  
  /*
   * Redraw the status line according to 'statusline' and take care of any
   * errors encountered.
   */
  static void redraw_custom_statusline(win_T *wp)
  {
    static int entered = false;
    int saved_did_emsg = did_emsg;
  
    /* When called recursively return.  This can happen when the statusline
     * contains an expression that triggers a redraw. */
    if (entered)
      return;
    entered = TRUE;
  
    did_emsg = false;
    win_redr_custom(wp, false);
    if (did_emsg) {
      // When there is an error disable the statusline, otherwise the
      // display is messed up with errors and a redraw triggers the problem
      // again and again.
      set_string_option_direct((char_u *)"statusline", -1,
          (char_u *)"", OPT_FREE | (*wp->w_p_stl != NUL
                                    ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
    }
    did_emsg |= saved_did_emsg;
    entered = false;
  }
  
  /*
   * Return TRUE if the status line of window "wp" is connected to the status
   * line of the window right of it.  If not, then it's a vertical separator.
   * Only call if (wp->w_vsep_width != 0).
   */
  int stl_connected(win_T *wp)
  {
    frame_T     *fr;
  
    fr = wp->w_frame;
    while (fr->fr_parent != NULL) {
      if (fr->fr_parent->fr_layout == FR_COL) {
        if (fr->fr_next != NULL)
          break;
      } else {
        if (fr->fr_next != NULL)
          return TRUE;
      }
      fr = fr->fr_parent;
    }
    return FALSE;
  }
  
  
  /*
   * Get the value to show for the language mappings, active 'keymap'.
   */
  int
  get_keymap_str (
      win_T *wp,
      char_u *fmt,        // format string containing one %s item
      char_u *buf,        // buffer for the result
      int len             // length of buffer
  )
  {
    char_u      *p;
  
    if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP)
      return FALSE;
  
    {
      buf_T   *old_curbuf = curbuf;
      win_T   *old_curwin = curwin;
      char_u  *s;
  
      curbuf = wp->w_buffer;
      curwin = wp;
      STRCPY(buf, "b:keymap_name");       /* must be writable */
      ++emsg_skip;
      s = p = eval_to_string(buf, NULL, FALSE);
      --emsg_skip;
      curbuf = old_curbuf;
      curwin = old_curwin;
      if (p == NULL || *p == NUL) {
        if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) {
          p = wp->w_buffer->b_p_keymap;
        } else {
          p = (char_u *)"lang";
        }
      }
      if (vim_snprintf((char *)buf, len, (char *)fmt, p) > len - 1) {
        buf[0] = NUL;
      }
      xfree(s);
    }
    return buf[0] != NUL;
  }
  
  /*
   * Redraw the status line or ruler of window "wp".
   * When "wp" is NULL redraw the tab pages line from 'tabline'.
   */
  static void
  win_redr_custom (
      win_T *wp,
      int draw_ruler                 /* TRUE or FALSE */
  )
  {
    static int entered = FALSE;
    int attr;
    int curattr;
    int row;
    int col = 0;
    int maxwidth;
    int width;
    int n;
    int len;
    int fillchar;
    char_u buf[MAXPATHL];
    char_u      *stl;
    char_u      *p;
    struct      stl_hlrec hltab[STL_MAX_ITEM];
    StlClickRecord tabtab[STL_MAX_ITEM];
    int use_sandbox = false;
    win_T       *ewp;
    int p_crb_save;
  
    ScreenGrid *grid = &default_grid;
  
    /* There is a tiny chance that this gets called recursively: When
     * redrawing a status line triggers redrawing the ruler or tabline.
     * Avoid trouble by not allowing recursion. */
    if (entered)
      return;
    entered = TRUE;
  
    /* setup environment for the task at hand */
    if (wp == NULL) {
      /* Use 'tabline'.  Always at the first line of the screen. */
      stl = p_tal;
      row = 0;
      fillchar = ' ';
      attr = HL_ATTR(HLF_TPF);
      maxwidth = Columns;
      use_sandbox = was_set_insecurely((char_u *)"tabline", 0);
    } else {
      row = W_ENDROW(wp);
      fillchar = fillchar_status(&attr, wp);
      maxwidth = wp->w_width;
  
      if (draw_ruler) {
        stl = p_ruf;
        /* advance past any leading group spec - implicit in ru_col */
        if (*stl == '%') {
          if (*++stl == '-')
            stl++;
          if (atoi((char *)stl))
            while (ascii_isdigit(*stl))
              stl++;
          if (*stl++ != '(')
            stl = p_ruf;
        }
        col = ru_col - (Columns - wp->w_width);
        if (col < (wp->w_width + 1) / 2) {
          col = (wp->w_width + 1) / 2;
        }
        maxwidth = wp->w_width - col;
        if (!wp->w_status_height) {
          grid = &msg_grid_adj;
          row = Rows - 1;
          maxwidth--;  // writing in last column may cause scrolling
          fillchar = ' ';
          attr = HL_ATTR(HLF_MSG);
        }
  
        use_sandbox = was_set_insecurely((char_u *)"rulerformat", 0);
      } else {
        if (*wp->w_p_stl != NUL)
          stl = wp->w_p_stl;
        else
          stl = p_stl;
        use_sandbox = was_set_insecurely((char_u *)"statusline",
            *wp->w_p_stl == NUL ? 0 : OPT_LOCAL);
      }
  
      col += wp->w_wincol;
    }
  
    if (maxwidth <= 0)
      goto theend;
  
    /* Temporarily reset 'cursorbind', we don't want a side effect from moving
     * the cursor away and back. */
    ewp = wp == NULL ? curwin : wp;
    p_crb_save = ewp->w_p_crb;
    ewp->w_p_crb = FALSE;
  
    /* Make a copy, because the statusline may include a function call that
     * might change the option value and free the memory. */
    stl = vim_strsave(stl);
    width = build_stl_str_hl(ewp, buf, sizeof(buf),
        stl, use_sandbox,
        fillchar, maxwidth, hltab, tabtab);
    xfree(stl);
    ewp->w_p_crb = p_crb_save;
  
    // Make all characters printable.
    p = (char_u *)transstr((const char *)buf);
    len = STRLCPY(buf, p, sizeof(buf));
    len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1;
    xfree(p);
  
    /* fill up with "fillchar" */
    while (width < maxwidth && len < (int)sizeof(buf) - 1) {
      len += utf_char2bytes(fillchar, buf + len);
      width++;
    }
    buf[len] = NUL;
  
    /*
     * Draw each snippet with the specified highlighting.
     */
    grid_puts_line_start(grid, row);
  
    curattr = attr;
    p = buf;
    for (n = 0; hltab[n].start != NULL; n++) {
      int textlen = (int)(hltab[n].start - p);
      grid_puts_len(grid, p, textlen, row, col, curattr);
      col += vim_strnsize(p, textlen);
      p = hltab[n].start;
  
      if (hltab[n].userhl == 0)
        curattr = attr;
      else if (hltab[n].userhl < 0)
        curattr = syn_id2attr(-hltab[n].userhl);
      else if (wp != NULL && wp != curwin && wp->w_status_height != 0)
        curattr = highlight_stlnc[hltab[n].userhl - 1];
      else
        curattr = highlight_user[hltab[n].userhl - 1];
    }
    // Make sure to use an empty string instead of p, if p is beyond buf + len.
    grid_puts(grid, p >= buf + len ? (char_u *)"" : p, row, col,
              curattr);
  
    grid_puts_line_flush(false);
  
    if (wp == NULL) {
      // Fill the tab_page_click_defs array for clicking in the tab pages line.
      col = 0;
      len = 0;
      p = buf;
      StlClickDefinition cur_click_def = {
        .type = kStlClickDisabled,
      };
      for (n = 0; tabtab[n].start != NULL; n++) {
        len += vim_strnsize(p, (int)(tabtab[n].start - (char *) p));
        while (col < len) {
          tab_page_click_defs[col++] = cur_click_def;
        }
        p = (char_u *) tabtab[n].start;
        cur_click_def = tabtab[n].def;
      }
      while (col < Columns) {
        tab_page_click_defs[col++] = cur_click_def;
      }
    }
  
  theend:
    entered = FALSE;
  }
  
  // Low-level functions to manipulate invidual character cells on the
  // screen grid.
  
  /// Put a ASCII character in a screen cell.
  static void schar_from_ascii(char_u *p, const char c)
  {
    p[0] = c;
    p[1] = 0;
  }
  
  /// Put a unicode character in a screen cell.
  static int schar_from_char(char_u *p, int c)
  {
    int len = utf_char2bytes(c, p);
    p[len] = NUL;
    return len;
  }
  
  /// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell.
  static int schar_from_cc(char_u *p, int c, int u8cc[MAX_MCO])
  {
    int len = utf_char2bytes(c, p);
    for (int i = 0; i < MAX_MCO; i++) {
      if (u8cc[i] == 0) {
        break;
      }
      len += utf_char2bytes(u8cc[i], p + len);
    }
    p[len] = 0;
    return len;
  }
  
  /// compare the contents of two screen cells.
  static int schar_cmp(char_u *sc1, char_u *sc2)
  {
    return STRNCMP(sc1, sc2, sizeof(schar_T));
  }
  
  /// copy the contents of screen cell `sc2` into cell `sc1`
  static void schar_copy(char_u *sc1, char_u *sc2)
  {
    STRLCPY(sc1, sc2, sizeof(schar_T));
  }
  
  static int line_off2cells(schar_T *line, size_t off, size_t max_off)
  {
    return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1;
  }
  
  /// Return number of display cells for char at grid->chars[off].
  /// We make sure that the offset used is less than "max_off".
  static int grid_off2cells(ScreenGrid *grid, size_t off, size_t max_off)
  {
    return line_off2cells(grid->chars, off, max_off);
  }
  
  /// Return true if the character at "row"/"col" on the screen is the left side
  /// of a double-width character.
  ///
  /// Caller must make sure "row" and "col" are not invalid!
  bool grid_lefthalve(ScreenGrid *grid, int row, int col)
  {
    screen_adjust_grid(&grid, &row, &col);
  
    return grid_off2cells(grid, grid->line_offset[row] + col,
                          grid->line_offset[row] + grid->Columns) > 1;
  }
  
  /// Correct a position on the screen, if it's the right half of a double-wide
  /// char move it to the left half.  Returns the corrected column.
  int grid_fix_col(ScreenGrid *grid, int col, int row)
  {
    int coloff = 0;
    screen_adjust_grid(&grid, &row, &coloff);
  
    col += coloff;
    if (grid->chars != NULL && col > 0
        && grid->chars[grid->line_offset[row] + col][0] == 0) {
      return col - 1 - coloff;
    }
    return col - coloff;
  }
  
  /// output a single character directly to the grid
  void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr)
  {
    char_u buf[MB_MAXBYTES + 1];
  
    buf[utf_char2bytes(c, buf)] = NUL;
    grid_puts(grid, buf, row, col, attr);
  }
  
  /// get a single character directly from grid.chars into "bytes[]".
  /// Also return its attribute in *attrp;
  void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes,
                     int *attrp)
  {
    unsigned off;
  
    screen_adjust_grid(&grid, &row, &col);
  
    // safety check
    if (grid->chars != NULL && row < grid->Rows && col < grid->Columns) {
      off = grid->line_offset[row] + col;
      *attrp = grid->attrs[off];
      schar_copy(bytes, grid->chars[off]);
    }
  }
  
  
  /// put string '*text' on the window grid at position 'row' and 'col', with
  /// attributes 'attr', and update chars[] and attrs[].
  /// Note: only outputs within one row, message is truncated at grid boundary!
  /// Note: if grid, row and/or col is invalid, nothing is done.
  void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr)
  {
    grid_puts_len(grid, text, -1, row, col, attr);
  }
  
  static ScreenGrid *put_dirty_grid = NULL;
  static int put_dirty_row = -1;
  static int put_dirty_first = INT_MAX;
  static int put_dirty_last = 0;
  
  /// Start a group of grid_puts_len calls that builds a single grid line.
  ///
  /// Must be matched with a grid_puts_line_flush call before moving to
  /// another line.
  void grid_puts_line_start(ScreenGrid *grid, int row)
  {
    int col = 0;  // unused
    screen_adjust_grid(&grid, &row, &col);
    assert(put_dirty_row == -1);
    put_dirty_row = row;
    put_dirty_grid = grid;
  }
  
  /// like grid_puts(), but output "text[len]".  When "len" is -1 output up to
  /// a NUL.
  void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row,
                     int col, int attr)
  {
    unsigned off;
    char_u      *ptr = text;
    int len = textlen;
    int c;
    unsigned max_off;
    int mbyte_blen = 1;
    int mbyte_cells = 1;
    int u8c = 0;
    int u8cc[MAX_MCO];
    int clear_next_cell = FALSE;
    int prev_c = 0;                       /* previous Arabic character */
    int pc, nc, nc1;
    int pcc[MAX_MCO];
    int need_redraw;
    bool do_flush = false;
  
    screen_adjust_grid(&grid, &row, &col);
  
    // Safety check. The check for negative row and column is to fix issue
    // vim/vim#4102. TODO(neovim): find out why row/col could be negative.
    if (grid->chars == NULL
        || row >= grid->Rows || row < 0
        || col >= grid->Columns || col < 0) {
      return;
    }
  
    if (put_dirty_row == -1) {
      grid_puts_line_start(grid, row);
      do_flush = true;
    } else {
      if (grid != put_dirty_grid || row != put_dirty_row) {
        abort();
      }
    }
    off = grid->line_offset[row] + col;
  
    /* When drawing over the right halve of a double-wide char clear out the
     * left halve.  Only needed in a terminal. */
    if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) {
      // redraw the previous cell, make it empty
      put_dirty_first = -1;
      put_dirty_last = MAX(put_dirty_last, 1);
    }
  
    max_off = grid->line_offset[row] + grid->Columns;
    while (col < grid->Columns
           && (len < 0 || (int)(ptr - text) < len)
           && *ptr != NUL) {
      c = *ptr;
      // check if this is the first byte of a multibyte
      if (len > 0) {
        mbyte_blen = utfc_ptr2len_len(ptr, (int)((text + len) - ptr));
      } else {
        mbyte_blen = utfc_ptr2len(ptr);
      }
      if (len >= 0) {
        u8c = utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr));
      } else {
        u8c = utfc_ptr2char(ptr, u8cc);
      }
      mbyte_cells = utf_char2cells(u8c);
      if (p_arshape && !p_tbidi && arabic_char(u8c)) {
        // Do Arabic shaping.
        if (len >= 0 && (int)(ptr - text) + mbyte_blen >= len) {
          // Past end of string to be displayed.
          nc = NUL;
          nc1 = NUL;
        } else {
          nc = utfc_ptr2char_len(ptr + mbyte_blen, pcc,
                                 (int)((text + len) - ptr - mbyte_blen));
          nc1 = pcc[0];
        }
        pc = prev_c;
        prev_c = u8c;
        u8c = arabic_shape(u8c, &c, &u8cc[0], nc, nc1, pc);
      } else {
        prev_c = u8c;
      }
      if (col + mbyte_cells > grid->Columns) {
        // Only 1 cell left, but character requires 2 cells:
        // display a '>' in the last column to avoid wrapping. */
        c = '>';
        mbyte_cells = 1;
      }
  
      schar_T buf;
      schar_from_cc(buf, u8c, u8cc);
  
  
      need_redraw = schar_cmp(grid->chars[off], buf)
                    || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0)
                    || grid->attrs[off] != attr
                    || exmode_active;
  
      if (need_redraw) {
        // When at the end of the text and overwriting a two-cell
        // character with a one-cell character, need to clear the next
        // cell.  Also when overwriting the left halve of a two-cell char
        // with the right halve of a two-cell char.  Do this only once
        // (utf8_off2cells() may return 2 on the right halve).
        if (clear_next_cell) {
          clear_next_cell = false;
        } else if ((len < 0 ? ptr[mbyte_blen] == NUL
                    : ptr + mbyte_blen >= text + len)
                   && ((mbyte_cells == 1
                        && grid_off2cells(grid, off, max_off) > 1)
                       || (mbyte_cells == 2
                           && grid_off2cells(grid, off, max_off) == 1
                           && grid_off2cells(grid, off + 1, max_off) > 1))) {
          clear_next_cell = true;
        }
  
        schar_copy(grid->chars[off], buf);
        grid->attrs[off] = attr;
        if (mbyte_cells == 2) {
          grid->chars[off + 1][0] = 0;
          grid->attrs[off + 1] = attr;
        }
        put_dirty_first = MIN(put_dirty_first, col);
        put_dirty_last = MAX(put_dirty_last, col+mbyte_cells);
      }
  
      off += mbyte_cells;
      col += mbyte_cells;
      ptr += mbyte_blen;
      if (clear_next_cell) {
        // This only happens at the end, display one space next.
        ptr = (char_u *)" ";
        len = -1;
      }
    }
  
    if (do_flush) {
      grid_puts_line_flush(true);
    }
  }
  
  /// End a group of grid_puts_len calls and send the screen buffer to the UI
  /// layer.
  ///
  /// @param set_cursor Move the visible cursor to the end of the changed region.
  ///                   This is a workaround for not yet refactored code paths
  ///                   and shouldn't be used in new code.
  void grid_puts_line_flush(bool set_cursor)
  {
    assert(put_dirty_row != -1);
    if (put_dirty_first < put_dirty_last) {
      if (set_cursor) {
        ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row,
                            MIN(put_dirty_last, put_dirty_grid->Columns-1));
      }
      if (!put_dirty_grid->throttled) {
        ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last,
                put_dirty_last, 0, false);
      } else if (put_dirty_grid->dirty_col) {
        if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) {
          put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last;
        }
      }
      put_dirty_first = INT_MAX;
      put_dirty_last = 0;
    }
    put_dirty_row = -1;
    put_dirty_grid = NULL;
  }
  
  /*
   * Prepare for 'hlsearch' highlighting.
   */
  static void start_search_hl(void)
  {
    if (p_hls && !no_hlsearch) {
      last_pat_prog(&search_hl.rm);
      // Set the time limit to 'redrawtime'.
      search_hl.tm = profile_setlimit(p_rdt);
    }
  }
  
  /*
   * Clean up for 'hlsearch' highlighting.
   */
  static void end_search_hl(void)
  {
    if (search_hl.rm.regprog != NULL) {
      vim_regfree(search_hl.rm.regprog);
      search_hl.rm.regprog = NULL;
    }
  }
  
  
  /*
   * Init for calling prepare_search_hl().
   */
  static void init_search_hl(win_T *wp)
  {
    matchitem_T *cur;
  
    /* Setup for match and 'hlsearch' highlighting.  Disable any previous
     * match */
    cur = wp->w_match_head;
    while (cur != NULL) {
      cur->hl.rm = cur->match;
      if (cur->hlg_id == 0)
        cur->hl.attr = 0;
      else
        cur->hl.attr = syn_id2attr(cur->hlg_id);
      cur->hl.buf = wp->w_buffer;
      cur->hl.lnum = 0;
      cur->hl.first_lnum = 0;
      /* Set the time limit to 'redrawtime'. */
      cur->hl.tm = profile_setlimit(p_rdt);
      cur = cur->next;
    }
    search_hl.buf = wp->w_buffer;
    search_hl.lnum = 0;
    search_hl.first_lnum = 0;
    search_hl.attr = win_hl_attr(wp, HLF_L);
  
    // time limit is set at the toplevel, for all windows
  }
  
  /*
   * Advance to the match in window "wp" line "lnum" or past it.
   */
  static void prepare_search_hl(win_T *wp, linenr_T lnum)
  {
    matchitem_T *cur;             /* points to the match list */
    match_T     *shl;             /* points to search_hl or a match */
    int shl_flag;                 /* flag to indicate whether search_hl
                                     has been processed or not */
    int n;
  
    /*
     * When using a multi-line pattern, start searching at the top
     * of the window or just after a closed fold.
     * Do this both for search_hl and the match list.
     */
    cur = wp->w_match_head;
    shl_flag = false;
    while (cur != NULL || shl_flag == false) {
      if (shl_flag == false) {
        shl = &search_hl;
        shl_flag = true;
      } else {
        shl = &cur->hl;  // -V595
      }
      if (shl->rm.regprog != NULL
          && shl->lnum == 0
          && re_multiline(shl->rm.regprog)) {
        if (shl->first_lnum == 0) {
          for (shl->first_lnum = lnum;
               shl->first_lnum > wp->w_topline;
               shl->first_lnum--) {
            if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) {
              break;
            }
          }
        }
        if (cur != NULL) {
          cur->pos.cur = 0;
        }
        bool pos_inprogress = true; // mark that a position match search is
                                    // in progress
        n = 0;
        while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
                                          || (cur != NULL && pos_inprogress))) {
          next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n,
                         shl == &search_hl ? NULL : cur);
          pos_inprogress = !(cur == NULL ||  cur->pos.cur == 0);
          if (shl->lnum != 0) {
            shl->first_lnum = shl->lnum
                              + shl->rm.endpos[0].lnum
                              - shl->rm.startpos[0].lnum;
            n = shl->rm.endpos[0].col;
          } else {
            ++shl->first_lnum;
            n = 0;
          }
        }
      }
      if (shl != &search_hl && cur != NULL)
        cur = cur->next;
    }
  }
  
  /*
   * Search for a next 'hlsearch' or match.
   * Uses shl->buf.
   * Sets shl->lnum and shl->rm contents.
   * Note: Assumes a previous match is always before "lnum", unless
   * shl->lnum is zero.
   * Careful: Any pointers for buffer lines will become invalid.
   */
  static void
  next_search_hl (
      win_T *win,
      match_T *shl,               /* points to search_hl or a match */
      linenr_T lnum,
      colnr_T mincol,                /* minimal column for a match */
      matchitem_T *cur               /* to retrieve match positions if any */
  )
  {
    linenr_T l;
    colnr_T matchcol;
    long nmatched = 0;
    int save_called_emsg = called_emsg;
  
    if (shl->lnum != 0) {
      /* Check for three situations:
       * 1. If the "lnum" is below a previous match, start a new search.
       * 2. If the previous match includes "mincol", use it.
       * 3. Continue after the previous match.
       */
      l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
      if (lnum > l)
        shl->lnum = 0;
      else if (lnum < l || shl->rm.endpos[0].col > mincol)
        return;
    }
  
    /*
     * Repeat searching for a match until one is found that includes "mincol"
     * or none is found in this line.
     */
    called_emsg = FALSE;
    for (;; ) {
      /* Stop searching after passing the time limit. */
      if (profile_passed_limit(shl->tm)) {
        shl->lnum = 0;                    /* no match found in time */
        break;
      }
      /* Three situations:
       * 1. No useful previous match: search from start of line.
       * 2. Not Vi compatible or empty match: continue at next character.
       *    Break the loop if this is beyond the end of the line.
       * 3. Vi compatible searching: continue at end of previous match.
       */
      if (shl->lnum == 0)
        matchcol = 0;
      else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
          || (shl->rm.endpos[0].lnum == 0
            && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
        char_u      *ml;
  
        matchcol = shl->rm.startpos[0].col;
        ml = ml_get_buf(shl->buf, lnum, FALSE) + matchcol;
        if (*ml == NUL) {
          ++matchcol;
          shl->lnum = 0;
          break;
        }
        matchcol += mb_ptr2len(ml);
      } else {
        matchcol = shl->rm.endpos[0].col;
      }
  
      shl->lnum = lnum;
      if (shl->rm.regprog != NULL) {
        /* Remember whether shl->rm is using a copy of the regprog in
         * cur->match. */
        bool regprog_is_copy = (shl != &search_hl
                                && cur != NULL
                                && shl == &cur->hl
                                && cur->match.regprog == cur->hl.rm.regprog);
        int timed_out = false;
  
        nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol,
                                     &(shl->tm), &timed_out);
        // Copy the regprog, in case it got freed and recompiled.
        if (regprog_is_copy) {
          cur->match.regprog = cur->hl.rm.regprog;
        }
        if (called_emsg || got_int || timed_out) {
          // Error while handling regexp: stop using this regexp.
          if (shl == &search_hl) {
            // don't free regprog in the match list, it's a copy
            vim_regfree(shl->rm.regprog);
            set_no_hlsearch(true);
          }
          shl->rm.regprog = NULL;
          shl->lnum = 0;
          got_int = FALSE; // avoid the "Type :quit to exit Vim" message
          break;
        }
      } else if (cur != NULL) {
        nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
      }
      if (nmatched == 0) {
        shl->lnum = 0;                    /* no match found */
        break;
      }
      if (shl->rm.startpos[0].lnum > 0
          || shl->rm.startpos[0].col >= mincol
          || nmatched > 1
          || shl->rm.endpos[0].col > mincol) {
        shl->lnum += shl->rm.startpos[0].lnum;
        break;                            /* useful match found */
      }
  
      // Restore called_emsg for assert_fails().
      called_emsg = save_called_emsg;
    }
  }
  
  /// If there is a match fill "shl" and return one.
  /// Return zero otherwise.
  static int
  next_search_hl_pos(
      match_T *shl,         // points to a match
      linenr_T lnum,
      posmatch_T *posmatch, // match positions
      colnr_T mincol        // minimal column for a match
  )
  {
    int i;
    int found = -1;
  
    shl->lnum = 0;
    for (i = posmatch->cur; i < MAXPOSMATCH; i++) {
      llpos_T *pos = &posmatch->pos[i];
  
      if (pos->lnum == 0) {
        break;
      }
      if (pos->len == 0 && pos->col < mincol) {
        continue;
      }
      if (pos->lnum == lnum) {
        if (found >= 0) {
          // if this match comes before the one at "found" then swap
          // them
          if (pos->col < posmatch->pos[found].col) {
            llpos_T tmp = *pos;
  
            *pos = posmatch->pos[found];
            posmatch->pos[found] = tmp;
          }
        } else {
          found = i;
        }
      }
    }
    posmatch->cur = 0;
    if (found >= 0) {
      colnr_T start = posmatch->pos[found].col == 0
                      ? 0: posmatch->pos[found].col - 1;
      colnr_T end = posmatch->pos[found].col == 0
                    ? MAXCOL : start + posmatch->pos[found].len;
  
      shl->lnum = lnum;
      shl->rm.startpos[0].lnum = 0;
      shl->rm.startpos[0].col = start;
      shl->rm.endpos[0].lnum = 0;
      shl->rm.endpos[0].col = end;
      shl->is_addpos = true;
      posmatch->cur = found + 1;
      return 1;
    }
    return 0;
  }
  
  
  /// Fill the grid from 'start_row' to 'end_row', from 'start_col' to 'end_col'
  /// with character 'c1' in first column followed by 'c2' in the other columns.
  /// Use attributes 'attr'.
  void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col,
                 int end_col, int c1, int c2, int attr)
  {
    schar_T sc;
  
    int row_off = 0, col_off = 0;
    screen_adjust_grid(&grid, &row_off, &col_off);
    start_row += row_off;
    end_row += row_off;
    start_col += col_off;
    end_col += col_off;
  
    // safety check
    if (end_row > grid->Rows) {
      end_row = grid->Rows;
    }
    if (end_col > grid->Columns) {
      end_col = grid->Columns;
    }
  
    // nothing to do
    if (start_row >= end_row || start_col >= end_col) {
      return;
    }
  
    for (int row = start_row; row < end_row; row++) {
      // When drawing over the right halve of a double-wide char clear
      // out the left halve.  When drawing over the left halve of a
      // double wide-char clear out the right halve.  Only needed in a
      // terminal.
      if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) {
        grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0);
      }
      if (end_col < grid->Columns
          && grid_fix_col(grid, end_col, row) != end_col) {
        grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0);
      }
  
      // if grid was resized (in ext_multigrid mode), the UI has no redraw updates
      // for the newly resized grid. It is better mark everything as dirty and
      // send all the updates.
      int dirty_first = INT_MAX;
      int dirty_last = 0;
  
      int col = start_col;
      schar_from_char(sc, c1);
      int lineoff = grid->line_offset[row];
      for (col = start_col; col < end_col; col++) {
        int off = lineoff + col;
        if (schar_cmp(grid->chars[off], sc)
            || grid->attrs[off] != attr) {
          schar_copy(grid->chars[off], sc);
          grid->attrs[off] = attr;
          if (dirty_first == INT_MAX) {
            dirty_first = col;
          }
          dirty_last = col+1;
        }
        if (col == start_col) {
          schar_from_char(sc, c2);
        }
      }
      if (dirty_last > dirty_first) {
        // TODO(bfredl): support a cleared suffix even with a batched line?
        if (put_dirty_row == row) {
          put_dirty_first = MIN(put_dirty_first, dirty_first);
          put_dirty_last = MAX(put_dirty_last, dirty_last);
        } else if (grid->throttled) {
          // Note: assumes msg_grid is the only throttled grid
          assert(grid == &msg_grid);
          int dirty = 0;
          if (attr != HL_ATTR(HLF_MSG) || c2 != ' ') {
            dirty = dirty_last;
          } else if (c1 != ' ') {
            dirty = dirty_first + 1;
          }
          if (grid->dirty_col && dirty > grid->dirty_col[row]) {
            grid->dirty_col[row] = dirty;
          }
        } else {
          int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' ');
          ui_line(grid, row, dirty_first, last, dirty_last, attr, false);
        }
      }
  
      if (end_col == grid->Columns) {
        grid->line_wraps[row] = false;
      }
    }
  }
  
  /*
   * Check if there should be a delay.  Used before clearing or redrawing the
   * screen or the command line.
   */
  void check_for_delay(int check_msg_scroll)
  {
    if ((emsg_on_display || (check_msg_scroll && msg_scroll))
        && !did_wait_return
        && emsg_silent == 0) {
      ui_flush();
      os_delay(1000L, true);
      emsg_on_display = FALSE;
      if (check_msg_scroll)
        msg_scroll = FALSE;
    }
  }
  
  /// (Re)allocates a window grid if size changed while in ext_multigrid mode.
  /// Updates size, offsets and handle for the grid regardless.
  ///
  /// If "doclear" is true, don't try to copy from the old grid rather clear the
  /// resized grid.
  void win_grid_alloc(win_T *wp)
  {
    ScreenGrid *grid = &wp->w_grid;
  
    int rows = wp->w_height_inner;
    int cols = wp->w_width_inner;
  
    bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating;
    bool has_allocation = (grid->chars != NULL);
  
    if (grid->Rows != rows) {
      wp->w_lines_valid = 0;
      xfree(wp->w_lines);
      wp->w_lines = xcalloc(rows+1, sizeof(wline_T));
    }
  
    int was_resized = false;
    if ((has_allocation != want_allocation)
        || grid->Rows != rows
        || grid->Columns != cols) {
      if (want_allocation) {
        grid_alloc(grid, rows, cols, wp->w_grid.valid, wp->w_grid.valid);
        grid->valid = true;
      } else {
        // Single grid mode, all rendering will be redirected to default_grid.
        // Only keep track of the size and offset of the window.
        grid_free(grid);
        grid->Rows = rows;
        grid->Columns = cols;
        grid->valid = false;
      }
      was_resized = true;
    } else if (want_allocation && has_allocation && !wp->w_grid.valid) {
      grid_invalidate(grid);
      grid->valid = true;
    }
  
    grid->row_offset = wp->w_winrow;
    grid->col_offset = wp->w_wincol;
  
    // send grid resize event if:
    // - a grid was just resized
    // - screen_resize was called and all grid sizes must be sent
    // - the UI wants multigrid event (necessary)
    if ((send_grid_resize || was_resized) && want_allocation) {
      ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows);
    }
  }
  
  /// assign a handle to the grid. The grid need not be allocated.
  void grid_assign_handle(ScreenGrid *grid)
  {
    static int last_grid_handle = DEFAULT_GRID_HANDLE;
  
    // only assign a grid handle if not already
    if (grid->handle == 0) {
      grid->handle = ++last_grid_handle;
    }
  }
  
  /// Resize the screen to Rows and Columns.
  ///
  /// Allocate default_grid.chars[] and other grid arrays.
  ///
  /// There may be some time between setting Rows and Columns and (re)allocating
  /// default_grid arrays.  This happens when starting up and when
  /// (manually) changing the shell size.  Always use default_grid.Rows and
  /// default_grid.Columns to access items in default_grid.chars[].  Use Rows
  /// and Columns for positioning text etc. where the final size of the shell is
  /// needed.
  void screenalloc(void)
  {
    // It's possible that we produce an out-of-memory message below, which
    // will cause this function to be called again.  To break the loop, just
    // return here.
    if (resizing) {
      return;
    }
    resizing = true;
  
    int retry_count = 0;
  
  retry:
    // Allocation of the screen buffers is done only when the size changes and
    // when Rows and Columns have been set and we have started doing full
    // screen stuff.
    if ((default_grid.chars != NULL
         && Rows == default_grid.Rows
         && Columns == default_grid.Columns
         )
        || Rows == 0
        || Columns == 0
        || (!full_screen && default_grid.chars == NULL)) {
      resizing = false;
      return;
    }
  
    /*
     * Note that the window sizes are updated before reallocating the arrays,
     * thus we must not redraw here!
     */
    ++RedrawingDisabled;
  
    // win_new_shellsize will recompute floats position, but tell the
    // compositor to not redraw them yet
    ui_comp_set_screen_valid(false);
    if (msg_grid.chars) {
      msg_grid_invalid = true;
    }
  
    win_new_shellsize();      /* fit the windows in the new sized shell */
  
    comp_col();           /* recompute columns for shown command and ruler */
  
    // We're changing the size of the screen.
    // - Allocate new arrays for default_grid
    // - Move lines from the old arrays into the new arrays, clear extra
    //   lines (unless the screen is going to be cleared).
    // - Free the old arrays.
    //
    // If anything fails, make grid arrays NULL, so we don't do anything!
    // Continuing with the old arrays may result in a crash, because the
    // size is wrong.
  
    grid_alloc(&default_grid, Rows, Columns, true, true);
    StlClickDefinition *new_tab_page_click_defs = xcalloc(
        (size_t)Columns, sizeof(*new_tab_page_click_defs));
  
    clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size);
    xfree(tab_page_click_defs);
  
    tab_page_click_defs = new_tab_page_click_defs;
    tab_page_click_defs_size = Columns;
  
    default_grid.row_offset = 0;
    default_grid.col_offset = 0;
    default_grid.handle = DEFAULT_GRID_HANDLE;
  
    must_redraw = CLEAR;  // need to clear the screen later
  
    RedrawingDisabled--;
  
    /*
     * Do not apply autocommands more than 3 times to avoid an endless loop
     * in case applying autocommands always changes Rows or Columns.
     */
    if (starting == 0 && ++retry_count <= 3) {
      apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, FALSE, curbuf);
      /* In rare cases, autocommands may have altered Rows or Columns,
      * jump back to check if we need to allocate the screen again. */
      goto retry;
    }
  
    resizing = false;
  }
  
  void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
  {
    int new_row;
    ScreenGrid new = *grid;
    assert(rows >= 0 && columns >= 0);
    size_t ncells = (size_t)rows * columns;
    new.chars = xmalloc(ncells * sizeof(schar_T));
    new.attrs = xmalloc(ncells * sizeof(sattr_T));
    new.line_offset = xmalloc((size_t)(rows * sizeof(unsigned)));
    new.line_wraps = xmalloc((size_t)(rows * sizeof(char_u)));
  
    new.Rows = rows;
    new.Columns = columns;
  
    for (new_row = 0; new_row < new.Rows; new_row++) {
      new.line_offset[new_row] = new_row * new.Columns;
      new.line_wraps[new_row] = false;
  
      grid_clear_line(&new, new.line_offset[new_row], columns, valid);
  
      if (copy) {
        // If the screen is not going to be cleared, copy as much as
        // possible from the old screen to the new one and clear the rest
        // (used when resizing the window at the "--more--" prompt or when
        // executing an external command, for the GUI).
        if (new_row < grid->Rows && grid->chars != NULL) {
          int len = MIN(grid->Columns, new.Columns);
          memmove(new.chars + new.line_offset[new_row],
                  grid->chars + grid->line_offset[new_row],
                  (size_t)len * sizeof(schar_T));
          memmove(new.attrs + new.line_offset[new_row],
                  grid->attrs + grid->line_offset[new_row],
                  (size_t)len * sizeof(sattr_T));
        }
      }
    }
    grid_free(grid);
    *grid = new;
  
    // Share a single scratch buffer for all grids, by
    // ensuring it is as wide as the widest grid.
    if (linebuf_size < (size_t)columns) {
      xfree(linebuf_char);
      xfree(linebuf_attr);
      linebuf_char = xmalloc(columns * sizeof(schar_T));
      linebuf_attr = xmalloc(columns * sizeof(sattr_T));
      linebuf_size = columns;
    }
  }
  
  void grid_free(ScreenGrid *grid)
  {
    xfree(grid->chars);
    xfree(grid->attrs);
    xfree(grid->line_offset);
    xfree(grid->line_wraps);
  
    grid->chars = NULL;
    grid->attrs = NULL;
    grid->line_offset = NULL;
    grid->line_wraps = NULL;
  }
  
  /// Doesn't allow reinit, so must only be called by free_all_mem!
  void screen_free_all_mem(void)
  {
    grid_free(&default_grid);
    xfree(linebuf_char);
    xfree(linebuf_attr);
  }
  
  /// Clear tab_page_click_defs table
  ///
  /// @param[out]  tpcd  Table to clear.
  /// @param[in]  tpcd_size  Size of the table.
  void clear_tab_page_click_defs(StlClickDefinition *const tpcd,
                                 const long tpcd_size)
  {
    if (tpcd != NULL) {
      for (long i = 0; i < tpcd_size; i++) {
        if (i == 0 || tpcd[i].func != tpcd[i - 1].func) {
          xfree(tpcd[i].func);
        }
      }
      memset(tpcd, 0, (size_t) tpcd_size * sizeof(tpcd[0]));
    }
  }
  
  void screenclear(void)
  {
    check_for_delay(false);
    screenalloc();  // allocate screen buffers if size changed
  
    int i;
  
    if (starting == NO_SCREEN || default_grid.chars == NULL) {
      return;
    }
  
    // blank out the default grid
    for (i = 0; i < default_grid.Rows; i++) {
      grid_clear_line(&default_grid, default_grid.line_offset[i],
                      (int)default_grid.Columns, true);
      default_grid.line_wraps[i] = false;
    }
  
    ui_call_grid_clear(1);  // clear the display
    ui_comp_set_screen_valid(true);
  
    clear_cmdline = false;
    mode_displayed = false;
  
    redraw_all_later(NOT_VALID);
    redraw_cmdline = true;
    redraw_tabline = true;
    redraw_popupmenu = true;
    pum_invalidate();
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_floating) {
        wp->w_redr_type = CLEAR;
      }
    }
    if (must_redraw == CLEAR) {
      must_redraw = NOT_VALID;  // no need to clear again
    }
    compute_cmdrow();
    msg_row = cmdline_row;  // put cursor on last line for messages
    msg_col = 0;
    msg_scrolled = 0;  // can't scroll back
    msg_didany = false;
    msg_didout = false;
    if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) {
      grid_invalidate(&msg_grid);
      msg_grid_validate();
      msg_grid_invalid = false;
      clear_cmdline = true;
    }
  }
  
  /// clear a line in the grid starting at "off" until "width" characters
  /// are cleared.
  void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid)
  {
    for (int col = 0; col < width; col++) {
      schar_from_ascii(grid->chars[off + col], ' ');
    }
    int fill = valid ? 0 : -1;
    (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
  }
  
  void grid_invalidate(ScreenGrid *grid)
  {
    (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T));
  }
  
  bool grid_invalid_row(ScreenGrid *grid, int row)
  {
    return grid->attrs[grid->line_offset[row]] < 0;
  }
  
  
  
  /// Copy part of a grid line for vertically split window.
  static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
  {
    unsigned off_to = grid->line_offset[to] + col;
    unsigned off_from = grid->line_offset[from] + col;
  
    memmove(grid->chars + off_to, grid->chars + off_from,
            width * sizeof(schar_T));
    memmove(grid->attrs + off_to, grid->attrs + off_from,
            width * sizeof(sattr_T));
  }
  
  /*
   * Set cursor to its position in the current window.
   */
  void setcursor(void)
  {
    if (redrawing()) {
      validate_cursor();
  
      ScreenGrid *grid = &curwin->w_grid;
      int row = curwin->w_wrow;
      int col = curwin->w_wcol;
      if (curwin->w_p_rl) {
        // With 'rightleft' set and the cursor on a double-wide character,
        // position it on the leftmost column.
        col = curwin->w_width_inner - curwin->w_wcol
                      - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2
                          && vim_isprintc(gchar_cursor())) ? 2 : 1);
      }
  
      screen_adjust_grid(&grid, &row, &col);
      ui_grid_cursor_goto(grid->handle, row, col);
    }
  }
  
  /// Scroll 'line_count' lines at 'row' in window 'wp'.
  ///
  /// Positive `line_count' means scrolling down, so that more space is available
  /// at 'row'. Negative `line_count` implies deleting lines at `row`.
  void win_scroll_lines(win_T *wp, int row, int line_count)
  {
    if (!redrawing() || line_count == 0) {
      return;
    }
  
    // No lines are being moved, just draw over the entire area
    if (row + abs(line_count) >= wp->w_grid.Rows) {
      return;
    }
  
    if (line_count < 0) {
      grid_del_lines(&wp->w_grid, row, -line_count,
                     wp->w_grid.Rows, 0, wp->w_grid.Columns);
    } else {
      grid_ins_lines(&wp->w_grid, row, line_count,
                     wp->w_grid.Rows, 0, wp->w_grid.Columns);
    }
  }
  
  /*
   * The rest of the routines in this file perform screen manipulations. The
   * given operation is performed physically on the screen. The corresponding
   * change is also made to the internal screen image. In this way, the editor
   * anticipates the effect of editing changes on the appearance of the screen.
   * That way, when we call screenupdate a complete redraw isn't usually
   * necessary. Another advantage is that we can keep adding code to anticipate
   * screen changes, and in the meantime, everything still works.
   */
  
  
  /// insert lines on the screen and move the existing lines down
  /// 'line_count' is the number of lines to be inserted.
  /// 'end' is the line after the scrolled part. Normally it is Rows.
  /// 'col' is the column from with we start inserting.
  //
  /// 'row', 'col' and 'end' are relative to the start of the region.
  void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
                      int width)
  {
    int i;
    int j;
    unsigned temp;
  
    int row_off = 0;
    screen_adjust_grid(&grid, &row_off, &col);
    row += row_off;
    end += row_off;
  
    if (line_count <= 0) {
      return;
    }
  
    // Shift line_offset[] line_count down to reflect the inserted lines.
    // Clear the inserted lines.
    for (i = 0; i < line_count; i++) {
      if (width != grid->Columns) {
        // need to copy part of a line
        j = end - 1 - i;
        while ((j -= line_count) >= row) {
          linecopy(grid, j + line_count, j, col, width);
        }
        j += line_count;
        grid_clear_line(grid, grid->line_offset[j] + col, width, false);
        grid->line_wraps[j] = false;
      } else {
        j = end - 1 - i;
        temp = grid->line_offset[j];
        while ((j -= line_count) >= row) {
          grid->line_offset[j + line_count] = grid->line_offset[j];
          grid->line_wraps[j + line_count] = grid->line_wraps[j];
        }
        grid->line_offset[j + line_count] = temp;
        grid->line_wraps[j + line_count] = false;
        grid_clear_line(grid, temp, (int)grid->Columns, false);
      }
    }
  
    if (!grid->throttled) {
      ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0);
    }
  
    return;
  }
  
  /// delete lines on the screen and move lines up.
  /// 'end' is the line after the scrolled part. Normally it is Rows.
  /// When scrolling region used 'off' is the offset from the top for the region.
  /// 'row' and 'end' are relative to the start of the region.
  void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
                      int width)
  {
    int j;
    int i;
    unsigned temp;
  
    int row_off = 0;
    screen_adjust_grid(&grid, &row_off, &col);
    row += row_off;
    end += row_off;
  
    if (line_count <= 0) {
      return;
    }
  
    // Now shift line_offset[] line_count up to reflect the deleted lines.
    // Clear the inserted lines.
    for (i = 0; i < line_count; i++) {
      if (width != grid->Columns) {
        // need to copy part of a line
        j = row + i;
        while ((j += line_count) <= end - 1) {
          linecopy(grid, j - line_count, j, col, width);
        }
        j -= line_count;
        grid_clear_line(grid, grid->line_offset[j] + col, width, false);
        grid->line_wraps[j] = false;
      } else {
        // whole width, moving the line pointers is faster
        j = row + i;
        temp = grid->line_offset[j];
        while ((j += line_count) <= end - 1) {
          grid->line_offset[j - line_count] = grid->line_offset[j];
          grid->line_wraps[j - line_count] = grid->line_wraps[j];
        }
        grid->line_offset[j - line_count] = temp;
        grid->line_wraps[j - line_count] = false;
        grid_clear_line(grid, temp, (int)grid->Columns, false);
      }
    }
  
    if (!grid->throttled) {
      ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0);
    }
  
    return;
  }
  
  
  // Show the current mode and ruler.
  //
  // If clear_cmdline is TRUE, clear the rest of the cmdline.
  // If clear_cmdline is FALSE there may be a message there that needs to be
  // cleared only if a mode is shown.
  // Return the length of the message (0 if no message).
  int showmode(void)
  {
    int need_clear;
    int length = 0;
    int do_mode;
    int attr;
    int nwr_save;
    int sub_attr;
  
    if (ui_has(kUIMessages) && clear_cmdline) {
      msg_ext_clear(true);
    }
  
    // don't make non-flushed message part of the showmode
    msg_ext_ui_flush();
  
    msg_grid_validate();
  
    do_mode = ((p_smd && msg_silent == 0)
               && ((State & TERM_FOCUS)
                   || (State & INSERT)
                   || restart_edit
                   || VIsual_active));
    if (do_mode || reg_recording != 0) {
      // Don't show mode right now, when not redrawing or inside a mapping.
      // Call char_avail() only when we are going to show something, because
      // it takes a bit of time.
      if (!redrawing() || (char_avail() && !KeyTyped) || msg_silent != 0) {
        redraw_cmdline = TRUE;                    /* show mode later */
        return 0;
      }
  
      nwr_save = need_wait_return;
  
      /* wait a bit before overwriting an important message */
      check_for_delay(FALSE);
  
      /* if the cmdline is more than one line high, erase top lines */
      need_clear = clear_cmdline;
      if (clear_cmdline && cmdline_row < Rows - 1) {
        msg_clr_cmdline();  // will reset clear_cmdline
      }
  
      /* Position on the last line in the window, column 0 */
      msg_pos_mode();
      attr = HL_ATTR(HLF_CM);                     // Highlight mode
  
      // When the screen is too narrow to show the entire mode messsage,
      // avoid scrolling and truncate instead.
      msg_no_more = true;
      int save_lines_left = lines_left;
      lines_left = 0;
  
      if (do_mode) {
        MSG_PUTS_ATTR("--", attr);
        // CTRL-X in Insert mode
        if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) {
          // These messages can get long, avoid a wrap in a narrow window.
          // Prefer showing edit_submode_extra. With external messages there
          // is no imposed limit.
          if (ui_has(kUIMessages)) {
            length = INT_MAX;
          } else {
            length = (Rows - msg_row) * Columns - 3;
          }
          if (edit_submode_extra != NULL) {
            length -= vim_strsize(edit_submode_extra);
          }
          if (length > 0) {
            if (edit_submode_pre != NULL)
              length -= vim_strsize(edit_submode_pre);
            if (length - vim_strsize(edit_submode) > 0) {
              if (edit_submode_pre != NULL) {
                msg_puts_attr((const char *)edit_submode_pre, attr);
              }
              msg_puts_attr((const char *)edit_submode, attr);
            }
            if (edit_submode_extra != NULL) {
              MSG_PUTS_ATTR(" ", attr);  // Add a space in between.
              if ((int)edit_submode_highl < (int)HLF_COUNT) {
                sub_attr = win_hl_attr(curwin, edit_submode_highl);
              } else {
                sub_attr = attr;
              }
              msg_puts_attr((const char *)edit_submode_extra, sub_attr);
            }
          }
        } else {
          if (State & TERM_FOCUS) {
            MSG_PUTS_ATTR(_(" TERMINAL"), attr);
          } else if (State & VREPLACE_FLAG)
            MSG_PUTS_ATTR(_(" VREPLACE"), attr);
          else if (State & REPLACE_FLAG)
            MSG_PUTS_ATTR(_(" REPLACE"), attr);
          else if (State & INSERT) {
            if (p_ri)
              MSG_PUTS_ATTR(_(" REVERSE"), attr);
            MSG_PUTS_ATTR(_(" INSERT"), attr);
          } else if (restart_edit == 'I' || restart_edit == 'i'
                     || restart_edit == 'a') {
            MSG_PUTS_ATTR(_(" (insert)"), attr);
          } else if (restart_edit == 'R') {
            MSG_PUTS_ATTR(_(" (replace)"), attr);
          } else if (restart_edit == 'V') {
            MSG_PUTS_ATTR(_(" (vreplace)"), attr);
          }
          if (p_hkmap) {
            MSG_PUTS_ATTR(_(" Hebrew"), attr);
          }
          if (State & LANGMAP) {
            if (curwin->w_p_arab) {
              MSG_PUTS_ATTR(_(" Arabic"), attr);
            } else if (get_keymap_str(curwin, (char_u *)" (%s)",
                                      NameBuff, MAXPATHL)) {
              MSG_PUTS_ATTR(NameBuff, attr);
            }
          }
          if ((State & INSERT) && p_paste)
            MSG_PUTS_ATTR(_(" (paste)"), attr);
  
          if (VIsual_active) {
            char *p;
  
            /* Don't concatenate separate words to avoid translation
             * problems. */
            switch ((VIsual_select ? 4 : 0)
                    + (VIsual_mode == Ctrl_V) * 2
                    + (VIsual_mode == 'V')) {
            case 0: p = N_(" VISUAL"); break;
            case 1: p = N_(" VISUAL LINE"); break;
            case 2: p = N_(" VISUAL BLOCK"); break;
            case 4: p = N_(" SELECT"); break;
            case 5: p = N_(" SELECT LINE"); break;
            default: p = N_(" SELECT BLOCK"); break;
            }
            MSG_PUTS_ATTR(_(p), attr);
          }
          MSG_PUTS_ATTR(" --", attr);
        }
  
        need_clear = TRUE;
      }
      if (reg_recording != 0
          && edit_submode == NULL             // otherwise it gets too long
          ) {
        recording_mode(attr);
        need_clear = true;
      }
  
      mode_displayed = TRUE;
      if (need_clear || clear_cmdline)
        msg_clr_eos();
      msg_didout = FALSE;                 /* overwrite this message */
      length = msg_col;
      msg_col = 0;
      msg_no_more = false;
      lines_left = save_lines_left;
      need_wait_return = nwr_save;        // never ask for hit-return for this
    } else if (clear_cmdline && msg_silent == 0) {
      // Clear the whole command line.  Will reset "clear_cmdline".
      msg_clr_cmdline();
    }
  
    // NB: also handles clearing the showmode if it was emtpy or disabled
    msg_ext_flush_showmode();
  
    /* In Visual mode the size of the selected area must be redrawn. */
    if (VIsual_active)
      clear_showcmd();
  
    // If the last window has no status line, the ruler is after the mode
    // message and must be redrawn
    win_T *last = lastwin_nofloating();
    if (redrawing() && last->w_status_height == 0) {
      win_redr_ruler(last, true);
    }
    redraw_cmdline = false;
    clear_cmdline = false;
  
    return length;
  }
  
  /*
   * Position for a mode message.
   */
  static void msg_pos_mode(void)
  {
    msg_col = 0;
    msg_row = Rows - 1;
  }
  
  /// Delete mode message.  Used when ESC is typed which is expected to end
  /// Insert mode (but Insert mode didn't end yet!).
  /// Caller should check "mode_displayed".
  void unshowmode(bool force)
  {
    // Don't delete it right now, when not redrawing or inside a mapping.
    if (!redrawing() || (!force && char_avail() && !KeyTyped)) {
      redraw_cmdline = true;  // delete mode later
    } else {
      clearmode();
    }
  }
  
  // Clear the mode message.
  void clearmode(void)
  {
    const int save_msg_row = msg_row;
    const int save_msg_col = msg_col;
  
    msg_ext_ui_flush();
    msg_pos_mode();
    if (reg_recording != 0) {
      recording_mode(HL_ATTR(HLF_CM));
    }
    msg_clr_eos();
    msg_ext_flush_showmode();
  
    msg_col = save_msg_col;
    msg_row = save_msg_row;
  }
  
  static void recording_mode(int attr)
  {
    MSG_PUTS_ATTR(_("recording"), attr);
    if (!shortmess(SHM_RECORDING)) {
      char_u s[4];
      snprintf((char *)s, ARRAY_SIZE(s), " @%c", reg_recording);
      MSG_PUTS_ATTR(s, attr);
    }
  }
  
  /*
   * Draw the tab pages line at the top of the Vim window.
   */
  void draw_tabline(void)
  {
    int tabcount = 0;
    int tabwidth = 0;
    int col = 0;
    int scol = 0;
    int attr;
    win_T       *wp;
    win_T       *cwp;
    int wincount;
    int modified;
    int c;
    int len;
    int attr_nosel = HL_ATTR(HLF_TP);
    int attr_fill = HL_ATTR(HLF_TPF);
    char_u      *p;
    int room;
    int use_sep_chars = (t_colors < 8
                         );
  
    if (default_grid.chars == NULL) {
      return;
    }
    redraw_tabline = false;
  
    if (ui_has(kUITabline)) {
      ui_ext_tabline_update();
      return;
    }
  
    if (tabline_height() < 1)
      return;
  
  
    // Init TabPageIdxs[] to zero: Clicking outside of tabs has no effect.
    assert(Columns == tab_page_click_defs_size);
    clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size);
  
    /* Use the 'tabline' option if it's set. */
    if (*p_tal != NUL) {
      int saved_did_emsg = did_emsg;
  
      // Check for an error.  If there is one we would loop in redrawing the
      // screen.  Avoid that by making 'tabline' empty.
      did_emsg = false;
      win_redr_custom(NULL, false);
      if (did_emsg) {
        set_string_option_direct((char_u *)"tabline", -1,
                                 (char_u *)"", OPT_FREE, SID_ERROR);
      }
      did_emsg |= saved_did_emsg;
    } else {
      FOR_ALL_TABS(tp) {
        ++tabcount;
      }
  
      if (tabcount > 0) {
        tabwidth = (Columns - 1 + tabcount / 2) / tabcount;
      }
  
      if (tabwidth < 6) {
        tabwidth = 6;
      }
  
      attr = attr_nosel;
      tabcount = 0;
  
      FOR_ALL_TABS(tp) {
        if (col >= Columns - 4) {
          break;
        }
  
        scol = col;
  
        if (tp == curtab) {
          cwp = curwin;
          wp = firstwin;
        } else {
          cwp = tp->tp_curwin;
          wp = tp->tp_firstwin;
        }
  
  
        if (tp->tp_topframe == topframe) {
          attr = win_hl_attr(cwp, HLF_TPS);
        }
        if (use_sep_chars && col > 0) {
          grid_putchar(&default_grid, '|', 0, col++, attr);
        }
  
        if (tp->tp_topframe != topframe) {
          attr = win_hl_attr(cwp, HLF_TP);
        }
  
        grid_putchar(&default_grid, ' ', 0, col++, attr);
  
        modified = false;
  
        for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount) {
          if (bufIsChanged(wp->w_buffer)) {
            modified = true;
          }
        }
  
  
        if (modified || wincount > 1) {
          if (wincount > 1) {
            vim_snprintf((char *)NameBuff, MAXPATHL, "%d", wincount);
            len = (int)STRLEN(NameBuff);
            if (col + len >= Columns - 3) {
              break;
            }
            grid_puts_len(&default_grid, NameBuff, len, 0, col,
                          hl_combine_attr(attr, win_hl_attr(cwp, HLF_T)));
            col += len;
          }
          if (modified) {
            grid_puts_len(&default_grid, (char_u *)"+", 1, 0, col++, attr);
          }
          grid_putchar(&default_grid, ' ', 0, col++, attr);
        }
  
        room = scol - col + tabwidth - 1;
        if (room > 0) {
          /* Get buffer name in NameBuff[] */
          get_trans_bufname(cwp->w_buffer);
          (void)shorten_dir(NameBuff);
          len = vim_strsize(NameBuff);
          p = NameBuff;
          while (len > room) {
            len -= ptr2cells(p);
            MB_PTR_ADV(p);
          }
          if (len > Columns - col - 1) {
            len = Columns - col - 1;
          }
  
          grid_puts_len(&default_grid, p, (int)STRLEN(p), 0, col, attr);
          col += len;
        }
        grid_putchar(&default_grid, ' ', 0, col++, attr);
  
        // Store the tab page number in tab_page_click_defs[], so that
        // jump_to_mouse() knows where each one is.
        tabcount++;
        while (scol < col) {
          tab_page_click_defs[scol++] = (StlClickDefinition) {
            .type = kStlClickTabSwitch,
            .tabnr = tabcount,
            .func = NULL,
          };
        }
      }
  
      if (use_sep_chars)
        c = '_';
      else
        c = ' ';
      grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill);
  
      /* Put an "X" for closing the current tab if there are several. */
      if (first_tabpage->tp_next != NULL) {
        grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel);
        tab_page_click_defs[Columns - 1] = (StlClickDefinition) {
          .type = kStlClickTabClose,
          .tabnr = 999,
          .func = NULL,
        };
      }
    }
  
    /* Reset the flag here again, in case evaluating 'tabline' causes it to be
     * set. */
    redraw_tabline = FALSE;
  }
  
  void ui_ext_tabline_update(void)
  {
    Array tabs = ARRAY_DICT_INIT;
    FOR_ALL_TABS(tp) {
      Dictionary tab_info = ARRAY_DICT_INIT;
      PUT(tab_info, "tab", TABPAGE_OBJ(tp->handle));
  
      win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin;
      get_trans_bufname(cwp->w_buffer);
      PUT(tab_info, "name", STRING_OBJ(cstr_to_string((char *)NameBuff)));
  
      ADD(tabs, DICTIONARY_OBJ(tab_info));
    }
    ui_call_tabline_update(curtab->handle, tabs);
  }
  
  /*
   * Get buffer name for "buf" into NameBuff[].
   * Takes care of special buffer names and translates special characters.
   */
  void get_trans_bufname(buf_T *buf)
  {
    if (buf_spname(buf) != NULL)
      STRLCPY(NameBuff, buf_spname(buf), MAXPATHL);
    else
      home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE);
    trans_characters(NameBuff, MAXPATHL);
  }
  
  /*
   * Get the character to use in a status line.  Get its attributes in "*attr".
   */
  static int fillchar_status(int *attr, win_T *wp)
  {
    int fill;
    bool is_curwin = (wp == curwin);
    if (is_curwin) {
      *attr = win_hl_attr(wp, HLF_S);
      fill = wp->w_p_fcs_chars.stl;
    } else {
      *attr = win_hl_attr(wp, HLF_SNC);
      fill = wp->w_p_fcs_chars.stlnc;
    }
    /* Use fill when there is highlighting, and highlighting of current
     * window differs, or the fillchars differ, or this is not the
     * current window */
    if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC)
                        || !is_curwin || ONE_WINDOW)
                       || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) {
      return fill;
    }
    if (is_curwin) {
      return '^';
    }
    return '=';
  }
  
  /*
   * Get the character to use in a separator between vertically split windows.
   * Get its attributes in "*attr".
   */
  static int fillchar_vsep(win_T *wp, int *attr)
  {
    *attr = win_hl_attr(wp, HLF_C);
    return wp->w_p_fcs_chars.vert;
  }
  
  /*
   * Return TRUE if redrawing should currently be done.
   */
  int redrawing(void)
  {
    return !RedrawingDisabled
           && !(p_lz && char_avail() && !KeyTyped && !do_redraw);
  }
  
  /*
   * Return TRUE if printing messages should currently be done.
   */
  int messaging(void)
  {
    return !(p_lz && char_avail() && !KeyTyped);
  }
  
  /*
   * Show current status info in ruler and various other places
   * If always is FALSE, only show ruler if position has changed.
   */
  void showruler(int always)
  {
    if (!always && !redrawing())
      return;
    if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) {
      redraw_custom_statusline(curwin);
    } else {
      win_redr_ruler(curwin, always);
    }
  
    if (need_maketitle
        || (p_icon && (stl_syntax & STL_IN_ICON))
        || (p_title && (stl_syntax & STL_IN_TITLE))
        )
      maketitle();
    /* Redraw the tab pages line if needed. */
    if (redraw_tabline)
      draw_tabline();
  }
  
  static void win_redr_ruler(win_T *wp, int always)
  {
    static bool did_show_ext_ruler = false;
  
    // If 'ruler' off or redrawing disabled, don't do anything
    if (!p_ru) {
      return;
    }
  
    /*
     * Check if cursor.lnum is valid, since win_redr_ruler() may be called
     * after deleting lines, before cursor.lnum is corrected.
     */
    if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count)
      return;
  
    /* Don't draw the ruler while doing insert-completion, it might overwrite
     * the (long) mode message. */
    if (wp == lastwin && lastwin->w_status_height == 0)
      if (edit_submode != NULL)
        return;
  
    if (*p_ruf) {
      int save_called_emsg = called_emsg;
  
      called_emsg = FALSE;
      win_redr_custom(wp, TRUE);
      if (called_emsg)
        set_string_option_direct((char_u *)"rulerformat", -1,
            (char_u *)"", OPT_FREE, SID_ERROR);
      called_emsg |= save_called_emsg;
      return;
    }
  
    /*
     * Check if not in Insert mode and the line is empty (will show "0-1").
     */
    int empty_line = FALSE;
    if (!(State & INSERT)
        && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE) == NUL)
      empty_line = TRUE;
  
    /*
     * Only draw the ruler when something changed.
     */
    validate_virtcol_win(wp);
    if (       redraw_cmdline
               || always
               || wp->w_cursor.lnum != wp->w_ru_cursor.lnum
               || wp->w_cursor.col != wp->w_ru_cursor.col
               || wp->w_virtcol != wp->w_ru_virtcol
               || wp->w_cursor.coladd != wp->w_ru_cursor.coladd
               || wp->w_topline != wp->w_ru_topline
               || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count
               || wp->w_topfill != wp->w_ru_topfill
               || empty_line != wp->w_ru_empty) {
  
      int width;
      int row;
      int fillchar;
      int attr;
      int off;
      bool part_of_status = false;
  
      if (wp->w_status_height) {
        row = W_ENDROW(wp);
        fillchar = fillchar_status(&attr, wp);
        off = wp->w_wincol;
        width = wp->w_width;
        part_of_status = true;
      } else {
        row = Rows - 1;
        fillchar = ' ';
        attr = HL_ATTR(HLF_MSG);
        width = Columns;
        off = 0;
      }
  
      // In list mode virtcol needs to be recomputed
      colnr_T virtcol = wp->w_virtcol;
      if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
        wp->w_p_list = false;
        getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
        wp->w_p_list = true;
      }
  
  #define RULER_BUF_LEN 70
      char_u buffer[RULER_BUF_LEN];
  
      /*
       * Some sprintfs return the length, some return a pointer.
       * To avoid portability problems we use strlen() here.
       */
      vim_snprintf((char *)buffer, RULER_BUF_LEN, "%" PRId64 ",",
          (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L
                                                   : (int64_t)wp->w_cursor.lnum);
      size_t len = STRLEN(buffer);
      col_print(buffer + len, RULER_BUF_LEN - len,
          empty_line ? 0 : (int)wp->w_cursor.col + 1,
          (int)virtcol + 1);
  
      /*
       * Add a "50%" if there is room for it.
       * On the last line, don't print in the last column (scrolls the
       * screen up on some terminals).
       */
      int i = (int)STRLEN(buffer);
      get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
      int o = i + vim_strsize(buffer + i + 1);
      if (wp->w_status_height == 0) {  // can't use last char of screen
        o++;
      }
      int this_ru_col = ru_col - (Columns - width);
      if (this_ru_col < 0) {
        this_ru_col = 0;
      }
      // Never use more than half the window/screen width, leave the other half
      // for the filename.
      if (this_ru_col < (width + 1) / 2) {
        this_ru_col = (width + 1) / 2;
      }
      if (this_ru_col + o < width) {
        // Need at least 3 chars left for get_rel_pos() + NUL.
        while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
          i += utf_char2bytes(fillchar, buffer + i);
          o++;
        }
        get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
      }
  
      if (ui_has(kUIMessages) && !part_of_status) {
        Array content = ARRAY_DICT_INIT;
        Array chunk = ARRAY_DICT_INIT;
        ADD(chunk, INTEGER_OBJ(attr));
        ADD(chunk, STRING_OBJ(cstr_to_string((char *)buffer)));
        ADD(content, ARRAY_OBJ(chunk));
        ui_call_msg_ruler(content);
        did_show_ext_ruler = true;
      } else {
        if (did_show_ext_ruler) {
          ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
          did_show_ext_ruler = false;
        }
        // Truncate at window boundary.
        o = 0;
        for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
          o += utf_ptr2cells(buffer + i);
          if (this_ru_col + o > width) {
            buffer[i] = NUL;
            break;
          }
        }
  
        ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj;
        grid_puts(grid, buffer, row, this_ru_col + off, attr);
        grid_fill(grid, row, row + 1,
                  this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar,
                  fillchar, attr);
      }
  
      wp->w_ru_cursor = wp->w_cursor;
      wp->w_ru_virtcol = wp->w_virtcol;
      wp->w_ru_empty = empty_line;
      wp->w_ru_topline = wp->w_topline;
      wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count;
      wp->w_ru_topfill = wp->w_topfill;
    }
  }
  
  /*
   * Return the width of the 'number' and 'relativenumber' column.
   * Caller may need to check if 'number' or 'relativenumber' is set.
   * Otherwise it depends on 'numberwidth' and the line count.
   */
  int number_width(win_T *wp)
  {
    int n;
    linenr_T lnum;
  
    if (wp->w_p_rnu && !wp->w_p_nu) {
      // cursor line shows "0"
      lnum = wp->w_height_inner;
    } else {
      // cursor line shows absolute line number
      lnum = wp->w_buffer->b_ml.ml_line_count;
    }
  
    if (lnum == wp->w_nrwidth_line_count)
      return wp->w_nrwidth_width;
    wp->w_nrwidth_line_count = lnum;
  
    n = 0;
    do {
      lnum /= 10;
      ++n;
    } while (lnum > 0);
  
    /* 'numberwidth' gives the minimal width plus one */
    if (n < wp->w_p_nuw - 1)
      n = wp->w_p_nuw - 1;
  
    wp->w_nrwidth_width = n;
    return n;
  }
  
  /// Set dimensions of the Nvim application "shell".
  void screen_resize(int width, int height)
  {
    static int busy = FALSE;
  
    // Avoid recursiveness, can happen when setting the window size causes
    // another window-changed signal.
    if (updating_screen || busy) {
      return;
    }
  
    if (width < 0 || height < 0)      /* just checking... */
      return;
  
    if (State == HITRETURN || State == SETWSIZE) {
      /* postpone the resizing */
      State = SETWSIZE;
      return;
    }
  
    /* curwin->w_buffer can be NULL when we are closing a window and the
     * buffer has already been closed and removing a scrollbar causes a resize
     * event. Don't resize then, it will happen after entering another buffer.
     */
    if (curwin->w_buffer == NULL)
      return;
  
    ++busy;
  
    Rows = height;
    Columns = width;
    check_shellsize();
    height = Rows;
    width = Columns;
    p_lines = Rows;
    p_columns = Columns;
    ui_call_grid_resize(1, width, height);
  
    send_grid_resize = true;
  
    /* The window layout used to be adjusted here, but it now happens in
     * screenalloc() (also invoked from screenclear()).  That is because the
     * "busy" check above may skip this, but not screenalloc(). */
  
    if (State != ASKMORE && State != EXTERNCMD && State != CONFIRM) {
      screenclear();
    }
  
    if (starting != NO_SCREEN) {
      maketitle();
      changed_line_abv_curs();
      invalidate_botline();
  
      /*
       * We only redraw when it's needed:
       * - While at the more prompt or executing an external command, don't
       *   redraw, but position the cursor.
       * - While editing the command line, only redraw that.
       * - in Ex mode, don't redraw anything.
       * - Otherwise, redraw right now, and position the cursor.
       * Always need to call update_screen() or screenalloc(), to make
       * sure Rows/Columns and the size of the screen is correct!
       */
      if (State == ASKMORE || State == EXTERNCMD || State == CONFIRM
          || exmode_active) {
        screenalloc();
        if (msg_grid.chars) {
          msg_grid_validate();
        }
        // TODO(bfredl): sometimes messes up the output. Implement clear+redraw
        // also for the pager? (or: what if the pager was just a modal window?)
        ui_comp_set_screen_valid(true);
        repeat_message();
      } else {
        if (curwin->w_p_scb)
          do_check_scrollbind(TRUE);
        if (State & CMDLINE) {
          redraw_popupmenu = false;
          update_screen(NOT_VALID);
          redrawcmdline();
          if (pum_drawn()) {
            cmdline_pum_display(false);
          }
        } else {
          update_topline();
          if (pum_drawn()) {
            // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first.
            // For now make sure the nested update_screen(0) won't redraw the
            // pum at the old position. Try to untangle this later.
            redraw_popupmenu = false;
            ins_compl_show_pum();
          }
          update_screen(NOT_VALID);
          if (redrawing()) {
            setcursor();
          }
        }
      }
      ui_flush();
    }
    busy--;
  }
  
  /// Check if the new Nvim application "shell" dimensions are valid.
  /// Correct it if it's too small or way too big.
  void check_shellsize(void)
  {
    if (Rows < min_rows()) {
      // need room for one window and command line
      Rows = min_rows();
    }
    limit_screen_size();
  }
  
  // Limit Rows and Columns to avoid an overflow in Rows * Columns.
  void limit_screen_size(void)
  {
    if (Columns < MIN_COLUMNS) {
      Columns = MIN_COLUMNS;
    } else if (Columns > 10000) {
      Columns = 10000;
    }
  
    if (Rows > 1000) {
      Rows = 1000;
    }
  }
  
  void win_new_shellsize(void)
  {
    static long old_Rows = 0;
    static long old_Columns = 0;
  
    if (old_Rows != Rows) {
      // if 'window' uses the whole screen, keep it using that */
      if (p_window == old_Rows - 1 || old_Rows == 0) {
        p_window = Rows - 1;
      }
      old_Rows = Rows;
      shell_new_rows();  // update window sizes
    }
    if (old_Columns != Columns) {
      old_Columns = Columns;
      shell_new_columns();  // update window sizes
    }
  }
  
  win_T *get_win_by_grid_handle(handle_T handle)
  {
    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
      if (wp->w_grid.handle == handle) {
        return wp;
      }
    }
    return NULL;
  }
  