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/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 863d237062..012cce7b22 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1030,6 +1030,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int left_curline_col = 0; int right_curline_col = 0; + VirtText virt_inline = KV_INITIAL_VALUE; + size_t virt_inline_i = 0; + int match_conc = 0; ///< cchar for match functions bool on_last_col = false; int syntax_flags = 0; @@ -1706,7 +1709,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 @@ -1787,6 +1792,44 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, wlv.char_attr = 0; } } + + 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); + + // we could already be inside an existing virt_line with multiple chunks + if (!(virt_inline_i < kv_size(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) { + virt_inline = item->decor.virt_text; + virt_inline_i = 0; + item->win_col = -2; + break; + } + } + } + + if (wlv.n_extra <= 0 && virt_inline_i < kv_size(virt_inline)) { + VirtTextChunk vtc = kv_A(virt_inline, virt_inline_i); + wlv.p_extra = vtc.text; + wlv.n_extra = (int)strlen(wlv.p_extra); + wlv.c_extra = NUL; + wlv.c_final = NUL; + wlv.extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0; + n_attr = wlv.n_extra; + extmark_attr = 0; + virt_inline_i++; + } + } } // Get the next character to put on the screen. @@ -2019,10 +2062,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); diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 3e69e547cb..738057b696 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -295,17 +295,25 @@ unsigned linetabsize(win_T *wp, linenr_T lnum) /// 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_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". @@ -383,7 +391,22 @@ 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 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) { // TODO: or maybe unconditionally, what if byte-misaligned? + if (!mt_end(mark)) { + Decoration decor = get_decor(mark); + if (decor.virt_text_pos == kVTInline) { + cts->cts_cur_text_width = decor.virt_text_width; + size += cts->cts_cur_text_width; + } + } + } + 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..a15c234bba 100644 --- a/src/nvim/plines.h +++ b/src/nvim/plines.h @@ -11,9 +11,11 @@ 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 + MarkTreeIter cts_iter[1]; // TODO(bfredl): iterator in to the marktree for scanning virt text int cts_vcol; // virtual column at current position diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index ccf1810ee1..80dfebd33e 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -645,6 +645,7 @@ describe('extmark decorations', function() [25] = {background = Screen.colors.LightRed}; [26] = {background=Screen.colors.DarkGrey, foreground=Screen.colors.LightGrey}; [27] = {background = Screen.colors.Plum1}; + [28] = {foreground = Screen.colors.SlateBlue}; } ns = meths.create_namespace 'test' @@ -1166,6 +1167,84 @@ end]] screen:expect_unchanged(true) end) + it('can have virtual text of inline position', 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 | + colpos = colpos+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + + 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{28:: }{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 | + colpos = colpos+1 | + end | + end | + {1:~ }| + | + ]]} + + screen:try_resize(55, 15) + screen:expect{grid=[[ + ^for _,item in ipairs(items) do | + local text{28:: }{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 | + colpos = colpos+1 | + end | + end | + {1:~ }| + | + ]]} + + screen:try_resize(56, 15) + screen:expect{grid=[[ + ^for _,item in ipairs(items) do | + local text{28:: }{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 | + colpos = colpos+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + end) end) describe('decorations: virtual lines', function()