neovim

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

drawline.c (122819B)


      1 // drawline.c: Functions for drawing window lines on the screen.
      2 // This is the middle level, drawscreen.c is the top and grid.c the lower level.
      3 
      4 #include <assert.h>
      5 #include <limits.h>
      6 #include <stdbool.h>
      7 #include <stddef.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 
     12 #include "nvim/ascii_defs.h"
     13 #include "nvim/buffer.h"
     14 #include "nvim/buffer_defs.h"
     15 #include "nvim/charset.h"
     16 #include "nvim/cursor.h"
     17 #include "nvim/cursor_shape.h"
     18 #include "nvim/decoration.h"
     19 #include "nvim/decoration_defs.h"
     20 #include "nvim/decoration_provider.h"
     21 #include "nvim/diff.h"
     22 #include "nvim/drawline.h"
     23 #include "nvim/drawscreen.h"
     24 #include "nvim/eval/vars.h"
     25 #include "nvim/fold.h"
     26 #include "nvim/fold_defs.h"
     27 #include "nvim/globals.h"
     28 #include "nvim/grid.h"
     29 #include "nvim/grid_defs.h"
     30 #include "nvim/highlight.h"
     31 #include "nvim/highlight_defs.h"
     32 #include "nvim/highlight_group.h"
     33 #include "nvim/indent.h"
     34 #include "nvim/insexpand.h"
     35 #include "nvim/mark_defs.h"
     36 #include "nvim/marktree_defs.h"
     37 #include "nvim/match.h"
     38 #include "nvim/mbyte.h"
     39 #include "nvim/mbyte_defs.h"
     40 #include "nvim/memline.h"
     41 #include "nvim/memory.h"
     42 #include "nvim/move.h"
     43 #include "nvim/option.h"
     44 #include "nvim/option_vars.h"
     45 #include "nvim/os/os_defs.h"
     46 #include "nvim/plines.h"
     47 #include "nvim/pos_defs.h"
     48 #include "nvim/quickfix.h"
     49 #include "nvim/sign_defs.h"
     50 #include "nvim/spell.h"
     51 #include "nvim/state.h"
     52 #include "nvim/state_defs.h"
     53 #include "nvim/statusline.h"
     54 #include "nvim/statusline_defs.h"
     55 #include "nvim/strings.h"
     56 #include "nvim/syntax.h"
     57 #include "nvim/terminal.h"
     58 #include "nvim/types_defs.h"
     59 #include "nvim/ui.h"
     60 #include "nvim/ui_defs.h"
     61 #include "nvim/vim_defs.h"
     62 
     63 #define MB_FILLER_CHAR '<'  // character used when a double-width character doesn't fit.
     64 
     65 /// structure with variables passed between win_line() and other functions
     66 typedef struct {
     67  const linenr_T lnum;       ///< line number to be drawn
     68  const foldinfo_T foldinfo;  ///< fold info for this line
     69 
     70  const int startrow;        ///< first row in the window to be drawn
     71  int row;                   ///< row in the window, excl w_winrow
     72 
     73  colnr_T vcol;              ///< virtual column, before wrapping
     74  int col;                   ///< visual column on screen, after wrapping
     75  int boguscols;             ///< nonexistent columns added to "col" to force wrapping
     76  int old_boguscols;         ///< bogus boguscols
     77  int vcol_off_co;           ///< offset for concealed characters
     78 
     79  int off;                   ///< offset relative start of line
     80 
     81  int cul_attr;              ///< set when 'cursorline' active
     82  int line_attr;             ///< attribute for the whole line
     83  int line_attr_lowprio;     ///< low-priority attribute for the line
     84  int sign_num_attr;         ///< line number attribute (sign numhl)
     85  int prev_num_attr;         ///< previous line's number attribute (sign numhl)
     86  int sign_cul_attr;         ///< cursorline sign attribute (sign culhl)
     87 
     88  int fromcol;               ///< start of inverting
     89  int tocol;                 ///< end of inverting
     90 
     91  colnr_T vcol_sbr;          ///< virtual column after showbreak
     92  bool need_showbreak;       ///< overlong line, skipping first x chars
     93 
     94  int char_attr;             ///< attributes for next character
     95 
     96  int n_extra;               ///< number of extra bytes
     97  int n_attr;                ///< chars with special attr
     98  char *p_extra;             ///< string of extra chars, plus NUL, only used
     99                             ///< when sc_extra and sc_final are NUL
    100  int extra_attr;            ///< attributes for p_extra
    101  schar_T sc_extra;          ///< extra chars, all the same
    102  schar_T sc_final;          ///< final char, mandatory if set
    103 
    104  bool extra_for_extmark;    ///< n_extra set for inline virtual text
    105 
    106  char extra[11];            ///< must be as large as transchar_charbuf[] in charset.c
    107 
    108  hlf_T diff_hlf;            ///< type of diff highlighting
    109 
    110  int n_virt_lines;          ///< nr of virtual lines
    111  int n_virt_below;          ///< nr of virtual lines belonging to previous line
    112  int filler_lines;          ///< nr of filler lines to be drawn
    113  int filler_todo;           ///< nr of filler lines still to do + 1
    114  SignTextAttrs sattrs[SIGN_SHOW_MAX];  ///< sign attributes for the sign column
    115  /// do consider wrapping in linebreak mode only after encountering
    116  /// a non whitespace char
    117  bool need_lbr;
    118 
    119  VirtText virt_inline;
    120  size_t virt_inline_i;
    121  HlMode virt_inline_hl_mode;
    122 
    123  bool reset_extra_attr;
    124 
    125  int skip_cells;            ///< nr of cells to skip for w_leftcol
    126                             ///< or w_skipcol or concealing
    127  int skipped_cells;         ///< nr of skipped cells for virtual text
    128                             ///< to be added to wlv.vcol later
    129 
    130  int *color_cols;           ///< if not NULL, highlight colorcolumn using according columns array
    131 } winlinevars_T;
    132 
    133 #include "drawline.c.generated.h"
    134 
    135 static char *extra_buf = NULL;
    136 static size_t extra_buf_size = 0;
    137 
    138 static char *get_extra_buf(size_t size)
    139 {
    140  size = MAX(size, 64);
    141  if (extra_buf_size < size) {
    142    xfree(extra_buf);
    143    extra_buf = xmalloc(size);
    144    extra_buf_size = size;
    145  }
    146  return extra_buf;
    147 }
    148 
    149 #ifdef EXITFREE
    150 void drawline_free_all_mem(void)
    151 {
    152  xfree(extra_buf);
    153 }
    154 #endif
    155 
    156 /// Get the 'listchars' "extends" characters to use for "wp", or NUL if it
    157 /// shouldn't be used.
    158 static schar_T get_lcs_ext(win_T *wp)
    159 {
    160  if (wp->w_p_wrap) {
    161    // Line never continues beyond the right of the screen with 'wrap'.
    162    return NUL;
    163  }
    164  if (wp->w_p_wrap_flags & kOptFlagInsecure) {
    165    // If 'nowrap' was set from a modeline, forcibly use '>'.
    166    return schar_from_ascii('>');
    167  }
    168  return wp->w_p_list ? wp->w_p_lcs_chars.ext : NUL;
    169 }
    170 
    171 /// Advance wlv->color_cols if not NULL
    172 static void advance_color_col(winlinevars_T *wlv, int vcol)
    173 {
    174  if (wlv->color_cols) {
    175    while (*wlv->color_cols >= 0 && vcol > *wlv->color_cols) {
    176      wlv->color_cols++;
    177    }
    178    if (*wlv->color_cols < 0) {
    179      wlv->color_cols = NULL;
    180    }
    181  }
    182 }
    183 
    184 /// Used when 'cursorlineopt' contains "screenline": compute the margins between
    185 /// which the highlighting is used.
    186 static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
    187 {
    188  // cache previous calculations depending on w_virtcol
    189  static int saved_w_virtcol;
    190  static win_T *prev_wp;
    191  static int prev_width1;
    192  static int prev_width2;
    193  static int prev_left_col;
    194  static int prev_right_col;
    195 
    196  int cur_col_off = win_col_off(wp);
    197  int width1 = wp->w_view_width - cur_col_off;
    198  int width2 = width1 + win_col_off2(wp);
    199 
    200  if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp
    201      && prev_width1 == width1 && prev_width2 == width2) {
    202    *right_col = prev_right_col;
    203    *left_col = prev_left_col;
    204    return;
    205  }
    206 
    207  *left_col = 0;
    208  *right_col = width1;
    209 
    210  if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
    211    *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2;
    212  }
    213  if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
    214    *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1;
    215  }
    216 
    217  // cache values
    218  prev_left_col = *left_col;
    219  prev_right_col = *right_col;
    220  prev_wp = wp;
    221  prev_width1 = width1;
    222  prev_width2 = width2;
    223  saved_w_virtcol = wp->w_virtcol;
    224 }
    225 
    226 /// Put a single char from an UTF-8 buffer into a line buffer.
    227 ///
    228 /// If `*pp` is a double-width char and only one cell is left, emit a space,
    229 /// and don't advance *pp
    230 ///
    231 /// Handles composing chars
    232 static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells, int vcol)
    233 {
    234  // Caller should handle overwriting the right half of a double-width char.
    235  assert(dest[0] != 0);
    236 
    237  const char *p = *pp;
    238  int cells = utf_ptr2cells(p);
    239  int c_len = utfc_ptr2len(p);
    240  assert(maxcells > 0);
    241  if (cells > maxcells) {
    242    dest[0] = schar_from_ascii(' ');
    243    return 1;
    244  }
    245 
    246  if (*p == TAB) {
    247    cells = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array);
    248    cells = MIN(cells, maxcells);
    249  }
    250 
    251  // When overwriting the left half of a double-width char, clear the right half.
    252  if (cells < maxcells && dest[cells] == 0) {
    253    dest[cells] = schar_from_ascii(' ');
    254  }
    255  if (*p == TAB) {
    256    for (int c = 0; c < cells; c++) {
    257      dest[c] = schar_from_ascii(' ');
    258    }
    259  } else {
    260    int u8c;
    261    dest[0] = utfc_ptr2schar(p, &u8c);
    262    if (cells > 1) {
    263      dest[1] = 0;
    264    }
    265  }
    266 
    267  *pp += c_len;
    268  return cells;
    269 }
    270 
    271 static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int win_row)
    272 {
    273  DecorState *const state = &decor_state;
    274  int const max_col = wp->w_view_width;
    275  int right_pos = max_col;
    276  bool const do_eol = state->eol_col > -1;
    277 
    278  int const end = state->current_end;
    279  int *const indices = state->ranges_i.items;
    280  DecorRangeSlot *const slots = state->slots.items;
    281 
    282  /// Total width of all virtual text with "eol_right_align" alignment
    283  int totalWidthOfEolRightAlignedVirtText = 0;
    284 
    285  for (int i = 0; i < end; i++) {
    286    DecorRange *item = &slots[indices[i]].range;
    287    if (!(item->start_row == state->row && decor_virt_pos(item))) {
    288      continue;
    289    }
    290 
    291    DecorVirtText *vt = NULL;
    292    if (item->kind == kDecorKindVirtText) {
    293      assert(item->data.vt);
    294      vt = item->data.vt;
    295    }
    296    if (decor_virt_pos(item) && item->draw_col == -1) {
    297      bool updated = true;
    298      VirtTextPos pos = decor_virt_pos_kind(item);
    299 
    300      if (do_eol && pos == kVPosEndOfLineRightAlign) {
    301        int eolOffset = 0;
    302        if (totalWidthOfEolRightAlignedVirtText == 0) {
    303          // Look ahead to the remaining decor items
    304          for (int j = i; j < end; j++) {
    305            /// A future decor to be handled in this function's call
    306            DecorRange *lookaheadItem = &slots[indices[j]].range;
    307 
    308            if (lookaheadItem->start_row != state->row
    309                || !decor_virt_pos(lookaheadItem)
    310                || lookaheadItem->draw_col != -1) {
    311              continue;
    312            }
    313 
    314            /// The Virtual Text of the decor item we're looking ahead to
    315            DecorVirtText *lookaheadVt = NULL;
    316            if (lookaheadItem->kind == kDecorKindVirtText) {
    317              assert(lookaheadItem->data.vt);
    318              lookaheadVt = lookaheadItem->data.vt;
    319            }
    320 
    321            if (decor_virt_pos_kind(lookaheadItem) == kVPosEndOfLineRightAlign) {
    322              // An extra space is added for single character spacing in EOL alignment
    323              totalWidthOfEolRightAlignedVirtText += (lookaheadVt->width + 1);
    324            }
    325          }
    326 
    327          // Remove one space from the total width since there's no single space after the last entry
    328          totalWidthOfEolRightAlignedVirtText--;
    329 
    330          if (totalWidthOfEolRightAlignedVirtText <= (right_pos - state->eol_col)) {
    331            eolOffset = right_pos - totalWidthOfEolRightAlignedVirtText - state->eol_col;
    332          }
    333        }
    334 
    335        item->draw_col = state->eol_col + eolOffset;
    336      } else if (pos == kVPosRightAlign) {
    337        right_pos -= vt->width;
    338        item->draw_col = right_pos;
    339      } else if (pos == kVPosEndOfLine && do_eol) {
    340        item->draw_col = state->eol_col;
    341      } else if (pos == kVPosWinCol) {
    342        item->draw_col = MAX(col_off + vt->col, 0);
    343      } else {
    344        updated = false;
    345      }
    346      if (updated && (item->draw_col < 0 || item->draw_col >= wp->w_view_width)) {
    347        // Out of window, don't draw at all.
    348        item->draw_col = INT_MIN;
    349      }
    350    }
    351    if (item->draw_col < 0) {
    352      continue;
    353    }
    354    if (item->kind == kDecorKindUIWatched) {
    355      // send mark position to UI
    356      WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, item->draw_col };
    357      kv_push(win_extmark_arr, m);
    358    }
    359    if (vt) {
    360      int vcol = item->draw_col - col_off;
    361      int col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
    362                                    vt->hl_mode, max_col, vcol, 0);
    363      if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) {
    364        state->eol_col = col + 1;
    365      }
    366      *end_col = MAX(*end_col, col);
    367    }
    368    if (!vt || !(vt->flags & kVTRepeatLinebreak)) {
    369      item->draw_col = INT_MIN;  // deactivate
    370    }
    371  }
    372 }
    373 
    374 static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col,
    375                               int vcol, int skip_cells)
    376 {
    377  const char *virt_str = "";
    378  int virt_attr = 0;
    379  size_t virt_pos = 0;
    380 
    381  while (col < max_col) {
    382    if (skip_cells >= 0 && *virt_str == NUL) {
    383      if (virt_pos >= kv_size(vt)) {
    384        break;
    385      }
    386      virt_attr = 0;
    387      virt_str = next_virt_text_chunk(vt, &virt_pos, &virt_attr);
    388      if (virt_str == NULL) {
    389        break;
    390      }
    391    }
    392    // Skip cells in the text.
    393    while (skip_cells > 0 && *virt_str != NUL) {
    394      int c_len = utfc_ptr2len(virt_str);
    395      int cells = *virt_str == TAB
    396                  ? tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array)
    397                  : utf_ptr2cells(virt_str);
    398      skip_cells -= cells;
    399      vcol += cells;
    400      virt_str += c_len;
    401    }
    402    // If a double-width char or TAB doesn't fit, pad with spaces.
    403    const char *draw_str = skip_cells < 0 ? " " : virt_str;
    404    if (*draw_str == NUL) {
    405      continue;
    406    }
    407    assert(skip_cells <= 0);
    408    int attr;
    409    bool through = false;
    410    if (hl_mode == kHlModeCombine) {
    411      attr = hl_combine_attr(linebuf_attr[col], virt_attr);
    412    } else if (hl_mode == kHlModeBlend) {
    413      through = (*draw_str == ' ');
    414      attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
    415    } else {
    416      attr = virt_attr;
    417    }
    418    schar_T dummy[2] = { schar_from_ascii(' '), schar_from_ascii(' ') };
    419    int maxcells = max_col - col;
    420    // When overwriting the right half of a double-width char, clear the left half.
    421    if (!through && linebuf_char[col] == 0) {
    422      assert(col > 0);
    423      linebuf_char[col - 1] = schar_from_ascii(' ');
    424      // Clear the right half as well for the assertion in line_putchar().
    425      linebuf_char[col] = schar_from_ascii(' ');
    426    }
    427    int cells = line_putchar(buf, &draw_str, through ? dummy : &linebuf_char[col],
    428                             maxcells, vcol);
    429    for (int c = 0; c < cells; c++) {
    430      linebuf_attr[col] = attr;
    431      col++;
    432    }
    433    if (skip_cells < 0) {
    434      skip_cells++;
    435    } else {
    436      vcol += cells;
    437      virt_str = draw_str;
    438    }
    439  }
    440  return col;
    441 }
    442 
    443 // TODO(bfredl): integrate with grid.c linebuf code? madness?
    444 static void draw_col_buf(win_T *wp, winlinevars_T *wlv, const char *text, size_t len, int attr,
    445                         const colnr_T *fold_vcol, bool inc_vcol)
    446 {
    447  const char *ptr = text;
    448  while (ptr < text + len && wlv->off < wp->w_view_width) {
    449    int cells = line_putchar(wp->w_buffer, &ptr, &linebuf_char[wlv->off],
    450                             wp->w_view_width - wlv->off, wlv->off);
    451    int myattr = attr;
    452    if (inc_vcol) {
    453      advance_color_col(wlv, wlv->vcol);
    454      if (wlv->color_cols && wlv->vcol == *wlv->color_cols) {
    455        myattr = hl_combine_attr(win_hl_attr(wp, HLF_MC), myattr);
    456      }
    457    }
    458    for (int c = 0; c < cells; c++) {
    459      linebuf_attr[wlv->off] = myattr;
    460      linebuf_vcol[wlv->off] = inc_vcol ? wlv->vcol++ : fold_vcol ? *(fold_vcol++) : -1;
    461      wlv->off++;
    462    }
    463  }
    464 }
    465 
    466 static void draw_col_fill(winlinevars_T *wlv, schar_T fillchar, int width, int attr)
    467 {
    468  for (int i = 0; i < width; i++) {
    469    linebuf_char[wlv->off] = fillchar;
    470    linebuf_attr[wlv->off] = attr;
    471    wlv->off++;
    472  }
    473 }
    474 
    475 /// Return true if CursorLineSign highlight is to be used.
    476 bool use_cursor_line_highlight(win_T *wp, linenr_T lnum)
    477 {
    478  return wp->w_p_cul
    479         && lnum == wp->w_cursorline
    480         && (wp->w_p_culopt_flags & kOptCuloptFlagNumber);
    481 }
    482 
    483 /// Setup for drawing the 'foldcolumn', if there is one.
    484 static void draw_foldcolumn(win_T *wp, winlinevars_T *wlv)
    485 {
    486  int fdc = compute_foldcolumn(wp, 0);
    487  if (fdc > 0) {
    488    int attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLF : HLF_FC);
    489    fill_foldcolumn(wp, wlv->foldinfo, wlv->lnum, attr, fdc, &wlv->off, NULL, NULL);
    490  }
    491 }
    492 
    493 /// Draw the foldcolumn or fill "out_buffer". Assume monocell characters.
    494 ///
    495 /// @param fdc  Current width of the foldcolumn
    496 /// @param[out] wlv_off  Pointer to linebuf offset, incremented for default column
    497 /// @param[out] out_buffer  Char array to fill, only used for 'statuscolumn'
    498 /// @param[out] out_vcol  vcol array to fill, only used for 'statuscolumn'
    499 void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, int fdc, int *wlv_off,
    500                     colnr_T *out_vcol, schar_T *out_buffer)
    501 {
    502  bool closed = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
    503  int level = foldinfo.fi_level;
    504 
    505  // If the column is too narrow, we start at the lowest level that
    506  // fits and use numbers to indicate the depth.
    507  int first_level = MAX(level - fdc - closed + 1, 1);
    508  int closedcol = MIN(fdc, level);
    509 
    510  for (int i = 0; i < fdc; i++) {
    511    schar_T symbol = 0;
    512    if (i >= level) {
    513      symbol = schar_from_ascii(' ');
    514    } else if (i == closedcol - 1 && closed) {
    515      symbol = wp->w_p_fcs_chars.foldclosed;
    516    } else if (foldinfo.fi_lnum == lnum && first_level + i >= foldinfo.fi_low_level) {
    517      symbol = wp->w_p_fcs_chars.foldopen;
    518    } else if (first_level == 1) {
    519      symbol = wp->w_p_fcs_chars.foldsep;
    520    } else if (wp->w_p_fcs_chars.foldinner != NUL) {
    521      symbol = wp->w_p_fcs_chars.foldinner;
    522    } else if (first_level + i <= 9) {
    523      symbol = schar_from_ascii('0' + first_level + i);
    524    } else {
    525      symbol = schar_from_ascii('>');
    526    }
    527 
    528    int vcol = i >= level ? -1 : (i == closedcol - 1 && closed) ? -2 : -3;
    529    if (out_buffer) {
    530      out_vcol[i] = vcol;
    531      out_buffer[i] = symbol;
    532    } else {
    533      linebuf_vcol[*wlv_off] = vcol;
    534      linebuf_attr[*wlv_off] = attr;
    535      linebuf_char[(*wlv_off)++] = symbol;
    536    }
    537  }
    538 }
    539 
    540 /// Get information needed to display the sign in line "wlv->lnum" in window "wp".
    541 /// If "nrcol" is true, the sign is going to be displayed in the number column.
    542 /// Otherwise the sign is going to be displayed in the sign column. If there is no
    543 /// sign, draw blank cells instead.
    544 static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx)
    545 {
    546  SignTextAttrs sattr = wlv->sattrs[sign_idx];
    547  int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
    548 
    549  if (sattr.text[0] && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) {
    550    int fill = nrcol ? number_width(wp) + 1 : SIGN_WIDTH;
    551    int attr = wlv->sign_cul_attr ? wlv->sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0;
    552    attr = hl_combine_attr(scl_attr, attr);
    553    draw_col_fill(wlv, schar_from_ascii(' '), fill, attr);
    554    int sign_pos = wlv->off - SIGN_WIDTH - (int)nrcol;
    555    assert(sign_pos >= 0);
    556    linebuf_char[sign_pos] = sattr.text[0];
    557    linebuf_char[sign_pos + 1] = sattr.text[1];
    558  } else {
    559    assert(!nrcol);  // handled in draw_lnum_col()
    560    draw_col_fill(wlv, schar_from_ascii(' '), SIGN_WIDTH, scl_attr);
    561  }
    562 }
    563 
    564 static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len)
    565 {
    566  linenr_T num;
    567  char *fmt = "%*" PRIdLINENR " ";
    568 
    569  if (wp->w_p_nu && !wp->w_p_rnu) {
    570    // 'number' + 'norelativenumber'
    571    num = lnum;
    572  } else {
    573    // 'relativenumber', don't use negative numbers
    574    num = abs(get_cursor_rel_lnum(wp, lnum));
    575    if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
    576      // 'number' + 'relativenumber'
    577      num = lnum;
    578      fmt = "%-*" PRIdLINENR " ";
    579    }
    580  }
    581 
    582  snprintf(buf, buf_len, fmt, number_width(wp), num);
    583 }
    584 
    585 /// Return true if CursorLineNr highlight is to be used for the number column.
    586 /// - 'cursorline' must be set
    587 /// - "wlv->lnum" must be the cursor line
    588 /// - 'cursorlineopt' has "number"
    589 /// - don't highlight filler lines (when in diff mode)
    590 /// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number
    591 ///   itself on the first screenline of the wrapped line, otherwise highlight the number column of
    592 ///   all screenlines of the wrapped line.
    593 static bool use_cursor_line_nr(win_T *wp, winlinevars_T *wlv)
    594 {
    595  return wp->w_p_cul
    596         && wlv->lnum == wp->w_cursorline
    597         && (wp->w_p_culopt_flags & kOptCuloptFlagNumber)
    598         && (wlv->row == wlv->startrow + wlv->filler_lines
    599             || (wlv->row > wlv->startrow + wlv->filler_lines
    600                 && (wp->w_p_culopt_flags & kOptCuloptFlagLine)));
    601 }
    602 
    603 /// Return line number attribute, combining the appropriate LineNr* highlight
    604 /// with the highest priority sign numhl highlight, if any.
    605 static int get_line_number_attr(win_T *wp, winlinevars_T *wlv)
    606 {
    607  int numhl_attr = wlv->sign_num_attr;
    608 
    609  // Get previous sign numhl for virt_lines belonging to the previous line.
    610  if ((wlv->n_virt_lines - wlv->filler_todo) < wlv->n_virt_below) {
    611    if (wlv->prev_num_attr == -1) {
    612      decor_redraw_signs(wp, wp->w_buffer, wlv->lnum - 2, NULL, NULL, NULL, &wlv->prev_num_attr);
    613      if (wlv->prev_num_attr > 0) {
    614        wlv->prev_num_attr = syn_id2attr(wlv->prev_num_attr);
    615      }
    616    }
    617    numhl_attr = wlv->prev_num_attr;
    618  }
    619 
    620  if (use_cursor_line_nr(wp, wlv)) {
    621    // TODO(vim): Can we use CursorLine instead of CursorLineNr
    622    // when CursorLineNr isn't set?
    623    return hl_combine_attr(win_hl_attr(wp, HLF_CLN), numhl_attr);
    624  }
    625 
    626  if (wp->w_p_rnu) {
    627    if (wlv->lnum < wp->w_cursor.lnum) {
    628      // Use LineNrAbove
    629      return hl_combine_attr(win_hl_attr(wp, HLF_LNA), numhl_attr);
    630    }
    631    if (wlv->lnum > wp->w_cursor.lnum) {
    632      // Use LineNrBelow
    633      return hl_combine_attr(win_hl_attr(wp, HLF_LNB), numhl_attr);
    634    }
    635  }
    636 
    637  return hl_combine_attr(win_hl_attr(wp, HLF_N), numhl_attr);
    638 }
    639 
    640 /// Display the absolute or relative line number.  After the first row fill with
    641 /// blanks when the 'n' flag isn't in 'cpo'.
    642 static void draw_lnum_col(win_T *wp, winlinevars_T *wlv)
    643 {
    644  bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL;
    645 
    646  if ((wp->w_p_nu || wp->w_p_rnu)
    647      && (wlv->row == wlv->startrow + wlv->filler_lines || !has_cpo_n)
    648      // there is no line number in a wrapped line when "n" is in
    649      // 'cpoptions', but 'breakindent' assumes it anyway.
    650      && !((has_cpo_n && !wp->w_p_bri) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) {
    651    // If 'signcolumn' is set to 'number' and a sign is present in "lnum",
    652    // then display the sign instead of the line number.
    653    if (wp->w_minscwidth == SCL_NUM && wlv->sattrs[0].text[0]
    654        && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) {
    655      draw_sign(true, wp, wlv, 0);
    656    } else {
    657      // Draw the line number (empty space after wrapping).
    658      int width = number_width(wp) + 1;
    659      int attr = get_line_number_attr(wp, wlv);
    660      if (wlv->row == wlv->startrow + wlv->filler_lines
    661          && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) {
    662        char buf[32];
    663        get_line_number_str(wp, wlv->lnum, buf, sizeof(buf));
    664        if (wp->w_skipcol > 0 && wlv->startrow == 0) {
    665          for (char *c = buf; *c == ' '; c++) {
    666            *c = '-';
    667          }
    668        }
    669        if (wp->w_p_rl) {  // reverse line numbers
    670          char *num = skipwhite(buf);
    671          rl_mirror_ascii(num, skiptowhite(num));
    672        }
    673        draw_col_buf(wp, wlv, buf, (size_t)width, attr, NULL, false);
    674      } else {
    675        draw_col_fill(wlv, schar_from_ascii(' '), width, attr);
    676      }
    677    }
    678  }
    679 }
    680 
    681 /// Build and draw the 'statuscolumn' string for line "lnum" in window "wp".
    682 static void draw_statuscol(win_T *wp, winlinevars_T *wlv, int virtnum, int col_rows,
    683                           statuscol_T *stcp)
    684 {
    685  // Adjust lnum for filler lines belonging to the line above and set lnum v:vars for first
    686  // row, first non-filler line, and first filler line belonging to the current line.
    687  linenr_T lnum = wlv->lnum - ((wlv->n_virt_lines - wlv->filler_todo) < wlv->n_virt_below);
    688  linenr_T relnum = (virtnum == -wlv->filler_lines || virtnum == 0
    689                     || virtnum == (wlv->n_virt_below - wlv->filler_lines))
    690                    ? abs(get_cursor_rel_lnum(wp, lnum)) : -1;
    691 
    692  char buf[MAXPATHL];
    693  // When a buffer's line count has changed, make a best estimate for the full
    694  // width of the status column by building with the largest possible line number.
    695  // Add potentially truncated width and rebuild before drawing anything.
    696  if (wp->w_statuscol_line_count != wp->w_nrwidth_line_count) {
    697    wp->w_statuscol_line_count = wp->w_nrwidth_line_count;
    698    set_vim_var_nr(VV_VIRTNUM, 0);
    699    int width = build_statuscol_str(wp, wp->w_nrwidth_line_count,
    700                                    wp->w_nrwidth_line_count, buf, stcp);
    701    if (width > stcp->width) {
    702      int addwidth = MIN(width - stcp->width, MAX_STCWIDTH - stcp->width);
    703      wp->w_nrwidth += addwidth;
    704      wp->w_nrwidth_width = wp->w_nrwidth;
    705      if (col_rows > 0) {
    706        // If only column is being redrawn, we now need to redraw the text as well
    707        wp->w_redr_statuscol = true;
    708        return;
    709      }
    710      stcp->width += addwidth;
    711      wp->w_valid &= ~VALID_WCOL;
    712    }
    713  }
    714  set_vim_var_nr(VV_VIRTNUM, virtnum);
    715 
    716  int width = build_statuscol_str(wp, lnum, relnum, buf, stcp);
    717  // Force a redraw in case of error or when truncated
    718  if (*wp->w_p_stc == NUL || (width > stcp->width && stcp->width < MAX_STCWIDTH)) {
    719    if (*wp->w_p_stc == NUL) {  // 'statuscolumn' reset due to error
    720      wp->w_nrwidth_line_count = 0;
    721      wp->w_nrwidth = (wp->w_p_nu || wp->w_p_rnu) * number_width(wp);
    722    } else {  // Avoid truncating 'statuscolumn'
    723      wp->w_nrwidth += MIN(width - stcp->width, MAX_STCWIDTH - stcp->width);
    724      wp->w_nrwidth_width = wp->w_nrwidth;
    725    }
    726    wp->w_redr_statuscol = true;
    727    return;
    728  }
    729 
    730  char *p = buf;
    731  char transbuf[MAXPATHL];
    732  colnr_T *fold_vcol = NULL;
    733  size_t len = strlen(buf);
    734  int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
    735  int num_attr = get_line_number_attr(wp, wlv);
    736  int cur_attr = num_attr;
    737 
    738  // Draw each segment with the specified highlighting.
    739  for (stl_hlrec_t *sp = stcp->hlrec; sp->start != NULL; sp++) {
    740    ptrdiff_t textlen = sp->start - p;
    741    // Make all characters printable.
    742    size_t translen = transstr_buf(p, textlen, transbuf, MAXPATHL, true);
    743    draw_col_buf(wp, wlv, transbuf, translen, cur_attr, fold_vcol, false);
    744    int attr = sp->item == STL_SIGNCOL ? scl_attr : sp->item == STL_FOLDCOL ? 0 : num_attr;
    745    cur_attr = hl_combine_attr(attr, sp->userhl < 0 ? syn_id2attr(-sp->userhl) : 0);
    746    fold_vcol = sp->item == STL_FOLDCOL ? stcp->fold_vcol : NULL;
    747    p = sp->start;
    748  }
    749  size_t translen = transstr_buf(p, buf + len - p, transbuf, MAXPATHL, true);
    750  draw_col_buf(wp, wlv, transbuf, translen, cur_attr, fold_vcol, false);
    751  draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, cur_attr);
    752 }
    753 
    754 static void handle_breakindent(win_T *wp, winlinevars_T *wlv)
    755 {
    756  // draw 'breakindent': indent wrapped text accordingly
    757  // if wlv->need_showbreak is set, breakindent also applies
    758  if (wp->w_p_bri && (wlv->row > wlv->startrow + wlv->filler_lines
    759                      || wlv->need_showbreak)) {
    760    int attr = 0;
    761    if (wlv->diff_hlf != (hlf_T)0) {
    762      attr = win_hl_attr(wp, (int)wlv->diff_hlf);
    763    }
    764    int num = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, wlv->lnum));
    765    if (wlv->row == wlv->startrow) {
    766      num -= win_col_off2(wp);
    767      if (wlv->n_extra < 0) {
    768        num = 0;
    769      }
    770    }
    771 
    772    colnr_T vcol_before = wlv->vcol;
    773 
    774    for (int i = 0; i < num; i++) {
    775      linebuf_char[wlv->off] = schar_from_ascii(' ');
    776 
    777      advance_color_col(wlv, wlv->vcol);
    778      int myattr = attr;
    779      if (wlv->color_cols && wlv->vcol == *wlv->color_cols) {
    780        myattr = hl_combine_attr(win_hl_attr(wp, HLF_MC), myattr);
    781      }
    782      linebuf_attr[wlv->off] = myattr;
    783      linebuf_vcol[wlv->off] = wlv->vcol++;  // These are vcols, sorry I don't make the rules
    784      wlv->off++;
    785    }
    786 
    787    // Correct start of highlighted area for 'breakindent',
    788    if (wlv->fromcol >= vcol_before && wlv->fromcol < wlv->vcol) {
    789      wlv->fromcol = wlv->vcol;
    790    }
    791 
    792    // Correct end of highlighted area for 'breakindent',
    793    // required wen 'linebreak' is also set.
    794    if (wlv->tocol == vcol_before) {
    795      wlv->tocol = wlv->vcol;
    796    }
    797  }
    798 
    799  if (wp->w_skipcol > 0 && wlv->startrow == 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
    800    wlv->need_showbreak = false;
    801  }
    802 }
    803 
    804 static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv)
    805 {
    806  int remaining = wp->w_view_width - wlv->off;
    807  if (wlv->filler_todo > wlv->filler_lines - wlv->n_virt_lines) {
    808    // TODO(bfredl): check this doesn't inhibit TUI-style
    809    //               clear-to-end-of-line.
    810    draw_col_fill(wlv, schar_from_ascii(' '), remaining, 0);
    811  } else if (wlv->filler_todo > 0) {
    812    // Draw "deleted" diff line(s)
    813    schar_T c = wp->w_p_fcs_chars.diff;
    814    draw_col_fill(wlv, c, remaining, win_hl_attr(wp, HLF_DED));
    815  }
    816 
    817  char *const sbr = get_showbreak_value(wp);
    818  if (*sbr != NUL && wlv->need_showbreak) {
    819    // Draw 'showbreak' at the start of each broken line.
    820    // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
    821    int attr = hl_combine_attr(wlv->cul_attr, win_hl_attr(wp, HLF_AT));
    822    colnr_T vcol_before = wlv->vcol;
    823    draw_col_buf(wp, wlv, sbr, strlen(sbr), attr, NULL, true);
    824    wlv->vcol_sbr = wlv->vcol;
    825 
    826    // Correct start of highlighted area for 'showbreak'.
    827    if (wlv->fromcol >= vcol_before && wlv->fromcol < wlv->vcol) {
    828      wlv->fromcol = wlv->vcol;
    829    }
    830 
    831    // Correct end of highlighted area for 'showbreak',
    832    // required when 'linebreak' is also set.
    833    if (wlv->tocol == vcol_before) {
    834      wlv->tocol = wlv->vcol;
    835    }
    836  }
    837 
    838  if (wp->w_skipcol == 0 || wlv->startrow > 0 || !wp->w_p_wrap || !wp->w_briopt_sbr) {
    839    wlv->need_showbreak = false;
    840  }
    841 }
    842 
    843 static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv)
    844 {
    845  wlv->cul_attr = win_hl_attr(wp, HLF_CUL);
    846  HlAttrs ae = syn_attr2entry(wlv->cul_attr);
    847  // We make a compromise here (#7383):
    848  //  * low-priority CursorLine if fg is not set
    849  //  * high-priority ("same as Vim" priority) CursorLine if fg is set
    850  if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
    851    wlv->line_attr_lowprio = wlv->cul_attr;
    852  } else {
    853    if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer)
    854        && qf_current_entry(wp) == wlv->lnum) {
    855      wlv->line_attr = hl_combine_attr(wlv->cul_attr, wlv->line_attr);
    856    } else {
    857      wlv->line_attr = wlv->cul_attr;
    858    }
    859  }
    860 }
    861 
    862 static void set_line_attr_for_diff(win_T *wp, winlinevars_T *wlv)
    863 {
    864  wlv->line_attr = win_hl_attr(wp, (int)wlv->diff_hlf);
    865  // Overlay CursorLine onto diff-mode highlight.
    866  if (wlv->cul_attr) {
    867    wlv->line_attr = 0 != wlv->line_attr_lowprio  // Low-priority CursorLine
    868                     ? hl_combine_attr(hl_combine_attr(wlv->cul_attr, wlv->line_attr),
    869                                       hl_get_underline())
    870                     : hl_combine_attr(wlv->line_attr, wlv->cul_attr);
    871  }
    872 }
    873 
    874 /// Checks if there is more inline virtual text that need to be drawn.
    875 static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v)
    876 {
    877  if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) {
    878    return true;
    879  }
    880 
    881  int const count = (int)kv_size(decor_state.ranges_i);
    882  int const cur_end = decor_state.current_end;
    883  int const fut_beg = decor_state.future_begin;
    884  int *const indices = decor_state.ranges_i.items;
    885  DecorRangeSlot *const slots = decor_state.slots.items;
    886 
    887  int const beg_pos[] = { 0, fut_beg };
    888  int const end_pos[] = { cur_end, count };
    889 
    890  for (int pos_i = 0; pos_i < 2; pos_i++) {
    891    for (int i = beg_pos[pos_i]; i < end_pos[pos_i]; i++) {
    892      DecorRange *item = &slots[indices[i]].range;
    893      if (item->start_row != decor_state.row
    894          || item->kind != kDecorKindVirtText
    895          || item->data.vt->pos != kVPosInline
    896          || item->data.vt->width == 0) {
    897        continue;
    898      }
    899      if (item->draw_col >= -1 && item->start_col >= v) {
    900        return true;
    901      }
    902    }
    903  }
    904  return false;
    905 }
    906 
    907 static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v, bool selected)
    908 {
    909  while (wlv->n_extra == 0) {
    910    if (wlv->virt_inline_i >= kv_size(wlv->virt_inline)) {
    911      // need to find inline virtual text
    912      wlv->virt_inline = VIRTTEXT_EMPTY;
    913      wlv->virt_inline_i = 0;
    914      DecorState *state = &decor_state;
    915      int const end = state->current_end;
    916      int *const indices = state->ranges_i.items;
    917      DecorRangeSlot *const slots = state->slots.items;
    918 
    919      for (int i = 0; i < end; i++) {
    920        DecorRange *item = &slots[indices[i]].range;
    921        if (item->draw_col == -3) {
    922          // No more inline virtual text before this non-inline virtual text item,
    923          // so its position can be decided now.
    924          decor_init_draw_col(wlv->off, selected, item);
    925        }
    926        if (item->start_row != state->row
    927            || item->kind != kDecorKindVirtText
    928            || item->data.vt->pos != kVPosInline
    929            || item->data.vt->width == 0) {
    930          continue;
    931        }
    932        if (item->draw_col >= -1 && item->start_col == v) {
    933          wlv->virt_inline = item->data.vt->data.virt_text;
    934          wlv->virt_inline_hl_mode = item->data.vt->hl_mode;
    935          item->draw_col = INT_MIN;
    936          break;
    937        }
    938      }
    939      if (!kv_size(wlv->virt_inline)) {
    940        // no more inline virtual text here
    941        break;
    942      }
    943    } else {
    944      // already inside existing inline virtual text with multiple chunks
    945      int attr = 0;
    946      char *text = next_virt_text_chunk(wlv->virt_inline, &wlv->virt_inline_i, &attr);
    947      if (text == NULL) {
    948        continue;
    949      }
    950      wlv->p_extra = text;
    951      wlv->n_extra = (int)strlen(text);
    952      if (wlv->n_extra == 0) {
    953        continue;
    954      }
    955      wlv->sc_extra = NUL;
    956      wlv->sc_final = NUL;
    957      wlv->extra_attr = attr;
    958      wlv->n_attr = mb_charlen(text);
    959      // If the text didn't reach until the first window
    960      // column we need to skip cells.
    961      if (wlv->skip_cells > 0) {
    962        int virt_text_width = (int)mb_string2cells(wlv->p_extra);
    963        if (virt_text_width > wlv->skip_cells) {
    964          int skip_cells_remaining = wlv->skip_cells;
    965          // Skip cells in the text.
    966          while (skip_cells_remaining > 0) {
    967            int cells = utf_ptr2cells(wlv->p_extra);
    968            if (cells > skip_cells_remaining) {
    969              break;
    970            }
    971            int c_len = utfc_ptr2len(wlv->p_extra);
    972            skip_cells_remaining -= cells;
    973            wlv->p_extra += c_len;
    974            wlv->n_extra -= c_len;
    975            wlv->n_attr--;
    976          }
    977          // Skipped cells needed to be accounted for in vcol.
    978          wlv->skipped_cells += wlv->skip_cells - skip_cells_remaining;
    979          wlv->skip_cells = skip_cells_remaining;
    980        } else {
    981          // The whole text is left of the window, drop
    982          // it and advance to the next one.
    983          wlv->skip_cells -= virt_text_width;
    984          // Skipped cells needed to be accounted for in vcol.
    985          wlv->skipped_cells += virt_text_width;
    986          wlv->n_attr = 0;
    987          wlv->n_extra = 0;
    988          // Go to the start so the next virtual text chunk can be selected.
    989          continue;
    990        }
    991      }
    992      assert(wlv->n_extra > 0);
    993      wlv->extra_for_extmark = true;
    994    }
    995  }
    996 }
    997 
    998 /// Start a screen line at column zero.
    999 static void win_line_start(win_T *wp, winlinevars_T *wlv)
   1000 {
   1001  wlv->col = 0;
   1002  wlv->off = 0;
   1003  wlv->need_lbr = false;
   1004  for (int i = 0; i < wp->w_view_width; i++) {
   1005    linebuf_char[i] = schar_from_ascii(' ');
   1006    linebuf_attr[i] = 0;
   1007    linebuf_vcol[i] = -1;
   1008  }
   1009 }
   1010 
   1011 static void fix_for_boguscols(winlinevars_T *wlv)
   1012 {
   1013  wlv->n_extra += wlv->vcol_off_co;
   1014  wlv->vcol -= wlv->vcol_off_co;
   1015  wlv->vcol_off_co = 0;
   1016  wlv->col -= wlv->boguscols;
   1017  wlv->old_boguscols = wlv->boguscols;
   1018  wlv->boguscols = 0;
   1019 }
   1020 
   1021 static int get_rightmost_vcol(win_T *wp, const int *color_cols)
   1022 {
   1023  int ret = 0;
   1024 
   1025  if (wp->w_p_cuc) {
   1026    ret = wp->w_virtcol;
   1027  }
   1028 
   1029  if (color_cols) {
   1030    // determine rightmost colorcolumn to possibly draw
   1031    for (int i = 0; color_cols[i] >= 0; i++) {
   1032      ret = MAX(ret, color_cols[i]);
   1033    }
   1034  }
   1035 
   1036  return ret;
   1037 }
   1038 
   1039 /// Display line "lnum" of window "wp" on the screen.
   1040 /// wp->w_virtcol needs to be valid.
   1041 ///
   1042 /// @param lnum         line to display
   1043 /// @param startrow     first row relative to window grid
   1044 /// @param endrow       last grid row to be redrawn
   1045 /// @param col_rows     set to the height of the line when only updating the columns,
   1046 ///                     otherwise set to 0
   1047 /// @param concealed    only draw virtual lines belonging to the line above
   1048 /// @param spv          'spell' related variables kept between calls for "wp"
   1049 /// @param foldinfo     fold info for this line
   1050 /// @param[in, out] providers  decoration providers active this line
   1051 ///                            items will be disables if they cause errors
   1052 ///                            or explicitly return `false`.
   1053 ///
   1054 /// @return             the number of last row the line occupies.
   1055 int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, bool concealed,
   1056             spellvars_T *spv, foldinfo_T foldinfo)
   1057 {
   1058  colnr_T vcol_prev = -1;             // "wlv.vcol" of previous character
   1059  GridView *grid = &wp->w_grid;       // grid specific to the window
   1060  const int view_width = wp->w_view_width;
   1061  const int view_height = wp->w_view_height;
   1062 
   1063  const bool in_curline = wp == curwin && lnum == curwin->w_cursor.lnum;
   1064  const bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
   1065  const bool has_foldtext = has_fold && *wp->w_p_fdt != NUL;
   1066 
   1067  const bool is_wrapped = wp->w_p_wrap
   1068                          && !has_fold;       // Never wrap folded lines
   1069 
   1070  int saved_attr2 = 0;                  // char_attr saved for n_attr
   1071  int n_attr3 = 0;                      // chars with overruling special attr
   1072  int saved_attr3 = 0;                  // char_attr saved for n_attr3
   1073 
   1074  int fromcol_prev = -2;                // start of inverting after cursor
   1075  bool noinvcur = false;                // don't invert the cursor
   1076  bool lnum_in_visual_area = false;
   1077 
   1078  int char_attr_pri = 0;                // attributes with high priority
   1079  int char_attr_base = 0;               // attributes with low priority
   1080  bool area_highlighting = false;       // Visual or incsearch highlighting in this line
   1081  int vi_attr = 0;                      // attributes for Visual and incsearch highlighting
   1082  int area_attr = 0;                    // attributes desired by highlighting
   1083  int search_attr = 0;                  // attributes desired by 'hlsearch' or ComplMatchIns
   1084  int vcol_save_attr = 0;               // saved attr for 'cursorcolumn'
   1085  int decor_attr = 0;                   // attributes desired by syntax and extmarks
   1086  bool has_syntax = false;              // this buffer has syntax highl.
   1087  int folded_attr = 0;                  // attributes for folded line
   1088  int eol_hl_off = 0;                   // 1 if highlighted char after EOL
   1089 #define SPWORDLEN 150
   1090  char nextline[SPWORDLEN * 2];         // text with start of the next line
   1091  int nextlinecol = 0;                  // column where nextline[] starts
   1092  int nextline_idx = 0;                 // index in nextline[] where next line
   1093                                        // starts
   1094  int spell_attr = 0;                   // attributes desired by spelling
   1095  int word_end = 0;                     // last byte with same spell_attr
   1096  int cur_checked_col = 0;              // checked column for current line
   1097  bool extra_check = false;             // has extra highlighting
   1098  int multi_attr = 0;                   // attributes desired by multibyte
   1099  int mb_l = 1;                         // multi-byte byte length
   1100  int mb_c = 0;                         // decoded multi-byte character
   1101  schar_T mb_schar = 0;                 // complete screen char
   1102  int change_start = MAXCOL;            // first col of changed area
   1103  int change_end = -1;                  // last col of changed area
   1104  bool in_multispace = false;           // in multiple consecutive spaces
   1105  int multispace_pos = 0;               // position in lcs-multispace string
   1106 
   1107  int n_extra_next = 0;                 // n_extra to use after current extra chars
   1108  int extra_attr_next = -1;             // extra_attr to use after current extra chars
   1109 
   1110  bool search_attr_from_match = false;  // if search_attr is from :match
   1111  bool has_decor = false;               // this buffer has decoration
   1112 
   1113  int saved_search_attr = 0;            // search_attr to be used when n_extra goes to zero
   1114  int saved_area_attr = 0;              // idem for area_attr
   1115  int saved_decor_attr = 0;             // idem for decor_attr
   1116  bool saved_search_attr_from_match = false;
   1117 
   1118  int win_col_offset = 0;               // offset for window columns
   1119  bool area_active = false;             // whether in Visual selection, for virtual text
   1120  bool decor_need_recheck = false;      // call decor_recheck_draw_col() at next char
   1121 
   1122  char buf_fold[FOLD_TEXT_LEN];         // Hold value returned by get_foldtext
   1123  VirtText fold_vt = VIRTTEXT_EMPTY;
   1124  char *foldtext_free = NULL;
   1125 
   1126  // 'cursorlineopt' has "screenline" and cursor is in this line
   1127  bool cul_screenline = false;
   1128  // margin columns for the screen line, needed for when 'cursorlineopt'
   1129  // contains "screenline"
   1130  int left_curline_col = 0;
   1131  int right_curline_col = 0;
   1132 
   1133  int match_conc = 0;              ///< cchar for match functions
   1134  bool on_last_col = false;
   1135  int syntax_flags = 0;
   1136  int syntax_seqnr = 0;
   1137  int prev_syntax_id = 0;
   1138  int conceal_attr = win_hl_attr(wp, HLF_CONCEAL);
   1139  bool is_concealing = false;
   1140  bool did_wcol = false;
   1141 #define vcol_hlc(wlv) ((wlv).vcol - (wlv).vcol_off_co)
   1142 
   1143  assert(startrow < endrow);
   1144 
   1145  // variables passed between functions
   1146  winlinevars_T wlv = {
   1147    .lnum = lnum,
   1148    .foldinfo = foldinfo,
   1149    .startrow = startrow,
   1150    .row = startrow,
   1151    .fromcol = -10,
   1152    .tocol = MAXCOL,
   1153    .vcol_sbr = -1,
   1154    .old_boguscols = 0,
   1155    .prev_num_attr = -1,
   1156  };
   1157 
   1158  buf_T *buf = wp->w_buffer;
   1159  // Not drawing text when line is concealed or drawing filler lines beyond last line.
   1160  const bool draw_text = !concealed && (lnum != buf->b_ml.ml_line_count + 1);
   1161 
   1162  int decor_provider_end_col;
   1163  bool check_decor_providers = false;
   1164 
   1165  if (col_rows == 0 && draw_text) {
   1166    // To speed up the loop below, set extra_check when there is linebreak,
   1167    // trailing white space and/or syntax processing to be done.
   1168    extra_check = wp->w_p_lbr;
   1169    if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow && !has_foldtext) {
   1170      // Prepare for syntax highlighting in this line.  When there is an
   1171      // error, stop syntax highlighting.
   1172      int save_did_emsg = did_emsg;
   1173      did_emsg = false;
   1174      syntax_start(wp, lnum);
   1175      if (did_emsg) {
   1176        wp->w_s->b_syn_error = true;
   1177      } else {
   1178        did_emsg = save_did_emsg;
   1179        if (!wp->w_s->b_syn_slow) {
   1180          has_syntax = true;
   1181          extra_check = true;
   1182        }
   1183      }
   1184    }
   1185 
   1186    check_decor_providers = true;
   1187 
   1188    // Check for columns to display for 'colorcolumn'.
   1189    wlv.color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
   1190    advance_color_col(&wlv, vcol_hlc(wlv));
   1191 
   1192    // handle Visual active in this window
   1193    if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
   1194      pos_T *top, *bot;
   1195 
   1196      if (ltoreq(curwin->w_cursor, VIsual)) {
   1197        // Visual is after curwin->w_cursor
   1198        top = &curwin->w_cursor;
   1199        bot = &VIsual;
   1200      } else {
   1201        // Visual is before curwin->w_cursor
   1202        top = &VIsual;
   1203        bot = &curwin->w_cursor;
   1204      }
   1205      lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum);
   1206      if (VIsual_mode == Ctrl_V) {
   1207        // block mode
   1208        if (lnum_in_visual_area) {
   1209          wlv.fromcol = wp->w_old_cursor_fcol;
   1210          wlv.tocol = wp->w_old_cursor_lcol;
   1211        }
   1212      } else {
   1213        // non-block mode
   1214        if (lnum > top->lnum && lnum <= bot->lnum) {
   1215          wlv.fromcol = 0;
   1216        } else if (lnum == top->lnum) {
   1217          if (VIsual_mode == 'V') {       // linewise
   1218            wlv.fromcol = 0;
   1219          } else {
   1220            getvvcol(wp, top, (colnr_T *)&wlv.fromcol, NULL, NULL);
   1221            if (gchar_pos(top) == NUL) {
   1222              wlv.tocol = wlv.fromcol + 1;
   1223            }
   1224          }
   1225        }
   1226        if (VIsual_mode != 'V' && lnum == bot->lnum) {
   1227          if (*p_sel == 'e' && bot->col == 0
   1228              && bot->coladd == 0) {
   1229            wlv.fromcol = -10;
   1230            wlv.tocol = MAXCOL;
   1231          } else if (bot->col == MAXCOL) {
   1232            wlv.tocol = MAXCOL;
   1233          } else {
   1234            pos_T pos = *bot;
   1235            if (*p_sel == 'e') {
   1236              getvvcol(wp, &pos, (colnr_T *)&wlv.tocol, NULL, NULL);
   1237            } else {
   1238              getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&wlv.tocol);
   1239              wlv.tocol++;
   1240            }
   1241          }
   1242        }
   1243      }
   1244 
   1245      // Check if the char under the cursor should be inverted (highlighted).
   1246      if (!highlight_match && in_curline
   1247          && cursor_is_block_during_visual(*p_sel == 'e')) {
   1248        noinvcur = true;
   1249      }
   1250 
   1251      // if inverting in this line set area_highlighting
   1252      if (wlv.fromcol >= 0) {
   1253        area_highlighting = true;
   1254        vi_attr = win_hl_attr(wp, HLF_V);
   1255      }
   1256      // handle 'incsearch' and ":s///c" highlighting
   1257    } else if (highlight_match
   1258               && wp == curwin
   1259               && !has_foldtext
   1260               && lnum >= curwin->w_cursor.lnum
   1261               && lnum <= curwin->w_cursor.lnum + search_match_lines) {
   1262      if (lnum == curwin->w_cursor.lnum) {
   1263        getvcol(curwin, &(curwin->w_cursor),
   1264                (colnr_T *)&wlv.fromcol, NULL, NULL);
   1265      } else {
   1266        wlv.fromcol = 0;
   1267      }
   1268      if (lnum == curwin->w_cursor.lnum + search_match_lines) {
   1269        pos_T pos = {
   1270          .lnum = lnum,
   1271          .col = search_match_endcol,
   1272        };
   1273        getvcol(curwin, &pos, (colnr_T *)&wlv.tocol, NULL, NULL);
   1274      }
   1275      // do at least one character; happens when past end of line
   1276      if (wlv.fromcol == wlv.tocol && search_match_endcol) {
   1277        wlv.tocol = wlv.fromcol + 1;
   1278      }
   1279      area_highlighting = true;
   1280      vi_attr = win_hl_attr(wp, HLF_I);
   1281    }
   1282  }
   1283 
   1284  int bg_attr = win_bg_attr(wp);
   1285 
   1286  int linestatus = 0;
   1287  wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
   1288  diffline_T line_changes = { 0 };
   1289  int change_index = -1;
   1290  if (linestatus < 0) {
   1291    if (linestatus == -1) {
   1292      if (diff_find_change(wp, lnum, &line_changes)) {
   1293        wlv.diff_hlf = HLF_ADD;      // added line
   1294      } else if (line_changes.num_changes > 0) {
   1295        bool added = diff_change_parse(&line_changes, &line_changes.changes[0],
   1296                                       &change_start, &change_end);
   1297        if (change_start == 0) {
   1298          if (added) {
   1299            wlv.diff_hlf = HLF_TXA;  // added text on changed line
   1300          } else {
   1301            wlv.diff_hlf = HLF_TXD;  // changed text on changed line
   1302          }
   1303        } else {
   1304          wlv.diff_hlf = HLF_CHD;    // unchanged text on changed line
   1305        }
   1306        change_index = 0;
   1307      } else {
   1308        wlv.diff_hlf = HLF_CHD;      // changed line
   1309        change_index = 0;
   1310      }
   1311    } else {
   1312      wlv.diff_hlf = HLF_ADD;               // added line
   1313    }
   1314    area_highlighting = true;
   1315  }
   1316  VirtLines virt_lines = KV_INITIAL_VALUE;
   1317  wlv.n_virt_lines = decor_virt_lines(wp, lnum - 1, lnum, &wlv.n_virt_below, &virt_lines, true);
   1318  wlv.filler_lines += wlv.n_virt_lines;
   1319  if (lnum == wp->w_topline) {
   1320    wlv.filler_lines = wp->w_topfill;
   1321    wlv.n_virt_lines = MIN(wlv.n_virt_lines, wlv.filler_lines);
   1322  }
   1323  wlv.filler_todo = wlv.filler_lines;
   1324 
   1325  // Cursor line highlighting for 'cursorline' in the current window.
   1326  if (wp->w_p_cul && wp->w_p_culopt_flags != kOptCuloptFlagNumber && lnum == wp->w_cursorline
   1327      // Do not show the cursor line in the text when Visual mode is active,
   1328      // because it's not clear what is selected then.
   1329      && !(wp == curwin && VIsual_active)) {
   1330    cul_screenline = (is_wrapped && (wp->w_p_culopt_flags & kOptCuloptFlagScreenline));
   1331    if (!cul_screenline) {
   1332      apply_cursorline_highlight(wp, &wlv);
   1333    } else {
   1334      margin_columns_win(wp, &left_curline_col, &right_curline_col);
   1335    }
   1336    area_highlighting = true;
   1337  }
   1338 
   1339  int sign_line_attr = 0;
   1340  // TODO(bfredl, vigoux): line_attr should not take priority over decoration!
   1341  decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs,
   1342                     &sign_line_attr, &wlv.sign_cul_attr, &wlv.sign_num_attr);
   1343 
   1344  statuscol_T statuscol = { 0 };
   1345  if (*wp->w_p_stc != NUL) {
   1346    // Draw the 'statuscolumn' if option is set.
   1347    statuscol.draw = true;
   1348    statuscol.sattrs = wlv.sattrs;
   1349    statuscol.foldinfo = foldinfo;
   1350    statuscol.width = win_col_off(wp) - (wp == cmdwin_win);
   1351    statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? wlv.sign_cul_attr : 0;
   1352  } else if (wlv.sign_cul_attr > 0) {
   1353    wlv.sign_cul_attr = use_cursor_line_highlight(wp, lnum) ? syn_id2attr(wlv.sign_cul_attr) : 0;
   1354  }
   1355  if (wlv.sign_num_attr > 0) {
   1356    wlv.sign_num_attr = syn_id2attr(wlv.sign_num_attr);
   1357  }
   1358  if (sign_line_attr > 0) {
   1359    wlv.line_attr = syn_id2attr(sign_line_attr);
   1360  }
   1361 
   1362  // Highlight the current line in the quickfix window.
   1363  if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) {
   1364    wlv.line_attr = win_hl_attr(wp, HLF_QFL);
   1365  }
   1366 
   1367  if (wlv.line_attr_lowprio || wlv.line_attr) {
   1368    area_highlighting = true;
   1369  }
   1370 
   1371  int line_attr_save = wlv.line_attr;
   1372  int line_attr_lowprio_save = wlv.line_attr_lowprio;
   1373 
   1374  if (spv->spv_has_spell && col_rows == 0 && draw_text) {
   1375    // Prepare for spell checking.
   1376    extra_check = true;
   1377 
   1378    // When a word wrapped from the previous line the start of the
   1379    // current line is valid.
   1380    if (lnum == spv->spv_checked_lnum) {
   1381      cur_checked_col = spv->spv_checked_col;
   1382    }
   1383    // Previous line was not spell checked, check for capital. This happens
   1384    // for the first line in an updated region or after a closed fold.
   1385    if (spv->spv_capcol_lnum == 0 && check_need_cap(wp, lnum, 0)) {
   1386      spv->spv_cap_col = 0;
   1387    } else if (lnum != spv->spv_capcol_lnum) {
   1388      spv->spv_cap_col = -1;
   1389    }
   1390    spv->spv_checked_lnum = 0;
   1391 
   1392    // Get the start of the next line, so that words that wrap to the
   1393    // next line are found too: "et<line-break>al.".
   1394    // Trick: skip a few chars for C/shell/Vim comments
   1395    nextline[SPWORDLEN] = NUL;
   1396    if (lnum < wp->w_buffer->b_ml.ml_line_count) {
   1397      char *line = ml_get_buf(wp->w_buffer, lnum + 1);
   1398      spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
   1399    }
   1400    char *line = ml_get_buf(wp->w_buffer, lnum);
   1401 
   1402    // If current line is empty, check first word in next line for capital.
   1403    char *ptr = skipwhite(line);
   1404    if (*ptr == NUL) {
   1405      spv->spv_cap_col = 0;
   1406      spv->spv_capcol_lnum = lnum + 1;
   1407    } else if (spv->spv_cap_col == 0) {
   1408      // For checking first word with a capital skip white space.
   1409      spv->spv_cap_col = (int)(ptr - line);
   1410    }
   1411 
   1412    // Copy the end of the current line into nextline[].
   1413    if (nextline[SPWORDLEN] == NUL) {
   1414      // No next line or it is empty.
   1415      nextlinecol = MAXCOL;
   1416      nextline_idx = 0;
   1417    } else {
   1418      const colnr_T line_len = ml_get_buf_len(wp->w_buffer, lnum);
   1419      if (line_len < SPWORDLEN) {
   1420        // Short line, use it completely and append the start of the
   1421        // next line.
   1422        nextlinecol = 0;
   1423        memmove(nextline, line, (size_t)line_len);
   1424        STRMOVE(nextline + line_len, nextline + SPWORDLEN);
   1425        nextline_idx = line_len + 1;
   1426      } else {
   1427        // Long line, use only the last SPWORDLEN bytes.
   1428        nextlinecol = line_len - SPWORDLEN;
   1429        memmove(nextline, line + nextlinecol, SPWORDLEN);
   1430        nextline_idx = SPWORDLEN + 1;
   1431      }
   1432    }
   1433  }
   1434 
   1435  // current line
   1436  char *line = draw_text ? ml_get_buf(wp->w_buffer, lnum) : "";
   1437  // current position in "line"
   1438  char *ptr = line;
   1439 
   1440  colnr_T trailcol = MAXCOL;  // start of trailing spaces
   1441  colnr_T leadcol = 0;        // start of leading spaces
   1442 
   1443  bool lcs_eol_todo = true;  // need to keep track of this even if lcs_eol is NUL
   1444  const schar_T lcs_eol = wp->w_p_lcs_chars.eol;  // 'eol' value
   1445  schar_T lcs_prec_todo = wp->w_p_lcs_chars.prec;  // 'prec' until it's been used, then NUL
   1446 
   1447  if (wp->w_p_list && !has_foldtext && draw_text) {
   1448    if (wp->w_p_lcs_chars.space
   1449        || wp->w_p_lcs_chars.multispace != NULL
   1450        || wp->w_p_lcs_chars.leadmultispace != NULL
   1451        || wp->w_p_lcs_chars.trail
   1452        || wp->w_p_lcs_chars.lead
   1453        || wp->w_p_lcs_chars.nbsp) {
   1454      extra_check = true;
   1455    }
   1456    // find start of trailing whitespace
   1457    if (wp->w_p_lcs_chars.trail) {
   1458      trailcol = ml_get_buf_len(wp->w_buffer, lnum);
   1459      while (trailcol > 0 && ascii_iswhite(ptr[trailcol - 1])) {
   1460        trailcol--;
   1461      }
   1462      trailcol += (colnr_T)(ptr - line);
   1463    }
   1464    // find end of leading whitespace
   1465    if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL
   1466        || wp->w_p_lcs_chars.leadtab1 != NUL) {
   1467      leadcol = 0;
   1468      while (ascii_iswhite(ptr[leadcol])) {
   1469        leadcol++;
   1470      }
   1471      if (ptr[leadcol] == NUL) {
   1472        // in a line full of spaces all of them are treated as trailing
   1473        leadcol = 0;
   1474      } else {
   1475        // keep track of the first column not filled with spaces
   1476        leadcol += (colnr_T)(ptr - line + 1);
   1477      }
   1478    }
   1479  }
   1480 
   1481  // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
   1482  // first character to be displayed.
   1483  const int start_vcol = wp->w_p_wrap
   1484                         ? (startrow == 0 ? wp->w_skipcol : 0)
   1485                         : wp->w_leftcol;
   1486 
   1487  if (has_foldtext) {
   1488    wlv.vcol = start_vcol;
   1489  } else if (start_vcol > 0 && col_rows == 0) {
   1490    char *prev_ptr = ptr;
   1491    CharSize cs = { 0 };
   1492 
   1493    CharsizeArg csarg;
   1494    CSType cstype = init_charsize_arg(&csarg, wp, lnum, line);
   1495    csarg.max_head_vcol = start_vcol;
   1496    int vcol = wlv.vcol;
   1497    StrCharInfo ci = utf_ptr2StrCharInfo(ptr);
   1498    while (vcol < start_vcol) {
   1499      cs = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg);
   1500      vcol += cs.width;
   1501      prev_ptr = ci.ptr;
   1502      if (*prev_ptr == NUL) {
   1503        break;
   1504      }
   1505      ci = utfc_next(ci);
   1506      if (wp->w_p_list) {
   1507        in_multispace = *prev_ptr == ' ' && (*ci.ptr == ' '
   1508                                             || (prev_ptr > line && prev_ptr[-1] == ' '));
   1509        if (!in_multispace) {
   1510          multispace_pos = 0;
   1511        } else if (ci.ptr >= line + leadcol
   1512                   && wp->w_p_lcs_chars.multispace != NULL) {
   1513          multispace_pos++;
   1514          if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
   1515            multispace_pos = 0;
   1516          }
   1517        } else if (ci.ptr < line + leadcol
   1518                   && wp->w_p_lcs_chars.leadmultispace != NULL) {
   1519          multispace_pos++;
   1520          if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
   1521            multispace_pos = 0;
   1522          }
   1523        }
   1524      }
   1525    }
   1526    wlv.vcol = vcol;
   1527    ptr = ci.ptr;
   1528    int charsize = cs.width;
   1529    int head = cs.head;
   1530 
   1531    // When:
   1532    // - 'cuc' is set, or
   1533    // - 'colorcolumn' is set, or
   1534    // - 'virtualedit' is set, or
   1535    // - the visual mode is active, or
   1536    // - drawing a fold
   1537    // the end of the line may be before the start of the displayed part.
   1538    if (wlv.vcol < start_vcol && (wp->w_p_cuc
   1539                                  || wlv.color_cols
   1540                                  || virtual_active(wp)
   1541                                  || (VIsual_active && wp->w_buffer == curwin->w_buffer)
   1542                                  || has_fold)) {
   1543      wlv.vcol = start_vcol;
   1544    }
   1545 
   1546    // Handle a character that's not completely on the screen: Put ptr at
   1547    // that character but skip the first few screen characters.
   1548    if (wlv.vcol > start_vcol) {
   1549      wlv.vcol -= charsize;
   1550      ptr = prev_ptr;
   1551    }
   1552 
   1553    if (start_vcol > wlv.vcol) {
   1554      wlv.skip_cells = start_vcol - wlv.vcol - head;
   1555    }
   1556 
   1557    // Adjust for when the inverted text is before the screen,
   1558    // and when the start of the inverted text is before the screen.
   1559    if (wlv.tocol <= wlv.vcol) {
   1560      wlv.fromcol = 0;
   1561    } else if (wlv.fromcol >= 0 && wlv.fromcol < wlv.vcol) {
   1562      wlv.fromcol = wlv.vcol;
   1563    }
   1564 
   1565    // When w_skipcol is non-zero, first line needs 'showbreak'
   1566    if (wp->w_p_wrap) {
   1567      wlv.need_showbreak = true;
   1568    }
   1569    // When spell checking a word we need to figure out the start of the
   1570    // word and if it's badly spelled or not.
   1571    if (spv->spv_has_spell) {
   1572      colnr_T linecol = (colnr_T)(ptr - line);
   1573      hlf_T spell_hlf = HLF_COUNT;
   1574 
   1575      pos_T pos = wp->w_cursor;
   1576      wp->w_cursor.lnum = lnum;
   1577      wp->w_cursor.col = linecol;
   1578      size_t len = spell_move_to(wp, FORWARD, SMT_ALL, true, &spell_hlf);
   1579 
   1580      // spell_move_to() may call ml_get() and make "line" invalid
   1581      line = ml_get_buf(wp->w_buffer, lnum);
   1582      ptr = line + linecol;
   1583 
   1584      if (len == 0 || wp->w_cursor.col > linecol) {
   1585        // no bad word found at line start, don't check until end of a
   1586        // word
   1587        spell_hlf = HLF_COUNT;
   1588        word_end = (int)(spell_to_word_end(ptr, wp) - line + 1);
   1589      } else {
   1590        // bad word found, use attributes until end of word
   1591        assert(len <= INT_MAX);
   1592        word_end = wp->w_cursor.col + (int)len + 1;
   1593 
   1594        // Turn index into actual attributes.
   1595        if (spell_hlf != HLF_COUNT) {
   1596          spell_attr = highlight_attr[spell_hlf];
   1597        }
   1598      }
   1599      wp->w_cursor = pos;
   1600 
   1601      // Need to restart syntax highlighting for this line.
   1602      if (has_syntax) {
   1603        syntax_start(wp, lnum);
   1604      }
   1605    }
   1606  }
   1607 
   1608  if (check_decor_providers) {
   1609    int const col = (int)(ptr - line);
   1610    decor_provider_end_col = decor_providers_setup(endrow - startrow,
   1611                                                   start_vcol == 0,
   1612                                                   lnum,
   1613                                                   col,
   1614                                                   wp);
   1615    line = ml_get_buf(wp->w_buffer, lnum);
   1616    ptr = line + col;
   1617  }
   1618 
   1619  decor_redraw_line(wp, lnum - 1, &decor_state);
   1620  if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) {
   1621    has_decor = true;
   1622    extra_check = true;
   1623  }
   1624 
   1625  // Correct highlighting for cursor that can't be disabled.
   1626  // Avoids having to check this for each character.
   1627  if (wlv.fromcol >= 0) {
   1628    if (noinvcur) {
   1629      if ((colnr_T)wlv.fromcol == wp->w_virtcol) {
   1630        // highlighting starts at cursor, let it start just after the
   1631        // cursor
   1632        fromcol_prev = wlv.fromcol;
   1633        wlv.fromcol = -1;
   1634      } else if ((colnr_T)wlv.fromcol < wp->w_virtcol) {
   1635        // restart highlighting after the cursor
   1636        fromcol_prev = wp->w_virtcol;
   1637      }
   1638    }
   1639    if (wlv.fromcol >= wlv.tocol) {
   1640      wlv.fromcol = -1;
   1641    }
   1642  }
   1643 
   1644  if (col_rows == 0 && draw_text && !has_foldtext) {
   1645    const int v = (int)(ptr - line);
   1646    area_highlighting |= prepare_search_hl_line(wp, lnum, v,
   1647                                                &line, &screen_search_hl, &search_attr,
   1648                                                &search_attr_from_match);
   1649    ptr = line + v;  // "line" may have been updated
   1650  }
   1651 
   1652  if ((State & MODE_INSERT) && ins_compl_win_active(wp)
   1653      && (in_curline || ins_compl_lnum_in_range(lnum))) {
   1654    area_highlighting = true;
   1655  }
   1656 
   1657  win_line_start(wp, &wlv);
   1658  bool draw_cols = true;
   1659  int leftcols_width = 0;
   1660 
   1661  // won't highlight after TERM_ATTRS_MAX columns
   1662  int term_attrs[TERM_ATTRS_MAX] = { 0 };
   1663  if (wp->w_buffer->terminal) {
   1664    terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs);
   1665    extra_check = true;
   1666  }
   1667 
   1668  const bool may_have_inline_virt
   1669    = !has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0;
   1670  int virt_line_index = -1;
   1671  int virt_line_flags = 0;
   1672 
   1673  // Repeat for each cell in the displayed line.
   1674  while (true) {
   1675    int has_match_conc = 0;  ///< match wants to conceal
   1676    int decor_conceal = 0;
   1677 
   1678    bool did_decrement_ptr = false;
   1679 
   1680    // Get next chunk of extmark highlights if previous approximation was smaller than needed.
   1681    if (check_decor_providers && (int)(ptr - line) >= decor_provider_end_col) {
   1682      int const col = (int)(ptr - line);
   1683      decor_provider_end_col = invoke_range_next(wp, lnum, col, 100);
   1684      line = ml_get_buf(wp->w_buffer, lnum);
   1685      ptr = line + col;
   1686      if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) {
   1687        has_decor = true;
   1688        extra_check = true;
   1689      }
   1690    }
   1691 
   1692    // Skip this quickly when working on the text.
   1693    if (draw_cols) {
   1694      if (cul_screenline) {
   1695        wlv.cul_attr = 0;
   1696        wlv.line_attr = line_attr_save;
   1697        wlv.line_attr_lowprio = line_attr_lowprio_save;
   1698      }
   1699 
   1700      assert(wlv.off == 0);
   1701 
   1702      if (wp == cmdwin_win) {
   1703        // Draw the cmdline character.
   1704        draw_col_fill(&wlv, schar_from_ascii(cmdwin_type), 1, win_hl_attr(wp, HLF_AT));
   1705      }
   1706 
   1707      if (wlv.filler_todo > 0) {
   1708        int index = wlv.filler_todo - (wlv.filler_lines - wlv.n_virt_lines);
   1709        if (index > 0) {
   1710          virt_line_index = (int)kv_size(virt_lines) - index;
   1711          assert(virt_line_index >= 0);
   1712          virt_line_flags = kv_A(virt_lines, virt_line_index).flags;
   1713        }
   1714      }
   1715 
   1716      if (virt_line_index >= 0 && (virt_line_flags & kVLLeftcol)) {
   1717        // skip columns
   1718      } else if (statuscol.draw) {
   1719        // Draw 'statuscolumn' if it is set.
   1720        const int v = (int)(ptr - line);
   1721        draw_statuscol(wp, &wlv, wlv.row - startrow - wlv.filler_lines, col_rows, &statuscol);
   1722        if (wp->w_redr_statuscol) {
   1723          break;
   1724        }
   1725        if (draw_text) {
   1726          // Get the line again as evaluating 'statuscolumn' may free it.
   1727          line = ml_get_buf(wp->w_buffer, lnum);
   1728          ptr = line + v;
   1729        }
   1730      } else {
   1731        // draw builtin info columns: fold, sign, number
   1732        draw_foldcolumn(wp, &wlv);
   1733 
   1734        // wp->w_scwidth is zero if signcol=number is used
   1735        for (int sign_idx = 0; sign_idx < wp->w_scwidth; sign_idx++) {
   1736          draw_sign(false, wp, &wlv, sign_idx);
   1737        }
   1738 
   1739        draw_lnum_col(wp, &wlv);
   1740      }
   1741 
   1742      win_col_offset = wlv.off;
   1743 
   1744      // When only updating the columns and that's done, stop here.
   1745      if (col_rows > 0) {
   1746        wlv_put_linebuf(wp, &wlv, MIN(wlv.off, view_width), false, bg_attr, 0);
   1747        // Need to update more screen lines if:
   1748        // - 'statuscolumn' needs to be drawn, or
   1749        // - LineNrAbove or LineNrBelow is used, or
   1750        // - still drawing filler lines.
   1751        if ((wlv.row + 1 - wlv.startrow < col_rows
   1752             && (statuscol.draw
   1753                 || win_hl_attr(wp, HLF_LNA) != win_hl_attr(wp, HLF_N)
   1754                 || win_hl_attr(wp, HLF_LNB) != win_hl_attr(wp, HLF_N)))
   1755            || wlv.filler_todo > 0) {
   1756          wlv.row++;
   1757          if (wlv.row == endrow) {
   1758            break;
   1759          }
   1760          wlv.filler_todo--;
   1761          virt_line_index = -1;
   1762          if (wlv.filler_todo == 0 && (wp->w_botfill || !draw_text)) {
   1763            break;
   1764          }
   1765          // win_line_start(wp, &wlv);
   1766          wlv.col = 0;
   1767          wlv.off = 0;
   1768          continue;
   1769        } else {
   1770          break;
   1771        }
   1772      }
   1773 
   1774      // Check if 'breakindent' applies and show it.
   1775      if (!wp->w_briopt_sbr) {
   1776        handle_breakindent(wp, &wlv);
   1777      }
   1778      handle_showbreak_and_filler(wp, &wlv);
   1779      if (wp->w_briopt_sbr) {
   1780        handle_breakindent(wp, &wlv);
   1781      }
   1782 
   1783      wlv.col = wlv.off;
   1784      draw_cols = false;
   1785      if (wlv.filler_todo <= 0) {
   1786        leftcols_width = wlv.off;
   1787      }
   1788      if (has_decor && wlv.row == startrow + wlv.filler_lines) {
   1789        // hide virt_text on text hidden by 'nowrap' or 'smoothscroll'
   1790        decor_redraw_col(wp, (colnr_T)(ptr - line) - 1, wlv.off, true, &decor_state);
   1791      }
   1792      if (wlv.col >= view_width) {
   1793        wlv.col = wlv.off = view_width;
   1794        goto end_check;
   1795      }
   1796    }
   1797 
   1798    if (cul_screenline && wlv.filler_todo <= 0
   1799        && wlv.vcol >= left_curline_col && wlv.vcol < right_curline_col) {
   1800      apply_cursorline_highlight(wp, &wlv);
   1801    }
   1802 
   1803    // When still displaying '$' of change command, stop at cursor.
   1804    if (dollar_vcol >= 0 && in_curline && wlv.vcol >= wp->w_virtcol) {
   1805      draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
   1806      // don't clear anything after wlv.col
   1807      wlv_put_linebuf(wp, &wlv, wlv.col, false, bg_attr, 0);
   1808      // Pretend we have finished updating the window.  Except when
   1809      // 'cursorcolumn' is set.
   1810      if (wp->w_p_cuc) {
   1811        wlv.row = wp->w_cline_row + wp->w_cline_height;
   1812      } else {
   1813        wlv.row = view_height;
   1814      }
   1815      break;
   1816    }
   1817 
   1818    const bool draw_folded = has_fold && wlv.row == startrow + wlv.filler_lines;
   1819    if (draw_folded && wlv.n_extra == 0) {
   1820      wlv.char_attr = folded_attr = win_hl_attr(wp, HLF_FL);
   1821      decor_attr = 0;
   1822    }
   1823 
   1824    int extmark_attr = 0;
   1825    if (wlv.filler_todo <= 0
   1826        && (area_highlighting || spv->spv_has_spell || extra_check)) {
   1827      if (wlv.n_extra == 0 || !wlv.extra_for_extmark) {
   1828        wlv.reset_extra_attr = false;
   1829      }
   1830 
   1831      if (has_decor && wlv.n_extra == 0) {
   1832        // Duplicate the Visual area check after this block,
   1833        // but don't check inside p_extra here.
   1834        if (wlv.vcol == wlv.fromcol
   1835            || (wlv.vcol + 1 == wlv.fromcol
   1836                && (wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1))
   1837            || (vcol_prev == fromcol_prev
   1838                && vcol_prev < wlv.vcol
   1839                && wlv.vcol < wlv.tocol)) {
   1840          area_active = true;
   1841        } else if (area_active
   1842                   && (wlv.vcol == wlv.tocol
   1843                       || (noinvcur && wlv.vcol == wp->w_virtcol))) {
   1844          area_active = false;
   1845        }
   1846 
   1847        bool selected = (area_active || (area_highlighting && noinvcur
   1848                                         && wlv.vcol == wp->w_virtcol));
   1849        // When there may be inline virtual text, position of non-inline virtual text
   1850        // can only be decided after drawing inline virtual text with lower priority.
   1851        if (decor_need_recheck) {
   1852          if (!may_have_inline_virt) {
   1853            decor_recheck_draw_col(wlv.off, selected, &decor_state);
   1854          }
   1855          decor_need_recheck = false;
   1856        }
   1857        extmark_attr = decor_redraw_col(wp, (colnr_T)(ptr - line),
   1858                                        may_have_inline_virt ? -3 : wlv.off,
   1859                                        selected, &decor_state);
   1860        if (may_have_inline_virt) {
   1861          handle_inline_virtual_text(wp, &wlv, ptr - line, selected);
   1862          if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) {
   1863            // restore search_attr and area_attr when n_extra is down to zero
   1864            // TODO(bfredl): this is ugly as fuck. look if we can do this some other way.
   1865            saved_search_attr = search_attr;
   1866            saved_area_attr = area_attr;
   1867            saved_decor_attr = decor_attr;
   1868            saved_search_attr_from_match = search_attr_from_match;
   1869            search_attr = 0;
   1870            area_attr = 0;
   1871            decor_attr = 0;
   1872            search_attr_from_match = false;
   1873          }
   1874        }
   1875      }
   1876 
   1877      int *area_attr_p = wlv.extra_for_extmark && wlv.virt_inline_hl_mode <= kHlModeReplace
   1878                         ? &saved_area_attr : &area_attr;
   1879 
   1880      // handle Visual or match highlighting in this line
   1881      if (wlv.vcol == wlv.fromcol
   1882          || (wlv.vcol + 1 == wlv.fromcol
   1883              && ((wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1)
   1884                  || (wlv.n_extra > 0 && wlv.p_extra != NULL
   1885                      && utf_ptr2cells(wlv.p_extra) > 1)))
   1886          || (vcol_prev == fromcol_prev
   1887              && vcol_prev < wlv.vcol               // not at margin
   1888              && wlv.vcol < wlv.tocol)) {
   1889        *area_attr_p = vi_attr;                     // start highlighting
   1890        area_active = true;
   1891      } else if (*area_attr_p != 0
   1892                 && (wlv.vcol == wlv.tocol
   1893                     || (noinvcur && wlv.vcol == wp->w_virtcol))) {
   1894        *area_attr_p = 0;                           // stop highlighting
   1895        area_active = false;
   1896      }
   1897 
   1898      if (!has_foldtext && wlv.n_extra == 0) {
   1899        // Check for start/end of 'hlsearch' and other matches.
   1900        // After end, check for start/end of next match.
   1901        // When another match, have to check for start again.
   1902        const int v = (int)(ptr - line);
   1903        search_attr = update_search_hl(wp, lnum, v, &line, &screen_search_hl,
   1904                                       &has_match_conc, &match_conc, lcs_eol_todo,
   1905                                       &on_last_col, &search_attr_from_match);
   1906        ptr = line + v;  // "line" may have been changed
   1907 
   1908        // Do not allow a conceal over EOL otherwise EOL will be missed
   1909        // and bad things happen.
   1910        if (*ptr == NUL) {
   1911          has_match_conc = 0;
   1912        }
   1913 
   1914        // Check if ComplMatchIns highlight is needed.
   1915        if ((State & MODE_INSERT) && ins_compl_win_active(wp)
   1916            && (in_curline || ins_compl_lnum_in_range(lnum))) {
   1917          int ins_match_attr = ins_compl_col_range_attr(lnum, (int)(ptr - line));
   1918          if (ins_match_attr > 0) {
   1919            search_attr = hl_combine_attr(search_attr, ins_match_attr);
   1920          }
   1921        }
   1922      }
   1923 
   1924      if (wlv.diff_hlf != (hlf_T)0) {
   1925        if (line_changes.num_changes > 0
   1926            && change_index >= 0
   1927            && change_index < line_changes.num_changes - 1) {
   1928          if (ptr - line
   1929              >= line_changes.changes[change_index + 1].dc_start[line_changes.bufidx]) {
   1930            change_index += 1;
   1931          }
   1932        }
   1933        bool added = false;
   1934        if (line_changes.num_changes > 0 && change_index >= 0
   1935            && change_index < line_changes.num_changes) {
   1936          added = diff_change_parse(&line_changes, &line_changes.changes[change_index],
   1937                                    &change_start, &change_end);
   1938        }
   1939        // When there is extra text (eg: virtual text) it gets the
   1940        // diff highlighting for the line, but not for changed text.
   1941        if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start
   1942            && wlv.n_extra == 0) {
   1943          wlv.diff_hlf = added ? HLF_TXA : HLF_TXD;   // added/changed text
   1944        }
   1945        if ((wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA)
   1946            && ((ptr - line >= change_end && wlv.n_extra == 0)
   1947                || (wlv.n_extra > 0 && wlv.extra_for_extmark))) {
   1948          wlv.diff_hlf = HLF_CHD;                     // changed line
   1949        }
   1950        set_line_attr_for_diff(wp, &wlv);
   1951      }
   1952 
   1953      // Decide which of the highlight attributes to use.
   1954      if (area_attr != 0) {
   1955        char_attr_pri = hl_combine_attr(wlv.line_attr, area_attr);
   1956        if (!highlight_match) {
   1957          // let search highlight show in Visual area if possible
   1958          char_attr_pri = hl_combine_attr(search_attr, char_attr_pri);
   1959        }
   1960      } else if (search_attr != 0) {
   1961        char_attr_pri = hl_combine_attr(wlv.line_attr, search_attr);
   1962      } else if (wlv.line_attr != 0
   1963                 && ((wlv.fromcol == -10 && wlv.tocol == MAXCOL)
   1964                     || wlv.vcol < wlv.fromcol
   1965                     || vcol_prev < fromcol_prev
   1966                     || wlv.vcol >= wlv.tocol)) {
   1967        // Use wlv.line_attr when not in the Visual or 'incsearch' area
   1968        // (area_attr may be 0 when "noinvcur" is set).
   1969        char_attr_pri = wlv.line_attr;
   1970      } else {
   1971        char_attr_pri = 0;
   1972      }
   1973      char_attr_base = hl_combine_attr(folded_attr, decor_attr);
   1974      wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
   1975    }
   1976 
   1977    if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) {
   1978      const int v = (int)(ptr - line);
   1979      linenr_T lnume = lnum + foldinfo.fi_lines - 1;
   1980      memset(buf_fold, ' ', FOLD_TEXT_LEN);
   1981      wlv.p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold, &fold_vt);
   1982      wlv.n_extra = (int)strlen(wlv.p_extra);
   1983 
   1984      if (wlv.p_extra != buf_fold) {
   1985        assert(foldtext_free == NULL);
   1986        foldtext_free = wlv.p_extra;
   1987      }
   1988      wlv.sc_extra = NUL;
   1989      wlv.sc_final = NUL;
   1990      wlv.p_extra[wlv.n_extra] = NUL;
   1991 
   1992      // Get the line again as evaluating 'foldtext' may free it.
   1993      line = ml_get_buf(wp->w_buffer, lnum);
   1994      ptr = line + v;
   1995    }
   1996 
   1997    // Draw 'fold' fillchar after 'foldtext', or after 'eol' listchar for transparent 'foldtext'.
   1998    if (draw_folded && wlv.n_extra == 0 && wlv.col < view_width
   1999        && (has_foldtext || (*ptr == NUL && (!wp->w_p_list || !lcs_eol_todo || lcs_eol == NUL)))) {
   2000      // Fill rest of line with 'fold'.
   2001      wlv.sc_extra = wp->w_p_fcs_chars.fold;
   2002      wlv.sc_final = NUL;
   2003      wlv.n_extra = view_width - wlv.col;
   2004      // Don't continue search highlighting past the first filler char.
   2005      search_attr = 0;
   2006    }
   2007 
   2008    if (draw_folded && wlv.n_extra != 0 && wlv.col >= view_width) {
   2009      // Truncate the folding.
   2010      wlv.n_extra = 0;
   2011    }
   2012 
   2013    // Get the next character to put on the screen.
   2014    //
   2015    // The "p_extra" points to the extra stuff that is inserted to
   2016    // represent special characters (non-printable stuff) and other
   2017    // things.  When all characters are the same, sc_extra is used.
   2018    // If sc_final is set, it will compulsorily be used at the end.
   2019    // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past
   2020    // "p_extra[n_extra]".
   2021    // For the '$' of the 'list' option, n_extra == 1, p_extra == "".
   2022    if (wlv.n_extra > 0) {
   2023      if (wlv.sc_extra != NUL || (wlv.n_extra == 1 && wlv.sc_final != NUL)) {
   2024        mb_schar = (wlv.n_extra == 1 && wlv.sc_final != NUL) ? wlv.sc_final : wlv.sc_extra;
   2025        mb_c = schar_get_first_codepoint(mb_schar);
   2026        wlv.n_extra--;
   2027      } else {
   2028        assert(wlv.p_extra != NULL);
   2029        mb_l = utfc_ptr2len(wlv.p_extra);
   2030        mb_schar = utfc_ptr2schar(wlv.p_extra, &mb_c);
   2031        // mb_l=0 at the end-of-line NUL
   2032        if (mb_l > wlv.n_extra || mb_l == 0) {
   2033          mb_l = 1;
   2034        }
   2035 
   2036        // If a double-width char doesn't fit display a '>' in the last column.
   2037        // Don't advance the pointer but put the character at the start of the next line.
   2038        if (wlv.col >= view_width - 1 && schar_cells(mb_schar) == 2) {
   2039          mb_c = '>';
   2040          mb_l = 1;
   2041          mb_schar = schar_from_ascii(mb_c);
   2042          multi_attr = win_hl_attr(wp, HLF_AT);
   2043 
   2044          if (wlv.cul_attr) {
   2045            multi_attr = 0 != wlv.line_attr_lowprio
   2046                         ? hl_combine_attr(wlv.cul_attr, multi_attr)
   2047                         : hl_combine_attr(multi_attr, wlv.cul_attr);
   2048          }
   2049        } else {
   2050          wlv.n_extra -= mb_l;
   2051          wlv.p_extra += mb_l;
   2052        }
   2053 
   2054        // If a double-width char doesn't fit at the left side display a '<'.
   2055        if (wlv.filler_todo <= 0 && wlv.skip_cells > 0 && mb_l > 1) {
   2056          if (wlv.n_extra > 0) {
   2057            n_extra_next = wlv.n_extra;
   2058            extra_attr_next = wlv.extra_attr;
   2059          }
   2060          wlv.n_extra = 1;
   2061          wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
   2062          wlv.sc_final = NUL;
   2063          mb_schar = schar_from_ascii(' ');
   2064          mb_c = ' ';
   2065          mb_l = 1;
   2066          (void)mb_l;
   2067          wlv.n_attr++;
   2068          wlv.extra_attr = win_hl_attr(wp, HLF_AT);
   2069        }
   2070      }
   2071 
   2072      if (wlv.n_extra <= 0) {
   2073        // Only restore search_attr and area_attr when there is no "n_extra" to show.
   2074        if (n_extra_next <= 0) {
   2075          if (search_attr == 0) {
   2076            search_attr = saved_search_attr;
   2077            saved_search_attr = 0;
   2078          }
   2079          if (area_attr == 0 && *ptr != NUL) {
   2080            area_attr = saved_area_attr;
   2081            saved_area_attr = 0;
   2082          }
   2083          if (decor_attr == 0) {
   2084            decor_attr = saved_decor_attr;
   2085            saved_decor_attr = 0;
   2086          }
   2087          if (wlv.extra_for_extmark) {
   2088            // wlv.extra_attr should be used at this position but not any further.
   2089            wlv.reset_extra_attr = true;
   2090            extra_attr_next = -1;
   2091          }
   2092          wlv.extra_for_extmark = false;
   2093        } else {
   2094          assert(wlv.sc_extra != NUL || wlv.sc_final != NUL);
   2095          assert(wlv.p_extra != NULL);
   2096          wlv.sc_extra = NUL;
   2097          wlv.sc_final = NUL;
   2098          wlv.n_extra = n_extra_next;
   2099          n_extra_next = 0;
   2100          // wlv.extra_attr should be used at this position, but extra_attr_next
   2101          // should be used after that.
   2102          wlv.reset_extra_attr = true;
   2103          assert(extra_attr_next >= 0);
   2104        }
   2105      }
   2106    } else if (wlv.filler_todo > 0) {
   2107      // Wait with reading text until filler lines are done. Still need to
   2108      // initialize these.
   2109      mb_c = ' ';
   2110      mb_schar = schar_from_ascii(' ');
   2111    } else if (has_foldtext || (has_fold && wlv.col >= view_width)) {
   2112      // skip writing the buffer line itself
   2113      mb_schar = NUL;
   2114    } else {
   2115      const char *prev_ptr = ptr;
   2116 
   2117      // first byte of next char
   2118      int c0 = (uint8_t)(*ptr);
   2119      if (c0 == NUL) {
   2120        // no more cells to skip
   2121        wlv.skip_cells = 0;
   2122      }
   2123 
   2124      // Get a character from the line itself.
   2125      mb_l = utfc_ptr2len(ptr);
   2126      mb_schar = utfc_ptr2schar(ptr, &mb_c);
   2127 
   2128      // Overlong encoded ASCII or ASCII with composing char
   2129      // is displayed normally, except a NUL.
   2130      if (mb_l > 1 && mb_c < 0x80) {
   2131        c0 = mb_c;
   2132      }
   2133 
   2134      if ((mb_l == 1 && c0 >= 0x80)
   2135          || (mb_l >= 1 && mb_c == 0)
   2136          || (mb_l > 1 && (!vim_isprintc(mb_c)))) {
   2137        // Illegal UTF-8 byte: display as <xx>.
   2138        // Non-printable character : display as ? or fullwidth ?.
   2139        transchar_hex(wlv.extra, mb_c);
   2140        if (wp->w_p_rl) {  // reverse
   2141          rl_mirror_ascii(wlv.extra, NULL);
   2142        }
   2143 
   2144        wlv.p_extra = wlv.extra;
   2145        mb_c = mb_ptr2char_adv((const char **)&wlv.p_extra);
   2146        mb_schar = schar_from_char(mb_c);
   2147        wlv.n_extra = (int)strlen(wlv.p_extra);
   2148        wlv.sc_extra = NUL;
   2149        wlv.sc_final = NUL;
   2150        if (area_attr == 0 && search_attr == 0) {
   2151          wlv.n_attr = wlv.n_extra + 1;
   2152          wlv.extra_attr = win_hl_attr(wp, HLF_8);
   2153          saved_attr2 = wlv.char_attr;               // save current attr
   2154        }
   2155      } else if (mb_l == 0) {        // at the NUL at end-of-line
   2156        mb_l = 1;
   2157      }
   2158      // If a double-width char doesn't fit display a '>' in the
   2159      // last column; the character is displayed at the start of the
   2160      // next line.
   2161      if (wlv.col >= view_width - 1 && schar_cells(mb_schar) == 2) {
   2162        mb_schar = schar_from_ascii('>');
   2163        mb_c = '>';
   2164        mb_l = 1;
   2165        multi_attr = win_hl_attr(wp, HLF_AT);
   2166        // Put pointer back so that the character will be
   2167        // displayed at the start of the next line.
   2168        ptr--;
   2169        did_decrement_ptr = true;
   2170      } else if (*ptr != NUL) {
   2171        ptr += mb_l - 1;
   2172      }
   2173 
   2174      // If a double-width char doesn't fit at the left side display a '<' in
   2175      // the first column.  Don't do this for unprintable characters.
   2176      if (wlv.skip_cells > 0 && mb_l > 1 && wlv.n_extra == 0) {
   2177        wlv.n_extra = 1;
   2178        wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
   2179        wlv.sc_final = NUL;
   2180        mb_schar = schar_from_ascii(' ');
   2181        mb_c = ' ';
   2182        mb_l = 1;
   2183        if (area_attr == 0 && search_attr == 0) {
   2184          wlv.n_attr = wlv.n_extra + 1;
   2185          wlv.extra_attr = win_hl_attr(wp, HLF_AT);
   2186          saved_attr2 = wlv.char_attr;             // save current attr
   2187        }
   2188      }
   2189      ptr++;
   2190 
   2191      decor_attr = 0;
   2192      if (extra_check) {
   2193        const bool no_plain_buffer = (wp->w_s->b_p_spo_flags & kOptSpoFlagNoplainbuffer) != 0;
   2194        bool can_spell = !no_plain_buffer;
   2195 
   2196        // Get extmark and syntax attributes, unless still at the start of the line
   2197        // (double-wide char that doesn't fit).
   2198        const int v = (int)(ptr - line);
   2199        const ptrdiff_t prev_v = prev_ptr - line;
   2200        if (has_syntax && v > 0) {
   2201          // Get the syntax attribute for the character.  If there
   2202          // is an error, disable syntax highlighting.
   2203          int save_did_emsg = did_emsg;
   2204          did_emsg = false;
   2205 
   2206          decor_attr = get_syntax_attr(v - 1, spv->spv_has_spell ? &can_spell : NULL, false);
   2207 
   2208          if (did_emsg) {
   2209            wp->w_s->b_syn_error = true;
   2210            has_syntax = false;
   2211          } else {
   2212            did_emsg = save_did_emsg;
   2213          }
   2214 
   2215          if (wp->w_s->b_syn_slow) {
   2216            has_syntax = false;
   2217          }
   2218 
   2219          // Need to get the line again, a multi-line regexp may
   2220          // have made it invalid.
   2221          line = ml_get_buf(wp->w_buffer, lnum);
   2222          ptr = line + v;
   2223          prev_ptr = line + prev_v;
   2224 
   2225          // no concealing past the end of the line, it interferes
   2226          // with line highlighting.
   2227          syntax_flags = (mb_schar == 0) ? 0 : get_syntax_info(&syntax_seqnr);
   2228        }
   2229 
   2230        if (has_decor && v > 0) {
   2231          // extmarks take preceedence over syntax.c
   2232          decor_attr = hl_combine_attr(decor_attr, extmark_attr);
   2233          decor_conceal = decor_state.conceal;
   2234          can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell);
   2235        }
   2236 
   2237        char_attr_base = hl_combine_attr(folded_attr, decor_attr);
   2238        wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
   2239 
   2240        // Check spelling (unless at the end of the line).
   2241        // Only do this when there is no syntax highlighting, the
   2242        // @Spell cluster is not used or the current syntax item
   2243        // contains the @Spell cluster.
   2244        int v1 = (int)(ptr - line);
   2245        if (spv->spv_has_spell && v1 >= word_end && v1 > cur_checked_col) {
   2246          spell_attr = 0;
   2247          // do not calculate cap_col at the end of the line or when
   2248          // only white space is following
   2249          if (mb_schar != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) {
   2250            char *p;
   2251            hlf_T spell_hlf = HLF_COUNT;
   2252            v1 -= mb_l - 1;
   2253 
   2254            // Use nextline[] if possible, it has the start of the
   2255            // next line concatenated.
   2256            if ((prev_ptr - line) - nextlinecol >= 0) {
   2257              p = nextline + ((prev_ptr - line) - nextlinecol);
   2258            } else {
   2259              p = (char *)prev_ptr;
   2260            }
   2261            spv->spv_cap_col -= (int)(prev_ptr - line);
   2262            size_t tmplen = spell_check(wp, p, &spell_hlf, &spv->spv_cap_col, spv->spv_unchanged);
   2263            assert(tmplen <= INT_MAX);
   2264            int len = (int)tmplen;
   2265            word_end = v1 + len;
   2266 
   2267            // In Insert mode only highlight a word that
   2268            // doesn't touch the cursor.
   2269            if (spell_hlf != HLF_COUNT
   2270                && (State & MODE_INSERT)
   2271                && wp->w_cursor.lnum == lnum
   2272                && wp->w_cursor.col >= (colnr_T)(prev_ptr - line)
   2273                && wp->w_cursor.col < (colnr_T)word_end) {
   2274              spell_hlf = HLF_COUNT;
   2275              spell_redraw_lnum = lnum;
   2276            }
   2277 
   2278            if (spell_hlf == HLF_COUNT && p != prev_ptr
   2279                && (p - nextline) + len > nextline_idx) {
   2280              // Remember that the good word continues at the
   2281              // start of the next line.
   2282              spv->spv_checked_lnum = lnum + 1;
   2283              spv->spv_checked_col = (int)((p - nextline) + len - nextline_idx);
   2284            }
   2285 
   2286            // Turn index into actual attributes.
   2287            if (spell_hlf != HLF_COUNT) {
   2288              spell_attr = highlight_attr[spell_hlf];
   2289            }
   2290 
   2291            if (spv->spv_cap_col > 0) {
   2292              if (p != prev_ptr && (p - nextline) + spv->spv_cap_col >= nextline_idx) {
   2293                // Remember that the word in the next line
   2294                // must start with a capital.
   2295                spv->spv_capcol_lnum = lnum + 1;
   2296                spv->spv_cap_col = (int)((p - nextline) + spv->spv_cap_col - nextline_idx);
   2297              } else {
   2298                // Compute the actual column.
   2299                spv->spv_cap_col += (int)(prev_ptr - line);
   2300              }
   2301            }
   2302          }
   2303        }
   2304        if (spell_attr != 0) {
   2305          char_attr_base = hl_combine_attr(char_attr_base, spell_attr);
   2306          wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
   2307        }
   2308 
   2309        if (wp->w_buffer->terminal) {
   2310          wlv.char_attr = hl_combine_attr(wlv.vcol < TERM_ATTRS_MAX ? term_attrs[wlv.vcol] : 0,
   2311                                          wlv.char_attr);
   2312        }
   2313 
   2314        // we don't want linebreak to apply for lines that start with
   2315        // leading spaces, followed by long letters (since it would add
   2316        // a break at the beginning of a line and this might be unexpected)
   2317        //
   2318        // So only allow to linebreak, once we have found chars not in
   2319        // 'breakat' in the line.
   2320        if (wp->w_p_lbr && !wlv.need_lbr && mb_schar != NUL
   2321            && !vim_isbreak((uint8_t)(*ptr))) {
   2322          wlv.need_lbr = true;
   2323        }
   2324        // Found last space before word: check for line break.
   2325        if (wp->w_p_lbr && c0 == mb_c && mb_c < 128 && wlv.need_lbr
   2326            && vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) {
   2327          int mb_off = utf_head_off(line, ptr - 1);
   2328          char *p = ptr - (mb_off + 1);
   2329 
   2330          CharsizeArg csarg;
   2331          // lnum == 0, do not want virtual text to be counted here
   2332          CSType cstype = init_charsize_arg(&csarg, wp, 0, line);
   2333          wlv.n_extra = win_charsize(cstype, wlv.vcol, p, utf_ptr2CharInfo(p).value,
   2334                                     &csarg).width - 1;
   2335 
   2336          if (on_last_col && mb_c != TAB) {
   2337            // Do not continue search/match highlighting over the
   2338            // line break, but for TABs the highlighting should
   2339            // include the complete width of the character
   2340            search_attr = 0;
   2341          }
   2342 
   2343          if (mb_c == TAB && wlv.n_extra + wlv.col > view_width) {
   2344            wlv.n_extra = tabstop_padding(wlv.vcol, wp->w_buffer->b_p_ts,
   2345                                          wp->w_buffer->b_p_vts_array) - 1;
   2346          }
   2347          wlv.sc_extra = schar_from_ascii(mb_off > 0 ? MB_FILLER_CHAR : ' ');
   2348          wlv.sc_final = NUL;
   2349          if (mb_c < 128 && ascii_iswhite(mb_c)) {
   2350            if (mb_c == TAB) {
   2351              // See "Tab alignment" below.
   2352              fix_for_boguscols(&wlv);
   2353            }
   2354            if (!wp->w_p_list) {
   2355              mb_c = ' ';
   2356              mb_schar = schar_from_ascii(mb_c);
   2357            }
   2358          }
   2359        }
   2360 
   2361        if (wp->w_p_list) {
   2362          in_multispace = mb_c == ' ' && (*ptr == ' ' || (prev_ptr > line && prev_ptr[-1] == ' '));
   2363          if (!in_multispace) {
   2364            multispace_pos = 0;
   2365          }
   2366        }
   2367 
   2368        // 'list': Change char 160 to 'nbsp' and space to 'space'.
   2369        // But not when the character is followed by a composing
   2370        // character (use mb_l to check that).
   2371        if (wp->w_p_list
   2372            && ((((mb_c == 160 && mb_l == 2) || (mb_c == 0x202f && mb_l == 3))
   2373                 && wp->w_p_lcs_chars.nbsp)
   2374                || (mb_c == ' '
   2375                    && mb_l == 1
   2376                    && (wp->w_p_lcs_chars.space
   2377                        || (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
   2378                    && ptr - line >= leadcol
   2379                    && ptr - line <= trailcol))) {
   2380          if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
   2381            mb_schar = wp->w_p_lcs_chars.multispace[multispace_pos++];
   2382            if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
   2383              multispace_pos = 0;
   2384            }
   2385          } else {
   2386            mb_schar = (mb_c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
   2387          }
   2388          wlv.n_attr = 1;
   2389          wlv.extra_attr = win_hl_attr(wp, HLF_0);
   2390          saved_attr2 = wlv.char_attr;  // save current attr
   2391          mb_c = schar_get_first_codepoint(mb_schar);
   2392        }
   2393 
   2394        if (mb_c == ' ' && mb_l == 1 && ((trailcol != MAXCOL && ptr > line + trailcol)
   2395                                         || (leadcol != 0 && ptr < line + leadcol))) {
   2396          if (leadcol != 0 && in_multispace && ptr < line + leadcol
   2397              && wp->w_p_lcs_chars.leadmultispace != NULL) {
   2398            mb_schar = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
   2399            if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
   2400              multispace_pos = 0;
   2401            }
   2402          } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) {
   2403            mb_schar = wp->w_p_lcs_chars.trail;
   2404          } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) {
   2405            mb_schar = wp->w_p_lcs_chars.lead;
   2406          } else if (leadcol != 0 && wp->w_p_lcs_chars.space) {
   2407            mb_schar = wp->w_p_lcs_chars.space;
   2408          }
   2409 
   2410          wlv.n_attr = 1;
   2411          wlv.extra_attr = win_hl_attr(wp, HLF_0);
   2412          saved_attr2 = wlv.char_attr;  // save current attr
   2413          mb_c = schar_get_first_codepoint(mb_schar);
   2414        }
   2415      }
   2416 
   2417      // Handling of non-printable characters.
   2418      if (!vim_isprintc(mb_c)) {
   2419        // when getting a character from the file, we may have to
   2420        // turn it into something else on the way to putting it on the screen.
   2421        if (mb_c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
   2422          int tab_len = 0;
   2423          colnr_T vcol_adjusted = wlv.vcol;  // removed showbreak length
   2424          schar_T lcs_tab1 = wp->w_p_lcs_chars.tab1;
   2425          schar_T lcs_tab2 = wp->w_p_lcs_chars.tab2;
   2426          schar_T lcs_tab3 = wp->w_p_lcs_chars.tab3;
   2427          // check if leadtab is set in 'listchars'
   2428          if (wp->w_p_list && wp->w_p_lcs_chars.leadtab1 != NUL
   2429              && ptr < line + leadcol) {
   2430            lcs_tab1 = wp->w_p_lcs_chars.leadtab1;
   2431            lcs_tab2 = wp->w_p_lcs_chars.leadtab2;
   2432            lcs_tab3 = wp->w_p_lcs_chars.leadtab3;
   2433          }
   2434          char *const sbr = get_showbreak_value(wp);
   2435 
   2436          // Only adjust the tab_len, when at the first column after the
   2437          // showbreak value was drawn.
   2438          if (*sbr != NUL && wlv.vcol == wlv.vcol_sbr && wp->w_p_wrap) {
   2439            vcol_adjusted = wlv.vcol - mb_charlen(sbr);
   2440          }
   2441          // tab amount depends on current column
   2442          tab_len = tabstop_padding(vcol_adjusted,
   2443                                    wp->w_buffer->b_p_ts,
   2444                                    wp->w_buffer->b_p_vts_array) - 1;
   2445 
   2446          if (!wp->w_p_lbr || !wp->w_p_list) {
   2447            wlv.n_extra = tab_len;
   2448          } else {
   2449            int saved_nextra = wlv.n_extra;
   2450 
   2451            if (wlv.vcol_off_co > 0) {
   2452              // there are characters to conceal
   2453              tab_len += wlv.vcol_off_co;
   2454            }
   2455            // boguscols before fix_for_boguscols() from above.
   2456            if (lcs_tab1 && wlv.old_boguscols > 0 && wlv.n_extra > tab_len) {
   2457              tab_len += wlv.n_extra - tab_len;
   2458            }
   2459 
   2460            if (tab_len > 0) {
   2461              // If wlv.n_extra > 0, it gives the number of chars
   2462              // to use for a tab, else we need to calculate the
   2463              // width for a tab.
   2464              size_t tab2_len = schar_len(lcs_tab2);
   2465              size_t len = (size_t)tab_len * tab2_len;
   2466              if (lcs_tab3) {
   2467                len += schar_len(lcs_tab3) - tab2_len;
   2468              }
   2469              if (wlv.n_extra > 0) {
   2470                len += (size_t)(wlv.n_extra - tab_len);
   2471              }
   2472              mb_schar = lcs_tab1;
   2473              mb_c = schar_get_first_codepoint(mb_schar);
   2474              char *p = get_extra_buf(len + 1);
   2475              memset(p, ' ', len);
   2476              p[len] = NUL;
   2477              wlv.p_extra = p;
   2478              for (int i = 0; i < tab_len; i++) {
   2479                if (*p == NUL) {
   2480                  tab_len = i;
   2481                  break;
   2482                }
   2483                schar_T lcs = lcs_tab2;
   2484 
   2485                // if tab3 is given, use it for the last char
   2486                if (lcs_tab3 && i == tab_len - 1) {
   2487                  lcs = lcs_tab3;
   2488                }
   2489                size_t slen = schar_get_adv(&p, lcs);
   2490                wlv.n_extra += (int)slen - (saved_nextra > 0 ? 1 : 0);
   2491              }
   2492 
   2493              // n_extra will be increased by fix_for_boguscols()
   2494              // below, so need to adjust for that here
   2495              if (wlv.vcol_off_co > 0) {
   2496                wlv.n_extra -= wlv.vcol_off_co;
   2497              }
   2498            }
   2499          }
   2500 
   2501          {
   2502            int vc_saved = wlv.vcol_off_co;
   2503 
   2504            // Tab alignment should be identical regardless of
   2505            // 'conceallevel' value. So tab compensates of all
   2506            // previous concealed characters, and thus resets
   2507            // vcol_off_co and boguscols accumulated so far in the
   2508            // line. Note that the tab can be longer than
   2509            // 'tabstop' when there are concealed characters.
   2510            fix_for_boguscols(&wlv);
   2511 
   2512            // Make sure, the highlighting for the tab char will be
   2513            // correctly set further below (effectively reverts the
   2514            // fix_for_boguscols() call).
   2515            if (wlv.n_extra == tab_len + vc_saved && wp->w_p_list
   2516                && wp->w_p_lcs_chars.tab1) {
   2517              tab_len += vc_saved;
   2518            }
   2519          }
   2520 
   2521          if (wp->w_p_list) {
   2522            mb_schar = (wlv.n_extra == 0 && lcs_tab3) ? lcs_tab3 : lcs_tab1;
   2523            if (wp->w_p_lbr && wlv.p_extra != NULL && *wlv.p_extra != NUL) {
   2524              wlv.sc_extra = NUL;  // using p_extra from above
   2525            } else {
   2526              wlv.sc_extra = lcs_tab2;
   2527            }
   2528            wlv.sc_final = lcs_tab3;
   2529            wlv.n_attr = tab_len + 1;
   2530            wlv.extra_attr = win_hl_attr(wp, HLF_0);
   2531            saved_attr2 = wlv.char_attr;  // save current attr
   2532          } else {
   2533            wlv.sc_final = NUL;
   2534            wlv.sc_extra = schar_from_ascii(' ');
   2535            mb_schar = schar_from_ascii(' ');
   2536          }
   2537          mb_c = schar_get_first_codepoint(mb_schar);
   2538        } else if (mb_schar == NUL
   2539                   && (wp->w_p_list
   2540                       || ((wlv.fromcol >= 0 || fromcol_prev >= 0)
   2541                           && wlv.tocol > wlv.vcol
   2542                           && VIsual_mode != Ctrl_V
   2543                           && wlv.col < view_width
   2544                           && !(noinvcur
   2545                                && lnum == wp->w_cursor.lnum
   2546                                && wlv.vcol == wp->w_virtcol)))
   2547                   && lcs_eol_todo && lcs_eol != NUL) {
   2548          // Display a '$' after the line or highlight an extra
   2549          // character if the line break is included.
   2550          // For a diff line the highlighting continues after the "$".
   2551          if (wlv.diff_hlf == (hlf_T)0
   2552              && wlv.line_attr == 0
   2553              && wlv.line_attr_lowprio == 0) {
   2554            // In virtualedit, visual selections may extend beyond end of line
   2555            if (!(area_highlighting && virtual_active(wp)
   2556                  && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol)) {
   2557              wlv.p_extra = "";
   2558            }
   2559            wlv.n_extra = 0;
   2560          }
   2561          if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
   2562            mb_schar = wp->w_p_lcs_chars.eol;
   2563          } else {
   2564            mb_schar = schar_from_ascii(' ');
   2565          }
   2566          lcs_eol_todo = false;
   2567          ptr--;  // put it back at the NUL
   2568          wlv.extra_attr = win_hl_attr(wp, HLF_AT);
   2569          wlv.n_attr = 1;
   2570          mb_c = schar_get_first_codepoint(mb_schar);
   2571        } else if (mb_schar != NUL) {
   2572          wlv.p_extra = transchar_buf(wp->w_buffer, mb_c);
   2573          if (wlv.n_extra == 0) {
   2574            wlv.n_extra = byte2cells(mb_c) - 1;
   2575          }
   2576          if ((dy_flags & kOptDyFlagUhex) && wp->w_p_rl) {
   2577            rl_mirror_ascii(wlv.p_extra, NULL);   // reverse "<12>"
   2578          }
   2579          wlv.sc_extra = NUL;
   2580          wlv.sc_final = NUL;
   2581          if (wp->w_p_lbr) {
   2582            mb_c = (uint8_t)(*wlv.p_extra);
   2583            char *p = get_extra_buf((size_t)wlv.n_extra + 1);
   2584            memset(p, ' ', (size_t)wlv.n_extra);
   2585            memcpy(p, wlv.p_extra + 1, strlen(wlv.p_extra) - 1);
   2586            p[wlv.n_extra] = NUL;
   2587            wlv.p_extra = p;
   2588          } else {
   2589            wlv.n_extra = byte2cells(mb_c) - 1;
   2590            mb_c = (uint8_t)(*wlv.p_extra++);
   2591          }
   2592          wlv.n_attr = wlv.n_extra + 1;
   2593          wlv.extra_attr = win_hl_attr(wp, HLF_8);
   2594          saved_attr2 = wlv.char_attr;  // save current attr
   2595          mb_schar = schar_from_ascii(mb_c);
   2596        } else if (VIsual_active
   2597                   && (VIsual_mode == Ctrl_V || VIsual_mode == 'v')
   2598                   && virtual_active(wp)
   2599                   && wlv.tocol != MAXCOL
   2600                   && wlv.vcol < wlv.tocol
   2601                   && wlv.col < view_width) {
   2602          mb_c = ' ';
   2603          mb_schar = schar_from_char(mb_c);
   2604          ptr--;  // put it back at the NUL
   2605        }
   2606      }
   2607 
   2608      if (wp->w_p_cole > 0
   2609          && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp))
   2610          && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0)
   2611          && !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) {
   2612        bool syntax_conceal = (syntax_flags & HL_CONCEAL) != 0;
   2613        wlv.char_attr = conceal_attr;
   2614        if (((prev_syntax_id != syntax_seqnr && syntax_conceal)
   2615             || has_match_conc > 1 || decor_conceal > 1)
   2616            && ((syntax_conceal && syn_get_sub_char() != NUL)
   2617                || (has_match_conc && match_conc)
   2618                || (decor_conceal && decor_state.conceal_char)
   2619                || wp->w_p_cole == 1)
   2620            && wp->w_p_cole != 3) {
   2621          if (schar_cells(mb_schar) > 1) {
   2622            // When the first char to be concealed is double-width,
   2623            // need to advance one more virtual column.
   2624            wlv.n_extra++;
   2625          }
   2626 
   2627          // First time at this concealed item: display one
   2628          // character.
   2629          if (has_match_conc && match_conc) {
   2630            mb_schar = schar_from_char(match_conc);
   2631          } else if (decor_conceal && decor_state.conceal_char) {
   2632            mb_schar = decor_state.conceal_char;
   2633            if (decor_state.conceal_attr) {
   2634              wlv.char_attr = decor_state.conceal_attr;
   2635            }
   2636          } else if (syntax_conceal && syn_get_sub_char() != NUL) {
   2637            mb_schar = schar_from_char(syn_get_sub_char());
   2638          } else if (wp->w_p_lcs_chars.conceal != NUL) {
   2639            mb_schar = wp->w_p_lcs_chars.conceal;
   2640          } else {
   2641            mb_schar = schar_from_ascii(' ');
   2642          }
   2643 
   2644          mb_c = schar_get_first_codepoint(mb_schar);
   2645 
   2646          prev_syntax_id = syntax_seqnr;
   2647 
   2648          if (wlv.n_extra > 0) {
   2649            wlv.vcol_off_co += wlv.n_extra;
   2650          }
   2651          wlv.vcol += wlv.n_extra;
   2652          if (is_wrapped && wlv.n_extra > 0) {
   2653            wlv.boguscols += wlv.n_extra;
   2654            wlv.col += wlv.n_extra;
   2655          }
   2656          wlv.n_extra = 0;
   2657          wlv.n_attr = 0;
   2658        } else if (wlv.skip_cells == 0) {
   2659          is_concealing = true;
   2660          wlv.skip_cells = 1;
   2661        }
   2662      } else {
   2663        prev_syntax_id = 0;
   2664        is_concealing = false;
   2665      }
   2666 
   2667      if (wlv.skip_cells > 0 && did_decrement_ptr) {
   2668        // not showing the '>', put pointer back to avoid getting stuck
   2669        ptr++;
   2670      }
   2671    }  // end of printing from buffer content
   2672 
   2673    // In the cursor line and we may be concealing characters: correct
   2674    // the cursor column when we reach its position.
   2675    // With 'virtualedit' we may never reach cursor position, but we still
   2676    // need to correct the cursor column, so do that at end of line.
   2677    if (!did_wcol && wlv.filler_todo <= 0
   2678        && in_curline && conceal_cursor_line(wp)
   2679        && (wlv.vcol + wlv.skip_cells >= wp->w_virtcol || mb_schar == NUL)) {
   2680      wp->w_wcol = wlv.col - wlv.boguscols;
   2681      if (wlv.vcol + wlv.skip_cells < wp->w_virtcol) {
   2682        // Cursor beyond end of the line with 'virtualedit'.
   2683        wp->w_wcol += wp->w_virtcol - wlv.vcol - wlv.skip_cells;
   2684      }
   2685      wp->w_wrow = wlv.row;
   2686      did_wcol = true;
   2687      wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
   2688    }
   2689 
   2690    // Use "wlv.extra_attr", but don't override visual selection highlighting.
   2691    if (wlv.n_attr > 0 && !search_attr_from_match) {
   2692      wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr);
   2693      if (wlv.reset_extra_attr) {
   2694        wlv.reset_extra_attr = false;
   2695        if (extra_attr_next >= 0) {
   2696          wlv.extra_attr = extra_attr_next;
   2697          extra_attr_next = -1;
   2698        } else {
   2699          wlv.extra_attr = 0;
   2700          // search_attr_from_match can be restored now that the extra_attr has been applied
   2701          search_attr_from_match = saved_search_attr_from_match;
   2702        }
   2703      }
   2704    }
   2705 
   2706    // Handle the case where we are in column 0 but not on the first
   2707    // character of the line and the user wants us to show us a
   2708    // special character (via 'listchars' option "precedes:<char>").
   2709    if (lcs_prec_todo != NUL
   2710        && wp->w_p_list
   2711        && (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0) : wp->w_leftcol > 0)
   2712        && wlv.filler_todo <= 0
   2713        && wlv.skip_cells <= 0
   2714        && mb_schar != NUL) {
   2715      lcs_prec_todo = NUL;
   2716      if (schar_cells(mb_schar) > 1) {
   2717        // Double-width character being overwritten by the "precedes"
   2718        // character, need to fill up half the character.
   2719        wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
   2720        wlv.sc_final = NUL;
   2721        if (wlv.n_extra > 0) {
   2722          assert(wlv.p_extra != NULL);
   2723          n_extra_next = wlv.n_extra;
   2724          extra_attr_next = wlv.extra_attr;
   2725          wlv.n_attr = MAX(wlv.n_attr + 1, 2);
   2726        } else {
   2727          wlv.n_attr = 2;
   2728        }
   2729        wlv.n_extra = 1;
   2730        wlv.extra_attr = win_hl_attr(wp, HLF_AT);
   2731      }
   2732      mb_schar = wp->w_p_lcs_chars.prec;
   2733      mb_c = schar_get_first_codepoint(mb_schar);
   2734      saved_attr3 = wlv.char_attr;  // save current attr
   2735      wlv.char_attr = win_hl_attr(wp, HLF_AT);  // overwriting char_attr
   2736      n_attr3 = 1;
   2737    }
   2738 
   2739    // At end of the text line or just after the last character.
   2740    if (mb_schar == NUL && eol_hl_off == 0) {
   2741      // flag to indicate whether prevcol equals startcol of search_hl or
   2742      // one of the matches
   2743      const bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl,
   2744                                                       (colnr_T)(ptr - line) - 1);
   2745 
   2746      // Invert at least one char, used for Visual and empty line or
   2747      // highlight match at end of line. If it's beyond the last
   2748      // char on the screen, just overwrite that one (tricky!)  Not
   2749      // needed when a '$' was displayed for 'list'.
   2750      if (lcs_eol_todo
   2751          && ((area_attr != 0 && wlv.vcol == wlv.fromcol
   2752               && (VIsual_mode != Ctrl_V
   2753                   || lnum == VIsual.lnum
   2754                   || lnum == curwin->w_cursor.lnum))
   2755              // highlight 'hlsearch' match at end of line
   2756              || prevcol_hl_flag)) {
   2757        int n = 0;
   2758 
   2759        if (wlv.col >= view_width) {
   2760          n = -1;
   2761        }
   2762        if (n != 0) {
   2763          // At the window boundary, highlight the last character
   2764          // instead (better than nothing).
   2765          wlv.off += n;
   2766          wlv.col += n;
   2767        } else {
   2768          // Add a blank character to highlight.
   2769          linebuf_char[wlv.off] = schar_from_ascii(' ');
   2770        }
   2771        if (area_attr == 0 && !has_fold) {
   2772          // Use attributes from match with highest priority among
   2773          // 'search_hl' and the match list.
   2774          get_search_match_hl(wp,
   2775                              &screen_search_hl,
   2776                              (colnr_T)(ptr - line),
   2777                              &wlv.char_attr);
   2778        }
   2779 
   2780        const int eol_attr = wlv.cul_attr
   2781                             ? hl_combine_attr(wlv.cul_attr, wlv.char_attr)
   2782                             : wlv.char_attr;
   2783 
   2784        linebuf_attr[wlv.off] = eol_attr;
   2785        linebuf_vcol[wlv.off] = wlv.vcol;
   2786        wlv.col++;
   2787        wlv.off++;
   2788        wlv.vcol++;
   2789        eol_hl_off = 1;
   2790      }
   2791    }
   2792 
   2793    // At end of the text line.
   2794    if (mb_schar == NUL) {
   2795      // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
   2796 
   2797      // check if line ends before left margin
   2798      wlv.vcol = MAX(wlv.vcol, start_vcol + wlv.col - win_col_off(wp));
   2799      // Get rid of the boguscols now, we want to draw until the right
   2800      // edge for 'cursorcolumn'.
   2801      wlv.col -= wlv.boguscols;
   2802      wlv.boguscols = 0;
   2803 
   2804      advance_color_col(&wlv, vcol_hlc(wlv));
   2805 
   2806      // Make sure alignment is the same regardless
   2807      // if listchars=eol:X is used or not.
   2808      const int eol_skip = (lcs_eol_todo && eol_hl_off == 0 ? 1 : 0);
   2809 
   2810      if (has_decor) {
   2811        decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip);
   2812      }
   2813 
   2814      for (int i = wlv.col; i < view_width; i++) {
   2815        linebuf_vcol[wlv.off + (i - wlv.col)] = wlv.vcol + (i - wlv.col);
   2816      }
   2817 
   2818      if (((wp->w_p_cuc
   2819            && wp->w_virtcol >= vcol_hlc(wlv) - eol_hl_off
   2820            && wp->w_virtcol < view_width * (ptrdiff_t)(wlv.row - startrow + 1) + start_vcol
   2821            && lnum != wp->w_cursor.lnum)
   2822           || wlv.color_cols || wlv.line_attr_lowprio || wlv.line_attr
   2823           || wlv.diff_hlf != 0 || wp->w_buffer->terminal)) {
   2824        int rightmost_vcol = get_rightmost_vcol(wp, wlv.color_cols);
   2825        const int cuc_attr = win_hl_attr(wp, HLF_CUC);
   2826        const int mc_attr = win_hl_attr(wp, HLF_MC);
   2827 
   2828        if (wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA) {
   2829          wlv.diff_hlf = HLF_CHD;
   2830          set_line_attr_for_diff(wp, &wlv);
   2831        }
   2832 
   2833        const int diff_attr = wlv.diff_hlf != 0
   2834                              ? win_hl_attr(wp, (int)wlv.diff_hlf)
   2835                              : 0;
   2836 
   2837        const int base_attr = hl_combine_attr(wlv.line_attr_lowprio, diff_attr);
   2838        if (base_attr || wlv.line_attr || wp->w_buffer->terminal) {
   2839          rightmost_vcol = INT_MAX;
   2840        }
   2841 
   2842        while (wlv.col < view_width) {
   2843          linebuf_char[wlv.off] = schar_from_ascii(' ');
   2844 
   2845          advance_color_col(&wlv, vcol_hlc(wlv));
   2846 
   2847          int col_attr = base_attr;
   2848 
   2849          if (wp->w_p_cuc && vcol_hlc(wlv) == wp->w_virtcol
   2850              && lnum != wp->w_cursor.lnum) {
   2851            col_attr = hl_combine_attr(col_attr, cuc_attr);
   2852          } else if (wlv.color_cols && vcol_hlc(wlv) == *wlv.color_cols) {
   2853            col_attr = hl_combine_attr(col_attr, mc_attr);
   2854          }
   2855 
   2856          if (wp->w_buffer->terminal && wlv.vcol < TERM_ATTRS_MAX) {
   2857            col_attr = hl_combine_attr(col_attr, term_attrs[wlv.vcol]);
   2858          }
   2859 
   2860          col_attr = hl_combine_attr(col_attr, wlv.line_attr);
   2861 
   2862          linebuf_attr[wlv.off] = col_attr;
   2863          // linebuf_vcol[] already filled by the for loop above
   2864          wlv.off++;
   2865          wlv.col++;
   2866          wlv.vcol++;
   2867 
   2868          if (vcol_hlc(wlv) > rightmost_vcol) {
   2869            break;
   2870          }
   2871        }
   2872      }
   2873 
   2874      if (kv_size(fold_vt) > 0) {
   2875        draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, view_width, 0, 0);
   2876      }
   2877      draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
   2878      // Set increasing virtual columns in grid->vcols[] to set correct curswant
   2879      // (or "coladd" for 'virtualedit') when clicking after end of line.
   2880      wlv_put_linebuf(wp, &wlv, wlv.col, true, bg_attr, SLF_INC_VCOL);
   2881      wlv.row++;
   2882 
   2883      // Update w_cline_height and w_cline_folded if the cursor line was
   2884      // updated (saves a call to plines_win() later).
   2885      if (in_curline) {
   2886        curwin->w_cline_row = startrow;
   2887        curwin->w_cline_height = wlv.row - startrow;
   2888        curwin->w_cline_folded = has_fold;
   2889        curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
   2890      }
   2891 
   2892      break;
   2893    }
   2894 
   2895    // Show "extends" character from 'listchars' if beyond the line end.
   2896    const schar_T lcs_ext = get_lcs_ext(wp);
   2897    if (lcs_ext != NUL
   2898        && wlv.filler_todo <= 0
   2899        && wlv.col == view_width - 1
   2900        && !has_foldtext) {
   2901      if (has_decor && *ptr == NUL && lcs_eol == 0 && lcs_eol_todo) {
   2902        // Tricky: there might be a virtual text just _after_ the last char
   2903        decor_redraw_col(wp, (colnr_T)(ptr - line), -1, false, &decor_state);
   2904      }
   2905      if (*ptr != NUL
   2906          || (lcs_eol > 0 && lcs_eol_todo)
   2907          || (wlv.n_extra > 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL))
   2908          || (may_have_inline_virt && has_more_inline_virt(&wlv, ptr - line))) {
   2909        mb_schar = lcs_ext;
   2910        wlv.char_attr = win_hl_attr(wp, HLF_AT);
   2911        mb_c = schar_get_first_codepoint(mb_schar);
   2912      }
   2913    }
   2914 
   2915    advance_color_col(&wlv, vcol_hlc(wlv));
   2916 
   2917    // Highlight the cursor column if 'cursorcolumn' is set.  But don't
   2918    // highlight the cursor position itself.
   2919    // Also highlight the 'colorcolumn' if it is different than
   2920    // 'cursorcolumn'
   2921    vcol_save_attr = -1;
   2922    if (!lnum_in_visual_area
   2923        && search_attr == 0
   2924        && area_attr == 0
   2925        && wlv.filler_todo <= 0) {
   2926      if (wp->w_p_cuc && vcol_hlc(wlv) == wp->w_virtcol
   2927          && lnum != wp->w_cursor.lnum) {
   2928        vcol_save_attr = wlv.char_attr;
   2929        wlv.char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), wlv.char_attr);
   2930      } else if (wlv.color_cols && vcol_hlc(wlv) == *wlv.color_cols) {
   2931        vcol_save_attr = wlv.char_attr;
   2932        wlv.char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), wlv.char_attr);
   2933      }
   2934    }
   2935 
   2936    if (wlv.filler_todo <= 0) {
   2937      // Apply lowest-priority line attr now, so everything can override it.
   2938      int low = wlv.line_attr_lowprio;
   2939      int high = wlv.char_attr;
   2940 
   2941      if (wlv.line_attr_lowprio != 0) {
   2942        HlAttrs line_ae = syn_attr2entry(wlv.line_attr_lowprio);
   2943        HlAttrs char_ae = syn_attr2entry(wlv.char_attr);
   2944        int win_normal_bg = normal_bg;
   2945        int win_normal_cterm_bg = cterm_normal_bg_color;
   2946 
   2947        // Get window-local Normal background (respects 'winhighlight' option).
   2948        if (bg_attr != 0) {
   2949          HlAttrs norm_ae = syn_attr2entry(bg_attr);
   2950          win_normal_bg = norm_ae.rgb_bg_color;
   2951          win_normal_cterm_bg = norm_ae.cterm_bg_color;
   2952        }
   2953        bool char_is_normal_bg = ui_rgb_attached()
   2954                                 ? (char_ae.rgb_bg_color == win_normal_bg)
   2955                                 : (char_ae.cterm_bg_color == win_normal_cterm_bg);
   2956 
   2957        // If line has background (CursorLine) and char's background equals Normal's background,
   2958        // reverse the combination order to let CursorLine override normal_bg.
   2959        if ((line_ae.rgb_bg_color >= 0 || line_ae.cterm_bg_color > 0) && char_is_normal_bg) {
   2960          low = wlv.char_attr;
   2961          high = wlv.line_attr_lowprio;
   2962        }
   2963      }
   2964      wlv.char_attr = hl_combine_attr(low, high);
   2965    }
   2966 
   2967    if (wlv.filler_todo <= 0) {
   2968      vcol_prev = wlv.vcol;
   2969    }
   2970 
   2971    // Store character to be displayed.
   2972    // Skip characters that are left of the screen for 'nowrap'.
   2973    if (wlv.filler_todo > 0) {
   2974      // TODO(bfredl): the main render loop should get called also with the virtual
   2975      // lines chunks, so we get line wrapping and other Nice Things.
   2976    } else if (wlv.skip_cells <= 0) {
   2977      // Store the character.
   2978      linebuf_char[wlv.off] = mb_schar;
   2979      if (multi_attr) {
   2980        linebuf_attr[wlv.off] = multi_attr;
   2981        multi_attr = 0;
   2982      } else {
   2983        linebuf_attr[wlv.off] = wlv.char_attr;
   2984      }
   2985 
   2986      linebuf_vcol[wlv.off] = wlv.vcol;
   2987 
   2988      if (schar_cells(mb_schar) > 1) {
   2989        // Need to fill two screen columns.
   2990        wlv.off++;
   2991        wlv.col++;
   2992        // UTF-8: Put a 0 in the second screen char.
   2993        linebuf_char[wlv.off] = 0;
   2994        linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1];
   2995 
   2996        linebuf_vcol[wlv.off] = ++wlv.vcol;
   2997 
   2998        // When "wlv.tocol" is halfway through a character, set it to the end
   2999        // of the character, otherwise highlighting won't stop.
   3000        if (wlv.tocol == wlv.vcol) {
   3001          wlv.tocol++;
   3002        }
   3003      }
   3004      wlv.off++;
   3005      wlv.col++;
   3006    } else if (wp->w_p_cole > 0 && is_concealing) {
   3007      bool concealed_wide = schar_cells(mb_schar) > 1;
   3008 
   3009      wlv.skip_cells--;
   3010      wlv.vcol_off_co++;
   3011      if (concealed_wide) {
   3012        // When a double-width char is concealed,
   3013        // need to advance one more virtual column.
   3014        wlv.vcol++;
   3015        wlv.vcol_off_co++;
   3016      }
   3017 
   3018      if (wlv.n_extra > 0) {
   3019        wlv.vcol_off_co += wlv.n_extra;
   3020      }
   3021 
   3022      if (is_wrapped) {
   3023        // Special voodoo required if 'wrap' is on.
   3024        //
   3025        // Advance the column indicator to force the line
   3026        // drawing to wrap early. This will make the line
   3027        // take up the same screen space when parts are concealed,
   3028        // so that cursor line computations aren't messed up.
   3029        //
   3030        // To avoid the fictitious advance of 'wlv.col' causing
   3031        // trailing junk to be written out of the screen line
   3032        // we are building, 'boguscols' keeps track of the number
   3033        // of bad columns we have advanced.
   3034        if (wlv.n_extra > 0) {
   3035          wlv.vcol += wlv.n_extra;
   3036          wlv.col += wlv.n_extra;
   3037          wlv.boguscols += wlv.n_extra;
   3038          wlv.n_extra = 0;
   3039          wlv.n_attr = 0;
   3040        }
   3041 
   3042        if (concealed_wide) {
   3043          // Need to fill two screen columns.
   3044          wlv.boguscols++;
   3045          wlv.col++;
   3046        }
   3047 
   3048        wlv.boguscols++;
   3049        wlv.col++;
   3050      } else {
   3051        if (wlv.n_extra > 0) {
   3052          wlv.vcol += wlv.n_extra;
   3053          wlv.n_extra = 0;
   3054          wlv.n_attr = 0;
   3055        }
   3056      }
   3057    } else {
   3058      wlv.skip_cells--;
   3059    }
   3060 
   3061    // The skipped cells need to be accounted for in vcol.
   3062    if (wlv.skipped_cells > 0) {
   3063      wlv.vcol += wlv.skipped_cells;
   3064      wlv.skipped_cells = 0;
   3065    }
   3066 
   3067    // Only advance the "wlv.vcol" when after the 'number' or
   3068    // 'relativenumber' column.
   3069    if (wlv.filler_todo <= 0) {
   3070      wlv.vcol++;
   3071    }
   3072 
   3073    if (vcol_save_attr >= 0) {
   3074      wlv.char_attr = vcol_save_attr;
   3075    }
   3076 
   3077    // restore attributes after "precedes" in 'listchars'
   3078    if (n_attr3 > 0 && --n_attr3 == 0) {
   3079      wlv.char_attr = saved_attr3;
   3080    }
   3081 
   3082    // restore attributes after last 'listchars' or 'number' char
   3083    if (wlv.n_attr > 0 && --wlv.n_attr == 0) {
   3084      wlv.char_attr = saved_attr2;
   3085    }
   3086 
   3087    if (has_decor && wlv.filler_todo <= 0 && wlv.col >= view_width) {
   3088      // At the end of screen line: might need to peek for decorations just after
   3089      // this position.
   3090      if (is_wrapped && wlv.n_extra == 0) {
   3091        decor_redraw_col(wp, (colnr_T)(ptr - line), -3, false, &decor_state);
   3092        // Check position/hiding of virtual text again on next screen line.
   3093        decor_need_recheck = true;
   3094      } else if (!is_wrapped) {
   3095        // Without wrapping, we might need to display right_align and win_col
   3096        // virt_text for the entire text line.
   3097        decor_recheck_draw_col(-1, true, &decor_state);
   3098        decor_redraw_col(wp, MAXCOL, -1, true, &decor_state);
   3099      }
   3100    }
   3101 
   3102 end_check:
   3103    // At end of screen line and there is more to come: Display the line
   3104    // so far.  If there is no more to display it is caught above.
   3105    if (wlv.col >= view_width && (!has_foldtext || virt_line_index >= 0)
   3106        && (wlv.col <= leftcols_width
   3107            || *ptr != NUL
   3108            || wlv.filler_todo > 0
   3109            || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && lcs_eol_todo)
   3110            || (wlv.n_extra != 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL))
   3111            || (may_have_inline_virt && has_more_inline_virt(&wlv, ptr - line)))) {
   3112      int grid_width = wp->w_grid.target->cols;
   3113      const bool wrap = is_wrapped                      // Wrapping enabled (not a folded line).
   3114                        && wlv.filler_todo <= 0         // Not drawing diff filler lines.
   3115                        && lcs_eol_todo                 // Haven't printed the lcs_eol character.
   3116                        && wlv.row != endrow - 1        // Not the last line being displayed.
   3117                        && view_width == grid_width     // Window spans the width of its grid.
   3118                        && !wp->w_p_rl;                 // Not right-to-left.
   3119 
   3120      int draw_col = wlv.col - wlv.boguscols;
   3121 
   3122      for (int i = draw_col; i < view_width; i++) {
   3123        linebuf_vcol[wlv.off + (i - draw_col)] = wlv.vcol - 1;
   3124      }
   3125 
   3126      // Apply 'cursorline' highlight.
   3127      if (wlv.boguscols != 0 && (wlv.line_attr_lowprio != 0 || wlv.line_attr != 0)) {
   3128        int attr = hl_combine_attr(wlv.line_attr_lowprio, wlv.line_attr);
   3129        while (draw_col < view_width) {
   3130          linebuf_char[wlv.off] = schar_from_char(' ');
   3131          linebuf_attr[wlv.off] = attr;
   3132          // linebuf_vcol[] already filled by the for loop above
   3133          wlv.off++;
   3134          draw_col++;
   3135        }
   3136      }
   3137 
   3138      if (virt_line_index >= 0) {
   3139        draw_virt_text_item(buf,
   3140                            virt_line_flags & kVLLeftcol ? 0 : win_col_offset,
   3141                            kv_A(virt_lines, virt_line_index).line,
   3142                            kHlModeReplace,
   3143                            view_width,
   3144                            0,
   3145                            virt_line_flags & kVLScroll ? wp->w_leftcol : 0);
   3146      } else if (wlv.filler_todo <= 0) {
   3147        draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row);
   3148      }
   3149 
   3150      wlv_put_linebuf(wp, &wlv, draw_col, true, bg_attr, wrap ? SLF_WRAP : 0);
   3151      if (wrap) {
   3152        int current_row = wlv.row;
   3153        int dummy_col = 0;  // unused
   3154        ScreenGrid *current_grid = grid_adjust(grid, &current_row, &dummy_col);
   3155 
   3156        // Force a redraw of the first column of the next line.
   3157        current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1;
   3158      }
   3159 
   3160      wlv.boguscols = 0;
   3161      wlv.vcol_off_co = 0;
   3162      wlv.row++;
   3163 
   3164      // When not wrapping and finished diff lines, break here.
   3165      if (!is_wrapped && wlv.filler_todo <= 0) {
   3166        break;
   3167      }
   3168 
   3169      // When the window is too narrow draw all "@" lines.
   3170      if (wlv.col <= leftcols_width) {
   3171        win_draw_end(wp, schar_from_ascii('@'), true, wlv.row, wp->w_view_height, HLF_AT);
   3172        set_empty_rows(wp, wlv.row);
   3173        wlv.row = endrow;
   3174      }
   3175 
   3176      // When line got too long for screen break here.
   3177      if (wlv.row == endrow) {
   3178        wlv.row++;
   3179        break;
   3180      }
   3181 
   3182      win_line_start(wp, &wlv);
   3183      draw_cols = true;
   3184 
   3185      lcs_prec_todo = wp->w_p_lcs_chars.prec;
   3186      if (wlv.filler_todo <= 0) {
   3187        wlv.need_showbreak = true;
   3188      }
   3189      if (statuscol.draw && vim_strchr(p_cpo, CPO_NUMCOL)
   3190          && wlv.row > startrow + wlv.filler_lines) {
   3191        statuscol.draw = false;  // don't draw status column if "n" is in 'cpo'
   3192      }
   3193      wlv.filler_todo--;
   3194      virt_line_index = -1;
   3195      virt_line_flags = 0;
   3196      // When the filler lines are actually below the last line of the
   3197      // file, or we are not drawing text for this line, break here.
   3198      if (wlv.filler_todo == 0 && (wp->w_botfill || !draw_text)) {
   3199        break;
   3200      }
   3201    }
   3202  }     // for every character in the line
   3203 
   3204  clear_virttext(&fold_vt);
   3205  kv_destroy(virt_lines);
   3206  xfree(foldtext_free);
   3207  return wlv.row;
   3208 }
   3209 
   3210 /// Call grid_put_linebuf() using values from "wlv".
   3211 /// Also takes care of putting "<<<" on the first line for 'smoothscroll'
   3212 /// when 'showbreak' is not set.
   3213 ///
   3214 /// @param clear_end  clear until the end of the screen line.
   3215 /// @param flags  for grid_put_linebuf(), but shouldn't contain SLF_RIGHTLEFT.
   3216 static void wlv_put_linebuf(win_T *wp, const winlinevars_T *wlv, int endcol, bool clear_end,
   3217                            int bg_attr, int flags)
   3218 {
   3219  GridView *grid = &wp->w_grid;
   3220 
   3221  int startcol = 0;
   3222  int clear_width = clear_end ? wp->w_view_width : endcol;
   3223 
   3224  assert(!(flags & SLF_RIGHTLEFT));
   3225  if (wp->w_p_rl) {
   3226    linebuf_mirror(&startcol, &endcol, &clear_width, wp->w_view_width);
   3227    flags |= SLF_RIGHTLEFT;
   3228  }
   3229 
   3230  // Take care of putting "<<<" on the first line for 'smoothscroll'.
   3231  if (wlv->row == 0 && wp->w_skipcol > 0
   3232      // do not overwrite the 'showbreak' text with "<<<"
   3233      && *get_showbreak_value(wp) == NUL
   3234      // do not overwrite the 'listchars' "precedes" text with "<<<"
   3235      && !(wp->w_p_list && wp->w_p_lcs_chars.prec != 0)) {
   3236    int off = 0;
   3237    if (wp->w_p_nu && wp->w_p_rnu) {
   3238      // do not overwrite the line number, change "123 text" to "123<<<xt".
   3239      while (off < wp->w_view_width && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) {
   3240        off++;
   3241      }
   3242    }
   3243 
   3244    for (int i = 0; i < 3 && off < wp->w_view_width; i++) {
   3245      if (off + 1 < wp->w_view_width && linebuf_char[off + 1] == NUL) {
   3246        // When the first half of a double-width character is
   3247        // overwritten, change the second half to a space.
   3248        linebuf_char[off + 1] = schar_from_ascii(' ');
   3249      }
   3250      linebuf_char[off] = schar_from_ascii('<');
   3251      linebuf_attr[off] = HL_ATTR(HLF_AT);
   3252      off++;
   3253    }
   3254  }
   3255 
   3256  int row = wlv->row;
   3257  int coloff = 0;
   3258  ScreenGrid *g = grid_adjust(grid, &row, &coloff);
   3259  grid_put_linebuf(g, row, coloff, startcol, endcol, clear_width, bg_attr, 0, wlv->vcol - 1, flags);
   3260 }
   3261 
   3262 static int decor_providers_setup(int rows_to_draw, bool draw_from_line_start, linenr_T lnum,
   3263                                 colnr_T col, win_T *wp)
   3264 {
   3265  // Approximate the number of bytes that will be drawn.
   3266  // Assume we're dealing with 1-cell ascii and ignore
   3267  // the effects of 'linebreak', 'breakindent', etc.
   3268  int rem_vcols;
   3269  if (wp->w_p_wrap) {
   3270    int width = wp->w_view_width - win_col_off(wp);
   3271    int width2 = width + win_col_off2(wp);
   3272 
   3273    int first_row_width = draw_from_line_start ? width : width2;
   3274    rem_vcols = first_row_width + (rows_to_draw - 1) * width2;
   3275  } else {
   3276    rem_vcols = wp->w_view_height - win_col_off(wp);
   3277  }
   3278 
   3279  // Call it here since we need to invalidate the line pointer anyway.
   3280  decor_providers_invoke_line(wp, lnum - 1);
   3281  validate_virtcol(wp);
   3282 
   3283  return invoke_range_next(wp, lnum, col, rem_vcols + 1);
   3284 }
   3285 
   3286 /// @return New begin column, or INT_MAX.
   3287 static int invoke_range_next(win_T *wp, int lnum, colnr_T begin_col, colnr_T col_off)
   3288 {
   3289  char const *const line = ml_get_buf(wp->w_buffer, lnum);
   3290  int const line_len = ml_get_buf_len(wp->w_buffer, lnum);
   3291  col_off = MAX(col_off, 1);
   3292 
   3293  colnr_T new_col;
   3294  if (col_off <= line_len - begin_col) {
   3295    int end_col = begin_col + col_off;
   3296    end_col += mb_off_next(line, line + end_col);
   3297    decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum - 1, end_col);
   3298    validate_virtcol(wp);
   3299    new_col = end_col;
   3300  } else {
   3301    decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum, 0);
   3302    validate_virtcol(wp);
   3303    new_col = INT_MAX;
   3304  }
   3305 
   3306  return new_col;
   3307 }