diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/api/buffer.c | 86 | ||||
| -rw-r--r-- | src/nvim/api/deprecated.c | 2 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.c | 2 | ||||
| -rw-r--r-- | src/nvim/buffer.c | 8 | ||||
| -rw-r--r-- | src/nvim/buffer_defs.h | 6 | ||||
| -rw-r--r-- | src/nvim/charset.c | 16 | ||||
| -rw-r--r-- | src/nvim/decoration.c | 34 | ||||
| -rw-r--r-- | src/nvim/decoration.h | 8 | ||||
| -rw-r--r-- | src/nvim/diff.c | 28 | ||||
| -rw-r--r-- | src/nvim/edit.c | 14 | ||||
| -rw-r--r-- | src/nvim/eval/funcs.c | 4 | ||||
| -rw-r--r-- | src/nvim/extmark.c | 25 | ||||
| -rw-r--r-- | src/nvim/extmark_defs.h | 10 | ||||
| -rw-r--r-- | src/nvim/fold.c | 2 | ||||
| -rw-r--r-- | src/nvim/mouse.c | 17 | ||||
| -rw-r--r-- | src/nvim/move.c | 68 | ||||
| -rw-r--r-- | src/nvim/normal.c | 7 | ||||
| -rw-r--r-- | src/nvim/plines.c | 32 | ||||
| -rw-r--r-- | src/nvim/popupmnu.c | 2 | ||||
| -rw-r--r-- | src/nvim/screen.c | 195 | 
20 files changed, 376 insertions, 190 deletions
| diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 8973f8fef6..0ef2776263 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1415,6 +1415,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e  ///               - end_col : ending col of the mark, 0-based exclusive.  ///               - hl_group : name of the highlight group used to highlight  ///                   this mark. +///               - hl_eol : when true, for a multiline highlight covering the +///                          EOL of a line, continue the highlight for the rest +///                          of the screen line (just like for diff and +///                          cursorline highlight).  ///               - virt_text : virtual text to link to this mark.  ///                   A list of [text, highlight] tuples, each representing a  ///                   text chunk with specified highlight. `highlight` element @@ -1442,10 +1446,28 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e  ///                              default  ///                 - "combine": combine with background text color  ///                 - "blend": blend with background text color. -///               - hl_eol : when true, for a multiline highlight covering the -///                          EOL of a line, continue the highlight for the rest -///                          of the screen line (just like for diff and -///                          cursorline highlight). +/// +///               - virt_lines : virtual lines to add next to this mark +///                   This should be an array over lines, where each line in +///                   turn is an array over [text, highlight] tuples. In +///                   general, buffer and window options do not affect the +///                   display of the text. In particular 'wrap' +///                   and 'linebreak' options do not take effect, so +///                   the number of extra screen lines will always match +///                   the size of the array. However the 'tabstop' buffer +///                   option is still used for hard tabs. By default lines are +///                   placed below the buffer line containing the mark. +/// +///                   Note: currently virtual lines are limited to one block +///                   per buffer. Thus setting a new mark disables any previous +///                   `virt_lines` decoration. However plugins should not rely +///                   on this behaviour, as this limitation is planned to be +///                   removed. +/// +///               - virt_lines_above: place virtual lines above instead. +///               - virt_lines_leftcol: Place extmarks in the leftmost +///                                     column of the window, bypassing +///                                     sign and number columns.  ///  ///               - ephemeral : for use with |nvim_set_decoration_provider|  ///                   callbacks. The mark will only be used for the current @@ -1487,6 +1509,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer    bool end_right_gravity = false;    bool end_gravity_set = false; +  VirtLines virt_lines = KV_INITIAL_VALUE; +  bool virt_lines_above = false; +  bool virt_lines_leftcol = false; +    for (size_t i = 0; i < opts.size; i++) {      String k = opts.items[i].key;      Object *v = &opts.items[i].value; @@ -1584,6 +1610,36 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer        if (ERROR_SET(err)) {          goto error;        } +    } else if (strequal("virt_lines", k.data)) { +      if (v->type != kObjectTypeArray) { +        api_set_error(err, kErrorTypeValidation, +                      "virt_lines is not an Array"); +        goto error; +      } +      Array a = v->data.array; +      for (size_t j = 0; j < a.size; j++) { +        if (a.items[j].type != kObjectTypeArray) { +          api_set_error(err, kErrorTypeValidation, +                        "virt_text_line item is not an Array"); +          goto error; +        } +        int dummig; +        VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig); +        kv_push(virt_lines, jtem); +        if (ERROR_SET(err)) { +          goto error; +        } +      } +    } else if (strequal("virt_lines_above", k.data)) { +      virt_lines_above = api_object_to_bool(*v, "virt_lines_above", false, err); +      if (ERROR_SET(err)) { +        goto error; +      } +    } else if (strequal("virt_lines_leftcol", k.data)) { +      virt_lines_leftcol = api_object_to_bool(*v, "virt_lines_leftcol", false, err); +      if (ERROR_SET(err)) { +        goto error; +      }      } else if (strequal("hl_eol", k.data)) {        decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err);        if (ERROR_SET(err)) { @@ -1721,9 +1777,23 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer        goto error;      } -    id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, -                     line2, col2, d, right_gravity, -                     end_right_gravity, kExtmarkNoUndo); +    if (kv_size(virt_lines) && buf->b_virt_line_mark) { +      mtpos_t pos = marktree_lookup(buf->b_marktree, buf->b_virt_line_mark, NULL); +      clear_virt_lines(buf, pos.row);  // handles pos.row == -1 +    } + +    uint64_t mark = extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, +                                line2, col2, d, right_gravity, +                                end_right_gravity, kExtmarkNoUndo); + +    if (kv_size(virt_lines)) { +      buf->b_virt_lines = virt_lines; +      buf->b_virt_line_mark = mark; +      buf->b_virt_line_pos = -1; +      buf->b_virt_line_above = virt_lines_above; +      buf->b_virt_line_leftcol = virt_lines_leftcol; +      redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(virt_lines_above?0:1))); +    }    }    return (Integer)id; @@ -1827,7 +1897,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In      end_line++;    } -  extmark_set(buf, ns, 0, +  extmark_set(buf, ns, NULL,                (int)line, (colnr_T)col_start,                end_line, (colnr_T)col_end,                decor_hl(hl_id), true, false, kExtmarkNoUndo); diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 21b9db85c0..332fc0ba96 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -150,7 +150,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A    decor->virt_text = virt_text;    decor->virt_text_width = width; -  extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, true, +  extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true,                false, kExtmarkNoUndo);    return src_id;  } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 24d7c92ce3..193f1dd572 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1627,7 +1627,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)        }      } -    char *text = transstr(str.size > 0 ? str.data : "");  // allocates +    char *text = transstr(str.size > 0 ? str.data : "", false);  // allocates      w += (int)mb_string2cells((char_u *)text);      kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index cd84073460..54b5f8283f 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -65,6 +65,7 @@  #include "nvim/os/time.h"  #include "nvim/os_unix.h"  #include "nvim/path.h" +#include "nvim/plines.h"  #include "nvim/quickfix.h"  #include "nvim/regexp.h"  #include "nvim/screen.h" @@ -820,6 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)    uc_clear(&buf->b_ucmds);               // clear local user commands    buf_delete_signs(buf, (char_u *)"*");  // delete any signs    extmark_free_all(buf);                 // delete any extmarks +  clear_virt_lines(buf, -1);    map_clear_int(buf, MAP_ALL_MODES, true, false);    // clear local mappings    map_clear_int(buf, MAP_ALL_MODES, true, true);     // clear local abbrevs    XFREE_CLEAR(buf->b_start_fenc); @@ -3262,7 +3264,7 @@ void maketitle(void)          buf_p += MIN(size, SPACE_FOR_FNAME);        } else {          buf_p += transstr_buf((const char *)path_tail(curbuf->b_fname), -                              buf_p, SPACE_FOR_FNAME + 1); +                              buf_p, SPACE_FOR_FNAME + 1, true);        }        switch (bufIsChanged(curbuf) @@ -3311,7 +3313,7 @@ void maketitle(void)          // room for the server name.  When there is no room (very long          // file name) use (...).          if ((size_t)(buf_p - buf) < SPACE_FOR_DIR) { -          char *const tbuf = transstr(buf_p); +          char *const tbuf = transstr(buf_p, true);            const size_t free_space = SPACE_FOR_DIR - (size_t)(buf_p - buf) + 1;            const size_t dir_len = xstrlcpy(buf_p, tbuf, free_space);            buf_p += MIN(dir_len, free_space - 1); @@ -4657,7 +4659,7 @@ void get_rel_pos(win_T *wp, char_u *buf, int buflen)    long below;          // number of lines below window    above = wp->w_topline - 1; -  above += diff_check_fill(wp, wp->w_topline) - wp->w_topfill; +  above += win_get_fill(wp, wp->w_topline) - wp->w_topfill;    if (wp->w_topline == 1 && wp->w_topfill >= 1) {      // All buffer lines are displayed and there is an indication      // of filler lines, that can be considered seeing all lines. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index ba2bcd7223..0264a60117 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -868,6 +868,12 @@ struct file_buffer {    Map(uint64_t, ExtmarkItem) b_extmark_index[1];    Map(uint64_t, ExtmarkNs) b_extmark_ns[1];         // extmark namespaces +  VirtLines b_virt_lines; +  uint64_t b_virt_line_mark; +  int b_virt_line_pos; +  bool b_virt_line_above; +  bool b_virt_line_leftcol; +    // array of channel_id:s which have asked to receive updates for this    // buffer.    kvec_t(uint64_t) update_channels; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 0ad7dddd33..f899ebf57c 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -310,7 +310,7 @@ void trans_characters(char_u *buf, int bufsize)  ///  /// @return number of bytes needed to hold a translation of `s`, NUL byte not  ///         included. -size_t transstr_len(const char *const s) +size_t transstr_len(const char *const s, bool untab)    FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE  {    const char *p = s; @@ -331,6 +331,9 @@ size_t transstr_len(const char *const s)          }        }        p += l; +    } else if (*p == TAB && !untab) { +      len += 1; +      p++;      } else {        const int b2c_l = byte2cells((uint8_t)(*p++));        // Illegal byte sequence may occupy up to 4 characters. @@ -346,9 +349,10 @@ size_t transstr_len(const char *const s)  /// @param[out]  buf  Buffer to which result should be saved.  /// @param[in]  len  Buffer length. Resulting string may not occupy more then  ///                  len - 1 bytes (one for trailing NUL byte). +/// @param[in]  untab  remove tab characters  ///  /// @return length of the resulting string, without the NUL byte. -size_t transstr_buf(const char *const s, char *const buf, const size_t len) +size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool untab)    FUNC_ATTR_NONNULL_ALL  {    const char *p = s; @@ -379,6 +383,8 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len)          }        }        p += l; +    } else if (*p == TAB && !untab) { +      *buf_p++ = *p++;      } else {        const char *const tb = (const char *)transchar_byte((uint8_t)(*p++));        const size_t tb_len = strlen(tb); @@ -401,14 +407,14 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len)  /// @param[in]  s  String to replace characters from.  ///  /// @return [allocated] translated string -char *transstr(const char *const s) +char *transstr(const char *const s, bool untab)    FUNC_ATTR_NONNULL_RET  {    // Compute the length of the result, taking account of unprintable    // multi-byte characters. -  const size_t len = transstr_len((const char *)s) + 1; +  const size_t len = transstr_len((const char *)s, untab) + 1;    char *const buf = xmalloc(len); -  transstr_buf(s, buf, len); +  transstr_buf(s, buf, len, untab);    return buf;  } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 0f21b47261..7e2b6a666e 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -59,7 +59,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start        hl_start = pos_start.col + offset;        hl_end = pos_end.col + offset;      } -    (void)extmark_set(buf, (uint64_t)src_id, 0, +    (void)extmark_set(buf, (uint64_t)src_id, NULL,                        (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,                        decor, true, false, kExtmarkNoUndo);    } @@ -412,3 +412,35 @@ void decor_free_all_mem(void)    }    kv_destroy(decor_providers);  } + + +int decor_virtual_lines(win_T *wp, linenr_T lnum) +{ +  buf_T *buf = wp->w_buffer; +  if (!buf->b_virt_line_mark) { +    return 0; +  } +  if (buf->b_virt_line_pos < 0) { +      mtpos_t pos = marktree_lookup(buf->b_marktree, buf->b_virt_line_mark, NULL); +      if (pos.row < 0) { +        buf->b_virt_line_mark = 0; +      } +      buf->b_virt_line_pos = pos.row + (buf->b_virt_line_above ? 0 : 1); +  } + +  return (lnum-1 == buf->b_virt_line_pos) ? (int)kv_size(buf->b_virt_lines) : 0; +} + +void clear_virt_lines(buf_T *buf, int row) +{ +  if (row > -1) { +    redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, +                                   row+1+(buf->b_virt_line_above?0:1))); +  } +  for (size_t i = 0; i < kv_size(buf->b_virt_lines); i++) { +    clear_virttext(&kv_A(buf->b_virt_lines, i)); +  } +  kv_destroy(buf->b_virt_lines);  // re-initializes +  buf->b_virt_line_pos = -1; +  buf->b_virt_line_mark = 0; +} diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 28dabeeada..35f5af87ed 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -7,14 +7,6 @@  // actual Decoration data is in extmark_defs.h -typedef struct { -  char *text; -  int hl_id; -} VirtTextChunk; - -typedef kvec_t(VirtTextChunk) VirtText; -#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) -  typedef uint16_t DecorPriority;  #define DECOR_PRIORITY_BASE 0x1000 diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 93c0d636fa..5c43b2498e 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1985,26 +1985,6 @@ static int diff_cmp(char_u *s1, char_u *s2)    return 0;  } -/// Return the number of filler lines above "lnum". -/// -/// @param wp -/// @param lnum -/// -/// @return Number of filler lines above lnum -int diff_check_fill(win_T *wp, linenr_T lnum) -{ -  // be quick when there are no filler lines -  if (!(diff_flags & DIFF_FILLER)) { -    return 0; -  } -  int n = diff_check(wp, lnum); - -  if (n <= 0) { -    return 0; -  } -  return n; -} -  /// Set the topline of "towin" to match the position in "fromwin", so that they  /// show the same diff'ed lines.  /// @@ -2030,6 +2010,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin)    }    towin->w_topfill = 0; +    // search for a change that includes "lnum" in the list of diffblocks.    for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) {      if (lnum <= dp->df_lnum[fromidx] + dp->df_count[fromidx]) { @@ -2255,6 +2236,13 @@ bool diffopt_closeoff(void)    return (diff_flags & DIFF_CLOSE_OFF) != 0;  } +// Return true if 'diffopt' contains "filler". +bool diffopt_filler(void) +  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ +  return (diff_flags & DIFF_FILLER) != 0; +} +  /// Find the difference within a changed line.  ///  /// @param  wp      window whose current buffer to check diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 5c4030d8d5..085bbc2409 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3344,12 +3344,10 @@ static char_u *ins_compl_mode(void)   */  static int ins_compl_bs(void)  { -  char_u *line; -  char_u *p; - -  line = get_cursor_line_ptr(); -  p = line + curwin->w_cursor.col; +  char_u *line = get_cursor_line_ptr(); +  char_u *p = line + curwin->w_cursor.col;    MB_PTR_BACK(line, p); +  ptrdiff_t p_off = p - line;    // Stop completion when the whole word was deleted.  For Omni completion    // allow the word to be deleted, we won't match everything. @@ -3369,8 +3367,12 @@ static int ins_compl_bs(void)      ins_compl_restart();    } +  // ins_compl_restart() calls update_screen(0) which may invalidate the pointer +  // TODO(bfredl): get rid of random update_screen() calls deep inside completion logic +  line = get_cursor_line_ptr(); +    xfree(compl_leader); -  compl_leader = vim_strnsave(line + compl_col, (int)(p - line) - compl_col); +  compl_leader = vim_strnsave(line + compl_col, (int)p_off - compl_col);    ins_compl_new_leader();    if (compl_shown_match != NULL) {      // Make sure current match is not a hidden item. diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e2d485d892..9feecadb6f 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1802,7 +1802,7 @@ static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); +  rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars)));  }  /* @@ -10801,7 +10801,7 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    rettv->v_type = VAR_STRING; -  rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0])); +  rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]), true);  }  /* diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 819b8ad3dc..cf01c305d7 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -56,8 +56,8 @@ static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {  /// Create or update an extmark  ///  /// must not be used during iteration! -/// @returns the mark id -uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, int row, colnr_T col, int end_row, +/// @returns the internal mark id +uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T col, int end_row,                       colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,                       ExtmarkOp op)  { @@ -65,6 +65,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, int row, colnr_T c    assert(ns != NULL);    mtpos_t old_pos;    uint64_t mark = 0; +  uint64_t id = idp ? *idp : 0;    if (id == 0) {      id = ns->free_id++; @@ -118,7 +119,11 @@ revised:    if (decor) {      decor_redraw(buf, row, end_row > -1 ? end_row : row, decor);    } -  return id; + +  if (idp) { +    *idp = id; +  } +  return mark;  }  static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) @@ -169,6 +174,10 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)      decor_free(item.decor);    } +  if (mark == buf->b_virt_line_mark) { +    clear_virt_lines(buf, pos.row); +  } +    map_del(uint64_t, uint64_t)(ns->map, id);    map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); @@ -227,6 +236,9 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r      }      uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; +    if (start_id == buf->b_virt_line_mark) { +      clear_virt_lines(buf, mark.row); +    }      ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,                                                        start_id); @@ -496,6 +508,7 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)                            kExtmarkNoUndo);      }    } +  curbuf->b_virt_line_pos = -1;  } @@ -574,7 +587,8 @@ void extmark_splice_impl(buf_T *buf, int start_row, colnr_T start_col, bcount_t                           int old_row, colnr_T old_col, bcount_t old_byte, int new_row,                           colnr_T new_col, bcount_t new_byte, ExtmarkOp undo)  { -  curbuf->deleted_bytes2 = 0; +  buf->deleted_bytes2 = 0; +  buf->b_virt_line_pos = -1;    buf_updates_send_splice(buf, start_row, start_col, start_byte,                            old_row, old_col, old_byte,                            new_row, new_col, new_byte); @@ -665,7 +679,8 @@ void extmark_move_region(buf_T *buf, int start_row, colnr_T start_col, bcount_t                           int extent_row, colnr_T extent_col, bcount_t extent_byte, int new_row,                           colnr_T new_col, bcount_t new_byte, ExtmarkOp undo)  { -  curbuf->deleted_bytes2 = 0; +  buf->deleted_bytes2 = 0; +  buf->b_virt_line_pos = -1;    // TODO(bfredl): this is not synced to the buffer state inside the callback.    // But unless we make the undo implementation smarter, this is not ensured    // anyway. diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index b5d91382ec..2da4f3dc00 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -6,6 +6,16 @@  typedef struct Decoration Decoration; +typedef struct { +  char *text; +  int hl_id; +} VirtTextChunk; + +typedef kvec_t(VirtTextChunk) VirtText; +#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) +typedef kvec_t(VirtText) VirtLines; + +  typedef struct  {    uint64_t ns_id; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 7a017702ee..f22fa449ea 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1875,7 +1875,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin          }        }        if (*p != NUL) { -        p = (char_u *)transstr((const char *)text); +        p = (char_u *)transstr((const char *)text, true);          xfree(text);          text = p;        } diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index b65d87e617..cf463fd40a 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -241,7 +241,7 @@ retnomove:      if (row < 0) {        count = 0;        for (first = true; curwin->w_topline > 1; ) { -        if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) { +        if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) {            count++;          } else {            count += plines_win(curwin, curwin->w_topline - 1, true); @@ -251,8 +251,8 @@ retnomove:          }          first = false;          (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); -        if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) { -          ++curwin->w_topfill; +        if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { +          curwin->w_topfill++;          } else {            --curwin->w_topline;            curwin->w_topfill = 0; @@ -283,11 +283,10 @@ retnomove:          }          if (curwin->w_topfill > 0) { -          --curwin->w_topfill; +          curwin->w_topfill--;          } else { -          ++curwin->w_topline; -          curwin->w_topfill = -            diff_check_fill(curwin, curwin->w_topline); +          curwin->w_topline++; +          curwin->w_topfill = win_get_fill(curwin, curwin->w_topline);          }        }        check_topfill(curwin, false); @@ -373,12 +372,12 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)    while (row > 0) {      // Don't include filler lines in "count" -    if (win->w_p_diff +    if (win_may_fill(win)          && !hasFoldingWin(win, lnum, NULL, NULL, true, NULL)) {        if (lnum == win->w_topline) {          row -= win->w_topfill;        } else { -        row -= diff_check_fill(win, lnum); +        row -= win_get_fill(win, lnum);        }        count = plines_win_nofill(win, lnum, true);      } else { diff --git a/src/nvim/move.c b/src/nvim/move.c index 2f64f9ab29..ca3dd34204 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -197,7 +197,7 @@ void update_topline(win_T *wp)        }      }      // Check if there are more filler lines than allowed. -    if (!check_topline && wp->w_topfill > diff_check_fill(wp, wp->w_topline)) { +    if (!check_topline && wp->w_topfill > win_get_fill(wp, wp->w_topline)) {        check_topline = true;      } @@ -582,8 +582,7 @@ static void curs_rows(win_T *wp)          --i;                            // hold at inserted lines        }      } -    if (valid -        && (lnum != wp->w_topline || !wp->w_p_diff)) { +    if (valid && (lnum != wp->w_topline || !win_may_fill(wp))) {        lnum = wp->w_lines[i].wl_lastlnum + 1;        // Cursor inside folded lines, don't count this row        if (lnum > wp->w_cursor.lnum) { @@ -854,7 +853,7 @@ void curs_columns(win_T *wp, int may_scroll)    if (wp->w_cursor.lnum == wp->w_topline) {      wp->w_wrow += wp->w_topfill;    } else { -    wp->w_wrow += diff_check_fill(wp, wp->w_cursor.lnum); +    wp->w_wrow += win_get_fill(wp, wp->w_cursor.lnum);    }    prev_skipcol = wp->w_skipcol; @@ -1041,7 +1040,7 @@ bool scrolldown(long line_count, int byfold)    (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);    validate_cursor();            // w_wrow needs to be valid    while (line_count-- > 0) { -    if (curwin->w_topfill < diff_check(curwin, curwin->w_topline) +    if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)          && curwin->w_topfill < curwin->w_height_inner - 1) {        curwin->w_topfill++;        done++; @@ -1122,7 +1121,7 @@ bool scrollup(long line_count, int byfold)    linenr_T botline = curwin->w_botline;    if ((byfold && hasAnyFolding(curwin)) -      || curwin->w_p_diff) { +      || win_may_fill(curwin)) {      // count each sequence of folded lines as one logical line      linenr_T lnum = curwin->w_topline;      while (line_count--) { @@ -1135,8 +1134,8 @@ bool scrollup(long line_count, int byfold)          if (lnum >= curbuf->b_ml.ml_line_count) {            break;          } -        ++lnum; -        curwin->w_topfill = diff_check_fill(curwin, lnum); +        lnum++; +        curwin->w_topfill = win_get_fill(curwin, lnum);        }      }      // approximate w_botline @@ -1207,7 +1206,7 @@ static void max_topfill(void)    if (n >= curwin->w_height_inner) {      curwin->w_topfill = 0;    } else { -    curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline); +    curwin->w_topfill = win_get_fill(curwin, curwin->w_topline);      if (curwin->w_topfill + n > curwin->w_height_inner) {        curwin->w_topfill = curwin->w_height_inner - n;      } @@ -1220,8 +1219,7 @@ static void max_topfill(void)   */  void scrolldown_clamp(void)  { -  int can_fill = (curwin->w_topfill -                  < diff_check_fill(curwin, curwin->w_topline)); +  int can_fill = (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline));    if (curwin->w_topline <= 1        && !can_fill) { @@ -1302,7 +1300,7 @@ void scrollup_clamp(void)   */  static void topline_back(win_T *wp, lineoff_T *lp)  { -  if (lp->fill < diff_check_fill(wp, lp->lnum)) { +  if (lp->fill < win_get_fill(wp, lp->lnum)) {      // Add a filler line      lp->fill++;      lp->height = 1; @@ -1328,7 +1326,7 @@ static void topline_back(win_T *wp, lineoff_T *lp)   */  static void botline_forw(win_T *wp, lineoff_T *lp)  { -  if (lp->fill < diff_check_fill(wp, lp->lnum + 1)) { +  if (lp->fill < win_get_fill(wp, lp->lnum + 1)) {      // Add a filler line.      lp->fill++;      lp->height = 1; @@ -1355,8 +1353,8 @@ static void botline_forw(win_T *wp, lineoff_T *lp)  static void botline_topline(lineoff_T *lp)  {    if (lp->fill > 0) { -    ++lp->lnum; -    lp->fill = diff_check_fill(curwin, lp->lnum) - lp->fill + 1; +    lp->lnum++; +    lp->fill = win_get_fill(curwin, lp->lnum) - lp->fill + 1;    }  } @@ -1368,8 +1366,8 @@ static void botline_topline(lineoff_T *lp)  static void topline_botline(lineoff_T *lp)  {    if (lp->fill > 0) { -    lp->fill = diff_check_fill(curwin, lp->lnum) - lp->fill + 1; -    --lp->lnum; +    lp->fill = win_get_fill(curwin, lp->lnum) - lp->fill + 1; +    lp->lnum--;    }  } @@ -1417,7 +1415,7 @@ void scroll_cursor_top(int min_scroll, int always)    // "used" already contains the number of filler lines above, don't add it    // again.    // Hide filler lines above cursor line by adding them to "extra". -  int extra = diff_check_fill(curwin, curwin->w_cursor.lnum); +  int extra = win_get_fill(curwin, curwin->w_cursor.lnum);    /*     * Check if the lines from "top" to "bot" fit in the window.  If they do, @@ -1475,7 +1473,7 @@ void scroll_cursor_top(int min_scroll, int always)      if (curwin->w_topline > curwin->w_cursor.lnum) {        curwin->w_topline = curwin->w_cursor.lnum;      } -    curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline); +    curwin->w_topfill = win_get_fill(curwin, curwin->w_topline);      if (curwin->w_topfill > 0 && extra > off) {        curwin->w_topfill -= extra - off;        if (curwin->w_topfill < 0) { @@ -1505,7 +1503,7 @@ void set_empty_rows(win_T *wp, int used)    } else {      wp->w_empty_rows = wp->w_height_inner - used;      if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { -      wp->w_filler_rows = diff_check_fill(wp, wp->w_botline); +      wp->w_filler_rows = win_get_fill(wp, wp->w_botline);        if (wp->w_empty_rows > wp->w_filler_rows) {          wp->w_empty_rows -= wp->w_filler_rows;        } else { @@ -1590,7 +1588,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)    }    loff.fill = 0;    boff.fill = 0; -  fill_below_window = diff_check_fill(curwin, curwin->w_botline) +  fill_below_window = win_get_fill(curwin, curwin->w_botline)                        - curwin->w_filler_rows;    while (loff.lnum > 1) { @@ -1835,7 +1833,7 @@ void cursor_correct(void)        // Count filler lines below this line as context.        if (topline < botline) { -        above += diff_check_fill(curwin, topline + 1); +        above += win_get_fill(curwin, topline + 1);        }        ++topline;      } @@ -1889,9 +1887,7 @@ int onepage(Direction dir, long count)          ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - so)             && curwin->w_botline > curbuf->b_ml.ml_line_count)          : (curwin->w_topline == 1 -           && curwin->w_topfill == -           diff_check_fill(curwin, curwin->w_topline) -           )) { +           && curwin->w_topfill == win_get_fill(curwin, curwin->w_topline))) {        beep_flush();        retval = FAIL;        break; @@ -1919,7 +1915,7 @@ int onepage(Direction dir, long count)          /* For the overlap, start with the line just below the window           * and go upwards. */          loff.lnum = curwin->w_botline; -        loff.fill = diff_check_fill(curwin, loff.lnum) +        loff.fill = win_get_fill(curwin, loff.lnum)                      - curwin->w_filler_rows;          get_scroll_overlap(&loff, -1);          curwin->w_topline = loff.lnum; @@ -1956,8 +1952,7 @@ int onepage(Direction dir, long count)         * line at the bottom of the window.  Make sure this results in         * the same line as before doing CTRL-F. */        loff.lnum = curwin->w_topline - 1; -      loff.fill = diff_check_fill(curwin, loff.lnum + 1) -                  - curwin->w_topfill; +      loff.fill = win_get_fill(curwin, loff.lnum + 1) - curwin->w_topfill;        get_scroll_overlap(&loff, 1);        if (loff.lnum >= curbuf->b_ml.ml_line_count) { @@ -2000,8 +1995,7 @@ int onepage(Direction dir, long count)            /* First try using the maximum number of filler lines.  If             * that's not enough, backup one line. */            loff.fill = curwin->w_topfill; -          if (curwin->w_topfill < diff_check_fill(curwin, -                                                  curwin->w_topline)) { +          if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) {              max_topfill();            }            if (curwin->w_topfill == loff.fill) { @@ -2146,8 +2140,8 @@ void halfpage(bool flag, linenr_T Prenum)            break;          }          (void)hasFolding(curwin->w_topline, NULL, &curwin->w_topline); -        ++curwin->w_topline; -        curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline); +        curwin->w_topline++; +        curwin->w_topfill = win_get_fill(curwin, curwin->w_topline);          if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {            ++curwin->w_cursor.lnum; @@ -2158,11 +2152,9 @@ void halfpage(bool flag, linenr_T Prenum)        curwin->w_valid &= ~(VALID_CROW|VALID_WROW);        scrolled += i; -      /* -       * Correct w_botline for changed w_topline. -       * Won't work when there are filler lines. -       */ -      if (curwin->w_p_diff) { +      // Correct w_botline for changed w_topline. +      // Won't work when there are filler lines. +      if (win_may_fill(curwin)) {          curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);        } else {          room += i; @@ -2197,7 +2189,7 @@ void halfpage(bool flag, linenr_T Prenum)       * scroll the text down       */      while (n > 0 && curwin->w_topline > 1) { -      if (curwin->w_topfill < diff_check_fill(curwin, curwin->w_topline)) { +      if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) {          i = 1;          n--;          curwin->w_topfill++; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index b8a62a8fea..17ec5cd3be 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -5261,16 +5261,15 @@ static void nv_scroll(cmdarg_T *cap)    } else {      if (cap->cmdchar == 'M') {        // Don't count filler lines above the window. -      used -= diff_check_fill(curwin, curwin->w_topline) +      used -= win_get_fill(curwin, curwin->w_topline)                - curwin->w_topfill;        validate_botline(curwin);  // make sure w_empty_rows is valid        half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2;        for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) {          // Count half he number of filler lines to be "below this          // line" and half to be "above the next line". -        if (n > 0 && used + diff_check_fill(curwin, curwin->w_topline -                                            + n) / 2 >= half) { -          --n; +        if (n > 0 && used + win_get_fill(curwin, curwin->w_topline + n) / 2 >= half) { +          n--;            break;          }          used += plines_win(curwin, curwin->w_topline + n, true); diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 28138af13c..5b0418ed92 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -13,6 +13,7 @@  #include "nvim/buffer.h"  #include "nvim/charset.h"  #include "nvim/cursor.h" +#include "nvim/decoration.h"  #include "nvim/diff.h"  #include "nvim/fold.h"  #include "nvim/func_attr.h" @@ -41,7 +42,34 @@ int plines_win(win_T *wp, linenr_T lnum, bool winheight)  {    // Check for filler lines above this buffer line.  When folded the result    // is one line anyway. -  return plines_win_nofill(wp, lnum, winheight) + diff_check_fill(wp, lnum); +  return plines_win_nofill(wp, lnum, winheight) + win_get_fill(wp, lnum); +} + + +/// Return the number of filler lines above "lnum". +/// +/// @param wp +/// @param lnum +/// +/// @return Number of filler lines above lnum +int win_get_fill(win_T *wp, linenr_T lnum) +{ +  int virt_lines = decor_virtual_lines(wp, lnum); + +  // be quick when there are no filler lines +  if (diffopt_filler()) { +    int n = diff_check(wp, lnum); + +    if (n > 0) { +      return virt_lines+n; +    } +  } +  return virt_lines; +} + +bool win_may_fill(win_T *wp) +{ +  return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_mark;  }  /// @param winheight when true limit to window height @@ -107,7 +135,7 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column)  {    // Check for filler lines above this buffer line.  When folded the result    // is one line anyway. -  int lines = diff_check_fill(wp, lnum); +  int lines = win_get_fill(wp, lnum);    if (!wp->w_p_wrap) {      return lines + 1; diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index aeb2c8c44a..606c03f838 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -514,7 +514,7 @@ void pum_redraw(void)              char_u saved = *p;              *p = NUL; -            st = (char_u *)transstr((const char *)s); +            st = (char_u *)transstr((const char *)s, true);              *p = saved;              if (pum_rl) { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 5d887444f5..057e800ee2 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1003,8 +1003,7 @@ static void win_update(win_T *wp, Providers *providers)          i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1);          // insert extra lines for previously invisible filler lines          if (wp->w_lines[0].wl_lnum != wp->w_topline) { -          i += diff_check_fill(wp, wp->w_lines[0].wl_lnum) -               - wp->w_old_topfill; +          i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill;          }          if (i != 0 && i < wp->w_grid.Rows - 2) {  // less than a screen off            // Try to insert the correct number of lines. @@ -1067,7 +1066,7 @@ static void win_update(win_T *wp, Providers *providers)          if (wp->w_lines[0].wl_lnum == wp->w_topline) {            row += wp->w_old_topfill;          } else { -          row += diff_check_fill(wp, wp->w_topline); +          row += win_get_fill(wp, wp->w_topline);          }          // ... but don't delete new filler lines.          row -= wp->w_topfill; @@ -1101,12 +1100,12 @@ static void win_update(win_T *wp, Providers *providers)                break;              }            } -          /* Correct the first entry for filler lines at the top -           * when it won't get updated below. */ -          if (wp->w_p_diff && bot_start > 0) { -            wp->w_lines[0].wl_size = -              plines_win_nofill(wp, wp->w_topline, true) -              + wp->w_topfill; + +          // Correct the first entry for filler lines at the top +          // when it won't get updated below. +          if (win_may_fill(wp) && bot_start > 0) { +            wp->w_lines[0].wl_size = (plines_win_nofill(wp, wp->w_topline, true) +                                      + wp->w_topfill);            }          }        } @@ -1564,7 +1563,7 @@ static void win_update(win_T *wp, Providers *providers)            && lnum > wp->w_topline            && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))            && srow + wp->w_lines[idx].wl_size > wp->w_grid.Rows -          && diff_check_fill(wp, lnum) == 0) { +          && win_get_fill(wp, lnum) == 0) {          // This line is not going to fit.  Don't draw anything here,          // will draw "@  " lines below.          row = wp->w_grid.Rows + 1; @@ -1664,7 +1663,7 @@ static void win_update(win_T *wp, Providers *providers)         * Don't overwrite it, it can be edited.         */        wp->w_botline = lnum + 1; -    } else if (diff_check_fill(wp, lnum) >= wp->w_grid.Rows - srow) { +    } else if (win_get_fill(wp, lnum) >= wp->w_grid.Rows - srow) {        // Window ends in filler lines.        wp->w_botline = lnum;        wp->w_filler_rows = wp->w_grid.Rows - srow; @@ -1691,7 +1690,7 @@ static void win_update(win_T *wp, Providers *providers)    } else {      if (eof) {  // we hit the end of the file        wp->w_botline = buf->b_ml.ml_line_count + 1; -      j = diff_check_fill(wp, wp->w_botline); +      j = win_get_fill(wp, wp->w_botline);        if (j > 0 && !wp->w_botfill) {          // Display filler text below last line. win_line() will check          // for ml_line_count+1 and only draw filler lines @@ -1866,7 +1865,7 @@ static int compute_foldcolumn(win_T *wp, int col)  /// Put a single char from an UTF-8 buffer into a line buffer.  ///  /// Handles composing chars and arabic shaping state. -static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl) +static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol)  {    const char_u *p = (char_u *)s->p;    int cells = utf_ptr2cells(p); @@ -1876,7 +1875,13 @@ static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl)      return -1;    }    u8c = utfc_ptr2char(p, u8cc); -  if (*p < 0x80 && u8cc[0] == 0) { +  if (*p == TAB) { +    cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells); +    for (int c = 0; c < cells; c++) { +      schar_from_ascii(dest[c], ' '); +    } +    goto done; +  } else if (*p < 0x80 && u8cc[0] == 0) {      schar_from_ascii(dest[0], *p);      s->prev_c = u8c;    } else { @@ -1909,6 +1914,7 @@ static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl)    if (cells > 1) {      dest[1][0] = 0;    } +done:    s->p += c_len;    return cells;  } @@ -2366,8 +2372,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc      filler_lines = 0;      area_highlighting = true;    } +  int virtual_lines = decor_virtual_lines(wp, lnum); +  filler_lines += virtual_lines;    if (lnum == wp->w_topline) {      filler_lines = wp->w_topfill; +    virtual_lines = MIN(virtual_lines, filler_lines);    }    filler_todo = filler_lines; @@ -2895,7 +2904,18 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc        if (draw_state == WL_SBR - 1 && n_extra == 0) {          draw_state = WL_SBR; -        if (filler_todo > 0) { +        if (filler_todo > filler_lines - virtual_lines) { +          // TODO(bfredl): check this doesn't inhibit TUI-style +          //               clear-to-end-of-line. +          c_extra = ' '; +          c_final = NUL; +          if (wp->w_p_rl) { +            n_extra = col + 1; +          } else { +            n_extra = grid->Columns - col; +          } +          char_attr = 0; +        } else if (filler_todo > 0) {            // draw "deleted" diff line(s)            if (char2cells(wp->w_p_fcs_chars.diff) > 1) {              c_extra = '-'; @@ -4402,7 +4422,19 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc                    && !wp->w_p_rl;              // Not right-to-left.        int draw_col = col - boguscols; -      draw_virt_text(buf, win_col_offset, &draw_col, grid->Columns); +      if (filler_todo > 0) { +        int index = filler_todo - (filler_lines - virtual_lines); +        if (index > 0) { +          int fpos = kv_size(buf->b_virt_lines) - index; +          assert(fpos >= 0); +          int offset = buf->b_virt_line_leftcol ? 0 : win_col_offset; +          draw_virt_text_item(buf, offset, kv_A(buf->b_virt_lines, fpos), +                              kHlModeReplace, grid->Columns, offset); +        } +      } else { +        draw_virt_text(buf, win_col_offset, &draw_col, grid->Columns); +      } +        grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl,                         wp, wp->w_hl_attr_normal, wrap);        if (wrap) { @@ -4485,67 +4517,80 @@ void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col)    bool do_eol = state->eol_col > -1;    for (size_t i = 0; i < kv_size(state->active); i++) {      DecorRange *item = &kv_A(state->active, i); -    if (item->start_row == state->row && kv_size(item->decor.virt_text)) { -      if (item->win_col == -1) { -        if (item->decor.virt_text_pos == kVTRightAlign) { -          right_pos -= item->decor.virt_text_width; -          item->win_col = right_pos; -        } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { -          item->win_col = state->eol_col; -          state->eol_col += item->decor.virt_text_width; -        } else if (item->decor.virt_text_pos == kVTWinCol) { -          item->win_col = MAX(item->decor.col+col_off, 0); -        } -      } -      if (item->win_col < 0) { -        continue; -      } -      VirtText vt = item->decor.virt_text; -      HlMode hl_mode = item->decor.hl_mode; -      LineState s = LINE_STATE(""); -      int virt_attr = 0; -      int col = item->win_col; -      size_t virt_pos = 0; -      item->win_col = -2;  // deactivate - -      while (col < max_col) { -        if (!*s.p) { -          if (virt_pos >= kv_size(vt)) { -            break; -          } -          virt_attr = 0; -          do { -            s.p = kv_A(vt, virt_pos).text; -            int hl_id = kv_A(vt, virt_pos).hl_id; -            virt_attr = hl_combine_attr(virt_attr, -                                        hl_id > 0 ? syn_id2attr(hl_id) : 0); -            virt_pos++; -          } while (!s.p && virt_pos < kv_size(vt)); -          if (!s.p) { -            break; -          } -        } -        int attr; -        bool through = false; -        if (hl_mode == kHlModeCombine) { -          attr = hl_combine_attr(linebuf_attr[col], virt_attr); -        } else if (hl_mode == kHlModeBlend) { -          through = (*s.p == ' '); -          attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); -        } else { -          attr = virt_attr; -        } -        schar_T dummy[2]; -        int cells = line_putchar(&s, through ? dummy : &linebuf_char[col], -                                 max_col-col, false); -        linebuf_attr[col++] = attr; -        if (cells > 1) { -          linebuf_attr[col++] = attr; -        } +    if (!(item->start_row == state->row && kv_size(item->decor.virt_text))) { +      continue; +    } +    if (item->win_col == -1) { +      if (item->decor.virt_text_pos == kVTRightAlign) { +        right_pos -= item->decor.virt_text_width; +        item->win_col = right_pos; +      } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { +        item->win_col = state->eol_col; +      } else if (item->decor.virt_text_pos == kVTWinCol) { +        item->win_col = MAX(item->decor.col+col_off, 0); +      } +    } +    if (item->win_col < 0) { +      continue; +    } + +    int col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, +                                  item->decor.hl_mode, max_col, item->win_col-col_off); +    item->win_col = -2;  // deactivate +    if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { +      state->eol_col = col+1; +    } + +    *end_col = MAX(*end_col, col); +  } +} + +static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, +                               int max_col, int vcol) +{ +  LineState s = LINE_STATE(""); +  int virt_attr = 0; +  size_t virt_pos = 0; + +  while (col < max_col) { +    if (!*s.p) { +      if (virt_pos >= kv_size(vt)) { +        break; +      } +      virt_attr = 0; +      do { +        s.p = kv_A(vt, virt_pos).text; +        int hl_id = kv_A(vt, virt_pos).hl_id; +        virt_attr = hl_combine_attr(virt_attr, +                                    hl_id > 0 ? syn_id2attr(hl_id) : 0); +        virt_pos++; +      } while (!s.p && virt_pos < kv_size(vt)); +      if (!s.p) { +        break;        } -      *end_col = MAX(*end_col, col);      } +    int attr; +    bool through = false; +    if (hl_mode == kHlModeCombine) { +      attr = hl_combine_attr(linebuf_attr[col], virt_attr); +    } else if (hl_mode == kHlModeBlend) { +      through = (*s.p == ' '); +      attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); +    } else { +      attr = virt_attr; +    } +    schar_T dummy[2]; +    int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], +                             max_col-col, false, vcol); +    // if we failed to emit a char, we still need to advance +    cells = MAX(cells, 1); + +    for (int c = 0; c < cells; c++) { +      linebuf_attr[col++] = attr; +    } +    vcol += cells;    } +  return col;  }  /// Determine if dedicated window grid should be used or the default_grid @@ -5489,7 +5534,7 @@ static void win_redr_custom(win_T *wp, bool draw_ruler)    ewp->w_p_crb = p_crb_save;    // Make all characters printable. -  p = (char_u *)transstr((const char *)buf); +  p = (char_u *)transstr((const char *)buf, true);    len = STRLCPY(buf, p, sizeof(buf));    len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1;    xfree(p); | 
