diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c7e247133c..361cb3da5c 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5403,10 +5403,14 @@ A jump table for the options with a short description can be found at |Q_op|. *'signcolumn'* *'scl'* 'signcolumn' 'scl' string (default "auto") local to window - Whether or not to draw the signcolumn. Valid values are: + When and how to draw the signcolumn. Valid values are: "auto" only when there is a sign to display + "auto:[1-9]" resize to accommodate multiple signs up to the + given number (maximum 9), e.g. "auto:4" "no" never "yes" always + "yes:[1-9]" always, with fixed space for signs up to the given + number (maximum 9), e.g. "yes:3" *'smartcase'* *'scs'* *'nosmartcase'* *'noscs'* diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index da064ab89b..d3d9303d3c 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -857,7 +857,7 @@ Short explanation of each option: *option-list* 'showtabline' 'stal' tells when the tab pages line is displayed 'sidescroll' 'ss' minimum number of columns to scroll horizontal 'sidescrolloff' 'siso' min. nr. of columns to left and right of cursor -'signcolumn' 'scl' when to display the sign column +'signcolumn' 'scl' when and how to display the sign column 'smartcase' 'scs' no ignore case when pattern has uppercase 'smartindent' 'si' smart autoindenting for C programs 'smarttab' 'sta' use 'shiftwidth' when inserting diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index d3ac0ba613..30d7e972c3 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -193,11 +193,15 @@ Options: 'listchars' local to window 'pumblend' pseudo-transparent popupmenu 'scrollback' + 'signcolumn' supports up to 9 dynamic/fixed columns 'statusline' supports unlimited alignment sections 'tabline' %@Func@foo%X can call any function on mouse-click 'wildoptions' `pum` flag to use popupmenu for wildmode completion 'winhighlight' window-local highlights +Signs: + Signs are removed if the associated line is deleted. + Variables: |v:event| |v:exiting| diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index d67783baa0..b77bae47a9 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1,23 +1,23 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * buffer.c: functions for dealing with the buffer structure - */ +// +// buffer.c: functions for dealing with the buffer structure +// -/* - * The buffer list is a double linked list of all buffers. - * Each buffer can be in one of these states: - * never loaded: BF_NEVERLOADED is set, only the file name is valid - * not loaded: b_ml.ml_mfp == NULL, no memfile allocated - * hidden: b_nwindows == 0, loaded but not displayed in a window - * normal: loaded and displayed in a window - * - * Instead of storing file names all over the place, each file name is - * stored in the buffer list. It can be referenced by a number. - * - * The current implementation remembers all file names ever used. - */ +// +// The buffer list is a double linked list of all buffers. +// Each buffer can be in one of these states: +// never loaded: BF_NEVERLOADED is set, only the file name is valid +// not loaded: b_ml.ml_mfp == NULL, no memfile allocated +// hidden: b_nwindows == 0, loaded but not displayed in a window +// normal: loaded and displayed in a window +// +// Instead of storing file names all over the place, each file name is +// stored in the buffer list. It can be referenced by a number. +// +// The current implementation remembers all file names ever used. +// #include #include @@ -1688,6 +1688,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) buf = xcalloc(1, sizeof(buf_T)); // init b: variables buf->b_vars = tv_dict_alloc(); + buf->b_signcols_max = -1; init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); buf_init_changedtick(buf); } @@ -5046,6 +5047,7 @@ static void insert_sign( if (next != NULL) { next->prev = newsign; } + buf->b_signcols_max = -1; if (prev == NULL) { /* When adding first sign need to redraw the windows to create the @@ -5063,6 +5065,96 @@ static void insert_sign( } } +static int sign_compare(const void *a1, const void *a2) +{ + const signlist_T *s1 = *(const signlist_T **)a1; + const signlist_T *s2 = *(const signlist_T **)a2; + + // Sort by line number and the by id + + if (s1->lnum > s2->lnum) { + return 1; + } + if (s1->lnum < s2->lnum) { + return -1; + } + if (s1->id > s2->id) { + return 1; + } + if (s1->id < s2->id) { + return -1; + } + + return 0; +} + +int buf_signcols(buf_T *buf) +{ + if (buf->b_signcols_max == -1) { + signlist_T *sign; // a sign in the signlist + signlist_T **signs_array; + signlist_T **prev_sign; + int nr_signs = 0, i = 0, same; + + // Count the number of signs + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { + nr_signs++; + } + + // Make an array of all the signs + signs_array = xcalloc((size_t)nr_signs, sizeof(*sign)); + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { + signs_array[i] = sign; + i++; + } + + // Sort the array + qsort(signs_array, (size_t)nr_signs, sizeof(signlist_T *), + sign_compare); + + // Find the maximum amount of signs existing in a single line + buf->b_signcols_max = 0; + + same = 1; + for (i = 1; i < nr_signs; i++) { + if (signs_array[i - 1]->lnum != signs_array[i]->lnum) { + if (buf->b_signcols_max < same) { + buf->b_signcols_max = same; + } + same = 1; + } else { + same++; + } + } + + if (nr_signs > 0 && buf->b_signcols_max < same) { + buf->b_signcols_max = same; + } + + // Recreate the linked list with the sorted order of the array + buf->b_signlist = NULL; + prev_sign = &buf->b_signlist; + + for (i = 0; i < nr_signs; i++) { + sign = signs_array[i]; + sign->next = NULL; + *prev_sign = sign; + + prev_sign = &sign->next; + } + + xfree(signs_array); + + // Check if we need to redraw + if (buf->b_signcols_max != buf->b_signcols) { + buf->b_signcols = buf->b_signcols_max; + redraw_buf_later(buf, NOT_VALID); + } + } + + return buf->b_signcols; +} + /* * Add the sign into the signlist. Find the right spot to do it though. */ @@ -5073,8 +5165,9 @@ void buf_addsign( int typenr /* typenr of sign we are adding */ ) { - signlist_T *sign; /* a sign in the signlist */ - signlist_T *prev; /* the previous sign */ + signlist_T **lastp; // pointer to pointer to current sign + signlist_T *sign; // a sign in the signlist + signlist_T *prev; // the previous sign prev = NULL; for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { @@ -5110,7 +5203,18 @@ void buf_addsign( } insert_sign(buf, prev, sign, id, lnum, typenr); - return; + // Having more than one sign with _the same type_ and on the _same line_ is + // unwanted, let's prevent it. + + lastp = &buf->b_signlist; + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { + if (lnum == sign->lnum && sign->typenr == typenr && id != sign->id) { + *lastp = sign->next; + xfree(sign); + } else { + lastp = &sign->next; + } + } } // For an existing, placed sign "markId" change the type to "typenr". @@ -5133,16 +5237,23 @@ linenr_T buf_change_sign_type( return (linenr_T)0; } + /// Gets a sign from a given line. -/// In case of multiple signs, returns the most recently placed one. /// /// @param buf Buffer in which to search /// @param lnum Line in which to search /// @param type Type of sign to look for -/// @return Identifier of the first matching sign, or 0 -int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type) +/// @param idx if there multiple signs, this index will pick the n-th +// out of the most `max_signs` sorted ascending by Id. +/// @param max_signs the number of signs, with priority for the ones +// with the highest Ids. +/// @return Identifier of the matching sign, or 0 +int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, + int idx, int max_signs) { signlist_T *sign; // a sign in a b_signlist + signlist_T *matches[9]; + int nr_matches = 0; for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->lnum == lnum @@ -5153,13 +5264,30 @@ int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type) && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) || (type == SIGN_NUMHL && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { - return sign->typenr; + matches[nr_matches] = sign; + nr_matches++; + + if (nr_matches == ARRAY_SIZE(matches)) { + break; + } } } + + if (nr_matches > 0) { + if (nr_matches > max_signs) { + idx += nr_matches - max_signs; + } + + if (idx >= nr_matches) { + return 0; + } + + return matches[idx]->typenr; + } + return 0; } - linenr_T buf_delsign( buf_T *buf, /* buffer sign is stored in */ int id /* sign id */ @@ -5170,6 +5298,7 @@ linenr_T buf_delsign( signlist_T *next; /* the next sign in a b_signlist */ linenr_T lnum; /* line number whose sign was deleted */ + buf->b_signcols_max = -1; lastp = &buf->b_signlist; lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { @@ -5255,6 +5384,7 @@ void buf_delete_signs(buf_T *buf) xfree(buf->b_signlist); buf->b_signlist = next; } + buf->b_signcols_max = -1; } /* @@ -5309,18 +5439,27 @@ void sign_list_placed(buf_T *rbuf) */ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after) { - signlist_T *sign; /* a sign in a b_signlist */ + signlist_T *sign; // a sign in a b_signlist + signlist_T *next; // the next sign in a b_signlist + signlist_T **lastp; // pointer to pointer to current sign - for (sign = curbuf->b_signlist; sign != NULL; sign = sign->next) { + curbuf->b_signcols_max = -1; + lastp = &curbuf->b_signlist; + + for (sign = curbuf->b_signlist; sign != NULL; sign = next) { + next = sign->next; if (sign->lnum >= line1 && sign->lnum <= line2) { if (amount == MAXLNUM) { - sign->lnum = line1; + *lastp = next; + xfree(sign); + continue; } else { sign->lnum += amount; } } else if (sign->lnum > line2) sign->lnum += amount_after; + lastp = &sign->next; } } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 2f2546b360..1b0af8aa49 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -778,7 +778,9 @@ struct file_buffer { * normally points to this, but some windows * may use a different synblock_T. */ - signlist_T *b_signlist; /* list of signs to draw */ + signlist_T *b_signlist; // list of signs to draw + int b_signcols_max; // cached maximum number of sign columns + int b_signcols; // last calculated number of sign columns Terminal *terminal; // Terminal instance associated with the buffer diff --git a/src/nvim/edit.c b/src/nvim/edit.c index c8d98bce3b..a0dce5ff7d 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -5899,10 +5899,7 @@ comp_textwidth ( textwidth -= 1; } textwidth -= curwin->w_p_fdc; - - if (signcolumn_on(curwin)) { - textwidth -= 1; - } + textwidth -= win_signcol_count(curwin); if (curwin->w_p_nu || curwin->w_p_rnu) textwidth -= 8; diff --git a/src/nvim/move.c b/src/nvim/move.c index db96fdac5f..43fdf0a4d1 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -692,7 +692,7 @@ int win_col_off(win_T *wp) return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + (int)wp->w_p_fdc - + (signcolumn_on(wp) ? win_signcol_width(wp) : 0); + + (win_signcol_count(wp) * win_signcol_width(wp)); } int curwin_col_off(void) diff --git a/src/nvim/option.c b/src/nvim/option.c index ad0ccd9f15..0f6408c9d4 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -305,7 +305,10 @@ static char *(p_fcl_values[]) = { "all", NULL }; static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "noinsert", "noselect", NULL }; static char *(p_icm_values[]) = { "nosplit", "split", NULL }; -static char *(p_scl_values[]) = { "yes", "no", "auto", NULL }; +static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", + "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", + "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", + "yes:9", NULL }; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.c.generated.h" @@ -7091,16 +7094,34 @@ int csh_like_shell(void) return strstr((char *)path_tail(p_sh), "csh") != NULL; } -/// Return true when window "wp" has a column to draw signs in. -bool signcolumn_on(win_T *wp) +/// 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_p_scl == 'n') { - return false; - } - if (*wp->w_p_scl == 'y') { - return true; - } - return wp->w_buffer->b_signlist != NULL; + int maximum = 1, needed_signcols; + const char *scl = (const char *)wp->w_p_scl; + + if (*scl == 'n') { + return 0; + } + needed_signcols = buf_signcols(wp->w_buffer); + + // yes or yes + if (!strncmp(scl, "yes:", 4)) { + // Fixed amount of columns + return scl[4] - '0'; + } + if (*scl == 'y') { + return 1; + } + + // auto or auto: + if (!strncmp(scl, "auto:", 5)) { + // Variable depending on a configuration + maximum = scl[5] - '0'; + } + + return MIN(maximum, needed_signcols); } /// Get window or buffer local options diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 138736f31b..28a1b65fff 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -621,6 +621,11 @@ static void win_update(win_T *wp) linenr_T mod_bot = 0; int save_got_int; + // 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. + buf_signcols(buf); + type = wp->w_redr_type; win_grid_alloc(wp); @@ -1568,8 +1573,9 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h wp->w_grid.Columns, ' ', ' ', win_hl_attr(wp, HLF_FC)); } - if (signcolumn_on(wp)) { - int nn = n + win_signcol_width(wp); + int count = win_signcol_count(wp); + if (count > 0) { + int nn = n + win_signcol_width(wp) * count; // draw the sign column left of the fold column if (nn > wp->w_grid.Columns) { @@ -1607,8 +1613,9 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h n = nn; } - if (signcolumn_on(wp)) { - int nn = n + win_signcol_width(wp); + int count = win_signcol_count(wp); + if (count > 0) { + int nn = n + win_signcol_width(wp) * count; // draw the sign column after the fold column if (nn > wp->w_grid.Columns) { @@ -1773,10 +1780,10 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col); // If signs are being displayed, add spaces. - if (signcolumn_on(wp)) { + if (win_signcol_count(wp) > 0) { len = wp->w_grid.Columns - col; if (len > 0) { - int len_max = win_signcol_width(wp); + int len_max = win_signcol_width(wp) * win_signcol_count(wp); if (len > len_max) { len = len_max; } @@ -2404,7 +2411,7 @@ win_line ( } // If this line has a sign with line highlighting set line_attr. - v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL); + v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); if (v != 0) { line_attr = sign_get_attr((int)v, SIGN_LINEHL); } @@ -2654,6 +2661,7 @@ win_line ( extra_check = true; } + int sign_idx = 0; // Repeat for the whole displayed line. for (;; ) { has_match_conc = 0; @@ -2694,7 +2702,8 @@ win_line ( draw_state = WL_SIGN; /* Show the sign column when there are any signs in this * buffer or when using Netbeans. */ - if (signcolumn_on(wp)) { + int count = win_signcol_count(wp); + if (count > 0) { int text_sign; // Draw cells with the sign value or blank. c_extra = ' '; @@ -2703,7 +2712,8 @@ win_line ( n_extra = win_signcol_width(wp); if (row == startrow + filler_lines && filler_todo <= 0) { - text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT); + text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, + sign_idx, count); if (text_sign != 0) { p_extra = sign_get_text(text_sign); int symbol_blen = (int)STRLEN(p_extra); @@ -2721,6 +2731,11 @@ win_line ( char_attr = sign_get_attr(text_sign, SIGN_TEXT); } } + + sign_idx++; + if (sign_idx < count) { + draw_state = WL_SIGN - 1; + } } } @@ -2769,7 +2784,8 @@ win_line ( n_extra = number_width(wp) + 1; char_attr = win_hl_attr(wp, HLF_N); - int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL); + int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL, + 0, 1); if (num_sign != 0) { // :sign defined with "numhl" highlight. char_attr = sign_get_attr(num_sign, SIGN_NUMHL); @@ -2856,6 +2872,7 @@ win_line ( } if (draw_state == WL_LINE - 1 && n_extra == 0) { + sign_idx = 0; draw_state = WL_LINE; if (saved_n_extra) { /* Continue item from end of wrapped line. */ diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index bc0e2e3799..c9821ef619 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -115,6 +115,115 @@ describe('Signs', function() ]]) end) + it('multiple signs #9295', function() + feed('iabc') + command('set number') + command('set signcolumn=yes:2') + command('sign define pietSearch text=>> texthl=Search') + command('sign define pietError text=XX texthl=Error') + command('sign define pietWarn text=WW texthl=Warning') + command('sign place 1 line=1 name=pietSearch buffer=1') + command('sign place 2 line=1 name=pietError buffer=1') + -- Line 2 helps checking that signs in the same line are ordered by Id. + command('sign place 4 line=2 name=pietSearch buffer=1') + command('sign place 3 line=2 name=pietError buffer=1') + -- Line 3 checks that with a limit over the maximum number + -- of signs, the ones with the highest Ids are being picked, + -- and presented by their sorted Id order. + command('sign place 4 line=3 name=pietSearch buffer=1') + command('sign place 5 line=3 name=pietWarn buffer=1') + command('sign place 3 line=3 name=pietError buffer=1') + screen:expect([[ + {1:>>}XX{6: 1 }a | + XX{1:>>}{6: 2 }b | + {1:>>}WW{6: 3 }c | + {2: }{6: 4 }^ | + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + | + ]]) + -- With the default setting, we get the sign with the top id. + command('set signcolumn=yes:1') + screen:expect([[ + XX{6: 1 }a | + {1:>>}{6: 2 }b | + WW{6: 3 }c | + {2: }{6: 4 }^ | + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + | + ]]) + -- "auto:3" accommodates all the signs we defined so far. + command('set signcolumn=auto:3') + screen:expect([[ + {1:>>}XX{2: }{6: 1 }a | + XX{1:>>}{2: }{6: 2 }b | + XX{1:>>}WW{6: 3 }c | + {2: }{6: 4 }^ | + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + | + ]]) + -- Check "yes:9". + command('set signcolumn=yes:9') + screen:expect([[ + {1:>>}XX{2: }{6: 1 }a | + XX{1:>>}{2: }{6: 2 }b | + XX{1:>>}WW{2: }{6: 3 }c | + {2: }{6: 4 }^ | + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + | + ]]) + -- Check "auto:N" larger than the maximum number of signs defined in + -- a single line (same result as "auto:3"). + command('set signcolumn=auto:4') + screen:expect{grid=[[ + {1:>>}XX{2: }{6: 1 }a | + XX{1:>>}{2: }{6: 2 }b | + XX{1:>>}WW{6: 3 }c | + {2: }{6: 4 }^ | + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + | + ]]} + end) + it('can have 32bit sign IDs', function() command('sign define piet text=>> texthl=Search') command('sign place 100000 line=1 name=piet buffer=1')