diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0842469c59..2c937113e3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2189,7 +2189,6 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * int cul_id = 0; int num_id = 0; linenr_T lnum = statuscol_lnum; - wp->w_scwidth = win_signcol_count(wp); decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id); statuscol.sattrs = sattrs; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 8a594dea92..b5fac15af6 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -750,6 +750,8 @@ void buf_clear(void) { linenr_T line_count = curbuf->b_ml.ml_line_count; extmark_free_all(curbuf); // delete any extmarks + map_destroy(int, curbuf->b_signcols.invalid); + *curbuf->b_signcols.invalid = (Map(int, SignRange)) MAP_INIT; while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) { ml_delete(1, false); } @@ -920,6 +922,8 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) } uc_clear(&buf->b_ucmds); // clear local user commands extmark_free_all(buf); // delete any extmarks + map_destroy(int, buf->b_signcols.invalid); + *buf->b_signcols.invalid = (Map(int, SignRange)) MAP_INIT; map_clear_mode(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs XFREE_CLEAR(buf->b_start_fenc); @@ -1844,7 +1848,6 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags) buf = xcalloc(1, sizeof(buf_T)); // init b: variables buf->b_vars = tv_dict_alloc(); - buf->b_signcols.sentinel = 0; init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); buf_init_changedtick(buf); } @@ -4026,67 +4029,6 @@ char *buf_spname(buf_T *buf) return NULL; } -/// Invalidate the signcolumn if needed after deleting a sign ranging from line1 to line2. -void buf_signcols_del_check(buf_T *buf, linenr_T line1, linenr_T line2) -{ - linenr_T sent = buf->b_signcols.sentinel; - if (sent >= line1 && sent <= line2) { - // When removed sign overlaps the sentinel line, entire buffer needs to be checked. - buf->b_signcols.sentinel = buf->b_signcols.size = 0; - } -} - -/// Invalidate the signcolumn if needed after adding a sign ranging from line1 to line2. -void buf_signcols_add_check(buf_T *buf, linenr_T line1, linenr_T line2) -{ - if (!buf->b_signcols.sentinel) { - return; - } - - linenr_T sent = buf->b_signcols.sentinel; - if (sent >= line1 && sent <= line2) { - // If added sign overlaps sentinel line, increment without invalidating. - if (buf->b_signcols.size == buf->b_signcols.max) { - buf->b_signcols.max++; - } - buf->b_signcols.size++; - return; - } - - if (line1 < buf->b_signcols.invalid_top) { - buf->b_signcols.invalid_top = line1; - } - if (line2 > buf->b_signcols.invalid_bot) { - buf->b_signcols.invalid_bot = line2; - } -} - -int buf_signcols(buf_T *buf, int max) -{ - if (!buf->b_signs_with_text) { - buf->b_signcols.size = 0; - } else if (max <= 1 && buf->b_signs_with_text >= (size_t)max) { - buf->b_signcols.size = max; - } else { - linenr_T sent = buf->b_signcols.sentinel; - if (!sent || max > buf->b_signcols.max) { - // Recheck if the window scoped maximum 'signcolumn' is greater than the - // previous maximum or if there is no sentinel line yet. - buf->b_signcols.invalid_top = sent ? sent : 1; - buf->b_signcols.invalid_bot = sent ? sent : buf->b_ml.ml_line_count; - } - - if (buf->b_signcols.invalid_bot) { - decor_validate_signcols(buf, max); - } - } - - buf->b_signcols.max = max; - buf->b_signcols.invalid_top = MAXLNUM; - buf->b_signcols.invalid_bot = 0; - return buf->b_signcols.size; -} - /// Get "buf->b_fname", use "[No Name]" if it is NULL. char *buf_get_fname(const buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 7402e66403..beb3ec95b8 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -703,11 +703,10 @@ struct file_buffer { // may use a different synblock_T. struct { - int size; // last calculated number of sign columns - int max; // maximum value size is valid for. - linenr_T sentinel; // a line number which is holding up the signcolumn - linenr_T invalid_top; // first invalid line number that needs to be checked - linenr_T invalid_bot; // last invalid line number that needs to be checked + int max; // maximum number of signs on a single line + int max_count; // number of lines with max number of signs + bool resized; // whether max changed at start of redraw + Map(int, SignRange) invalid[1]; // map of invalid ranges to be checked } b_signcols; Terminal *terminal; // Terminal instance associated with the buffer diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index ccba8bd607..20311d80e5 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -203,21 +203,21 @@ void buf_put_decor_virt(buf_T *buf, DecorVirtText *vt) } static int sign_add_id = 0; -void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row, int row2) +void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row1, int row2) { if (sh->flags & kSHIsSign) { sh->sign_add_id = sign_add_id++; buf->b_signs++; if (sh->text.ptr) { buf->b_signs_with_text++; - buf_signcols_add_check(buf, row + 1, row2 + 1); + buf_signcols_invalidate_range(buf, row1, row2, 1); } } } -void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool free) +void buf_decor_remove(buf_T *buf, int row1, int row2, DecorInline decor, bool free) { - decor_redraw(buf, row, row2, decor); + decor_redraw(buf, row1, row2, decor); if (decor.ext) { DecorVirtText *vt = decor.data.ext.vt; while (vt) { @@ -227,7 +227,7 @@ void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool fre uint32_t idx = decor.data.ext.sh_idx; while (idx != DECOR_ID_INVALID) { DecorSignHighlight *sh = &kv_A(decor_items, idx); - buf_remove_decor_sh(buf, row, row2, sh); + buf_remove_decor_sh(buf, row1, row2, sh); idx = sh->next; } if (free) { @@ -249,7 +249,7 @@ void buf_remove_decor_virt(buf_T *buf, DecorVirtText *vt) } } -void buf_remove_decor_sh(buf_T *buf, int row, int row2, DecorSignHighlight *sh) +void buf_remove_decor_sh(buf_T *buf, int row1, int row2, DecorSignHighlight *sh) { if (sh->flags & kSHIsSign) { assert(buf->b_signs > 0); @@ -257,8 +257,8 @@ void buf_remove_decor_sh(buf_T *buf, int row, int row2, DecorSignHighlight *sh) if (sh->text.ptr) { assert(buf->b_signs_with_text > 0); buf->b_signs_with_text--; - if (row2 >= row) { - buf_signcols_del_check(buf, row + 1, row2 + 1); + if (row2 >= row1) { + buf_signcols_invalidate_range(buf, row1, row2, -1); } } } @@ -792,50 +792,128 @@ DecorSignHighlight *decor_find_sign(DecorInline decor) } } -// Increase the signcolumn size and update the sentinel line if necessary for -// the invalidated range. -void decor_validate_signcols(buf_T *buf, int max) +/// If "count" is greater than current max, set it and reset "max_count". +static void buf_signcols_validate_row(buf_T *buf, int count, int add) { - int signcols = 0; // highest value of count - int currow = buf->b_signcols.invalid_top - 1; - // TODO(bfredl): only need to use marktree_itr_get_overlap once. - // then we can process both start and end events and update state for each row - for (; currow < buf->b_signcols.invalid_bot; currow++) { - MarkTreeIter itr[1]; - if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) { - continue; - } + int del = add < 0 ? -add : 0; + if (count > buf->b_signcols.max) { + buf->b_signcols.max = count; + buf->b_signcols.max_count = 0; + buf->b_signcols.resized = true; + } + /// Add sign of "add" to "max_count" + if (count == buf->b_signcols.max - del) { + buf->b_signcols.max_count += (add > 0) - (add < 0); + } +} - int count = 0; - MTPair pair; - while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if (!mt_invalid(pair.start) && (pair.start.flags & MT_FLAG_DECOR_SIGNTEXT)) { - count++; - } - } +/// Validate a range by counting the number of overlapping signs and adjusting +/// "b_signcols" accordingly. +static void buf_signcols_validate_range(buf_T *buf, int row1, int row2, int add) +{ + int count = 0; // Number of signs on the current line + int currow = row1; + MTPair pair = { 0 }; + MarkTreeIter itr[1]; - while (itr->x) { - MTKey mark = marktree_itr_current(itr); - if (mark.pos.row != currow) { - break; - } - if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) { - count++; - } - marktree_itr_next(buf->b_marktree, itr); - } + // Allocate an array of integers holding the overlapping signs in the range. + assert(row2 >= row1); + int *overlap = xcalloc(sizeof(int), (size_t)(row2 + 1 - row1)); - if (count > signcols) { - if (count >= buf->b_signcols.size) { - buf->b_signcols.size = count; - buf->b_signcols.sentinel = currow + 1; - } - if (count >= max) { - return; - } - signcols = count; + // First find the number of overlapping signs at "row1". + marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr); + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + if (!mt_invalid(pair.start) && pair.start.flags & MT_FLAG_DECOR_SIGNTEXT) { + overlap[0]++; } } + + // Continue traversing the marktree until beyond "row2". Increment "count" for + // the start of a mark, increment the overlap array until the end of a paired mark. + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (mark.pos.row > row2) { + break; + } + // Finish the count at the previous row. + if (mark.pos.row != currow) { + buf_signcols_validate_row(buf, count + overlap[currow - row1], add); + currow = mark.pos.row; + count = 0; + } + + // Increment count and overlap array for the range of a paired sign mark. + if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) { + count++; + if (mt_paired(mark)) { + MTPos end = marktree_get_altpos(buf->b_marktree, mark, NULL); + for (int i = mark.pos.row; i < MIN(row2, end.row); i++) { + overlap[row2 - i]++; + } + } + } + + marktree_itr_next(buf->b_marktree, itr); + } + buf_signcols_validate_row(buf, count + overlap[currow - row1], add); + xfree(overlap); +} + +int buf_signcols_validate(win_T *wp, buf_T *buf, bool stc_check) +{ + int start; + SignRange range; + map_foreach(buf->b_signcols.invalid, start, range, { + // Leave rest of the ranges invalid if max is already greater than + // configured maximum or resize is detected for 'statuscolumn' rebuild. + if ((!stc_check || buf->b_signcols.resized) + && (range.add > 0 && buf->b_signcols.max >= wp->w_maxscwidth)) { + return wp->w_maxscwidth; + } + buf_signcols_validate_range(buf, start, range.end, range.add); + }); + + // Check if we need to scan the entire buffer. + if (buf->b_signcols.max_count == 0) { + buf->b_signcols.max = 0; + buf->b_signcols.resized = true; + buf_signcols_validate_range(buf, 0, buf->b_ml.ml_line_count, 0); + } + + map_clear(int, buf->b_signcols.invalid); + return buf->b_signcols.max; +} + +static void buf_signcols_invalidate_range(buf_T *buf, int row1, int row2, int add) +{ + if (!buf->b_signs_with_text) { + buf->b_signcols.max = buf->b_signcols.max_count = 0; + buf->b_signcols.resized = true; + map_clear(int, buf->b_signcols.invalid); + return; + } + + // Remove an invalid range if sum of added/removed signs is now 0. + SignRange *srp = map_ref(int, SignRange)(buf->b_signcols.invalid, row1, NULL); + if (srp && srp->end == row2 && srp->add + add == 0) { + map_del(int, SignRange)(buf->b_signcols.invalid, row1, NULL); + return; + } + + // Merge with overlapping invalid range. + int start; + SignRange range; + map_foreach(buf->b_signcols.invalid, start, range, { + if (row1 <= range.end && start <= row2) { + row1 = MIN(row1, start); + row2 = MAX(row2, range.end); + break; + } + }); + + srp = map_put_ref(int, SignRange)(buf->b_signcols.invalid, row1, NULL, NULL); + srp->end = row2; + srp->add += add; } void decor_redraw_end(DecorState *state) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 85f62db774..a436dd2766 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -603,15 +603,6 @@ int update_screen(void) buf->b_mod_tick_decor = display_tick; } } - - // Reset 'statuscolumn' if there is no dedicated signcolumn but it is invalid. - if (*wp->w_p_stc != NUL && wp->w_minscwidth <= SCL_NO - && (wp->w_buffer->b_signcols.invalid_bot || !wp->w_buffer->b_signcols.sentinel)) { - wp->w_nrwidth_line_count = 0; - wp->w_valid &= ~VALID_WCOL; - wp->w_redr_type = UPD_NOT_VALID; - wp->w_buffer->b_signcols.invalid_bot = 0; - } } // Go from top to bottom through the windows, redrawing the ones that need it. @@ -658,10 +649,11 @@ int update_screen(void) win_check_ns_hl(NULL); - // Reset b_mod_set flags. Going through all windows is probably faster - // than going through all buffers (there could be many buffers). + // Reset b_mod_set and b_signcols.resized flags. Going through all windows is + // probably faster than going through all buffers (there could be many buffers). FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { wp->w_buffer->b_mod_set = false; + wp->w_buffer->b_signcols.resized = false; } updating_screen = 0; @@ -1200,17 +1192,33 @@ void comp_col(void) set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); } -static void redraw_win_signcol(win_T *wp) +/// Redraw entire window "wp" if configured 'signcolumn' width changes. +static bool win_redraw_signcols(win_T *wp) { - // If we can compute a change in the automatic sizing of the sign column - // under 'signcolumn=auto:X' and signs currently placed in the buffer, better - // figuring it out here so we can redraw the entire screen for it. - int scwidth = wp->w_scwidth; - wp->w_scwidth = win_signcol_count(wp); - if (wp->w_scwidth != scwidth) { - changed_line_abv_curs_win(wp); - redraw_later(wp, UPD_NOT_VALID); + bool rebuild_stc = false; + buf_T *buf = wp->w_buffer; + int width = buf->b_signcols.max; + + if (wp->w_minscwidth <= SCL_NO) { + if (*wp->w_p_stc) { + if (map_size(buf->b_signcols.invalid)) { + buf_signcols_validate(wp, buf, true); + } + if (buf->b_signcols.resized) { + rebuild_stc = true; + wp->w_nrwidth_line_count = 0; + } + } + width = 0; + } else if (wp->w_maxscwidth <= 1 && buf->b_signs_with_text >= (size_t)wp->w_maxscwidth) { + width = wp->w_maxscwidth; + } else if (map_size(buf->b_signcols.invalid)) { + width = buf_signcols_validate(wp, buf, false); } + + int scwidth = wp->w_scwidth; + wp->w_scwidth = MAX(wp->w_minscwidth, width); + return (wp->w_scwidth != scwidth || rebuild_stc); } /// Check if horizontal separator of window "wp" at specified window corner is connected to the @@ -1490,7 +1498,11 @@ static void win_update(win_T *wp, DecorProviders *providers) DecorProviders line_providers; decor_providers_invoke_win(wp, providers, &line_providers); - redraw_win_signcol(wp); + if (win_redraw_signcols(wp)) { + wp->w_lines_valid = 0; + wp->w_redr_type = UPD_NOT_VALID; + changed_line_abv_curs_win(wp); + } init_search_hl(wp, &screen_search_hl); diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 0b8aea35c0..92fbc6fb79 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -67,18 +67,18 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col } else { assert(marktree_itr_valid(itr)); if (old_mark.pos.row == row && old_mark.pos.col == col) { + // not paired: we can revise in place if (mt_decor_any(old_mark)) { + mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_DECOR_SIGNTEXT; buf_decor_remove(buf, row, row, mt_decor(old_mark), true); } - - // not paired: we can revise in place mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK; mt_itr_rawkey(itr).flags |= flags; mt_itr_rawkey(itr).decor_data = decor.data; goto revised; } - buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, mt_decor(old_mark), true); marktree_del_itr(buf->b_marktree, itr, false); + buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, mt_decor(old_mark), true); } } else { *ns = MAX(*ns, id); @@ -515,20 +515,6 @@ void extmark_splice_impl(buf_T *buf, int start_row, colnr_T start_col, bcount_t extmark_splice_delete(buf, start_row, start_col, end_row, end_col, uvp, false, undo); } - // Move the signcolumn sentinel line - if (buf->b_signs_with_text && buf->b_signcols.sentinel) { - linenr_T se_lnum = buf->b_signcols.sentinel; - if (se_lnum >= start_row) { - if (old_row != 0 && se_lnum > old_row + start_row) { - buf->b_signcols.sentinel += new_row - old_row; - } else if (new_row == 0) { - buf->b_signcols.sentinel = 0; - } else { - buf->b_signcols.sentinel += new_row; - } - } - } - marktree_splice(buf->b_marktree, (int32_t)start_row, start_col, old_row, old_col, new_row, new_col); diff --git a/src/nvim/map.c b/src/nvim/map.c index be6bf58daa..0011c97f9b 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -111,6 +111,9 @@ void mh_clear(MapHash *h) #define VAL_NAME(x) quasiquote(x, String) #include "nvim/map_value_impl.c.h" #undef VAL_NAME +#define VAL_NAME(x) quasiquote(x, SignRange) +#include "nvim/map_value_impl.c.h" +#undef VAL_NAME #undef KEY_NAME #define KEY_NAME(x) x##ptr_t diff --git a/src/nvim/map_defs.h b/src/nvim/map_defs.h index f3c4e4ea95..b85ba5acaf 100644 --- a/src/nvim/map_defs.h +++ b/src/nvim/map_defs.h @@ -48,6 +48,7 @@ static const uint64_t value_init_uint64_t = 0; static const int64_t value_init_int64_t = 0; static const String value_init_String = STRING_INIT; static const ColorItem value_init_ColorItem = COLOR_ITEM_INITIALIZER; +static const SignRange value_init_SignRange = SIGNRANGE_INIT; // layer 0: type non-specific code @@ -150,6 +151,7 @@ KEY_DECLS(uint32_t) KEY_DECLS(String) KEY_DECLS(HlEntry) KEY_DECLS(ColorKey) +KEY_DECLS(SignRange) MAP_DECLS(int, int) MAP_DECLS(int, ptr_t) @@ -166,6 +168,7 @@ MAP_DECLS(uint32_t, uint32_t) MAP_DECLS(String, int) MAP_DECLS(int, String) MAP_DECLS(ColorKey, ColorItem) +MAP_DECLS(int, SignRange) #define set_has(T, set, key) set_has_##T(set, key) #define set_put(T, set, key) set_put_##T(set, key, NULL) diff --git a/src/nvim/move.c b/src/nvim/move.c index 227d064a27..12fb7d1f82 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -760,7 +760,7 @@ int win_col_off(win_T *wp) return ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL) ? (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) + ((cmdwin_type == 0 || wp != curwin) ? 0 : 1) - + win_fdccol_count(wp) + (win_signcol_count(wp) * SIGN_WIDTH); + + win_fdccol_count(wp) + (wp->w_scwidth * SIGN_WIDTH); } int curwin_col_off(void) diff --git a/src/nvim/option.c b/src/nvim/option.c index ba9d1262d4..c9d65c5683 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -6172,20 +6172,6 @@ bool fish_like_shell(void) return strstr(path_tail(p_sh), "fish") != NULL; } -/// Return the number of requested sign columns, based on current -/// buffer signs and on user configuration. -int win_signcol_count(win_T *wp) -{ - if (wp->w_minscwidth <= SCL_NO) { - return 0; - } - - int needed_signcols = buf_signcols(wp->w_buffer, wp->w_maxscwidth); - int ret = MAX(wp->w_minscwidth, MIN(wp->w_maxscwidth, needed_signcols)); - assert(ret <= SIGN_SHOW_MAX); - return ret; -} - /// Get window or buffer local options dict_T *get_winbuf_options(const int bufopt) FUNC_ATTR_WARN_UNUSED_RESULT diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 544524dd42..64145ba6ac 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2095,6 +2095,8 @@ const char *did_set_signcolumn(optset_T *args) if (check_signcolumn(win) != OK) { return e_invarg; } + int scwidth = win->w_minscwidth <= 0 ? 0 : MIN(win->w_maxscwidth, win->w_scwidth); + win->w_scwidth = MAX(win->w_minscwidth, scwidth); // When changing the 'signcolumn' to or from 'number', recompute the // width of the number column if 'number' or 'relativenumber' is set. if ((*oldval == 'n' && *(oldval + 1) == 'u') || win->w_minscwidth == SCL_NUM) { diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index 7219e04add..5d7f5b747b 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -761,7 +761,7 @@ int comp_textwidth(bool ff) textwidth -= 1; } textwidth -= win_fdccol_count(curwin); - textwidth -= win_signcol_count(curwin); + textwidth -= curwin->w_scwidth; if (curwin->w_p_nu || curwin->w_p_rnu) { textwidth -= 8; diff --git a/src/nvim/types_defs.h b/src/nvim/types_defs.h index c06737abb5..27227685fa 100644 --- a/src/nvim/types_defs.h +++ b/src/nvim/types_defs.h @@ -48,3 +48,10 @@ typedef enum { #define TRISTATE_FROM_INT(val) ((val) == 0 ? kFalse : ((val) >= 1 ? kTrue : kNone)) typedef int64_t OptInt; + +// Range entry for the "b_signcols.invalid" map in which the keys are the range start. +typedef struct { + int end; // End of the invalid range. + int add; // Number of signs added in the invalid range, negative for deleted signs. +} SignRange; +#define SIGNRANGE_INIT { 0, 0 } diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index e8fcfc46fc..a60562380a 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -4644,7 +4644,6 @@ l5 {2:~ }| | ]]} - end) it('can add a single sign (with end row)', function() @@ -4665,7 +4664,6 @@ l5 {2:~ }| | ]]} - end) it('can add a single sign and text highlight', function() @@ -4707,7 +4705,6 @@ l5 {2:~ }| | ]]} - end) it('can add multiple signs (multiple extmarks)', function() @@ -4867,7 +4864,6 @@ l5 {2:~ }| | ]]} - end) it('can add lots of signs', function() @@ -4977,6 +4973,22 @@ l5 | ]]} end) + + it('correct width when removing multiple signs from sentinel line', function() + screen:try_resize(20, 4) + insert(example_test3) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=3}) + meths.buf_set_extmark(0, ns, 1, -1, {invalidate = true, sign_text='S2'}) + meths.buf_set_extmark(0, ns, 1, -1, {invalidate = true, sign_text='S3'}) + feed('2Gdd') + + screen:expect{grid=[[ + S1l1 | + S1^l3 | + S1l4 | + | + ]]} + end) end) describe('decorations: virt_text', function() @@ -5073,5 +5085,4 @@ describe('decorations: virt_text', function() | ]]} end) - end)