diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 2917bb31ee..49890a460a 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1053,11 +1053,8 @@ 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; - if (cts.cts_cur_text_width > 0) { - incr = cts.cts_cur_text_width; - } + // 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; } @@ -1092,9 +1089,12 @@ 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 (((State & MODE_INSERT) == 0 || !cts.cts_has_right_gravity) && !on_NUL) { + if (!on_NUL) { // cursor is after inserted text, unless on the NUL - vcol += cts.cts_cur_text_width; + 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/drawline.c b/src/nvim/drawline.c index 9374464a93..17d23020fa 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1757,7 +1757,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, selected, &decor_state); while (true) { - // we could already be inside an existing virt_line with multiple chunks + // we could already be inside an existing inline text 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++) { diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 3b4883d5c2..25c745ae97 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -269,7 +269,7 @@ int linetabsize_col(int startcol, char *s) 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; + cts.cts_vcol += cts.cts_cur_text_width_left + cts.cts_cur_text_width_right; } clear_chartabsize_arg(&cts); return cts.cts_vcol; @@ -308,7 +308,7 @@ void win_linetabsize_cts(chartabsize_T *cts, colnr_T len) 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; + cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right; } } @@ -323,9 +323,9 @@ void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T cts->cts_vcol = col; cts->cts_line = line; cts->cts_ptr = ptr; - cts->cts_cur_text_width = 0; + cts->cts_cur_text_width_left = 0; + cts->cts_cur_text_width_right = 0; cts->cts_has_virt_text = false; - cts->cts_has_right_gravity = true; cts->cts_row = lnum - 1; if (cts->cts_row >= 0) { @@ -398,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 @@ -419,14 +420,15 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) 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) { // TODO(bfredl): or maybe unconditionally, what - // if byte-misaligned? + } 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) { - cts->cts_cur_text_width += decor.virt_text_width; - cts->cts_has_right_gravity = mt_right(mark); + 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 diff --git a/src/nvim/plines.h b/src/nvim/plines.h index 6e4768bc7d..fa55357193 100644 --- a/src/nvim/plines.h +++ b/src/nvim/plines.h @@ -14,8 +14,8 @@ typedef struct { int cts_row; bool cts_has_virt_text; // true if if a property inserts text - bool cts_has_right_gravity; - int cts_cur_text_width; // width of current inserted 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]; // TODO(bfredl): iterator in to the marktree for scanning virt text diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index f67c4be419..e8847258f4 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -1745,6 +1745,49 @@ bbbbbbb]]) ]]} 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 {28:>><<}^foo foo | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + feed('i') + screen:expect { grid = [[ + foo foo {28:>>^<<}foo foo | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {24:-- INSERT --} | + ]]} + end) + it('draws correctly with no wrap multiple virtual text, where one is hidden', function() insert('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz') command("set nowrap")