aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/message.c38
-rw-r--r--src/nvim/option.c56
-rw-r--r--src/nvim/screen.c20
-rw-r--r--src/nvim/testdir/test_listchars.vim142
5 files changed, 235 insertions, 22 deletions
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 89f7448188..ba2bcd7223 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1214,6 +1214,7 @@ struct window_S {
int tab3; ///< third tab character
int lead;
int trail;
+ int *multispace;
int conceal;
} w_p_lcs_chars;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index bd26b8608f..f9ed9f3004 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1707,6 +1707,8 @@ void msg_prt_line(char_u *s, int list)
int n;
int attr = 0;
char_u *lead = NULL;
+ bool in_multispace = false;
+ int multispace_pos = 0;
char_u *trail = NULL;
int l;
@@ -1771,6 +1773,10 @@ void msg_prt_line(char_u *s, int list)
} else {
attr = 0;
c = *s++;
+ in_multispace = c == ' ' && ((col > 0 && s[-2] == ' ') || *s == ' ');
+ if (!in_multispace) {
+ multispace_pos = 0;
+ }
if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) {
// tab amount depends on current column
n_extra = tabstop_padding(col,
@@ -1786,11 +1792,11 @@ void msg_prt_line(char_u *s, int list)
: curwin->w_p_lcs_chars.tab1;
c_extra = curwin->w_p_lcs_chars.tab2;
c_final = curwin->w_p_lcs_chars.tab3;
- attr = HL_ATTR(HLF_8);
+ attr = HL_ATTR(HLF_0);
}
} else if (c == 160 && list && curwin->w_p_lcs_chars.nbsp != NUL) {
c = curwin->w_p_lcs_chars.nbsp;
- attr = HL_ATTR(HLF_8);
+ attr = HL_ATTR(HLF_0);
} else if (c == NUL && list && curwin->w_p_lcs_chars.eol != NUL) {
p_extra = (char_u *)"";
c_extra = NUL;
@@ -1807,16 +1813,24 @@ void msg_prt_line(char_u *s, int list)
c = *p_extra++;
/* Use special coloring to be able to distinguish <hex> from
* the same in plain text. */
- attr = HL_ATTR(HLF_8);
- } else if (c == ' ' && lead != NULL && s <= lead) {
- c = curwin->w_p_lcs_chars.lead;
- attr = HL_ATTR(HLF_8);
- } else if (c == ' ' && trail != NULL && s > trail) {
- c = curwin->w_p_lcs_chars.trail;
- attr = HL_ATTR(HLF_8);
- } else if (c == ' ' && list && curwin->w_p_lcs_chars.space != NUL) {
- c = curwin->w_p_lcs_chars.space;
- attr = HL_ATTR(HLF_8);
+ attr = HL_ATTR(HLF_0);
+ } else if (c == ' ') {
+ if (lead != NULL && s <= lead) {
+ 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) {
+ c = curwin->w_p_lcs_chars.multispace[multispace_pos++];
+ if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ attr = HL_ATTR(HLF_0);
+ } else if (list && curwin->w_p_lcs_chars.space != NUL) {
+ c = curwin->w_p_lcs_chars.space;
+ attr = HL_ATTR(HLF_0);
+ }
}
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index fb7a0446b6..9fecd6521b 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3442,6 +3442,8 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
int c1;
int c2 = 0;
int c3 = 0;
+ char_u *last_multispace; // Last occurrence of "multispace:"
+ int multispace_len = 0; // Length of lcs-multispace string
struct chars_tab {
int *cp; ///< char value
@@ -3511,6 +3513,15 @@ static char_u *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);
+ }
+ 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;
+ }
}
}
p = *varp;
@@ -3527,22 +3538,22 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
int c1len = utf_ptr2len(s);
c1 = mb_cptr2char_adv((const char_u **)&s);
if (mb_char2cells(c1) > 1 || (c1len == 1 && c1 > 127)) {
- continue;
+ return e_invarg;
}
if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
if (*s == NUL) {
- continue;
+ return e_invarg;
}
int c2len = utf_ptr2len(s);
c2 = mb_cptr2char_adv((const char_u **)&s);
if (mb_char2cells(c2) > 1 || (c2len == 1 && c2 > 127)) {
- continue;
+ return e_invarg;
}
if (!(*s == ',' || *s == NUL)) {
int c3len = utf_ptr2len(s);
c3 = mb_cptr2char_adv((const char_u **)&s);
if (mb_char2cells(c3) > 1 || (c3len == 1 && c3 > 127)) {
- continue;
+ return e_invarg;
}
}
}
@@ -3563,7 +3574,42 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
}
if (i == entries) {
- return e_invarg;
+ len = STRLEN("multispace");
+ if ((varp == &p_lcs || varp == &wp->w_p_lcs)
+ && STRNCMP(p, "multispace", len) == 0
+ && p[len] == ':'
+ && p[len + 1] != NUL) {
+ s = p + len + 1;
+ if (round == 0) {
+ // Get length of lcs-multispace string in the first round
+ last_multispace = p;
+ multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ int c1len = utf_ptr2len(s);
+ c1 = mb_cptr2char_adv((const char_u **)&s);
+ if (mb_char2cells(c1) > 1 || (c1len == 1 && c1 > 127)) {
+ return e_invarg;
+ }
+ multispace_len++;
+ }
+ if (multispace_len == 0) {
+ // lcs-multispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ c1 = mb_cptr2char_adv((const char_u **)&s);
+ if (p == last_multispace) {
+ wp->w_p_lcs_chars.multispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
+ } else {
+ return e_invarg;
+ }
}
if (*p == ',') {
p++;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 1df5d6e8ef..79e960fe8b 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -2081,6 +2081,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
int change_end = -1; // last col of changed area
colnr_T trailcol = MAXCOL; // start of trailing spaces
colnr_T leadcol = 0; // start of leading spaces
+ bool in_multispace = false; // in multiple consecutive spaces
+ int multispace_pos = 0; // position in lcs-multispace string
bool need_showbreak = false; // overlong line, skip first x chars
sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs
int num_signs; // number of signs for line
@@ -2462,6 +2464,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.trail
|| wp->w_p_lcs_chars.lead
|| wp->w_p_lcs_chars.nbsp) {
@@ -3580,6 +3583,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
}
}
+ in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' ');
+ if (!in_multispace) {
+ multispace_pos = 0;
+ }
+
// 'list': Change char 160 to 'nbsp' and space to 'space'.
// But not when the character is followed by a composing
// character (use mb_l to check that).
@@ -3591,10 +3599,18 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
&& wp->w_p_lcs_chars.nbsp)
|| (c == ' '
&& mb_l == 1
- && wp->w_p_lcs_chars.space
+ && (wp->w_p_lcs_chars.space
+ || (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
&& ptr - line >= leadcol
&& ptr - line <= trailcol))) {
- c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
+ if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
+ c = wp->w_p_lcs_chars.multispace[multispace_pos++];
+ if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ } else {
+ c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
+ }
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 8fc1b519f4..8a1393a45d 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -140,6 +140,93 @@ func Test_listchars()
call assert_equal(expected, split(execute("%list"), "\n"))
+ " Test multispace
+ normal ggdG
+ set listchars=eol:$
+ set listchars+=multispace:yYzZ
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ 'yYzZffffyYzZ$',
+ \ 'yYi iyYzZygg$',
+ \ ' hyYzZyYzZyY$',
+ \ 'yYzZyYzZyYj $',
+ \ 'yYzZ0yY0yYzZ$',
+ \ '$'
+ \ ]
+ redraw!
+ 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+=space:x,multispace:XyY
+
+ let expected = [
+ \ 'XyYXffffXyYX$',
+ \ 'XyixiXyYXygg$',
+ \ 'xhXyYXyYXyYX$',
+ \ 'XyYXyYXyYXjx$',
+ \ 'XyYX0Xy0XyYX$',
+ \ '$'
+ \ ]
+ redraw!
+ 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"))
+
+ set listchars+=lead:>,trail:<
+
+ let expected = [
+ \ '>>>>ffff<<<<$',
+ \ '>>ixiXyYXygg$',
+ \ '>h<<<<<<<<<<$',
+ \ '>>>>>>>>>>j<$',
+ \ '>>>>0Xy0<<<<$',
+ \ '$'
+ \ ]
+ redraw!
+ 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"))
+
+ " removing 'multispace:'
+ set listchars-=multispace:XyY
+ set listchars-=multispace:yYzZ
+
+ let expected = [
+ \ '>>>>ffff<<<<$',
+ \ '>>ixixxxxxgg$',
+ \ '>h<<<<<<<<<<$',
+ \ '>>>>>>>>>>j<$',
+ \ '>>>>0xx0<<<<$',
+ \ '$'
+ \ ]
+ redraw!
+ 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 nbsp
normal ggdG
set listchars=nbsp:X,trail:Y
@@ -189,20 +276,69 @@ func Test_listchars_unicode()
set encoding=utf-8
set ff=unix
- set listchars=eol:⇔,space:␣,nbsp:≠,tab:←↔→
+ set listchars=eol:⇔,space:␣,multispace:≡≢≣,nbsp:≠,tab:←↔→
set list
let nbsp = nr2char(0xa0)
- call append(0, ["a\tb c" .. nbsp .. "d"])
- let expected = ['a←↔↔↔↔↔→b␣c≠d⇔']
+ call append(0, [" a\tb c" .. nbsp .. "d "])
+ let expected = ['≡≢≣≡≢≣≡≢a←↔↔↔↔↔→b␣c≠d≡≢⇔']
redraw!
call cursor(1, 1)
call assert_equal(expected, ScreenLines(1, virtcol('$')))
+
+ set listchars+=lead:⇨,trail:⇦
+ let expected = ['⇨⇨⇨⇨⇨⇨⇨⇨a←↔↔↔↔↔→b␣c≠d⇦⇦⇔']
+ redraw!
+ call cursor(1, 1)
+ call assert_equal(expected, ScreenLines(1, virtcol('$')))
+
let &encoding=oldencoding
enew!
set listchars& ff&
endfunction
+func Test_listchars_invalid()
+ enew!
+ set ff=unix
+
+ set listchars=eol:$
+ set list
+ set ambiwidth=double
+
+ " No colon
+ call assert_fails('set listchars=x', 'E474:')
+ call assert_fails('set listchars=x', 'E474:')
+ call assert_fails('set listchars=multispace', '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:')
+
+ " One occurrence too short
+ call assert_fails('set listchars=space:,space:x', 'E474:')
+ call assert_fails('set listchars=space:x,space:', 'E474:')
+ call assert_fails('set listchars=tab:x,tab:xx', 'E474:')
+ 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:')
+
+ " Too long
+ call assert_fails('set listchars=space:xx', 'E474:')
+ call assert_fails('set listchars=tab:xxxx', 'E474:')
+
+ " Has non-single width character
+ call assert_fails('set listchars=space:·', 'E474:')
+ call assert_fails('set listchars=tab:·x', 'E474:')
+ call assert_fails('set listchars=tab:x·', 'E474:')
+ call assert_fails('set listchars=tab:xx·', 'E474:')
+ call assert_fails('set listchars=multispace:·', 'E474:')
+ call assert_fails('set listchars=multispace:xxx·', 'E474:')
+
+ enew!
+ set ambiwidth& listchars& ff&
+endfunction
+
" Tests that space characters following composing character won't get replaced
" by listchars.
func Test_listchars_composing()