aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbfredl <bjorn.linse@gmail.com>2023-05-22 14:16:42 +0200
committerGitHub <noreply@github.com>2023-05-22 14:16:42 +0200
commit07883940b2294e0ab32fb58e6624d18d9dd1715a (patch)
tree2ce3b16ad7b6e96a4141a8502a3c667071ebf814
parentcb34d0ddd086141d6afcb9c48eae180abbeffecc (diff)
parent6eeb28845a930fbfe128dc3edc7969b0d9b2ed1c (diff)
downloadrneovim-07883940b2294e0ab32fb58e6624d18d9dd1715a.tar.gz
rneovim-07883940b2294e0ab32fb58e6624d18d9dd1715a.tar.bz2
rneovim-07883940b2294e0ab32fb58e6624d18d9dd1715a.zip
Merge pull request #20130 from bfredl/inline
feat(ui): inline virtual text
-rw-r--r--runtime/doc/api.txt2
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--src/nvim/api/extmark.c4
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/charset.c15
-rw-r--r--src/nvim/decoration.c7
-rw-r--r--src/nvim/decoration.h4
-rw-r--r--src/nvim/drawline.c208
-rw-r--r--src/nvim/edit.c4
-rw-r--r--src/nvim/extmark.c3
-rw-r--r--src/nvim/mbyte.c18
-rw-r--r--src/nvim/plines.c85
-rw-r--r--src/nvim/plines.h6
-rw-r--r--test/functional/ui/decorations_spec.lua805
14 files changed, 1075 insertions, 89 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index e7cda59906..f399f1ed25 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -2608,6 +2608,8 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
• "overlay": display over the specified column, without
shifting the underlying text.
• "right_align": display right aligned in the window.
+ • "inline": display at the specified column, and shift the
+ buffer text to the right as needed
• virt_text_win_col : position the virtual text at a fixed
window column (starting from the first text column)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 9f6ab2ddd0..5690dbf3bc 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -56,6 +56,8 @@ The following new APIs or features were added.
• |Query:iter_matches()| now has the ability to set the maximum start depth
for matches.
+• Added inline virtual text support to |nvim_buf_set_extmark()|.
+
==============================================================================
CHANGED FEATURES *news-changed*
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index d6f0288f94..dd256a54f0 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -477,6 +477,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
/// - "right_align": display right aligned in the window.
+/// - "inline": display at the specified column, and
+/// shift the buffer text to the right as needed
/// - virt_text_win_col : position the virtual text at a fixed
/// window column (starting from the first
/// text column)
@@ -695,6 +697,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
decor.virt_text_pos = kVTOverlay;
} else if (strequal("right_align", str.data)) {
decor.virt_text_pos = kVTRightAlign;
+ } else if (strequal("inline", str.data)) {
+ decor.virt_text_pos = kVTInline;
} else {
VALIDATE_S(false, "virt_text_pos", "", {
goto error;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index f8b26d9d16..f3f98bbd17 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -808,6 +808,7 @@ struct file_buffer {
MarkTree b_marktree[1];
Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces
+ size_t b_virt_text_inline; // number of inline virtual texts
size_t b_virt_line_blocks; // number of virt_line blocks
size_t b_signs; // number of sign extmarks
size_t b_signs_with_text; // number of sign extmarks with text
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 8cae831881..49890a460a 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -989,6 +989,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
}
chartabsize_T cts;
+ bool on_NUL = false;
init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line);
// This function is used very often, do some speed optimizations.
@@ -1052,8 +1053,9 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
// make sure we don't go past the end of the line
if (*cts.cts_ptr == NUL) {
- // NUL at end of line only takes one column
- incr = 1;
+ // NUL at end of line only takes one column, unless there is virtual text
+ incr = MAX(1, cts.cts_cur_text_width_left + cts.cts_cur_text_width_right);
+ on_NUL = true;
break;
}
@@ -1079,8 +1081,6 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
}
if (cursor != NULL) {
- // cursor is after inserted text
- vcol += cts.cts_cur_text_width;
if ((*ptr == TAB)
&& (State & MODE_NORMAL)
&& !wp->w_p_list
@@ -1089,6 +1089,13 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
// cursor at end
*cursor = vcol + incr - 1;
} else {
+ if (!on_NUL) {
+ // cursor is after inserted text, unless on the NUL
+ vcol += cts.cts_cur_text_width_left;
+ if ((State & MODE_INSERT) == 0) {
+ vcol += cts.cts_cur_text_width_right;
+ }
+ }
// cursor at start
*cursor = vcol + head;
}
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 87e4441f32..ce1af290c1 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -83,6 +83,9 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
if (decor && decor_virt_pos(*decor)) {
redraw_buf_line_later(buf, row1 + 1, false);
+ if (decor->virt_text_pos == kVTInline) {
+ changed_line_display_buf(buf);
+ }
}
if (decor && kv_size(decor->virt_lines)) {
@@ -95,6 +98,10 @@ void decor_remove(buf_T *buf, int row, int row2, Decoration *decor)
{
decor_redraw(buf, row, row2, decor);
if (decor) {
+ if (kv_size(decor->virt_text) && decor->virt_text_pos == kVTInline) {
+ assert(buf->b_virt_text_inline > 0);
+ buf->b_virt_text_inline--;
+ }
if (kv_size(decor->virt_lines)) {
assert(buf->b_virt_line_blocks > 0);
buf->b_virt_line_blocks--;
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 92001d496d..95c9655742 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -23,9 +23,11 @@ typedef enum {
kVTOverlay,
kVTWinCol,
kVTRightAlign,
+ kVTInline,
} VirtTextPos;
-EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align" });
+EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align",
+ "inline" });
typedef enum {
kHlModeUnknown,
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 67086b3248..510731a438 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -98,6 +98,7 @@ typedef struct {
int char_attr; ///< attributes for next character
int n_extra; ///< number of extra bytes
+ int n_attr; ///< chars with special attr
char *p_extra; ///< string of extra chars, plus NUL, only used
///< when c_extra and c_final are NUL
char *p_extra_free; ///< p_extra buffer that needs to be freed
@@ -105,9 +106,12 @@ typedef struct {
int c_extra; ///< extra chars, all the same
int c_final; ///< final char, mandatory if set
+ bool extra_for_extmark;
+
// saved "extra" items for when draw_state becomes WL_LINE (again)
int saved_n_extra;
char *saved_p_extra;
+ bool saved_extra_for_extmark;
int saved_c_extra;
int saved_c_final;
int saved_char_attr;
@@ -120,6 +124,14 @@ typedef struct {
int filler_lines; ///< nr of filler lines to be drawn
int filler_todo; ///< nr of filler lines still to do + 1
SignTextAttrs sattrs[SIGN_SHOW_MAX]; ///< sign attributes for the sign column
+
+ VirtText virt_inline;
+ size_t virt_inline_i;
+
+ bool reset_extra_attr;
+
+ int skip_cells; // nr of cells to skip for virtual text
+ int skipped_cells; // nr of skipped virtual text cells
} winlinevars_T;
/// for line_putchar. Contains the state that needs to be remembered from
@@ -851,6 +863,73 @@ static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv)
}
}
+static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v, bool *do_save)
+{
+ while (true) {
+ // we could already be inside an existing inline text with multiple chunks
+ if (!(wlv->virt_inline_i < kv_size(wlv->virt_inline))) {
+ DecorState *state = &decor_state;
+ 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)
+ || item->decor.virt_text_pos != kVTInline) {
+ continue;
+ }
+ if (item->win_col >= -1 && item->start_col == v) {
+ wlv->virt_inline = item->decor.virt_text;
+ wlv->virt_inline_i = 0;
+ item->win_col = -2;
+ break;
+ }
+ }
+ }
+
+ if (wlv->n_extra == 0 || !wlv->extra_for_extmark) {
+ wlv->reset_extra_attr = false;
+ }
+
+ if (wlv->n_extra <= 0 && wlv->virt_inline_i < kv_size(wlv->virt_inline)) {
+ VirtTextChunk vtc = kv_A(wlv->virt_inline, wlv->virt_inline_i);
+ wlv->p_extra = vtc.text;
+ wlv->n_extra = (int)strlen(wlv->p_extra);
+ wlv->extra_for_extmark = true;
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
+ wlv->extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0;
+ wlv->n_attr = mb_charlen(vtc.text);
+ wlv->virt_inline_i++;
+ *do_save = true;
+ // If the text didn't reach until the first window
+ // column we need to skip cells.
+ if (wlv->skip_cells > 0) {
+ int virt_text_len = wlv->n_attr;
+ if (virt_text_len > wlv->skip_cells) {
+ int len = mb_charlen2bytelen(wlv->p_extra, wlv->skip_cells);
+ wlv->n_extra -= len;
+ wlv->p_extra += len;
+ wlv->n_attr -= wlv->skip_cells;
+ // Skipped cells needed to be accounted for in vcol.
+ wlv->skipped_cells += wlv->skip_cells;
+ wlv->skip_cells = 0;
+ } else {
+ // the whole text is left of the window, drop
+ // it and advance to the next one
+ wlv->skip_cells -= virt_text_len;
+ // Skipped cells needed to be accounted for in vcol.
+ wlv->skipped_cells += virt_text_len;
+ wlv->n_attr = 0;
+ wlv->n_extra = 0;
+
+ // go to the start so the next virtual text chunk can be selected.
+ continue;
+ }
+ }
+ }
+ break;
+ }
+}
+
static bool check_mb_utf8(int *c, int *u8cc)
{
if (utf_char2len(*c) > 1) {
@@ -917,6 +996,7 @@ static void win_line_start(win_T *wp, winlinevars_T *wlv, bool save_extra)
wlv->draw_state = WL_START;
wlv->saved_n_extra = wlv->n_extra;
wlv->saved_p_extra = wlv->p_extra;
+ wlv->saved_extra_for_extmark = wlv->extra_for_extmark;
wlv->saved_c_extra = wlv->c_extra;
wlv->saved_c_final = wlv->c_final;
wlv->saved_char_attr = wlv->char_attr;
@@ -931,6 +1011,8 @@ static void win_line_continue(winlinevars_T *wlv)
if (wlv->saved_n_extra > 0) {
// Continue item from end of wrapped line.
wlv->n_extra = wlv->saved_n_extra;
+ wlv->saved_n_extra = 0;
+ wlv->extra_for_extmark = wlv->saved_extra_for_extmark;
wlv->c_extra = wlv->saved_c_extra;
wlv->c_final = wlv->saved_c_final;
wlv->p_extra = wlv->saved_p_extra;
@@ -969,12 +1051,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// at end-of-line
bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
- int n_attr = 0; // chars with special attr
int saved_attr2 = 0; // char_attr saved for n_attr
int n_attr3 = 0; // chars with overruling special attr
int saved_attr3 = 0; // char_attr saved for n_attr3
- int n_skip = 0; // nr of chars to skip for 'nowrap'
+ int n_skip = 0; // nr of chars to skip for 'nowrap' or
+ // concealing
int fromcol_prev = -2; // start of inverting after cursor
bool noinvcur = false; // don't invert the cursor
@@ -986,7 +1068,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
bool area_highlighting = false; // Visual or incsearch highlighting in this line
int vi_attr = 0; // attributes for Visual and incsearch highlighting
int area_attr = 0; // attributes desired by highlighting
+ int saved_area_attr = 0; // idem for area_attr
int search_attr = 0; // attributes desired by 'hlsearch'
+ int saved_search_attr = 0; // search_attr to be used when n_extra
+ // goes to zero
int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
int syntax_attr = 0; // attributes desired by syntax
bool has_syntax = false; // this buffer has syntax highl.
@@ -1024,6 +1109,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int prev_c1 = 0; // first composing char for prev_c
bool search_attr_from_match = false; // if search_attr is from :match
+ bool saved_search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
int win_col_offset = 0; // offset for window columns
@@ -1436,6 +1522,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
}
+ // If there the text doesn't reach to the desired column, need to skip
+ // "skip_cells" cells when virtual text follows.
+ if (!wp->w_p_wrap && v > wlv.vcol) {
+ wlv.skip_cells = (int)(v - wlv.vcol);
+ }
+
// Adjust for when the inverted text is before the screen,
// and when the start of the inverted text is before the screen.
if (wlv.tocol <= wlv.vcol) {
@@ -1714,7 +1806,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.n_extra = 0;
}
- if (wlv.draw_state == WL_LINE && (area_highlighting || has_spell)) {
+ int extmark_attr = 0;
+ if (wlv.draw_state == WL_LINE
+ && (area_highlighting || has_spell || (extra_check && !has_fold))) {
// handle Visual or match highlighting in this line
if (wlv.vcol == wlv.fromcol
|| (wlv.vcol + 1 == wlv.fromcol && wlv.n_extra == 0
@@ -1732,7 +1826,29 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
area_active = false;
}
- if (!wlv.n_extra) {
+ if (has_decor && v >= 0) {
+ bool selected = (area_active || (area_highlighting && noinvcur
+ && wlv.vcol == wp->w_virtcol));
+ extmark_attr = decor_redraw_col(wp, (colnr_T)v, wlv.off,
+ selected, &decor_state);
+
+ bool do_save = false;
+ handle_inline_virtual_text(wp, &wlv, v, &do_save);
+ if (do_save) {
+ // restore search_attr and area_attr when n_extra is down to zero
+ // TODO(bfredl): this is ugly as fuck. look if we can do this some other way.
+ saved_search_attr = search_attr;
+ saved_area_attr = area_attr;
+ saved_search_attr_from_match = search_attr_from_match;
+ search_attr_from_match = false;
+ search_attr = 0;
+ area_attr = 0;
+ extmark_attr = 0;
+ n_skip = 0;
+ }
+ }
+
+ if (wlv.n_extra == 0) {
// Check for start/end of 'hlsearch' and other matches.
// After end, check for start/end of next match.
// When another match, have to check for start again.
@@ -1750,12 +1866,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
if (wlv.diff_hlf != (hlf_T)0) {
+ // When there is extra text (eg: virtual text) it gets the
+ // diff highlighting for the line, but not for changed text.
if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start
&& wlv.n_extra == 0) {
wlv.diff_hlf = HLF_TXD; // changed text
}
- if (wlv.diff_hlf == HLF_TXD && ptr - line > change_end
- && wlv.n_extra == 0) {
+ if (wlv.diff_hlf == HLF_TXD && ((ptr - line > change_end && wlv.n_extra == 0)
+ || (wlv.n_extra > 0 && wlv.extra_for_extmark))) {
wlv.diff_hlf = HLF_CHD; // changed line
}
wlv.line_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
@@ -1856,6 +1974,26 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.p_extra++;
}
wlv.n_extra--;
+
+ // Only restore search_attr and area_attr after "n_extra" in
+ // the next screen line is also done.
+ if (wlv.n_extra <= 0) {
+ if (wlv.saved_n_extra <= 0) {
+ if (search_attr == 0) {
+ search_attr = saved_search_attr;
+ }
+ if (area_attr == 0 && *ptr != NUL) {
+ area_attr = saved_area_attr;
+ }
+
+ if (wlv.extra_for_extmark) {
+ // wlv.extra_attr should be used at this position but not
+ // any further.
+ wlv.reset_extra_attr = true;
+ }
+ }
+ wlv.extra_for_extmark = false;
+ }
} else if (foldinfo.fi_lines > 0) {
// skip writing the buffer line itself
c = NUL;
@@ -1907,7 +2045,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.c_extra = NUL;
wlv.c_final = NUL;
if (area_attr == 0 && search_attr == 0) {
- n_attr = wlv.n_extra + 1;
+ wlv.n_attr = wlv.n_extra + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_8);
saved_attr2 = wlv.char_attr; // save current attr
}
@@ -1962,7 +2100,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.c_final = NUL;
c = ' ';
if (area_attr == 0 && search_attr == 0) {
- n_attr = wlv.n_extra + 1;
+ wlv.n_attr = wlv.n_extra + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
saved_attr2 = wlv.char_attr; // save current attr
}
@@ -2027,10 +2165,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
if (has_decor && v > 0) {
- bool selected = (area_active || (area_highlighting && noinvcur
- && wlv.vcol == wp->w_virtcol));
- int extmark_attr = decor_redraw_col(wp, (colnr_T)v - 1, wlv.off,
- selected, &decor_state);
if (extmark_attr != 0) {
if (!attr_pri) {
wlv.char_attr = hl_combine_attr(wlv.char_attr, extmark_attr);
@@ -2135,7 +2269,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, p);
+ // do not want virtual text to be counted here
+ cts.cts_has_virt_text = false;
wlv.n_extra = win_lbr_chartabsize(&cts, NULL) - 1;
+ clear_chartabsize_arg(&cts);
// We have just drawn the showbreak value, no need to add
// space for it again.
@@ -2167,7 +2304,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
c = ' ';
}
}
- clear_chartabsize_arg(&cts);
}
in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' ');
@@ -2198,7 +2334,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
} else {
c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
}
- n_attr = 1;
+ wlv.n_attr = 1;
wlv.extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = wlv.char_attr; // save current attr
mb_c = c;
@@ -2221,7 +2357,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
c = wp->w_p_lcs_chars.space;
}
- n_attr = 1;
+ wlv.n_attr = 1;
wlv.extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = wlv.char_attr; // save current attr
mb_c = c;
@@ -2337,7 +2473,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.c_extra = wp->w_p_lcs_chars.tab2;
}
wlv.c_final = wp->w_p_lcs_chars.tab3;
- n_attr = tab_len + 1;
+ wlv.n_attr = tab_len + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = wlv.char_attr; // save current attr
mb_c = c;
@@ -2364,15 +2500,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& wlv.line_attr == 0
&& wlv.line_attr_lowprio == 0) {
// In virtualedit, visual selections may extend beyond end of line
- if (area_highlighting && virtual_active()
- && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol) {
- wlv.n_extra = 0;
- } else {
+ if (!(area_highlighting && virtual_active()
+ && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol)) {
wlv.p_extra = at_end_str;
- wlv.n_extra = 1;
- wlv.c_extra = NUL;
- wlv.c_final = NUL;
}
+ wlv.n_extra = 0;
}
if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
c = wp->w_p_lcs_chars.eol;
@@ -2382,7 +2514,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
lcs_eol_one = -1;
ptr--; // put it back at the NUL
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
- n_attr = 1;
+ wlv.n_attr = 1;
mb_c = c;
mb_utf8 = check_mb_utf8(&c, u8cc);
} else if (c != NUL) {
@@ -2411,7 +2543,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.n_extra = byte2cells(c) - 1;
c = (uint8_t)(*wlv.p_extra++);
}
- n_attr = wlv.n_extra + 1;
+ wlv.n_attr = wlv.n_extra + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_8);
saved_attr2 = wlv.char_attr; // save current attr
mb_utf8 = false; // don't draw as UTF-8
@@ -2471,7 +2603,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
}
wlv.n_extra = 0;
- n_attr = 0;
+ wlv.n_attr = 0;
} else if (n_skip == 0) {
is_concealing = true;
n_skip = 1;
@@ -2506,8 +2638,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
// Don't override visual selection highlighting.
- if (n_attr > 0 && wlv.draw_state == WL_LINE && !search_attr_from_match) {
+ if (wlv.n_attr > 0 && wlv.draw_state == WL_LINE && !search_attr_from_match) {
wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr);
+ if (wlv.reset_extra_attr) {
+ wlv.reset_extra_attr = false;
+ wlv.extra_attr = 0;
+ // search_attr_from_match can be restored now that the extra_attr has been applied
+ search_attr_from_match = saved_search_attr_from_match;
+ }
}
// Handle the case where we are in column 0 but not on the first
@@ -2527,7 +2665,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.c_extra = MB_FILLER_CHAR;
wlv.c_final = NUL;
wlv.n_extra = 1;
- n_attr = 2;
+ wlv.n_attr = 2;
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
}
mb_c = c;
@@ -2743,7 +2881,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& !has_fold
&& (*ptr != NUL
|| lcs_eol_one > 0
- || (wlv.n_extra && (wlv.c_extra != NUL || *wlv.p_extra != NUL)))) {
+ || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)))) {
c = wp->w_p_lcs_chars.ext;
wlv.char_attr = win_hl_attr(wp, HLF_AT);
mb_c = c;
@@ -2864,7 +3002,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
wlv.boguscols += wlv.n_extra;
}
wlv.n_extra = 0;
- n_attr = 0;
+ wlv.n_attr = 0;
}
if (utf_char2cells(mb_c) > 1) {
@@ -2889,13 +3027,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (wlv.n_extra > 0) {
wlv.vcol += wlv.n_extra;
wlv.n_extra = 0;
- n_attr = 0;
+ wlv.n_attr = 0;
}
}
} else {
n_skip--;
}
+ // The skipped cells need to be accounted for in vcol.
+ if (wlv.draw_state > WL_STC && wlv.skipped_cells > 0) {
+ wlv.vcol += wlv.skipped_cells;
+ wlv.skipped_cells = 0;
+ }
+
// Only advance the "wlv.vcol" when after the 'number' or
// 'relativenumber' column.
if (wlv.draw_state > WL_STC
@@ -2913,7 +3057,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
// restore attributes after last 'listchars' or 'number' char
- if (n_attr > 0 && wlv.draw_state == WL_LINE && --n_attr == 0) {
+ if (wlv.n_attr > 0 && wlv.draw_state == WL_LINE && --wlv.n_attr == 0) {
wlv.char_attr = saved_attr2;
}
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index e3321a8b99..a25387f5a6 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -232,7 +232,7 @@ static void insert_enter(InsertState *s)
stop_insert_mode = false;
// need to position cursor again when on a TAB
- if (gchar_cursor() == TAB) {
+ if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
}
@@ -3471,7 +3471,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
State = MODE_NORMAL;
may_trigger_modechanged();
// need to position cursor again when on a TAB
- if (gchar_cursor() == TAB) {
+ if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
}
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index acdc36f9c7..f4a6a02682 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -148,6 +148,9 @@ revised:
}
if (decor) {
+ if (kv_size(decor->virt_text) && decor->virt_text_pos == kVTInline) {
+ buf->b_virt_text_inline++;
+ }
if (kv_size(decor->virt_lines)) {
buf->b_virt_line_blocks++;
}
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 7d61b918d2..66c26275f1 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -2024,6 +2024,24 @@ int mb_charlen(const char *str)
return count;
}
+int mb_charlen2bytelen(const char *str, int charlen)
+{
+ const char *p = str;
+ int count = 0;
+
+ if (p == NULL) {
+ return 0;
+ }
+
+ for (int i = 0; *p != NUL && i < charlen; i++) {
+ int b = utfc_ptr2len(p);
+ p += b;
+ count += b;
+ }
+
+ return count;
+}
+
/// Like mb_charlen() but for a string with specified length.
int mb_charlen_len(const char *str, int len)
{
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index 3e69e547cb..25c745ae97 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -102,12 +102,16 @@ int plines_win_nofold(win_T *wp, linenr_T lnum)
char *s;
unsigned col;
int width;
+ chartabsize_T cts;
s = ml_get_buf(wp->w_buffer, lnum, false);
- if (*s == NUL) { // empty line
- return 1;
+ init_chartabsize_arg(&cts, wp, lnum, 0, s, s);
+ if (*s == NUL && !cts.cts_has_virt_text) {
+ return 1; // be quick for an empty line
}
- col = win_linetabsize(wp, lnum, s, MAXCOL);
+ win_linetabsize_cts(&cts, (colnr_T)MAXCOL);
+ clear_chartabsize_arg(&cts);
+ col = (unsigned)cts.cts_vcol;
// If list mode is on, then the '$' at the end of the line may take up one
// extra column.
@@ -262,6 +266,11 @@ int linetabsize_col(int startcol, char *s)
while (*cts.cts_ptr != NUL) {
cts.cts_vcol += lbr_chartabsize_adv(&cts);
}
+ if (cts.cts_has_virt_text && cts.cts_ptr == cts.cts_line) {
+ // check for virtual text in an empty line
+ (void)lbr_chartabsize_adv(&cts);
+ cts.cts_vcol += cts.cts_cur_text_width_left + cts.cts_cur_text_width_right;
+ }
clear_chartabsize_arg(&cts);
return cts.cts_vcol;
}
@@ -277,10 +286,7 @@ unsigned win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
{
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
- for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < line + len);
- MB_PTR_ADV(cts.cts_ptr)) {
- cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
- }
+ win_linetabsize_cts(&cts, len);
clear_chartabsize_arg(&cts);
return (unsigned)cts.cts_vcol;
}
@@ -292,20 +298,43 @@ unsigned linetabsize(win_T *wp, linenr_T lnum)
return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum, false), (colnr_T)MAXCOL);
}
+void win_linetabsize_cts(chartabsize_T *cts, colnr_T len)
+{
+ for (; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len);
+ MB_PTR_ADV(cts->cts_ptr)) {
+ cts->cts_vcol += win_lbr_chartabsize(cts, NULL);
+ }
+ // check for a virtual text on an empty line
+ if (cts->cts_has_virt_text && *cts->cts_ptr == NUL
+ && cts->cts_ptr == cts->cts_line) {
+ (void)win_lbr_chartabsize(cts, NULL);
+ cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right;
+ }
+}
+
/// Prepare the structure passed to chartabsize functions.
///
/// "line" is the start of the line, "ptr" is the first relevant character.
-/// When "lnum" is zero do not use text properties that insert text.
-void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum FUNC_ATTR_UNUSED,
- colnr_T col, char *line, char *ptr)
+/// When "lnum" is zero do not use inline virtual text.
+void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char *line,
+ char *ptr)
{
cts->cts_win = wp;
cts->cts_vcol = col;
cts->cts_line = line;
cts->cts_ptr = ptr;
- cts->cts_cur_text_width = 0;
- // TODO(bfredl): actually lookup inline virtual text here
+ cts->cts_cur_text_width_left = 0;
+ cts->cts_cur_text_width_right = 0;
cts->cts_has_virt_text = false;
+ cts->cts_row = lnum - 1;
+
+ if (cts->cts_row >= 0) {
+ marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter);
+ mtkey_t mark = marktree_itr_current(cts->cts_iter);
+ if (mark.pos.row == cts->cts_row) {
+ cts->cts_has_virt_text = true;
+ }
+ }
}
/// Free any allocated item in "cts".
@@ -369,7 +398,8 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
int mb_added = 0;
int numberextra;
- cts->cts_cur_text_width = 0;
+ cts->cts_cur_text_width_left = 0;
+ cts->cts_cur_text_width_right = 0;
// No 'linebreak', 'showbreak' and 'breakindent': return quickly.
if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL
@@ -383,7 +413,34 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
// First get normal size, without 'linebreak' or virtual text
int size = win_chartabsize(wp, s, vcol);
if (cts->cts_has_virt_text) {
- // TODO(bfredl): inline virtual text
+ int tab_size = size;
+ int charlen = *s == NUL ? 1 : utf_ptr2len(s);
+ int col = (int)(s - line);
+ while (true) {
+ mtkey_t mark = marktree_itr_current(cts->cts_iter);
+ if (mark.pos.row != cts->cts_row || mark.pos.col > col) {
+ break;
+ } else if (mark.pos.col >= col && mark.pos.col < col + charlen) {
+ if (!mt_end(mark)) {
+ Decoration decor = get_decor(mark);
+ if (decor.virt_text_pos == kVTInline) {
+ if (mt_right(mark)) {
+ cts->cts_cur_text_width_right += decor.virt_text_width;
+ } else {
+ cts->cts_cur_text_width_left += decor.virt_text_width;
+ }
+ size += decor.virt_text_width;
+ if (*s == TAB) {
+ // tab size changes because of the inserted text
+ size -= tab_size;
+ tab_size = win_chartabsize(wp, s, vcol + size);
+ size += tab_size;
+ }
+ }
+ }
+ }
+ marktree_itr_next(wp->w_buffer->b_marktree, cts->cts_iter);
+ }
}
int c = (uint8_t)(*s);
diff --git a/src/nvim/plines.h b/src/nvim/plines.h
index 808f6d284e..2ce7133705 100644
--- a/src/nvim/plines.h
+++ b/src/nvim/plines.h
@@ -11,10 +11,12 @@ typedef struct {
win_T *cts_win;
char *cts_line; // start of the line
char *cts_ptr; // current position in line
+ int cts_row;
bool cts_has_virt_text; // true if if a property inserts text
- int cts_cur_text_width; // width of current inserted text
- // TODO(bfredl): iterator in to the marktree for scanning virt text
+ int cts_cur_text_width_left; // width of virtual text left of cursor
+ int cts_cur_text_width_right; // width of virtual text right of cursor
+ MarkTreeIter cts_iter[1];
int cts_vcol; // virtual column at current position
} chartabsize_T;
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index e31361e74c..e430865df6 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -611,6 +611,20 @@ describe('decorations providers', function()
end)
end)
+local example_text = [[
+for _,item in ipairs(items) do
+ local text, hl_id_cell, count = unpack(item)
+ if hl_id_cell ~= nil then
+ hl_id = hl_id_cell
+ end
+ for _ = 1, (count or 1) do
+ local cell = line[colpos]
+ cell.text = text
+ cell.hl_id = hl_id
+ colpos = colpos+1
+ end
+end]]
+
describe('extmark decorations', function()
local screen, ns
before_each( function()
@@ -650,20 +664,6 @@ describe('extmark decorations', function()
ns = meths.create_namespace 'test'
end)
- local example_text = [[
-for _,item in ipairs(items) do
- local text, hl_id_cell, count = unpack(item)
- if hl_id_cell ~= nil then
- hl_id = hl_id_cell
- end
- for _ = 1, (count or 1) do
- local cell = line[colpos]
- cell.text = text
- cell.hl_id = hl_id
- colpos = colpos+1
- end
-end]]
-
it('empty virtual text at eol should not break colorcolumn #17860', function()
insert(example_text)
feed('gg')
@@ -1233,7 +1233,744 @@ end]]
meths.buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestBold', priority = 20 })
screen:expect_unchanged(true)
end)
+end)
+
+describe('decorations: inline virtual text', function()
+ local screen, ns
+ before_each( function()
+ clear()
+ screen = Screen.new(50, 10)
+ screen:attach()
+ screen:set_default_attr_ids {
+ [1] = {bold=true, foreground=Screen.colors.Blue};
+ [2] = {foreground = Screen.colors.Brown};
+ [3] = {bold = true, foreground = Screen.colors.SeaGreen};
+ [4] = {background = Screen.colors.Red1, foreground = Screen.colors.Gray100};
+ [5] = {background = Screen.colors.Red1, bold = true};
+ [6] = {foreground = Screen.colors.DarkCyan};
+ [7] = {background = Screen.colors.LightGrey};
+ [8] = {bold = true};
+ [9] = {background = Screen.colors.Plum1};
+ [10] = {foreground = Screen.colors.SlateBlue};
+ [11] = {blend = 30, background = Screen.colors.Red1};
+ [12] = {background = Screen.colors.Yellow1};
+ [13] = {reverse = true};
+ [14] = {foreground = Screen.colors.SlateBlue, background = Screen.colors.LightMagenta};
+ [15] = {bold = true, reverse = true};
+ }
+
+ ns = meths.create_namespace 'test'
+ end)
+
+
+ it('works', function()
+ insert(example_text)
+ feed 'gg'
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ cell.hl_id = hl_id |
+ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 1, 14, {virt_text={{': ', 'Special'}, {'string', 'Type'}}, virt_text_pos='inline'})
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text{10:: }{3:string}, hl_id_cell, count = unpack|
+ (item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ |
+ ]]}
+
+ screen:try_resize(55, 10)
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text{10:: }{3:string}, hl_id_cell, count = unpack(item|
+ ) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ |
+ ]]}
+
+ screen:try_resize(56, 10)
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text{10:: }{3:string}, hl_id_cell, count = unpack(item)|
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ cell.hl_id = hl_id |
+ |
+ ]]}
+ end)
+
+ it('cursor positions are correct with multiple inline virtual text', function()
+ insert('12345678')
+ meths.buf_set_extmark(0, ns, 0, 4,
+ { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 4,
+ { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
+ feed '^'
+ feed '4l'
+ screen:expect { grid = [[
+ 1234{10: virtual text virtual text }^5678 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('adjusts cursor location correctly when inserting around inline virtual text', function()
+ insert('12345678')
+ feed '$'
+ meths.buf_set_extmark(0, ns, 0, 4,
+ { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
+
+ screen:expect { grid = [[
+ 1234{10: virtual text }567^8 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('has correct highlighting with multi-byte characters', function()
+ insert('12345678')
+ meths.buf_set_extmark(0, ns, 0, 4,
+ { virt_text = { { 'múlti-byté chñröcters 修补', 'Special' } }, virt_text_pos = 'inline' })
+
+ screen:expect { grid = [[
+ 1234{10:múlti-byté chñröcters 修补}567^8 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('has correct cursor position when inserting around virtual text', function()
+ insert('12345678')
+ meths.buf_set_extmark(0, ns, 0, 4,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed '^'
+ feed '3l'
+ feed 'a'
+ screen:expect { grid = [[
+ 1234{10:^virtual text}5678 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed '<ESC>'
+ screen:expect{grid=[[
+ 123^4{10:virtual text}5678 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed '^'
+ feed '4l'
+ feed 'i'
+ screen:expect { grid = [[
+ 1234{10:^virtual text}5678 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ end)
+
+ it('has correct cursor position with virtual text on an empty line', function()
+ meths.buf_set_extmark(0, ns, 0, 0,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ screen:expect { grid = [[
+ {10:^virtual text} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('text is drawn correctly when inserting a wrapping virtual text on an empty line', function()
+ feed('o<esc>')
+ insert([[aaaaaaa
+
+bbbbbbb]])
+ meths.buf_set_extmark(0, ns, 0, 0,
+ { virt_text = { { string.rep('X', 51), 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 2, 0,
+ { virt_text = { { string.rep('X', 50), 'Special' } }, virt_text_pos = 'inline' })
+ feed('gg0')
+ screen:expect { grid = [[
+ {10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ aaaaaaa |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ bbbbbbb |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('j')
+ screen:expect { grid = [[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ ^aaaaaaa |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ bbbbbbb |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('j')
+ screen:expect { grid = [[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ aaaaaaa |
+ {10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ bbbbbbb |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('j')
+ screen:expect { grid = [[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ aaaaaaa |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ ^bbbbbbb |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('cursor position is correct with virtual text attached to hard tabs', function()
+ command('set noexpandtab')
+ feed('i')
+ feed('<TAB>')
+ feed('<TAB>')
+ feed('test')
+ feed('<ESC>')
+ meths.buf_set_extmark(0, ns, 0, 1,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed('0')
+ screen:expect { grid = [[
+ ^ {10:virtual text} test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect { grid = [[
+ {10:virtual text} ^ test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect { grid = [[
+ {10:virtual text} ^test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect { grid = [[
+ {10:virtual text} t^est |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect { grid = [[
+ {10:virtual text} te^st |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('cursor position is correct with virtual text on an empty line', function()
+ command('set linebreak')
+ insert('one twoword')
+ feed('0')
+ meths.buf_set_extmark(0, ns, 0, 3,
+ { virt_text = { { ': virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ screen:expect { grid = [[
+ ^one{10:: virtual text} twoword |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('search highlight is correct', function()
+ insert('foo foo foo foo')
+ feed('0')
+ meths.buf_set_extmark(0, ns, 0, 8,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ screen:expect { grid = [[
+ ^foo foo {10:virtual text}foo foo |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('/foo')
+ screen:expect { grid = [[
+ {12:foo} {13:foo} {10:virtual text}{12:foo} {12:foo} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ /foo^ |
+ ]]}
+ end)
+
+ it('visual select highlight is correct', function()
+ insert('foo foo foo foo')
+ feed('0')
+ meths.buf_set_extmark(0, ns, 0, 8,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed('8l')
+ screen:expect { grid = [[
+ foo foo {10:virtual text}^foo foo |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('v')
+ feed('2h')
+ screen:expect { grid = [[
+ foo fo^o{7: }{10:virtual text}{7:f}oo foo |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {8:-- VISUAL --} |
+ ]]}
+ end)
+
+ it('cursor position is correct when inserting around a virtual text with right gravity set to false', function()
+ insert('foo foo foo foo')
+ meths.buf_set_extmark(0, ns, 0, 8,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline', right_gravity = false })
+ feed('0')
+ feed('8l')
+ screen:expect { grid = [[
+ foo foo {10:virtual text}^foo foo |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('i')
+ screen:expect { grid = [[
+ foo foo {10:virtual text}^foo foo |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ end)
+
+ it('cursor position is correct when inserting around virtual texts with both left and right gravity ', function()
+ insert('foo foo foo foo')
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = {{ '>>', 'Special' }}, virt_text_pos = 'inline', right_gravity = false })
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = {{ '<<', 'Special' }}, virt_text_pos = 'inline', right_gravity = true })
+ feed('08l')
+ screen:expect{ grid = [[
+ foo foo {10:>><<}^foo foo |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('i')
+ screen:expect { grid = [[
+ foo foo {10:>>^<<}foo foo |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ end)
+
+ it('draws correctly with no wrap multiple virtual text, where one is hidden', function()
+ insert('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz')
+ command("set nowrap")
+ meths.buf_set_extmark(0, ns, 0, 50,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 2,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect { grid = [[
+ opqrstuvwxyzabcdefghijklmnopqrstuvwx{10:virtual text}y^z|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('draws correctly with no wrap and a long virtual text', function()
+ insert('abcdefghi')
+ command("set nowrap")
+ meths.buf_set_extmark(0, ns, 0, 2,
+ { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect { grid = [[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('tabs are the correct length with no wrap following virtual text', function()
+ command('set nowrap')
+ feed('itest<TAB>a<ESC>')
+ meths.buf_set_extmark(0, ns, 0, 0,
+ { virt_text = { { string.rep('a', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('gg$')
+ screen:expect { grid = [[
+ {10:aaaaaaaaaaaaaaaaaaaaaaaaa}test ^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('highlighting does not extend when no wrap is enabled with a long virtual text', function()
+ insert('abcdef')
+ command("set nowrap")
+ meths.buf_set_extmark(0, ns, 0, 3,
+ { virt_text = { { string.rep('X', 50), 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect { grid = [[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}de^f|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('highlighting is correct when virtual text wraps with number', function()
+ insert([[
+ test
+ test]])
+ command('set number')
+ meths.buf_set_extmark(0, ns, 0, 1,
+ { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('gg0')
+ screen:expect { grid = [[
+ {2: 1 }^t{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {2: }{10:XXXXXXXXXX}est |
+ {2: 2 }test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('highlighting is correct when virtual text is proceeded with a match', function()
+ insert([[test]])
+ meths.buf_set_extmark(0, ns, 0, 2,
+ { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed('gg0')
+ command('match ErrorMsg /e/')
+ screen:expect { grid = [[
+ ^t{4:e}{10:virtual text}st |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ command('match ErrorMsg /s/')
+ screen:expect { grid = [[
+ ^te{10:virtual text}{4:s}t |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('in diff mode is highlighted correct', function()
+ insert([[
+ 9000
+ 0009
+ 0009
+ 9000
+ 0009
+ ]])
+ command("set diff")
+ meths.buf_set_extmark(0, ns, 0, 1,
+ { virt_text = { { 'test', 'Special' } }, virt_text_pos = 'inline', right_gravity = false })
+ command("vnew")
+ insert([[
+ 000
+ 000
+ 000
+ 000
+ 000
+ ]])
+ command("set diff")
+ feed('gg0')
+ screen:expect { grid = [[
+ {9:^000 }│{5:9}{14:test}{9:000 }|
+ {9:000 }│{9:000}{5:9}{9: }|
+ {9:000 }│{9:000}{5:9}{9: }|
+ {9:000 }│{5:9}{9:000 }|
+ {9:000 }│{9:000}{5:9}{9: }|
+ │ |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {15:[No Name] [+] }{13:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('correctly draws when there are multiple overlapping virtual texts on the same line with nowrap', function()
+ command('set nowrap')
+ insert('a')
+ meths.buf_set_extmark(0, ns, 0, 0,
+ { virt_text = { { string.rep('a', 55), 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 0,
+ { virt_text = { { string.rep('b', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect { grid = [[
+ {10:bbbbbbbbbbbbbbbbbbbbbbbbb}^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('correctly draws when overflowing virtual text is followed by tab with no wrap', function()
+ command('set nowrap')
+ feed('i<TAB>test<ESC>')
+ meths.buf_set_extmark(
+ 0,
+ ns,
+ 0,
+ 0,
+ { virt_text = { { string.rep('a', 60), 'Special' } }, virt_text_pos = 'inline' }
+ )
+ feed('0')
+ screen:expect({
+ grid = [[
+ {10:aaaaaaaaaaaaaaaaaaaaaa} ^ test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]],
+ })
+ end)
end)
describe('decorations: virtual lines', function()
@@ -1257,7 +1994,7 @@ describe('decorations: virtual lines', function()
ns = meths.create_namespace 'test'
end)
- local example_text = [[
+ local example_text2 = [[
if (h->n_buckets < new_n_buckets) { // expand
khkey_t *new_keys = (khkey_t *)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t));
h->keys = new_keys;
@@ -1268,7 +2005,7 @@ if (h->n_buckets < new_n_buckets) { // expand
}]]
it('works with one line', function()
- insert(example_text)
+ insert(example_text2)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, 33, {
virt_lines={ {{">> ", "NonText"}, {"krealloc", "Identifier"}, {": change the size of an allocation"}}};
@@ -1377,7 +2114,7 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
it('works with text at the beginning of the buffer', function()
- insert(example_text)
+ insert(example_text2)
feed 'gg'
screen:expect{grid=[[
@@ -1438,7 +2175,7 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
it('works with text at the end of the buffer', function()
- insert(example_text)
+ insert(example_text2)
feed 'G'
screen:expect{grid=[[
@@ -1557,7 +2294,7 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
it('works beyond end of the buffer with virt_lines_above', function()
- insert(example_text)
+ insert(example_text2)
feed 'G'
screen:expect{grid=[[
@@ -1828,7 +2565,7 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
it('works with sign and numbercolumns', function()
- insert(example_text)
+ insert(example_text2)
feed 'gg'
command 'set number signcolumn=yes'
screen:expect{grid=[[
@@ -1894,7 +2631,7 @@ if (h->n_buckets < new_n_buckets) { // expand
it('works with hard tabs', function()
- insert(example_text)
+ insert(example_text2)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, 0, {
virt_lines={ {{">>", "NonText"}, {"\tvery\ttabby", "Identifier"}, {"text\twith\ttabs"}}};
@@ -1981,7 +2718,7 @@ describe('decorations: signs', function()
meths.set_option_value('signcolumn', 'auto:9', {})
end)
- local example_text = [[
+ local example_test3 = [[
l1
l2
l3
@@ -1990,7 +2727,7 @@ l5
]]
it('can add a single sign (no end row)', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S'})
@@ -2011,7 +2748,7 @@ l5
end)
it('can add a single sign (with end row)', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row=1})
@@ -2033,7 +2770,7 @@ l5
it('can add multiple signs (single extmark)', function()
pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row = 2})
@@ -2055,7 +2792,7 @@ l5
it('can add multiple signs (multiple extmarks)', function()
pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'})
@@ -2077,7 +2814,7 @@ l5
end)
it('can add multiple signs (multiple extmarks) 2', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'})
@@ -2117,7 +2854,7 @@ l5
it('can add multiple signs (multiple extmarks) 3', function()
pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1', end_row=2})
@@ -2138,7 +2875,7 @@ l5
end)
it('can add multiple signs (multiple extmarks) 4', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=0})
@@ -2159,7 +2896,7 @@ l5
end)
it('works with old signs', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
helpers.command('sign define Oldsign text=x')
@@ -2186,7 +2923,7 @@ l5
it('works with old signs (with range)', function()
pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed 'gg'
helpers.command('sign define Oldsign text=x')
@@ -2215,7 +2952,7 @@ l5
it('can add a ranged sign (with start out of view)', function()
pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
command 'set signcolumn=yes:2'
feed 'gg'
feed '2<C-e>'
@@ -2272,7 +3009,7 @@ l5
it('works with priority #19716', function()
screen:try_resize(20, 3)
- insert(example_text)
+ insert(example_test3)
feed 'gg'
helpers.command('sign define Oldsign text=O3')
@@ -2302,7 +3039,7 @@ l5
it('does not set signcolumn for signs without text', function()
screen:try_resize(20, 3)
meths.set_option_value('signcolumn', 'auto', {})
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 0, -1, {number_hl_group='Error'})
screen:expect{grid=[[