diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 90ba3b86d9..e2248e5d9a 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5476,7 +5476,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'showbreak'* *'sbr'* *E595* 'showbreak' 'sbr' string (default "") - global + global or local to window |global-local| String to put at the start of lines that have been wrapped. Useful values are "> " or "+++ ": > :set showbreak=>\ @@ -5490,7 +5490,10 @@ A jump table for the options with a short description can be found at |Q_op|. Note that tabs after the showbreak will be displayed differently. If you want the 'showbreak' to appear in between line numbers, add the "n" flag to 'cpoptions'. - + A window-local value overrules a global value. If the global value is + set and you want no value in the current window use NONE: > + :setlocal showbreak=NONE +< *'showcmd'* *'sc'* *'noshowcmd'* *'nosc'* 'showcmd' 'sc' boolean (Vim default: on, Vi default: off) global diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 0ae8dd3664..89f7448188 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -230,6 +230,8 @@ typedef struct { # define w_p_culopt w_onebuf_opt.wo_culopt // 'cursorlineopt' char_u *wo_cc; # define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn' + char_u *wo_sbr; +# define w_p_sbr w_onebuf_opt.wo_sbr // 'showbreak' char_u *wo_stl; #define w_p_stl w_onebuf_opt.wo_stl // 'statusline' int wo_scb; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 0252ef4e9c..4725c0d08f 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -941,7 +941,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, // Also use this when 'list' is set but tabs take their normal size. if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL)) && !wp->w_p_lbr - && (*p_sbr == NUL) + && *get_showbreak_value(wp) == NUL && !wp->w_p_bri ) { for (;;) { head = 0; diff --git a/src/nvim/move.c b/src/nvim/move.c index 09815d1e6a..21cbac4d79 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -788,11 +788,12 @@ void curs_columns( wp->w_wcol -= n * width; wp->w_wrow += n; - /* When cursor wraps to first char of next line in Insert - * mode, the 'showbreak' string isn't shown, backup to first - * column */ - if (*p_sbr && *get_cursor_pos_ptr() == NUL - && wp->w_wcol == (int)vim_strsize(p_sbr)) { + // When cursor wraps to first char of next line in Insert + // mode, the 'showbreak' string isn't shown, backup to first + // column + char_u *const sbr = get_showbreak_value(wp); + if (*sbr && *get_cursor_pos_ptr() == NUL + && wp->w_wcol == (int)vim_strsize(sbr)) { wp->w_wcol = 0; } } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 165629a6e0..74aaed87c1 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3407,12 +3407,15 @@ void clear_showcmd(void) lines = bot - top + 1; if (VIsual_mode == Ctrl_V) { - char_u *saved_sbr = p_sbr; + char_u *const saved_sbr = p_sbr; + char_u *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; + curwin->w_p_sbr = empty_option; getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); p_sbr = saved_sbr; + curwin->w_p_sbr = saved_w_sbr; snprintf((char *)showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64 "x%" PRId64, (int64_t)lines, (int64_t)rightcol - leftcol + 1); } else if (VIsual_mode == 'V' || VIsual.lnum != curwin->w_cursor.lnum) { @@ -4141,8 +4144,8 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) */ validate_virtcol(); colnr_T virtcol = curwin->w_virtcol; - if (virtcol > (colnr_T)width1 && *p_sbr != NUL) { - virtcol -= vim_strsize(p_sbr); + if (virtcol > (colnr_T)width1 && *get_showbreak_value(curwin) != NUL) { + virtcol -= vim_strsize(get_showbreak_value(curwin)); } if (virtcol > curwin->w_curswant diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 12c3681089..3f0d85dc88 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5726,16 +5726,19 @@ void cursor_pos_info(dict_T *dict) } if (l_VIsual_mode == Ctrl_V) { - char_u * saved_sbr = p_sbr; + char_u *const saved_sbr = p_sbr; + char_u *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; + curwin->w_p_sbr = empty_option; oparg.is_VIsual = true; oparg.motion_type = kMTBlockWise; oparg.op_type = OP_NOP; getvcols(curwin, &min_pos, &max_pos, &oparg.start_vcol, &oparg.end_vcol); p_sbr = saved_sbr; + curwin->w_p_sbr = saved_w_sbr; if (curwin->w_curswant == MAXCOL) { oparg.end_vcol = MAXCOL; } diff --git a/src/nvim/option.c b/src/nvim/option.c index d11bbc8ecc..fdfb409c5e 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2740,10 +2740,11 @@ ambw_end: if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0) { errmsg = (char_u *)N_("E528: Must specify a ' value"); } - } else if (varp == &p_sbr) { // 'showbreak' - for (s = p_sbr; *s; ) { + } else if (gvarp == &p_sbr) { // 'showbreak' + for (s = *varp; *s; ) { if (ptr2cells(s) != 1) { - errmsg = (char_u *)N_("E595: contains unprintable or wide character"); + errmsg = (char_u *)N_( + "E595: 'showbreak' contains unprintable or wide character"); } MB_PTR_ADV(s); } @@ -5523,6 +5524,9 @@ void unset_global_local_option(char *name, void *from) case PV_MP: clear_string_option(&buf->b_p_mp); break; + case PV_SBR: + clear_string_option(&((win_T *)from)->w_p_sbr); + break; case PV_STL: clear_string_option(&((win_T *)from)->w_p_stl); break; @@ -5576,6 +5580,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_DICT: return (char_u *)&(curbuf->b_p_dict); case PV_TSR: return (char_u *)&(curbuf->b_p_tsr); case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); + case PV_SBR: return (char_u *)&(curwin->w_p_sbr); case PV_STL: return (char_u *)&(curwin->w_p_stl); case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); @@ -5635,6 +5640,8 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_gp) : p->var; case PV_MP: return *curbuf->b_p_mp != NUL ? (char_u *)&(curbuf->b_p_mp) : p->var; + case PV_SBR: return *curwin->w_p_sbr != NUL + ? (char_u *)&(curwin->w_p_sbr) : p->var; case PV_STL: return *curwin->w_p_stl != NUL ? (char_u *)&(curwin->w_p_stl) : p->var; case PV_UL: return curbuf->b_p_ul != NO_LOCAL_UNDOLEVEL @@ -5788,6 +5795,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_nuw = from->wo_nuw; to->wo_rl = from->wo_rl; to->wo_rlc = vim_strsave(from->wo_rlc); + to->wo_sbr = vim_strsave(from->wo_sbr); to->wo_stl = vim_strsave(from->wo_stl); to->wo_wrap = from->wo_wrap; to->wo_wrap_save = from->wo_wrap_save; @@ -5851,6 +5859,7 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_fmr); check_string_option(&wop->wo_scl); check_string_option(&wop->wo_rlc); + check_string_option(&wop->wo_sbr); check_string_option(&wop->wo_stl); check_string_option(&wop->wo_culopt); check_string_option(&wop->wo_cc); @@ -5874,6 +5883,7 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_fmr); clear_string_option(&wop->wo_scl); clear_string_option(&wop->wo_rlc); + clear_string_option(&wop->wo_sbr); clear_string_option(&wop->wo_stl); clear_string_option(&wop->wo_culopt); clear_string_option(&wop->wo_cc); @@ -7472,6 +7482,22 @@ unsigned int get_bkc_value(buf_T *buf) return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } +/// Get the local or global value of 'showbreak'. +/// +/// @param win If not NULL, the window to get the local option from; global +/// otherwise. +char_u *get_showbreak_value(win_T *const win FUNC_ATTR_UNUSED) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (win->w_p_sbr == NULL || *win->w_p_sbr == NUL) { + return p_sbr; + } + if (STRCMP(win->w_p_sbr, "NONE") == 0) { + return empty_option; + } + return win->w_p_sbr; +} + /// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC. int get_fileformat(const buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL @@ -7672,12 +7698,6 @@ int win_signcol_configured(win_T *wp, int *is_fixed) return ret; } -// Get the local or global value of 'showbreak'. -char_u *get_showbreak_value(win_T *win FUNC_ATTR_UNUSED) -{ - return p_sbr; -} - /// Get window or buffer local options dict_T *get_winbuf_options(const int bufopt) FUNC_ATTR_WARN_UNUSED_RESULT diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 97ada9eb25..e588d3f373 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -877,6 +877,7 @@ enum { , WV_CUL , WV_CULOPT , WV_CC + , WV_SBR , WV_STL , WV_WFH , WV_WFW diff --git a/src/nvim/options.lua b/src/nvim/options.lua index df2a8edc04..8b9cdefd57 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2175,7 +2175,7 @@ return { { full_name='showbreak', abbreviation='sbr', short_desc=N_("string to use at the start of wrapped lines"), - type='string', scope={'global'}, + type='string', scope={'global', 'window'}, redraw={'all_windows'}, varname='p_sbr', defaults={if_true=""} diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 6718b7f7a4..a656686a95 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -263,7 +263,8 @@ unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) /// @return The number of characters taken up on the screen. int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) { - if (!curwin->w_p_lbr && (*p_sbr == NUL) && !curwin->w_p_bri) { + if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL + && !curwin->w_p_bri) { if (curwin->w_p_wrap) { return win_nolbr_chartabsize(curwin, s, col, NULL); } @@ -314,7 +315,7 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, int n; // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && (*p_sbr == NUL)) { + if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL) { if (wp->w_p_wrap) { return win_nolbr_chartabsize(wp, s, col, headp); } @@ -381,7 +382,8 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, // Set *headp to the size of what we add. added = 0; - if ((*p_sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && (col != 0)) { + char_u *const sbr = get_showbreak_value(wp); + if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && col != 0) { colnr_T sbrlen = 0; int numberwidth = win_col_off(wp); @@ -394,8 +396,8 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, if (col >= numberextra && numberextra > 0) { col %= numberextra; } - if (*p_sbr != NUL) { - sbrlen = (colnr_T)MB_CHARLEN(p_sbr); + if (*sbr != NUL) { + sbrlen = (colnr_T)MB_CHARLEN(sbr); if (col >= sbrlen) { col -= sbrlen; } @@ -410,7 +412,7 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, } if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) { - if (*p_sbr != NUL) { + if (*sbr != NUL) { if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { // Calculate effective window width. int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth; @@ -420,13 +422,13 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, if (width <= 0) { width = 1; } - added += ((size - prev_width) / width) * vim_strsize(p_sbr); + added += ((size - prev_width) / width) * vim_strsize(sbr); if ((size - prev_width) % width) { // Wrapped, add another length of 'sbr'. - added += vim_strsize(p_sbr); + added += vim_strsize(sbr); } } else { - added += vim_strsize(p_sbr); + added += vim_strsize(sbr); } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index c0b6f002b2..6fdcf8a838 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2850,7 +2850,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } if (wp->w_briopt_sbr && draw_state == WL_BRI - 1 - && n_extra == 0 && *p_sbr != NUL) { + && n_extra == 0 && *get_showbreak_value(wp) != NUL) { // draw indent after showbreak value draw_state = WL_BRI; } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) { @@ -2909,19 +2909,20 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } char_attr = win_hl_attr(wp, HLF_DED); } - if (*p_sbr != NUL && need_showbreak) { + char_u *const sbr = get_showbreak_value(wp); + if (*sbr != NUL && need_showbreak) { // Draw 'showbreak' at the start of each broken line. - p_extra = p_sbr; + p_extra = sbr; c_extra = NUL; c_final = NUL; - n_extra = (int)STRLEN(p_sbr); + n_extra = (int)STRLEN(sbr); char_attr = win_hl_attr(wp, HLF_AT); if (wp->w_skipcol == 0 || !wp->w_p_wrap) { need_showbreak = false; } - vcol_sbr = vcol + MB_CHARLEN(p_sbr); - /* Correct end of highlighted area for 'showbreak', - * required when 'linebreak' is also set. */ + vcol_sbr = vcol + MB_CHARLEN(sbr); + // Correct end of highlighted area for 'showbreak', + // required when 'linebreak' is also set. if (tocol == vcol) { tocol += n_extra; } @@ -3553,6 +3554,16 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc char_u *p = ptr - (mb_off + 1); // TODO: is passing p for start of the line OK? n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; + + // We have just drawn the showbreak value, no need to add + // space for it again. + if (vcol == vcol_sbr) { + n_extra -= MB_CHARLEN(get_showbreak_value(wp)); + if (n_extra < 0) { + n_extra = 0; + } + } + if (c == TAB && n_extra + col > grid->Columns) { n_extra = tabstop_padding(vcol, wp->w_buffer->b_p_ts, wp->w_buffer->b_p_vts_array) - 1; @@ -3619,10 +3630,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { int tab_len = 0; long vcol_adjusted = vcol; // removed showbreak length + char_u *const sbr = get_showbreak_value(wp); + // Only adjust the tab_len, when at the first column after the // showbreak value was drawn. - if (*p_sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) { - vcol_adjusted = vcol - MB_CHARLEN(p_sbr); + if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) { + vcol_adjusted = vcol - MB_CHARLEN(sbr); } // tab amount depends on current column tab_len = tabstop_padding(vcol_adjusted, diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index 5542746a04..97b570e64f 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -67,7 +67,8 @@ endfunc func Test_breakindent02() " simple breakindent test with showbreak set - call s:test_windows('setl briopt=min:0 sbr=>>') + set sbr=>> + call s:test_windows('setl briopt=min:0 sbr=') let lines = s:screen_lines(line('.'),8) let expect = [ \ " abcd", @@ -127,7 +128,8 @@ endfunc func Test_breakindent04() " breakindent set with min width 18 - call s:test_windows('setl sbr= briopt=min:18') + set sbr=<<< + call s:test_windows('setl sbr=NONE briopt=min:18') let lines = s:screen_lines(line('.'),8) let expect = [ \ " abcd", @@ -137,6 +139,7 @@ func Test_breakindent04() call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr=') + set sbr= endfunc func Test_breakindent04_vartabs() @@ -868,4 +871,22 @@ func Test_breakindent20_list() call s:close_windows('set breakindent& briopt& linebreak& list& listchars& showbreak&') endfunc +" The following used to crash Vim. This is fixed by 8.2.3391. +" This is a regression introduced by 8.2.2903. +func Test_window_resize_with_linebreak() + new + 53vnew + set linebreak + set showbreak=>> + set breakindent + set breakindentopt=shift:4 + call setline(1, "\naaaaaaaaa\n\na\naaaaa\n¯aaaaaaaaaa\naaaaaaaaaaaa\naaa\n\"a:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaa\"\naaaaaaaa\n\"a") + redraw! + call assert_equal([" >>aa^@\"a: "], ScreenLines(2, 14)) + vertical resize 52 + redraw! + call assert_equal([" >>aaa^@\"a:"], ScreenLines(2, 14)) + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index c702b44b88..12327f34d6 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -262,3 +262,21 @@ func Test_display_scroll_at_topline() call StopVimInTerminal(buf) endfunc + +func Test_display_linebreak_breakat() + new + vert resize 25 + let _breakat = &breakat + setl signcolumn=yes linebreak breakat=) showbreak=+\ + call setline(1, repeat('x', winwidth(0) - 2) .. ')abc') + let lines = ScreenLines([1, 2], 25) + let expected = [ + \ ' xxxxxxxxxxxxxxxxxxxxxxx', + \ ' + )abc ' + \ ] + call assert_equal(expected, lines) + %bw! + let &breakat=_breakat +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 24c9c3580e..6fd9477ce9 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -426,6 +426,7 @@ func Test_highlight_eol_with_cursorline_breakindent() let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine() call NewWindow('topleft 5', 10) + set showbreak=xxx setlocal breakindent breakindentopt=min:0,shift:1 showbreak=> call setline(1, ' ' . repeat('a', 9) . 'bcd') call matchadd('Search', '\n') @@ -483,6 +484,7 @@ func Test_highlight_eol_with_cursorline_breakindent() call CloseWindow() set showbreak= + setlocal showbreak= exe hiCursorLine endfunc