signs: support multiple columns #9295

closes #990
closes #9295

- Support for multiple auto-adjusted sign columns.
  With this change, having more than one sign on a line, and with the
  'auto' setting on 'signcolumn', extra columns will shown automatically
  to accomodate all the existing signs.

  For example, suppose we have this view:

   5147             }
   5148
   5149             return sign->typenr;
   5150         }
   5151     }
   5152     return 0;
   5153 }
   5154

  We have GitGutter installed, so it tells us about modified lines that
  are not commmited. So let's change line 5152:

     5147             }
     5148
     5149             return sign->typenr;
     5150         }
     5151     }
   ~ 5152     return 0;
     5153 }
     5154

  Now we add a mark over line 5152 using 'ma' in normal mode:

      5147             }
      5148
      5149             return sign->typenr;
      5150         }
      5151     }
  a ~ 5152     return 0;
      5153 }
      5154

  Previously, Vim/Nvim would have picked only one of the signs,
  because there was no support for having multiple signs in a line.

- Remove signs from deleted lines.
  Suppose we have highlights on a group of lines and we delete them:

   +     6 use std::ops::Deref;
   --+   7 use std::borrow::Cow;
   --+   8 use std::io::{Cursor};
         9 use proc_macro2::TokenStream;
        10 use syn::export::ToTokens;
   --+  11 use std::io::Write;
   >>   12 use std::ops::Deref;

  Without this change, these signs will momentarily accumulate in
  the sign column until the plugins wake up to refresh them.

  + --+ --+ --+ >>  6

  Discussion: It may be better to extend the API a bit and allow this
  to happen for only certain types of signs. For example, VIM marks
  and vim-gitgutter removal signs may want to be presreved, unlike
  line additions and linter highlights.

- 'signcolumn': support 'auto:NUM' and 'yes:NUM' settings
- sort signs according to id, from lowest to highest. If you have
  git-gutter, vim-signature, and ALE, it would appear in this order:
  git-gutter - vim-signature - ALE.
- recalculate size before screen update
- If no space for all signs, prefer the higher ids (while keeping the
  rendering order from low to high).
- Prevent duplicate signs. Duplicate signs were invisible to the user,
  before using our extended non-standard signcolumn settings.
- multi signcols: fix bug related to wrapped lines.
  In wrapped lines, the wrapped parts of a line did not include the extra
  columns if they existed. The result was a misdrawing of the wrapped
  parts. Fix the issue by:
    1. initializing the signcol counter to 0 when we are on a wrap boundary
    2. allowing for the draw of spaces in that case.
This commit is contained in:
Dan Aloni 2019-03-25 02:16:58 +01:00 committed by Justin M. Keyes
parent f705ed22fd
commit 36762a00a8
10 changed files with 348 additions and 55 deletions

View File

@ -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'*

View File

@ -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 <Tab>

View File

@ -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|

View File

@ -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 <stdbool.h>
#include <string.h>
@ -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;
}
}

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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:<NUM>
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

View File

@ -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. */

View File

@ -115,6 +115,115 @@ describe('Signs', function()
]])
end)
it('multiple signs #9295', function()
feed('ia<cr>b<cr>c<cr><esc>')
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')