aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/options.txt13
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/message.c14
-rw-r--r--src/nvim/option.c54
-rw-r--r--src/nvim/screen.c19
-rw-r--r--src/nvim/testdir/test_listchars.vim147
-rw-r--r--src/nvim/window.c1
7 files changed, 230 insertions, 19 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index bd484366db..c9f978471f 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3782,14 +3782,23 @@ A jump table for the options with a short description can be found at |Q_op|.
"space" setting is used. For example,
`:set listchars=multispace:---+` shows ten consecutive
spaces as:
- ---+---+--
+ ---+---+-- ~
*lcs-lead*
lead:c Character to show for leading spaces. When omitted,
leading spaces are blank. Overrides the "space" and
"multispace" settings for leading spaces. You can
combine it with "tab:", for example: >
:set listchars+=tab:>-,lead:.
-< *lcs-trail*
+< *lcs-leadmultispace*
+ leadmultispace:c...
+ Like multispace value, but only for leading whitespace
+ Overrides |lcs-lead| for leading multiple spaces.
+ `:set listchars=leadmultispace:---+` shows ten consecutive
+ leading spaces as:
+ ---+---+--XXX ~
+ Where "XXX" denotes the first non-blank characters in
+ the line.
+ *lcs-trail*
trail:c Character to show for trailing spaces. When omitted,
trailing spaces are blank. Overrides the "space" and
"multispace" settings for trailing spaces.
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index b5980612f8..f1dd8aadeb 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1232,6 +1232,7 @@ struct window_S {
int lead;
int trail;
int *multispace;
+ int *leadmultispace;
int conceal;
} w_p_lcs_chars;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 2e57d11fb6..8085b90ddd 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1783,7 +1783,7 @@ void msg_prt_line(char_u *s, int list)
}
}
// find end of leading whitespace
- if (curwin->w_p_lcs_chars.lead) {
+ if (curwin->w_p_lcs_chars.lead || curwin->w_p_lcs_chars.leadmultispace != NULL) {
lead = s;
while (ascii_iswhite(lead[0])) {
lead++;
@@ -1873,13 +1873,21 @@ void msg_prt_line(char_u *s, int list)
// the same in plain text.
attr = HL_ATTR(HLF_0);
} else if (c == ' ') {
- if (lead != NULL && s <= lead) {
+ if (list && lead != NULL && s <= lead && in_multispace
+ && curwin->w_p_lcs_chars.leadmultispace != NULL) {
+ c = curwin->w_p_lcs_chars.leadmultispace[multispace_pos++];
+ if (curwin->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ attr = HL_ATTR(HLF_0);
+ } else if (lead != NULL && s <= lead && curwin->w_p_lcs_chars.lead != NUL) {
c = curwin->w_p_lcs_chars.lead;
attr = HL_ATTR(HLF_0);
} else if (trail != NULL && s > trail) {
c = curwin->w_p_lcs_chars.trail;
attr = HL_ATTR(HLF_0);
- } else if (list && in_multispace && curwin->w_p_lcs_chars.multispace != NULL) {
+ } else if (list && in_multispace
+ && curwin->w_p_lcs_chars.multispace != NULL) {
c = curwin->w_p_lcs_chars.multispace[multispace_pos++];
if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
multispace_pos = 0;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a15c6f4854..35736fd5eb 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3552,13 +3552,15 @@ static int get_encoded_char_adv(char_u **p)
/// @return error message, NULL if it's OK.
static char *set_chars_option(win_T *wp, char_u **varp, bool set)
{
- int round, i, len, entries;
+ int round, i, len, len2, entries;
char_u *p, *s;
int c1;
int c2 = 0;
int c3 = 0;
- char_u *last_multispace = NULL; // Last occurrence of "multispace:"
- int multispace_len = 0; // Length of lcs-multispace string
+ char_u *last_multispace = NULL; // Last occurrence of "multispace:"
+ char_u *last_lmultispace = NULL; // Last occurrence of "leadmultispace:"
+ int multispace_len = 0; // Length of lcs-multispace string
+ int lead_multispace_len = 0; // Length of lcs-leadmultispace string
struct chars_tab {
int *cp; ///< char value
@@ -3637,15 +3639,23 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set)
if (varp == &p_lcs || varp == &wp->w_p_lcs) {
wp->w_p_lcs_chars.tab1 = NUL;
wp->w_p_lcs_chars.tab3 = NUL;
- if (wp->w_p_lcs_chars.multispace != NULL) {
- xfree(wp->w_p_lcs_chars.multispace);
- }
+
+ xfree(wp->w_p_lcs_chars.multispace);
if (multispace_len > 0) {
wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int));
wp->w_p_lcs_chars.multispace[multispace_len] = NUL;
} else {
wp->w_p_lcs_chars.multispace = NULL;
}
+
+ xfree(wp->w_p_lcs_chars.leadmultispace);
+ if (lead_multispace_len > 0) {
+ wp->w_p_lcs_chars.leadmultispace
+ = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int));
+ wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL;
+ } else {
+ wp->w_p_lcs_chars.leadmultispace = NULL;
+ }
}
}
p = *varp;
@@ -3694,6 +3704,7 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set)
if (i == entries) {
len = (int)STRLEN("multispace");
+ len2 = (int)STRLEN("leadmultispace");
if ((varp == &p_lcs || varp == &wp->w_p_lcs)
&& STRNCMP(p, "multispace", len) == 0
&& p[len] == ':'
@@ -3725,6 +3736,37 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set)
}
p = s;
}
+ } else if ((varp == &p_lcs || varp == &wp->w_p_lcs)
+ && STRNCMP(p, "leadmultispace", len2) == 0
+ && p[len2] == ':'
+ && p[len2 + 1] != NUL) {
+ s = p + len2 + 1;
+ if (round == 0) {
+ // get length of lcs-leadmultispace string in first round
+ last_lmultispace = p;
+ lead_multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ lead_multispace_len++;
+ }
+ if (lead_multispace_len == 0) {
+ // lcs-leadmultispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ c1 = get_encoded_char_adv(&s);
+ if (p == last_lmultispace) {
+ wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
} else {
return e_invarg;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index fe306f8c6b..fb7e34214f 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -2453,6 +2453,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
if (wp->w_p_list && !has_fold && !end_fill) {
if (wp->w_p_lcs_chars.space
|| wp->w_p_lcs_chars.multispace != NULL
+ || wp->w_p_lcs_chars.leadmultispace != NULL
|| wp->w_p_lcs_chars.trail
|| wp->w_p_lcs_chars.lead
|| wp->w_p_lcs_chars.nbsp) {
@@ -2467,7 +2468,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
trailcol += (colnr_T)(ptr - line);
}
// find end of leading whitespace
- if (wp->w_p_lcs_chars.lead) {
+ if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
leadcol = 0;
while (ascii_iswhite(ptr[leadcol])) {
leadcol++;
@@ -3441,8 +3442,20 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
if ((trailcol != MAXCOL && ptr > line + trailcol && c == ' ')
|| (leadcol != 0 && ptr < line + leadcol && c == ' ')) {
- c = (ptr > line + trailcol) ? wp->w_p_lcs_chars.trail
- : wp->w_p_lcs_chars.lead;
+ if (leadcol != 0 && in_multispace && ptr < line + leadcol
+ && wp->w_p_lcs_chars.leadmultispace != NULL) {
+ c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
+ if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) {
+ c = wp->w_p_lcs_chars.trail;
+ } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) {
+ c = wp->w_p_lcs_chars.lead;
+ } else if (leadcol != 0 && c == ' ' && wp->w_p_lcs_chars.space) {
+ c = wp->w_p_lcs_chars.space;
+ }
+
n_attr = 1;
extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = char_attr; // save current attr
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index b239c9c6b5..eed4d7e30c 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -132,7 +132,7 @@ func Test_listchars()
\ 'h<<<<<<<<<<<$',
\ '<<<<<<<<<<<<$',
\ '>>>>0xx0<<<<$',
- \ '$'
+ \ '$'
\ ]
redraw!
for i in range(1, 5)
@@ -162,7 +162,7 @@ func Test_listchars()
\ ' hyYzZyYzZyY$',
\ 'yYzZyYzZyYj $',
\ 'yYzZ0yY0yYzZ$',
- \ '$'
+ \ '$'
\ ]
redraw!
for i in range(1, 5)
@@ -172,7 +172,133 @@ func Test_listchars()
call assert_equal(expected, split(execute("%list"), "\n"))
+ " Test leadmultispace + multispace
+ normal ggdG
+ set listchars=eol:$,multispace:yYzZ,nbsp:S
+ set listchars+=leadmultispace:.-+*
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i  gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffffyYzZ$',
+ \ '.-i iSyYzZgg$',
+ \ ' hyYzZyYzZyY$',
+ \ '.-+*.-+*.-j $',
+ \ '.-+*0yY0yYzZ$',
+ \ '$'
+ \ ]
+ redraw!
+ call assert_equal('eol:$,multispace:yYzZ,nbsp:S,leadmultispace:.-+*', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test leadmultispace without multispace
+ normal ggdG
+ set listchars-=multispace:yYzZ
+ set listchars+=space:+,trail:>,eol:$
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffff>>>>$',
+ \ '.-i+i+++++gg$',
+ \ '+h>>>>>>>>>>$',
+ \ '.-+*.-+*.-j>$',
+ \ '.-+*0++0>>>>$',
+ \ '$',
+ \ ]
+
+ redraw!
+ call assert_equal('eol:$,nbsp:S,leadmultispace:.-+*,space:+,trail:>,eol:$', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test leadmultispace only
+ normal ggdG
+ set listchars=eol:$ " Accommodate Nvim default
+ set listchars=leadmultispace:.-+*
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffff ',
+ \ '.-i i gg',
+ \ ' h ',
+ \ '.-+*.-+*.-j ',
+ \ '.-+*0 0 ',
+ \ ' ',
+ \ ]
+ redraw!
+ call assert_equal('leadmultispace:.-+*', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, 12))
+ endfor
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test leadmultispace and lead and space
+ normal ggdG
+ set listchars=eol:$ " Accommodate Nvim default
+ set listchars+=lead:<,space:-
+ set listchars+=leadmultispace:.-+*
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffff----$',
+ \ '.-i-i-----gg$',
+ \ '<h----------$',
+ \ '.-+*.-+*.-j-$',
+ \ '.-+*0--0----$',
+ \ '$',
+ \ ]
+ redraw!
+ call assert_equal('eol:$,lead:<,space:-,leadmultispace:.-+*', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
" the last occurrence of 'multispace:' is used
+ set listchars=eol:$ " Accommodate Nvim default
+ set listchars+=multispace:yYzZ
set listchars+=space:x,multispace:XyY
let expected = [
@@ -181,9 +307,10 @@ func Test_listchars()
\ 'xhXyYXyYXyYX$',
\ 'XyYXyYXyYXjx$',
\ 'XyYX0Xy0XyYX$',
- \ '$'
+ \ '$'
\ ]
redraw!
+ call assert_equal('eol:$,multispace:yYzZ,space:x,multispace:XyY', &listchars)
for i in range(1, 5)
call cursor(i, 1)
call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
@@ -199,7 +326,7 @@ func Test_listchars()
\ '>h<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0Xy0<<<<$',
- \ '$'
+ \ '$'
\ ]
redraw!
for i in range(1, 5)
@@ -219,7 +346,7 @@ func Test_listchars()
\ '>h<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0xx0<<<<$',
- \ '$'
+ \ '$'
\ ]
redraw!
for i in range(1, 5)
@@ -315,11 +442,13 @@ func Test_listchars_invalid()
call assert_fails('set listchars=x', 'E474:')
call assert_fails('set listchars=x', 'E474:')
call assert_fails('set listchars=multispace', 'E474:')
+ call assert_fails('set listchars=leadmultispace', 'E474:')
" Too short
call assert_fails('set listchars=space:', 'E474:')
call assert_fails('set listchars=tab:x', 'E474:')
call assert_fails('set listchars=multispace:', 'E474:')
+ call assert_fails('set listchars=leadmultispace:', 'E474:')
" One occurrence too short
call assert_fails('set listchars=space:,space:x', 'E474:')
@@ -328,6 +457,8 @@ func Test_listchars_invalid()
call assert_fails('set listchars=tab:xx,tab:x', 'E474:')
call assert_fails('set listchars=multispace:,multispace:x', 'E474:')
call assert_fails('set listchars=multispace:x,multispace:', 'E474:')
+ call assert_fails('set listchars=leadmultispace:,leadmultispace:x', 'E474:')
+ call assert_fails('set listchars=leadmultispace:x,leadmultispace:', 'E474:')
" Too long
call assert_fails('set listchars=space:xx', 'E474:')
@@ -340,6 +471,8 @@ func Test_listchars_invalid()
call assert_fails('set listchars=tab:xx·', 'E474:')
call assert_fails('set listchars=multispace:·', 'E474:')
call assert_fails('set listchars=multispace:xxx·', 'E474:')
+ call assert_fails('set listchars=leadmultispace:·', 'E474:')
+ call assert_fails('set listchars=leadmultispace:xxx·', 'E474:')
" Has control character
call assert_fails("set listchars=space:\x01", 'E474:')
@@ -354,6 +487,10 @@ func Test_listchars_invalid()
call assert_fails('set listchars=tab:xx\\x01', 'E474:')
call assert_fails('set listchars=multispace:\\x01', 'E474:')
call assert_fails('set listchars=multispace:xxx\\x01', 'E474:')
+ call assert_fails("set listchars=leadmultispace:\x01", 'E474:')
+ call assert_fails('set listchars=leadmultispace:\\x01', 'E474:')
+ call assert_fails("set listchars=leadmultispace:xxx\x01", 'E474:')
+ call assert_fails('set listchars=leadmultispace:xxx\\x01', 'E474:')
enew!
set ambiwidth& listchars& ff&
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 5f4179944d..f429c493a7 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -5085,6 +5085,7 @@ static void win_free(win_T *wp, tabpage_T *tp)
clear_winopt(&wp->w_allbuf_opt);
xfree(wp->w_p_lcs_chars.multispace);
+ xfree(wp->w_p_lcs_chars.leadmultispace);
vars_clear(&wp->w_vars->dv_hashtab); // free all w: variables
hash_init(&wp->w_vars->dv_hashtab);