# HG changeset patch # User Bram Moolenaar # Date 1613418304 -3600 # Node ID 44be09b2561985a8808889ad67edc0589f8cfa09 # Parent c58a5a123ca5584a99f72aada3f31ffd868c7e92 patch 8.2.2518: 'listchars' should be window-local Commit: https://github.com/vim/vim/commit/eed9d46293f0842aad0d50ff3a526f9a48b12421 Author: Bram Moolenaar Date: Mon Feb 15 20:38:25 2021 +0100 patch 8.2.2518: 'listchars' should be window-local Problem: 'listchars' should be window-local. Solution: Make 'listchars' global-local. (Yegappan Lakshmanan, Marco Hinz, closes #5206, closes #7850) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4854,7 +4854,7 @@ A jump table for the options with a shor *'listchars'* *'lcs'* 'listchars' 'lcs' string (default "eol:$") - global + global or local to window |global-local| Strings to use in 'list' mode and for the |:list| command. It is a comma separated list of string settings. *lcs-eol* diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -4549,7 +4549,7 @@ build_stl_str_hl( case STL_VIRTCOL_ALT: // In list mode virtcol needs to be recomputed virtcol = wp->w_virtcol; - if (wp->w_p_list && lcs_tab1 == NUL) + if (wp->w_p_list && wp->w_lcs_chars.tab1 == NUL) { wp->w_p_list = FALSE; getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); diff --git a/src/charset.c b/src/charset.c --- a/src/charset.c +++ b/src/charset.c @@ -753,7 +753,7 @@ vim_strnsize(char_u *s, int len) #ifdef FEAT_VARTABS # define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ - if (*(p) == TAB && (!(wp)->w_p_list || lcs_tab1)) \ + if (*(p) == TAB && (!(wp)->w_p_list || wp->w_lcs_chars.tab1)) \ { \ return tabstop_padding(col, (buf)->b_p_ts, (buf)->b_p_vts_array); \ } \ @@ -761,7 +761,7 @@ vim_strnsize(char_u *s, int len) return ptr2cells(p); #else # define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ - if (*(p) == TAB && (!(wp)->w_p_list || lcs_tab1)) \ + if (*(p) == TAB && (!(wp)->w_p_list || wp->w_lcs_chars.tab1)) \ { \ int ts; \ ts = (buf)->b_p_ts; \ @@ -1153,7 +1153,7 @@ win_nolbr_chartabsize( { int n; - if (*s == TAB && (!wp->w_p_list || lcs_tab1)) + if (*s == TAB && (!wp->w_p_list || wp->w_lcs_chars.tab1)) { # ifdef FEAT_VARTABS return tabstop_padding(col, wp->w_buffer->b_p_ts, @@ -1248,7 +1248,7 @@ getvcol( * use a simple loop. * Also use this when 'list' is set but tabs take their normal size. */ - if ((!wp->w_p_list || lcs_tab1 != NUL) + if ((!wp->w_p_list || wp->w_lcs_chars.tab1 != NUL) #ifdef FEAT_LINEBREAK && !wp->w_p_lbr && *get_showbreak_value(wp) == NUL && !wp->w_p_bri #endif diff --git a/src/drawline.c b/src/drawline.c --- a/src/drawline.c +++ b/src/drawline.c @@ -248,9 +248,9 @@ win_line( int c_final = NUL; // final char, mandatory if set int extra_attr = 0; // attributes when n_extra != 0 static char_u *at_end_str = (char_u *)""; // used for p_extra when - // displaying lcs_eol at end-of-line - int lcs_eol_one = lcs_eol; // lcs_eol until it's been used - int lcs_prec_todo = lcs_prec; // lcs_prec until it's been used + // displaying eol at end-of-line + int lcs_eol_one = wp->w_lcs_chars.eol; // eol until it's been used + int lcs_prec_todo = wp->w_lcs_chars.prec; // prec until it's been used // saved "extra" items for when draw_state becomes WL_LINE (again) int saved_n_extra = 0; @@ -735,11 +735,14 @@ win_line( if (wp->w_p_list) { - if (lcs_space || lcs_trail || lcs_lead || lcs_nbsp) + if (wp->w_lcs_chars.space + || wp->w_lcs_chars.trail + || wp->w_lcs_chars.lead + || wp->w_lcs_chars.nbsp) extra_check = TRUE; // find start of trailing whitespace - if (lcs_trail) + if (wp->w_lcs_chars.trail) { trailcol = (colnr_T)STRLEN(ptr); while (trailcol > (colnr_T)0 && VIM_ISWHITE(ptr[trailcol - 1])) @@ -747,7 +750,7 @@ win_line( trailcol += (colnr_T) (ptr - line); } // find end of leading whitespace - if (lcs_lead) + if (wp->w_lcs_chars.lead) { leadcol = 0; while (VIM_ISWHITE(ptr[leadcol])) @@ -2000,22 +2003,23 @@ win_line( } #endif - // 'list': Change char 160 to lcs_nbsp and space to lcs_space. - // But not when the character is followed by a composing - // character (use mb_l to check that). + // 'list': Change char 160 to 'nbsp' and space to 'space' + // setting in 'listchars'. But not when the character is + // followed by a composing character (use mb_l to check that). if (wp->w_p_list && ((((c == 160 && mb_l == 1) || (mb_utf8 && ((mb_c == 160 && mb_l == 2) || (mb_c == 0x202f && mb_l == 3)))) - && lcs_nbsp) + && wp->w_lcs_chars.nbsp) || (c == ' ' && mb_l == 1 - && lcs_space + && wp->w_lcs_chars.space && ptr - line >= leadcol && ptr - line <= trailcol))) { - c = (c == ' ') ? lcs_space : lcs_nbsp; + c = (c == ' ') ? wp->w_lcs_chars.space : + wp->w_lcs_chars.nbsp; if (area_attr == 0 && search_attr == 0) { n_attr = 1; @@ -2036,7 +2040,8 @@ win_line( if ((trailcol != MAXCOL && ptr > line + trailcol && c == ' ') || (leadcol != 0 && ptr < line + leadcol && c == ' ')) { - c = (ptr > line + trailcol) ? lcs_trail : lcs_lead; + c = (ptr > line + trailcol) ? wp->w_lcs_chars.trail + : wp->w_lcs_chars.lead; if (!attr_pri) { n_attr = 1; @@ -2061,7 +2066,7 @@ win_line( // when getting a character from the file, we may have to // turn it into something else on the way to putting it // into "ScreenLines". - if (c == TAB && (!wp->w_p_list || lcs_tab1)) + if (c == TAB && (!wp->w_p_list || wp->w_lcs_chars.tab1)) { int tab_len = 0; long vcol_adjusted = vcol; // removed showbreak length @@ -2101,18 +2106,19 @@ win_line( // there are characters to conceal tab_len += vcol_off; // boguscols before FIX_FOR_BOGUSCOLS macro from above - if (wp->w_p_list && lcs_tab1 && old_boguscols > 0 - && n_extra > tab_len) + if (wp->w_p_list && wp->w_lcs_chars.tab1 + && old_boguscols > 0 + && n_extra > tab_len) tab_len += n_extra - tab_len; #endif // if n_extra > 0, it gives the number of chars, to // use for a tab, else we need to calculate the width // for a tab - len = (tab_len * mb_char2len(lcs_tab2)); + len = (tab_len * mb_char2len(wp->w_lcs_chars.tab2)); if (n_extra > 0) len += n_extra - tab_len; - c = lcs_tab1; + c = wp->w_lcs_chars.tab1; p = alloc(len + 1); vim_memset(p, ' ', len); p[len] = NUL; @@ -2120,7 +2126,7 @@ win_line( p_extra_free = p; for (i = 0; i < tab_len; i++) { - int lcs = lcs_tab2; + int lcs = wp->w_lcs_chars.tab2; if (*p == NUL) { @@ -2128,10 +2134,10 @@ win_line( break; } - // if lcs_tab3 is given, need to change the char + // if tab3 is given, need to change the char // for tab - if (lcs_tab3 && i == tab_len - 1) - lcs = lcs_tab3; + if (wp->w_lcs_chars.tab3 && i == tab_len - 1) + lcs = wp->w_lcs_chars.tab3; mb_char2bytes(lcs, p); p += mb_char2len(lcs); n_extra += mb_char2len(lcs) @@ -2162,21 +2168,23 @@ win_line( // correctly set further below (effectively reverts the // FIX_FOR_BOGSUCOLS macro if (n_extra == tab_len + vc_saved && wp->w_p_list - && lcs_tab1) + && wp->w_lcs_chars.tab1) tab_len += vc_saved; } #endif mb_utf8 = FALSE; // don't draw as UTF-8 if (wp->w_p_list) { - c = (n_extra == 0 && lcs_tab3) ? lcs_tab3 : lcs_tab1; + c = (n_extra == 0 && wp->w_lcs_chars.tab3) + ? wp->w_lcs_chars.tab3 + : wp->w_lcs_chars.tab1; #ifdef FEAT_LINEBREAK if (wp->w_p_lbr) c_extra = NUL; // using p_extra from above else #endif - c_extra = lcs_tab2; - c_final = lcs_tab3; + c_extra = wp->w_lcs_chars.tab2; + c_final = wp->w_lcs_chars.tab3; n_attr = tab_len + 1; extra_attr = hl_combine_attr(win_attr, HL_ATTR(HLF_8)); saved_attr2 = char_attr; // save current attr @@ -2241,8 +2249,8 @@ win_line( c_final = NUL; } } - if (wp->w_p_list && lcs_eol > 0) - c = lcs_eol; + if (wp->w_p_list && wp->w_lcs_chars.eol > 0) + c = wp->w_lcs_chars.eol; else c = ' '; lcs_eol_one = -1; @@ -2344,7 +2352,8 @@ win_line( // don't do search HL for the rest of the line if (line_attr != 0 && char_attr == search_attr && (did_line_attr > 1 - || (wp->w_p_list && lcs_eol > 0))) + || (wp->w_p_list && + wp->w_lcs_chars.eol > 0))) char_attr = line_attr; # ifdef FEAT_DIFF if (diff_hlf == HLF_TXD) @@ -2404,8 +2413,8 @@ win_line( c = match_conc; else if (syn_get_sub_char() != NUL) c = syn_get_sub_char(); - else if (lcs_conceal != NUL) - c = lcs_conceal; + else if (wp->w_lcs_chars.conceal != NUL) + c = wp->w_lcs_chars.conceal; else c = ' '; @@ -2548,7 +2557,7 @@ win_line( && draw_state > WL_NR && c != NUL) { - c = lcs_prec; + c = wp->w_lcs_chars.prec; lcs_prec_todo = NUL; if (has_mbyte && (*mb_char2cells)(mb_c) > 1) { @@ -2594,7 +2603,7 @@ win_line( // highlight match at end of line. If it's beyond the last // char on the screen, just overwrite that one (tricky!) Not // needed when a '$' was displayed for 'list'. - if (lcs_eol == lcs_eol_one + if (wp->w_lcs_chars.eol == lcs_eol_one && ((area_attr != 0 && vcol == fromcol && (VIsual_mode != Ctrl_V || lnum == VIsual.lnum @@ -2764,7 +2773,7 @@ win_line( // Show "extends" character from 'listchars' if beyond the line end and // 'list' is set. - if (lcs_ext != NUL + if (wp->w_lcs_chars.ext != NUL && wp->w_p_list && !wp->w_p_wrap #ifdef FEAT_DIFF @@ -2779,7 +2788,7 @@ win_line( || (wp->w_p_list && lcs_eol_one > 0) || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { - c = lcs_ext; + c = wp->w_lcs_chars.ext; char_attr = hl_combine_attr(win_attr, HL_ATTR(HLF_AT)); mb_c = c; if (enc_utf8 && utf_char2len(c) > 1) @@ -3036,7 +3045,8 @@ win_line( #ifdef FEAT_DIFF || filler_todo > 0 #endif - || (wp->w_p_list && lcs_eol != NUL && p_extra != at_end_str) + || (wp->w_p_list && wp->w_lcs_chars.eol != NUL + && p_extra != at_end_str) || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { @@ -3161,7 +3171,7 @@ win_line( #endif saved_char_attr = 0; n_extra = 0; - lcs_prec_todo = lcs_prec; + lcs_prec_todo = wp->w_lcs_chars.prec; #ifdef FEAT_LINEBREAK # ifdef FEAT_DIFF if (filler_todo <= 0) diff --git a/src/drawscreen.c b/src/drawscreen.c --- a/src/drawscreen.c +++ b/src/drawscreen.c @@ -696,7 +696,7 @@ win_redr_ruler(win_T *wp, int always, in // In list mode virtcol needs to be recomputed virtcol = wp->w_virtcol; - if (wp->w_p_list && lcs_tab1 == NUL) + if (wp->w_p_list && wp->w_lcs_chars.tab1 == NUL) { wp->w_p_list = FALSE; getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -9736,7 +9736,8 @@ f_synconcealed(typval_T *argvars UNUSED, { cchar = syn_get_sub_char(); if (cchar == NUL && curwin->w_p_cole == 1) - cchar = (lcs_conceal == NUL) ? ' ' : lcs_conceal; + cchar = (curwin->w_lcs_chars.conceal == NUL) ? ' ' + : curwin->w_lcs_chars.conceal; if (cchar != NUL) { if (has_mbyte) diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1342,21 +1342,6 @@ EXTERN char_u *homedir INIT(= NULL); // directory is not a local directory, globaldir is NULL. EXTERN char_u *globaldir INIT(= NULL); -// Characters from 'listchars' option -EXTERN int lcs_eol INIT(= '$'); -EXTERN int lcs_ext INIT(= NUL); -EXTERN int lcs_prec INIT(= NUL); -EXTERN int lcs_nbsp INIT(= NUL); -EXTERN int lcs_space INIT(= NUL); -EXTERN int lcs_tab1 INIT(= NUL); -EXTERN int lcs_tab2 INIT(= NUL); -EXTERN int lcs_tab3 INIT(= NUL); -EXTERN int lcs_trail INIT(= NUL); -EXTERN int lcs_lead INIT(= NUL); -#ifdef FEAT_CONCEAL -EXTERN int lcs_conceal INIT(= ' '); -#endif - // Characters from 'fillchars' option EXTERN int fill_stl INIT(= ' '); EXTERN int fill_stlnc INIT(= ' '); diff --git a/src/indent.c b/src/indent.c --- a/src/indent.c +++ b/src/indent.c @@ -432,7 +432,8 @@ get_indent_str( { if (*ptr == TAB) { - if (!list || lcs_tab1) // count a tab for what it is worth + if (!list || curwin->w_lcs_chars.tab1) + // count a tab for what it is worth count += ts - (count % ts); else // In list mode, when tab is not set, count screen char width @@ -462,7 +463,7 @@ get_indent_str_vtab(char_u *ptr, int ts, { if (*ptr == TAB) // count a tab for what it is worth { - if (!list || lcs_tab1) + if (!list || curwin->w_lcs_chars.tab1) count += tabstop_padding(count, ts, vts); else // In list mode, when tab is not set, count screen char width diff --git a/src/message.c b/src/message.c --- a/src/message.c +++ b/src/message.c @@ -1848,14 +1848,14 @@ msg_prt_line(char_u *s, int list) if (list) { // find start of trailing whitespace - if (lcs_trail) + if (curwin->w_lcs_chars.trail) { trail = s + STRLEN(s); while (trail > s && VIM_ISWHITE(trail[-1])) --trail; } // find end of leading whitespace - if (lcs_lead) + if (curwin->w_lcs_chars.lead) { lead = s; while (VIM_ISWHITE(lead[0])) @@ -1868,7 +1868,7 @@ msg_prt_line(char_u *s, int list) // output a space for an empty line, otherwise the line will be // overwritten - if (*s == NUL && !(list && lcs_eol != NUL)) + if (*s == NUL && !(list && curwin->w_lcs_chars.eol != NUL)) msg_putchar(' '); while (!got_int) @@ -1890,11 +1890,11 @@ msg_prt_line(char_u *s, int list) { STRCPY(buf, "?"); } - else if (lcs_nbsp != NUL && list + else if (curwin->w_lcs_chars.nbsp != NUL && list && (mb_ptr2char(s) == 160 || mb_ptr2char(s) == 0x202f)) { - mb_char2bytes(lcs_nbsp, buf); + mb_char2bytes(curwin->w_lcs_chars.nbsp, buf); buf[(*mb_ptr2len)(buf)] = NUL; } else @@ -1910,7 +1910,7 @@ msg_prt_line(char_u *s, int list) { attr = 0; c = *s++; - if (c == TAB && (!list || lcs_tab1)) + if (c == TAB && (!list || curwin->w_lcs_chars.tab1)) { // tab amount depends on current column #ifdef FEAT_VARTABS @@ -1927,24 +1927,26 @@ msg_prt_line(char_u *s, int list) } else { - c = (n_extra == 0 && lcs_tab3) ? lcs_tab3 : lcs_tab1; - c_extra = lcs_tab2; - c_final = lcs_tab3; + c = (n_extra == 0 && curwin->w_lcs_chars.tab3) + ? curwin->w_lcs_chars.tab3 + : curwin->w_lcs_chars.tab1; + c_extra = curwin->w_lcs_chars.tab2; + c_final = curwin->w_lcs_chars.tab3; attr = HL_ATTR(HLF_8); } } - else if (c == 160 && list && lcs_nbsp != NUL) + else if (c == 160 && list && curwin->w_lcs_chars.nbsp != NUL) { - c = lcs_nbsp; + c = curwin->w_lcs_chars.nbsp; attr = HL_ATTR(HLF_8); } - else if (c == NUL && list && lcs_eol != NUL) + else if (c == NUL && list && curwin->w_lcs_chars.eol != NUL) { p_extra = (char_u *)""; c_extra = NUL; c_final = NUL; n_extra = 1; - c = lcs_eol; + c = curwin->w_lcs_chars.eol; attr = HL_ATTR(HLF_AT); --s; } @@ -1961,17 +1963,17 @@ msg_prt_line(char_u *s, int list) } else if (c == ' ' && lead != NULL && s <= lead) { - c = lcs_lead; + c = curwin->w_lcs_chars.lead; attr = HL_ATTR(HLF_8); } else if (c == ' ' && trail != NULL && s > trail) { - c = lcs_trail; + c = curwin->w_lcs_chars.trail; attr = HL_ATTR(HLF_8); } - else if (c == ' ' && list && lcs_space != NUL) + else if (c == ' ' && list && curwin->w_lcs_chars.space != NUL) { - c = lcs_space; + c = curwin->w_lcs_chars.space; attr = HL_ATTR(HLF_8); } } diff --git a/src/misc1.c b/src/misc1.c --- a/src/misc1.c +++ b/src/misc1.c @@ -403,7 +403,7 @@ plines_win_nofold(win_T *wp, linenr_T ln * If list mode is on, then the '$' at the end of the line may take up one * extra column. */ - if (wp->w_p_list && lcs_eol != NUL) + if (wp->w_p_list && wp->w_lcs_chars.eol != NUL) col += 1; /* @@ -460,7 +460,8 @@ plines_win_col(win_T *wp, linenr_T lnum, * from one screen line to the next (when 'columns' is not a multiple of * 'ts') -- webb. */ - if (*s == TAB && (State & NORMAL) && (!wp->w_p_list || lcs_tab1)) + if (*s == TAB && (State & NORMAL) && (!wp->w_p_list || + wp->w_lcs_chars.tab1)) col += win_lbr_chartabsize(wp, line, s, (colnr_T)col, NULL) - 1; /* diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -2337,9 +2337,11 @@ didset_options2(void) // Parse default for 'wildmode' check_opt_wim(); - (void)set_chars_option(&p_lcs); + // Parse default for 'listchars'. + (void)set_chars_option(curwin, &curwin->w_p_lcs); + // Parse default for 'fillchars'. - (void)set_chars_option(&p_fcs); + (void)set_chars_option(curwin, &p_fcs); #ifdef FEAT_CLIPBOARD // Parse default for 'clipboard' @@ -5063,6 +5065,11 @@ unset_global_local_option(char_u *name, case PV_MENC: clear_string_option(&buf->b_p_menc); break; + case PV_LCS: + clear_string_option(&((win_T *)from)->w_p_lcs); + set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs); + redraw_later(NOT_VALID); + break; } } #endif @@ -5121,6 +5128,8 @@ get_varp_scope(struct vimoption *p, int #endif case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); case PV_MENC: return (char_u *)&(curbuf->b_p_menc); + case PV_LCS: return (char_u *)&(curwin->w_p_lcs); + } return NULL; // "cannot happen" } @@ -5218,6 +5227,8 @@ get_varp(struct vimoption *p) case PV_ARAB: return (char_u *)&(curwin->w_p_arab); #endif case PV_LIST: return (char_u *)&(curwin->w_p_list); + case PV_LCS: return *curwin->w_p_lcs != NUL + ? (char_u *)&(curwin->w_p_lcs) : p->var; #ifdef FEAT_SPELL case PV_SPELL: return (char_u *)&(curwin->w_p_spell); #endif @@ -5445,6 +5456,7 @@ after_copy_winopt(win_T *wp UNUSED) fill_culopt_flags(NULL, wp); check_colorcolumn(wp); #endif + set_chars_option(wp, &wp->w_p_lcs); } /* @@ -5460,6 +5472,7 @@ copy_winopt(winopt_T *from, winopt_T *to to->wo_arab = from->wo_arab; #endif to->wo_list = from->wo_list; + to->wo_lcs = vim_strsave(from->wo_lcs); to->wo_nu = from->wo_nu; to->wo_rnu = from->wo_rnu; #ifdef FEAT_LINEBREAK @@ -5594,6 +5607,7 @@ check_winopt(winopt_T *wop UNUSED) check_string_option(&wop->wo_briopt); #endif check_string_option(&wop->wo_wcr); + check_string_option(&wop->wo_lcs); } /* @@ -5639,6 +5653,7 @@ clear_winopt(winopt_T *wop UNUSED) clear_string_option(&wop->wo_twk); clear_string_option(&wop->wo_tws); #endif + clear_string_option(&wop->wo_lcs); } #ifdef FEAT_EVAL diff --git a/src/option.h b/src/option.h --- a/src/option.h +++ b/src/option.h @@ -1231,6 +1231,7 @@ enum enum { WV_LIST = 0 + , WV_LCS #ifdef FEAT_ARABIC , WV_ARAB #endif diff --git a/src/optiondefs.h b/src/optiondefs.h --- a/src/optiondefs.h +++ b/src/optiondefs.h @@ -185,6 +185,7 @@ #ifdef FEAT_LINEBREAK # define PV_LBR OPT_WIN(WV_LBR) #endif +#define PV_LCS OPT_BOTH(OPT_WIN(WV_LCS)) #define PV_NU OPT_WIN(WV_NU) #define PV_RNU OPT_WIN(WV_RNU) #ifdef FEAT_LINEBREAK @@ -1598,7 +1599,7 @@ static struct vimoption options[] = (char_u *)VAR_WIN, PV_LIST, {(char_u *)FALSE, (char_u *)0L} SCTX_INIT}, {"listchars", "lcs", P_STRING|P_VI_DEF|P_RALL|P_ONECOMMA|P_NODUP, - (char_u *)&p_lcs, PV_NONE, + (char_u *)&p_lcs, PV_LCS, {(char_u *)"eol:$", (char_u *)0L} SCTX_INIT}, {"loadplugins", "lpl", P_BOOL|P_VI_DEF, (char_u *)&p_lpl, PV_NONE, diff --git a/src/optionstr.c b/src/optionstr.c --- a/src/optionstr.c +++ b/src/optionstr.c @@ -862,10 +862,24 @@ did_set_string_option( { if (check_opt_strings(p_ambw, p_ambw_values, FALSE) != OK) errmsg = e_invarg; - else if (set_chars_option(&p_lcs) != NULL) - errmsg = _("E834: Conflicts with value of 'listchars'"); - else if (set_chars_option(&p_fcs) != NULL) + else if (set_chars_option(curwin, &p_fcs) != NULL) errmsg = _("E835: Conflicts with value of 'fillchars'"); + else + { + tabpage_T *tp; + win_T *wp; + + FOR_ALL_TAB_WINDOWS(tp, wp) + { + if (set_chars_option(wp, &wp->w_p_lcs) != NULL) + { + errmsg = _("E834: Conflicts with value of 'listchars'"); + goto ambw_end; + } + } + } +ambw_end: + {} } // 'background' @@ -1292,16 +1306,37 @@ did_set_string_option( } } - // 'listchars' + // global 'listchars' else if (varp == &p_lcs) { - errmsg = set_chars_option(varp); + errmsg = set_chars_option(curwin, varp); + if (errmsg == NULL) + { + tabpage_T *tp; + win_T *wp; + + // The current window is set to use the global 'listchars' value. + // So clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) + clear_string_option(&curwin->w_p_lcs); + FOR_ALL_TAB_WINDOWS(tp, wp) + { + errmsg = set_chars_option(wp, &wp->w_p_lcs); + if (errmsg) + break; + } + redraw_all_later(NOT_VALID); + } } + // local 'listchars' + else if (varp == &curwin->w_p_lcs) + errmsg = set_chars_option(curwin, varp); + // 'fillchars' else if (varp == &p_fcs) { - errmsg = set_chars_option(varp); + errmsg = set_chars_option(curwin, varp); } #ifdef FEAT_CMDWIN diff --git a/src/proto/screen.pro b/src/proto/screen.pro --- a/src/proto/screen.pro +++ b/src/proto/screen.pro @@ -55,5 +55,5 @@ void comp_col(void); int number_width(win_T *wp); int screen_screencol(void); int screen_screenrow(void); -char *set_chars_option(char_u **varp); +char *set_chars_option(win_T *wp, char_u **varp); /* vim: set ft=c : */ diff --git a/src/screen.c b/src/screen.c --- a/src/screen.c +++ b/src/screen.c @@ -4745,10 +4745,11 @@ screen_screenrow(void) /* * Handle setting 'listchars' or 'fillchars'. + * Assume monocell characters. * Returns error message, NULL if it's OK. */ char * -set_chars_option(char_u **varp) +set_chars_option(win_T *wp, char_u **varp) { int round, i, len, entries; char_u *p, *s; @@ -4767,28 +4768,30 @@ set_chars_option(char_u **varp) {&fill_diff, "diff"}, {&fill_eob, "eob"}, }; - static struct charstab lcstab[] = + struct charstab lcstab[] = { - {&lcs_eol, "eol"}, - {&lcs_ext, "extends"}, - {&lcs_nbsp, "nbsp"}, - {&lcs_prec, "precedes"}, - {&lcs_space, "space"}, - {&lcs_tab2, "tab"}, - {&lcs_trail, "trail"}, - {&lcs_lead, "lead"}, + {&wp->w_lcs_chars.eol, "eol"}, + {&wp->w_lcs_chars.ext, "extends"}, + {&wp->w_lcs_chars.nbsp, "nbsp"}, + {&wp->w_lcs_chars.prec, "precedes"}, + {&wp->w_lcs_chars.space,"space"}, + {&wp->w_lcs_chars.tab2, "tab"}, + {&wp->w_lcs_chars.trail,"trail"}, + {&wp->w_lcs_chars.lead, "lead"}, #ifdef FEAT_CONCEAL - {&lcs_conceal, "conceal"}, + {&wp->w_lcs_chars.conceal, "conceal"}, #else {NULL, "conceal"}, #endif }; struct charstab *tab; - if (varp == &p_lcs) + if (varp == &p_lcs || varp == &wp->w_p_lcs) { tab = lcstab; entries = sizeof(lcstab) / sizeof(struct charstab); + if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) + varp = &p_lcs; } else { @@ -4805,12 +4808,13 @@ set_chars_option(char_u **varp) // 'fillchars', NUL for 'listchars' for (i = 0; i < entries; ++i) if (tab[i].cp != NULL) - *(tab[i].cp) = (varp == &p_lcs ? NUL : ' '); - - if (varp == &p_lcs) + *(tab[i].cp) = + ((varp == &p_lcs || varp == &wp->w_p_lcs) ? NUL : ' '); + + if (varp == &p_lcs || varp == &wp->w_p_lcs) { - lcs_tab1 = NUL; - lcs_tab3 = NUL; + wp->w_lcs_chars.tab1 = NUL; + wp->w_lcs_chars.tab3 = NUL; } else { @@ -4833,7 +4837,7 @@ set_chars_option(char_u **varp) c1 = mb_ptr2char_adv(&s); if (mb_char2cells(c1) > 1) continue; - if (tab[i].cp == &lcs_tab2) + if (tab[i].cp == &wp->w_lcs_chars.tab2) { if (*s == NUL) continue; @@ -4852,11 +4856,11 @@ set_chars_option(char_u **varp) { if (round) { - if (tab[i].cp == &lcs_tab2) + if (tab[i].cp == &wp->w_lcs_chars.tab2) { - lcs_tab1 = c1; - lcs_tab2 = c2; - lcs_tab3 = c3; + wp->w_lcs_chars.tab1 = c1; + wp->w_lcs_chars.tab2 = c2; + wp->w_lcs_chars.tab3 = c3; } else if (tab[i].cp != NULL) *(tab[i].cp) = c1; diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -225,6 +225,8 @@ typedef struct #endif int wo_list; #define w_p_list w_onebuf_opt.wo_list // 'list' + char_u *wo_lcs; +#define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars' int wo_nu; #define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; @@ -3333,6 +3335,26 @@ typedef struct { #endif /* + * Characters from the 'listchars' option + */ +typedef struct +{ + int eol; + int ext; + int prec; + int nbsp; + int space; + int tab1; + int tab2; + int tab3; + int trail; + int lead; +#ifdef FEAT_CONCEAL + int conceal; +#endif +} lcs_chars_T; + +/* * Structure which contains all information that belongs to a window * * All row numbers are relative to the start of the window, except w_winrow. @@ -3380,6 +3402,8 @@ struct window_S colnr_T w_old_visual_col; // last known start of visual part colnr_T w_old_curswant; // last known value of Curswant + lcs_chars_T w_lcs_chars; // 'listchars' characters + /* * "w_topline", "w_leftcol" and "w_skipcol" specify the offsets for * displaying the buffer. diff --git a/src/testdir/test_listchars.vim b/src/testdir/test_listchars.vim --- a/src/testdir/test_listchars.vim +++ b/src/testdir/test_listchars.vim @@ -234,4 +234,110 @@ func Test_listchars_composing() set listchars& ff& endfunction +" Check for the value of the 'listchars' option +func s:CheckListCharsValue(expected) + call assert_equal(a:expected, &listchars) + call assert_equal(a:expected, getwinvar(0, '&listchars')) +endfunc + +" Test for using a window local value for 'listchars' +func Test_listchars_window_local() + %bw! + set list listchars& + new + " set a local value for 'listchars' + setlocal listchars=tab:+-,eol:# + call s:CheckListCharsValue('tab:+-,eol:#') + " When local value is reset, global value should be used + setlocal listchars= + call s:CheckListCharsValue('eol:$') + " Use 'setlocal <' to copy global value + setlocal listchars=space:.,extends:> + setlocal listchars< + call s:CheckListCharsValue('eol:$') + " Use 'set <' to copy global value + setlocal listchars=space:.,extends:> + set listchars< + call s:CheckListCharsValue('eol:$') + " Changing global setting should not change the local setting + setlocal listchars=space:.,extends:> + setglobal listchars=tab:+-,eol:# + call s:CheckListCharsValue('space:.,extends:>') + " when split opening a new window, local value should be copied + split + call s:CheckListCharsValue('space:.,extends:>') + " clearing local value in one window should not change the other window + set listchars& + call s:CheckListCharsValue('eol:$') + close + call s:CheckListCharsValue('space:.,extends:>') + + " use different values for 'listchars' items in two different windows + call setline(1, ["\t one two "]) + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + split + setlocal listchars=tab:[.],lead:#,space:_,trail:.,eol:& + split + set listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + " changing the global setting should not change the local value + setglobal listchars=tab:[.],lead:#,space:_,trail:.,eol:& + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + set listchars< + call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$'))) + + " Using setglobal in a window with local setting should not affect the + " window. But should impact other windows using the global setting. + enew! | only + call setline(1, ["\t one two "]) + set listchars=tab:[.],lead:#,space:_,trail:.,eol:& + split + setlocal listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$ + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$'))) + + " Setting the global setting to the default value should not impact a window + " using a local setting + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + setglobal listchars&vim + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$'))) + + " Setting the local setting to the default value should not impact a window + " using a global setting + set listchars=tab:{.},lead:-,space:=,trail:#,eol:$ + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + setlocal listchars&vim + call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$'))) + + " Using set in a window with a local setting should change it to use the + " global setting and also impact other windows using the global setting + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + set listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + + %bw! + set list& listchars& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_listlbr.vim b/src/testdir/test_listlbr.vim --- a/src/testdir/test_listlbr.vim +++ b/src/testdir/test_listlbr.vim @@ -43,6 +43,7 @@ func Test_set_linebreak() endfunc func Test_linebreak_with_list() + set listchars= call s:test_windows('setl ts=4 sbr=+ list listchars=') call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ") let lines = s:screen_lines([1, 4], winwidth(0)) @@ -54,6 +55,7 @@ func Test_linebreak_with_list() \ ] call s:compare_lines(expect, lines) call s:close_windows() + set listchars&vim endfunc func Test_linebreak_with_nolist() diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2518, +/**/ 2517, /**/ 2516,