diff options
author | luukvbaal <31730729+luukvbaal@users.noreply.github.com> | 2023-01-09 18:12:06 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-09 17:12:06 +0000 |
commit | 364b131f42509326c912c9b0fef5dfc94ed23b41 (patch) | |
tree | 654f24fde95819257002aa9b36990efd6a80535c | |
parent | 50f03773f4b9f4638489ccfd0503dc9e39e5de78 (diff) | |
download | rneovim-364b131f42509326c912c9b0fef5dfc94ed23b41.tar.gz rneovim-364b131f42509326c912c9b0fef5dfc94ed23b41.tar.bz2 rneovim-364b131f42509326c912c9b0fef5dfc94ed23b41.zip |
feat(ui): add 'statuscolumn' option
Problem: Unable to customize the column next to a window ('gutter').
Solution: Add 'statuscolumn' option that follows the 'statusline' syntax,
allowing to customize the status column. Also supporting the %@
click execute function label. Adds new items @C and @s which
will print the fold and sign columns. Line numbers and signs
can be clicked, highlighted, aligned, transformed, margined etc.
-rw-r--r-- | runtime/doc/eval.txt | 17 | ||||
-rw-r--r-- | runtime/doc/news.txt | 5 | ||||
-rw-r--r-- | runtime/doc/options.txt | 46 | ||||
-rw-r--r-- | runtime/doc/quickref.txt | 1 | ||||
-rw-r--r-- | runtime/optwin.vim | 3 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 1 | ||||
-rw-r--r-- | src/nvim/buffer.c | 4 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 28 | ||||
-rw-r--r-- | src/nvim/drawline.c | 138 | ||||
-rw-r--r-- | src/nvim/drawscreen.c | 16 | ||||
-rw-r--r-- | src/nvim/eval.c | 2 | ||||
-rw-r--r-- | src/nvim/eval.h | 2 | ||||
-rw-r--r-- | src/nvim/mouse.c | 26 | ||||
-rw-r--r-- | src/nvim/mouse.h | 15 | ||||
-rw-r--r-- | src/nvim/move.c | 3 | ||||
-rw-r--r-- | src/nvim/option.c | 13 | ||||
-rw-r--r-- | src/nvim/option.h | 2 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 8 | ||||
-rw-r--r-- | src/nvim/options.lua | 9 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 9 | ||||
-rw-r--r-- | src/nvim/screen.c | 10 | ||||
-rw-r--r-- | src/nvim/statusline.c | 84 | ||||
-rw-r--r-- | src/nvim/window.c | 3 | ||||
-rw-r--r-- | test/functional/lua/ffi_spec.lua | 5 | ||||
-rw-r--r-- | test/functional/ui/statuscolumn_spec.lua | 273 | ||||
-rw-r--r-- | test/unit/buffer_spec.lua | 1 |
26 files changed, 681 insertions, 43 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 151036529d..6feb5cbb49 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2001,10 +2001,10 @@ v:lc_time The current locale setting for time messages of the runtime command. See |multi-lang|. *v:lnum* *lnum-variable* -v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr' and - 'indentexpr' expressions, tab page number for 'guitablabel' - and 'guitabtooltip'. Only valid while one of these - expressions is being evaluated. Read-only when in the +v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr', + 'indentexpr' and 'statuscolumn' expressions, tab page number + for 'guitablabel' and 'guitabtooltip'. Only valid while one of + these expressions is being evaluated. Read-only when in the |sandbox|. *v:lua* *lua-variable* @@ -2138,6 +2138,10 @@ v:register The name of the register in effect for the current normal mode '*' or '+'. Also see |getreg()| and |setreg()| + *v:relnum* *relnum-variable* +v:relnum Relative line number for the 'statuscolumn' expression. + Read-only. + *v:scrollstart* *scrollstart-variable* v:scrollstart String describing the script or function that caused the screen to scroll up. It's only set when it is empty, thus the @@ -2295,6 +2299,11 @@ v:version Vim version number: major version times 100 plus minor v:vim_did_enter 0 during startup, 1 just before |VimEnter|. Read-only. + *v:wrap* *wrap-variable* +v:wrap Boolean indicating whether 'statuscolumn' is being evaluated + for the wrapped part of a line. + Read-only. + *v:warningmsg* *warningmsg-variable* v:warningmsg Last given warning message. Modifiable (can be set). diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 33ac9ddd20..88bad1b7df 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -118,6 +118,11 @@ The following new APIs or features were added. • |'splitkeep'| option to control the scroll behavior of horizontal splits. +• |'statuscolumn'| option to customize the area to the side of a window, + normally containing the fold, sign and number columns. This new option follows + the 'statusline' syntax and can be used to transform the line numbers, create + mouse click callbacks for |signs|, introduce a custom margin or separator etc. + • |nvim_select_popupmenu_item()| now supports |cmdline-completion| popup menu. • |'diffopt'| now includes a `linematch` option to enable a second-stage diff diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ee6c14e73f..aa2f528d1b 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6001,6 +6001,52 @@ A jump table for the options with a short description can be found at |Q_op|. In case of buffer changing commands the cursor is placed at the column where it was the last time the buffer was edited. + *'statuscolumn'* *'stc'* +'statuscolumn' 'stc' string (default: empty) + local to window + EXPERIMENTAL + When non-empty, this option determines the content of the area to the + side of a window, normally containing the fold, sign and number columns. + The format of this option is like that of 'statusline'. + + Some of the items from the 'statusline' format are different for + 'statuscolumn': + + %l line number of currently drawn line + %r relative line number of currently drawn line + %s sign column for currently drawn line + %C fold column for currently drawn line + + To draw the sign and fold columns, they must be included in + 'statuscolumn'. + + The |v:lnum| variable holds the line number to be drawn. + The |v:relnum| variable holds the relative line number to be drawn. + The |v:wrap| variable holds true for the wrapped part of a line. + + Examples: >vim + " Relative number with bar separator and click handlers: + :set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T + + " Right aligned relative cursor line number: + :let &stc='%=%{v:relnum?v:relnum:v:lnum} ' + + " Line numbers in hexadecimal for non wrapped part of lines: + :let &stc='%=%{v:wrap?"":printf("%x",v:lnum)} ' + + " Human readable line numbers with thousands separator: + :let &stc='%{substitute(v:lnum,"\\d\\zs\\ze\\' + . '%(\\d\\d\\d\\)\\+$",",","g")}' + + " Both relative and absolute line numbers with different + " highlighting for odd and even relative numbers: + :let &stc='%#NonText#%{&nu?v:lnum:""}' . + '%=%{&rnu&&(v:lnum%2)?"\ ".v:relnum:""}' . + '%#LineNr#%{&rnu&&!(v:lnum%2)?"\ ".v:relnum:""}' + +< WARNING: this expression is evaluated for each screen line so defining + an expensive expression can negatively affect render performance. + *'statusline'* *'stl'* *E540* *E542* 'statusline' 'stl' string (default empty) global or local to window |global-local| diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index d0088f5b9b..d17df3cd61 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -883,6 +883,7 @@ Short explanation of each option: *option-list* 'splitkeep' 'spk' determines scroll behavior for split windows 'splitright' 'spr' new window is put right of the current one 'startofline' 'sol' commands move cursor to first non-blank in line +'statuscolumn' 'stc' custom format for the status column 'statusline' 'stl' custom format for the status line 'suffixes' 'su' suffixes that are ignored with multiple match 'suffixesadd' 'sua' suffixes added when searching for a file diff --git a/runtime/optwin.vim b/runtime/optwin.vim index b29b60e145..200254321e 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -452,6 +452,9 @@ call <SID>Header(gettext("multiple windows")) call <SID>AddOption("laststatus", gettext("0, 1, 2 or 3; when to use a status line for the last window")) call append("$", " \tset ls=" . &ls) if has("statusline") + call <SID>AddOption("statuscolumn", gettext("custom format for the status column")) + call append("$", "\t" .. s:local_to_window) + call <SID>OptionG("stc", &stc) call <SID>AddOption("statusline", gettext("alternate format to be used for a status line")) call <SID>OptionG("stl", &stl) endif diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1e8964d912..65b08ecade 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2238,6 +2238,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * fillchar, maxwidth, hltab_ptr, + NULL, NULL); PUT(result, "width", INTEGER_OBJ(width)); diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index e6d0dcb8cf..1bba146caf 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3236,7 +3236,7 @@ void maketitle(void) if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { build_stl_str_hl(curwin, buf, sizeof(buf), p_titlestring, - "titlestring", 0, 0, maxlen, NULL, NULL); + "titlestring", 0, 0, maxlen, NULL, NULL, NULL); title_str = buf; } else { title_str = p_titlestring; @@ -3342,7 +3342,7 @@ void maketitle(void) if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring, - "iconstring", 0, 0, 0, NULL, NULL); + "iconstring", 0, 0, 0, NULL, NULL, NULL); } else { icon_str = p_iconstring; } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index df4ebdffc0..7442e60024 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -204,6 +204,8 @@ typedef struct { #define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn' char *wo_sbr; #define w_p_sbr w_onebuf_opt.wo_sbr // 'showbreak' + char *wo_stc; +#define w_p_stc w_onebuf_opt.wo_stc // 'statuscolumn' char *wo_stl; #define w_p_stl w_onebuf_opt.wo_stl // 'statusline' char *wo_wbr; @@ -1300,6 +1302,7 @@ struct window_S { linenr_T w_redraw_bot; // when != 0: last line needing redraw bool w_redr_status; // if true statusline/winbar must be redrawn bool w_redr_border; // if true border must be redrawn + bool w_redr_statuscol; // if true 'statuscolumn' must be redrawn // remember what is shown in the ruler for this window (if 'ruler' set) pos_T w_ru_cursor; // cursor position shown in ruler @@ -1404,6 +1407,31 @@ struct window_S { StlClickDefinition *w_winbar_click_defs; // Size of the w_winbar_click_defs array size_t w_winbar_click_defs_size; + + // Status column click definitions + StlClickDefinition *w_statuscol_click_defs; + // Size of the w_statuscol_click_defs array + size_t w_statuscol_click_defs_size; +}; + +/// Struct to hold info for 'statuscolumn' +typedef struct statuscol statuscol_T; + +struct statuscol { + int width; // width of the status column + int cur_attr; // current attributes in text + int num_attr; // attributes used for line number + int fold_attr; // attributes used for fold column + int sign_attr[SIGN_SHOW_MAX]; // attributes used for signs + int truncate; // truncated width + bool draw; // draw statuscolumn or not + char fold_text[10]; // text in fold column (%C) + char *sign_text[SIGN_SHOW_MAX]; // text in sign column (%s) + char text[MAXPATHL]; // text in status column + char *textp; // current position in text + size_t text_len; // length of text + stl_hlrec_t *hlrec; // highlight groups + stl_hlrec_t *hlrecp; // current highlight group }; /// Macros defined in Vim, but not in Neovim diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 924251a679..d3e8545b88 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -21,6 +21,7 @@ #include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawline.h" +#include "nvim/eval.h" #include "nvim/extmark_defs.h" #include "nvim/fold.h" #include "nvim/garray.h" @@ -43,6 +44,7 @@ #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" @@ -60,6 +62,7 @@ typedef enum { WL_FOLD, // 'foldcolumn' WL_SIGN, // column for signs WL_NR, // line number + WL_STC, // 'statuscolumn' WL_BRI, // 'breakindent' WL_SBR, // 'showbreak' or 'diff' WL_LINE, // text in the line @@ -395,6 +398,90 @@ static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int return num_signs; } +static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, + int cul_attr, int sign_num_attr, SignTextAttrs *sattrs, + foldinfo_T foldinfo, char_u *extra, statuscol_T *stcp) +{ + long relnum; + bool wrapped = row != startrow + filler_lines; + bool use_cul = use_cursor_line_sign(wp, lnum); + + // Set num, fold and sign text and attrs, empty when wrapped + if (row == startrow) { + relnum = labs(get_cursor_rel_lnum(wp, lnum)); + stcp->num_attr = sign_num_attr ? sign_num_attr + : get_line_number_attr(wp, lnum, row, startrow, filler_lines); + + if (compute_foldcolumn(wp, 0)) { + size_t n = fill_foldcolumn((char_u *)stcp->fold_text, wp, foldinfo, lnum); + stcp->fold_text[n] = NUL; + stcp->fold_attr = win_hl_attr(wp, use_cul ? HLF_CLF : HLF_FC); + } + } + + int i = 0; + for (; i < wp->w_scwidth; i++) { + SignTextAttrs *sattr = wrapped ? NULL : sign_get_attr(i, sattrs, wp->w_scwidth); + stcp->sign_text[i] = sattr && sattr->text ? sattr->text : " "; + stcp->sign_attr[i] = use_cul && cul_attr ? cul_attr : sattr ? sattr->hl_attr_id : 0; + } + stcp->sign_text[i] = NULL; + + int width = build_statuscol_str(wp, row == startrow, wrapped, lnum, relnum, + stcp->width, ' ', stcp->text, &stcp->hlrec, stcp); + // Force a redraw in case of error or when truncated + if (*wp->w_p_stc == NUL || (stcp->truncate > 0 && wp->w_nrwidth < MAX_NUMBERWIDTH)) { + if (stcp->truncate) { // Avoid truncating 'statuscolumn' + wp->w_nrwidth = MIN(MAX_NUMBERWIDTH, wp->w_nrwidth + stcp->truncate); + wp->w_nrwidth_width = wp->w_nrwidth; + } else { // 'statuscolumn' reset due to error + wp->w_nrwidth_line_count = 0; + wp->w_nrwidth = (wp->w_p_nu || wp->w_p_rnu) * number_width(wp); + } + wp->w_redr_statuscol = true; + return; + } + + // Reset text/highlight pointer and current attr for new line + stcp->textp = stcp->text; + stcp->hlrecp = stcp->hlrec; + stcp->cur_attr = stcp->num_attr; + stcp->text_len = strlen(stcp->text); + + int fill = stcp->width - width; + if (fill > 0) { + // Fill up with ' ' + memset(&stcp->text[stcp->text_len], ' ', (size_t)fill); + stcp->text_len += (size_t)fill; + stcp->text[stcp->text_len] = NUL; + } +} + +static void get_statuscol_display_info(LineDrawState *draw_state, int *char_attr, int *n_extrap, + int *c_extrap, int *c_finalp, char_u *extra, char **pp_extra, + statuscol_T *stcp) +{ + *c_extrap = NUL; + *c_finalp = NUL; + do { + *draw_state = WL_STC; + *char_attr = stcp->cur_attr; + *pp_extra = stcp->textp; + *n_extrap = stcp->hlrecp->start ? (int)(stcp->hlrecp->start - stcp->textp) + : (int)strlen(*pp_extra); + // Prepare for next highlight section if not yet at the end + if (stcp->textp + *n_extrap < stcp->text + stcp->text_len) { + int hl = stcp->hlrecp->userhl; + stcp->textp = stcp->hlrecp->start; + stcp->cur_attr = hl < 0 ? syn_id2attr(-stcp->hlrecp->userhl) + : hl > 0 ? hl : stcp->num_attr; + stcp->hlrecp++; + *draw_state = WL_STC - 1; + } + // Skip over empty highlight sections + } while (*n_extrap == 0 && stcp->textp < stcp->text + stcp->text_len); +} + /// Return true if CursorLineNr highlight is to be used for the number column. /// /// - 'cursorline' must be set @@ -1098,6 +1185,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, extra_check = true; } + statuscol_T statuscol = { 0 }; + if (*wp->w_p_stc != NUL) { + // Draw the 'statuscolumn' if option is set. + statuscol.draw = true; + statuscol.width = win_col_off(wp); + } + int sign_idx = 0; // Repeat for the whole displayed line. for (;;) { @@ -1125,9 +1219,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } } + // Skip fold, sign and number states if 'statuscolumn' is set. + if (draw_state == WL_FOLD - 1 && n_extra == 0 && statuscol.draw) { + draw_state = WL_STC - 1; + } + if (draw_state == WL_FOLD - 1 && n_extra == 0) { int fdc = compute_foldcolumn(wp, 0); - draw_state = WL_FOLD; if (fdc > 0) { // Draw the 'foldcolumn'. Allocate a buffer, "extra" may @@ -1217,7 +1315,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } } - if (draw_state == WL_NR && n_extra == 0) { + if (draw_state == WL_STC - 1 && n_extra == 0) { + draw_state = WL_STC; + // Draw the 'statuscolumn' if option is set. + if (statuscol.draw) { + if (statuscol.text_len == 0) { + get_statuscol_str(wp, lnum, row, startrow, filler_lines, cul_attr, + sign_num_attr, sattrs, foldinfo, extra, &statuscol); + if (wp->w_redr_statuscol) { + return 0; + } + } + get_statuscol_display_info(&draw_state, &char_attr, &n_extra, &c_extra, + &c_final, extra, &p_extra, &statuscol); + } + } + + if (draw_state == WL_STC && n_extra == 0) { win_col_offset = off; } @@ -1349,7 +1463,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && wp == curwin && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol) - || (number_only && draw_state > WL_NR)) + || (number_only && draw_state > WL_STC)) && filler_todo <= 0) { draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row); grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, bg_attr, false); @@ -2214,7 +2328,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && wp->w_p_list && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0) && filler_todo <= 0 - && draw_state > WL_NR + && draw_state > WL_STC && c != NUL) { c = wp->w_p_lcs_chars.prec; lcs_prec_todo = NUL; @@ -2508,7 +2622,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, col++; // UTF-8: Put a 0 in the second screen char. linebuf_char[off][0] = 0; - if (draw_state > WL_NR && filler_todo <= 0) { + if (draw_state > WL_STC && filler_todo <= 0) { vcol++; } // When "tocol" is halfway through a character, set it to the end of @@ -2591,7 +2705,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // Only advance the "vcol" when after the 'number' or 'relativenumber' // column. - if (draw_state > WL_NR + if (draw_state > WL_STC && filler_todo <= 0) { vcol++; } @@ -2601,7 +2715,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } // restore attributes after "predeces" in 'listchars' - if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) { + if (draw_state > WL_STC && n_attr3 > 0 && --n_attr3 == 0) { char_attr = saved_attr3; } @@ -2697,6 +2811,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (filler_todo <= 0) { need_showbreak = true; } + if (statuscol.draw) { + if (row == startrow + 1 || row == startrow + filler_lines) { + // Re-evaluate 'statuscolumn' for the first wrapped row and non filler line + statuscol.text_len = 0; + } else { // Otherwise just reset the text/hlrec pointers + statuscol.textp = statuscol.text; + statuscol.hlrecp = statuscol.hlrec; + } // Fall back to default columns if the 'n' flag isn't in 'cpo' + statuscol.draw = vim_strchr(p_cpo, CPO_NUMCOL) == NULL; + } filler_todo--; // When the filler lines are actually below the last line of the // file, don't draw the line itself, break here. diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index de6bdda71e..568dbe99c0 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -522,7 +522,7 @@ int update_screen(void) // TODO(bfredl): special casing curwin here is SÅ JÄVLA BULL. // Either this should be done for all windows or not at all. if (curwin->w_redr_type < UPD_NOT_VALID - && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu || *curwin->w_p_stc) ? number_width(curwin) : 0)) { curwin->w_redr_type = UPD_NOT_VALID; } @@ -1032,7 +1032,7 @@ static void win_update(win_T *wp, DecorProviders *providers) // Force redraw when width of 'number' or 'relativenumber' column // changes. - int nrwidth = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; + int nrwidth = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0; if (wp->w_nrwidth != nrwidth) { type = UPD_NOT_VALID; wp->w_nrwidth = nrwidth; @@ -1823,6 +1823,18 @@ static void win_update(win_T *wp, DecorProviders *providers) did_update = DID_NONE; } + // 'statuscolumn' width has changed or errored, start from the top. + if (wp->w_redr_statuscol) { + wp->w_redr_statuscol = false; + idx = 0; + row = 0; + lnum = wp->w_topline; + wp->w_lines_valid = 0; + wp->w_valid &= ~VALID_WCOL; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + continue; + } + if (lnum > buf->b_ml.ml_line_count) { eof = true; break; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a112a2e447..17eb08cee6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -267,6 +267,8 @@ static struct vimvar { VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), + VV(VV_RELNUM, "relnum", VAR_NUMBER, VV_RO), + VV(VV_WRAP, "wrap", VAR_BOOL, VV_RO), }; #undef VV diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 61c1363d54..d67414f12f 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -165,6 +165,8 @@ typedef enum { VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV__NULL_BLOB, // Blob with NULL value. For test purposes only. VV_LUA, + VV_RELNUM, + VV_WRAP, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index b4d6a533f1..88103d1888 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -291,6 +291,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) pos_T start_visual; bool moved; // Has cursor moved? bool in_winbar; // mouse in window bar + bool in_statuscol; // mouse in status column bool in_status_line; // mouse in status line static bool in_tab_line = false; // mouse clicked in tab line bool in_sep_line; // mouse in vertical separator line @@ -645,10 +646,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) moved = (jump_flags & CURSOR_MOVED); in_winbar = (jump_flags & MOUSE_WINBAR); + in_statuscol = (jump_flags & MOUSE_STATUSCOL); in_status_line = (jump_flags & IN_STATUS_LINE); in_sep_line = (jump_flags & IN_SEP_LINE); - if ((in_winbar || in_status_line) && is_click) { + if ((in_winbar || in_status_line || in_statuscol) && is_click) { // Handle click event on window bar or status lin int click_grid = mouse_grid; int click_row = mouse_row; @@ -659,7 +661,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs - : wp->w_winbar_click_defs; + : in_winbar ? wp->w_winbar_click_defs + : wp->w_statuscol_click_defs; if (in_status_line && global_stl_height() > 0) { // global statusline is displayed for the current window, @@ -682,8 +685,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } return false; - } else if (in_winbar) { - // A drag or release event in the window bar has no side effects. + } else if (in_winbar || in_statuscol) { + // A drag or release event in the window bar and status column has no side effects. return false; } @@ -1027,6 +1030,7 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button) static bool on_status_line = false; static bool on_sep_line = false; static bool on_winbar = false; + static bool on_statuscol = false; static int prev_row = -1; static int prev_col = -1; static int did_drag = false; // drag was noticed @@ -1069,6 +1073,9 @@ retnomove: if (on_winbar) { return IN_OTHER_WIN | MOUSE_WINBAR; } + if (on_statuscol) { + return IN_OTHER_WIN | MOUSE_STATUSCOL; + } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); redraw_curbuf_later(UPD_INVERTED); // delete the inversion @@ -1103,6 +1110,10 @@ retnomove: ? wp->w_winbar_height != 0 : false; + on_statuscol = grid == (col < win_col_off(wp)) + ? *wp->w_p_stc != NUL + : false; + on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width ? col - wp->w_width + 1 == 1 : false; @@ -1130,6 +1141,10 @@ retnomove: return IN_OTHER_WIN | MOUSE_WINBAR; } + if (on_statuscol) { + return IN_OTHER_WIN | MOUSE_STATUSCOL; + } + fdc = win_fdccol_count(wp); dragwin = NULL; @@ -1230,6 +1245,9 @@ retnomove: } else if (on_winbar && which_button == MOUSE_RIGHT) { // After a click on the window bar don't start Visual mode. return IN_OTHER_WIN | MOUSE_WINBAR; + } else if (on_statuscol && which_button == MOUSE_RIGHT) { + // After a click on the status column don't start Visual mode. + return IN_OTHER_WIN | MOUSE_STATUSCOL; } else { // keep_window_focus must be true // before moving the cursor for a left click, stop Visual mode diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index d493a479b1..8b2d7e0acd 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -9,17 +9,18 @@ #include "nvim/window.h" /// jump_to_mouse() returns one of first five these values, possibly with -/// some of the other four added. +/// some of the other five added. enum { IN_UNKNOWN = 0, IN_BUFFER = 1, - IN_STATUS_LINE = 2, ///< on status or command line - IN_SEP_LINE = 4, ///< on vertical separator line - IN_OTHER_WIN = 8, ///< in other window but can't go there + IN_STATUS_LINE = 2, ///< on status or command line + IN_SEP_LINE = 4, ///< on vertical separator line + IN_OTHER_WIN = 8, ///< in other window but can't go there CURSOR_MOVED = 0x100, - MOUSE_FOLD_CLOSE = 0x200, ///< clicked on '-' in fold column - MOUSE_FOLD_OPEN = 0x400, ///< clicked on '+' in fold column - MOUSE_WINBAR = 0x800, ///< in window toolbar + MOUSE_FOLD_CLOSE = 0x200, ///< clicked on '-' in fold column + MOUSE_FOLD_OPEN = 0x400, ///< clicked on '+' in fold column + MOUSE_WINBAR = 0x800, ///< in window toolbar + MOUSE_STATUSCOL = 0x1000, ///< in 'statuscolumn' }; /// flags for jump_to_mouse() diff --git a/src/nvim/move.c b/src/nvim/move.c index 227e19bca3..0d2a38b041 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -649,7 +649,8 @@ void validate_cursor_col(void) // fold column and sign column (these don't move when scrolling horizontally). int win_col_off(win_T *wp) { - return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + return ((wp->w_p_nu || wp->w_p_rnu || (*wp->w_p_stc != NUL)) ? + (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + win_fdccol_count(wp) + (win_signcol_count(wp) * win_signcol_width(wp)); diff --git a/src/nvim/option.c b/src/nvim/option.c index a6ec0a08c2..fe905a5a05 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2094,6 +2094,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va if (curwin->w_p_spell) { errmsg = did_set_spelllang(curwin); } + } else if (((int *)varp == &curwin->w_p_nu || (int *)varp == &curwin->w_p_rnu) + && *curwin->w_p_stc != NUL) { // '(relative)number' + 'statuscolumn' + curwin->w_nrwidth_line_count = 0; } if ((int *)varp == &curwin->w_p_arab) { @@ -2316,7 +2319,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) { if (value < 1) { errmsg = e_positive; - } else if (value > 20) { + } else if (value > MAX_NUMBERWIDTH) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { @@ -3630,6 +3633,9 @@ void unset_global_local_option(char *name, void *from) clear_string_option(&((win_T *)from)->w_p_ve); ((win_T *)from)->w_ve_flags = 0; break; + case PV_STC: + clear_string_option(&((win_T *)from)->w_p_stc); + break; } } @@ -4014,6 +4020,8 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curwin->w_p_winhl); case PV_WINBL: return (char_u *)&(curwin->w_p_winbl); + case PV_STC: + return (char_u *)&(curwin->w_p_stc); default: iemsg(_("E356: get_varp ERROR")); } @@ -4102,6 +4110,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_scl = copy_option_val(from->wo_scl); to->wo_winhl = copy_option_val(from->wo_winhl); to->wo_winbl = from->wo_winbl; + to->wo_stc = copy_option_val(from->wo_stc); // Copy the script context so that we know were the value was last set. memmove(to->wo_script_ctx, from->wo_script_ctx, sizeof(to->wo_script_ctx)); @@ -4139,6 +4148,7 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_ve); check_string_option(&wop->wo_wbr); + check_string_option(&wop->wo_stc); } /// Free the allocated memory inside a winopt_T. @@ -4165,6 +4175,7 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_ve); clear_string_option(&wop->wo_wbr); + clear_string_option(&wop->wo_stc); } void didset_window_options(win_T *wp, bool valid_cursor) diff --git a/src/nvim/option.h b/src/nvim/option.h index 6744678044..636257bfe8 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -19,6 +19,8 @@ typedef enum { #define BCO_ALWAYS 2 // always copy the options #define BCO_NOHELP 4 // don't touch the help related options +#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn' + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.h.generated.h" #endif diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 699a57c2aa..56fdb6de84 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -337,6 +337,8 @@ enum { STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y). STL_PAGENUM = 'N', ///< Page number (when printing). STL_SHOWCMD = 'S', ///< 'showcmd' buffer + STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn' + STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn' STL_VIM_EXPR = '{', ///< Start of expression to substitute. STL_SEPARATE = '=', ///< Separation between alignment sections. STL_TRUNCMARK = '<', ///< Truncation mark if line is too long. @@ -354,8 +356,9 @@ enum { STL_HELPFLAG, STL_HELPFLAG_ALT, STL_FILETYPE, STL_FILETYPE_ALT, \ STL_PREVIEWFLAG, STL_PREVIEWFLAG_ALT, STL_MODIFIED, STL_MODIFIED_ALT, \ STL_QUICKFIX, STL_PERCENTAGE, STL_ALTPERCENT, STL_ARGLISTSTAT, STL_PAGENUM, \ - STL_SHOWCMD, STL_VIM_EXPR, STL_SEPARATE, STL_TRUNCMARK, STL_USER_HL, \ - STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \ + STL_SHOWCMD, STL_FOLDCOL, STL_SIGNCOL, STL_VIM_EXPR, STL_SEPARATE, \ + STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, \ + STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \ 0, \ }) @@ -955,6 +958,7 @@ enum { WV_CULOPT, WV_CC, WV_SBR, + WV_STC, WV_STL, WV_WFH, WV_WFW, diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 8c7cf94465..387ccd0888 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2298,6 +2298,15 @@ return { defaults={if_true=false} }, { + full_name='statuscolumn', abbreviation='stc', + short_desc=N_("custom format for the status column"), + type='string', scope={'window'}, + redraw={'current_window'}, + secure=true, + alloced=true, + defaults={if_true=""} + }, + { full_name='statusline', abbreviation='stl', short_desc=N_("custom format for the status line"), type='string', scope={'global', 'window'}, diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 0c9d428ce7..a97e9b7c7e 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -1177,12 +1177,14 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf redraw_titles(); } } else if (gvarp == &p_stl || gvarp == &p_wbr || varp == &p_tal - || varp == &p_ruf) { - // 'statusline', 'winbar', 'tabline' or 'rulerformat' + || varp == &p_ruf || varp == &curwin->w_p_stc) { + // 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' int wid; if (varp == &p_ruf) { // reset ru_wid first ru_wid = 0; + } else if (varp == &curwin->w_p_stc) { + curwin->w_nrwidth_line_count = 0; } s = *varp; if (varp == &p_ruf && *s == '%') { @@ -1197,7 +1199,8 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf errmsg = check_stl_option(p_ruf); } } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { - // check 'statusline', 'winbar' or 'tabline' only if it doesn't start with "%!" + // check 'statusline', 'winbar', 'tabline' or 'statuscolumn' + // only if it doesn't start with "%!" errmsg = check_stl_option(s); } if (varp == &p_ruf && errmsg == NULL) { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 52ab2ad25e..afdb04ebd8 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -780,6 +780,16 @@ int number_width(win_T *wp) } wp->w_nrwidth_line_count = lnum; + // make best estimate for 'statuscolumn' + if (*wp->w_p_stc != NUL) { + char buf[MAXPATHL]; + wp->w_nrwidth_width = 0; + n = build_statuscol_str(wp, true, false, lnum, 0, 0, NUL, buf, NULL, NULL); + n = MAX(n, (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw); + wp->w_nrwidth_width = MIN(n, MAX_NUMBERWIDTH); + return wp->w_nrwidth_width; + } + n = 0; do { lnum /= 10; diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 0f28eae0d8..d548c285fb 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -367,8 +367,9 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) // Make a copy, because the statusline may include a function call that // might change the option value and free the memory. stl = xstrdup(stl); - width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, - opt_scope, fillchar, maxwidth, &hltab, &tabtab); + width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope, + fillchar, maxwidth, &hltab, &tabtab, NULL); + xfree(stl); ewp->w_p_crb = p_crb_save; @@ -867,6 +868,32 @@ void draw_tabline(void) redraw_tabline = false; } +int build_statuscol_str(win_T *wp, bool setnum, bool wrap, linenr_T lnum, long relnum, int maxwidth, + int fillchar, char *buf, stl_hlrec_t **hlrec, statuscol_T *stcp) +{ + if (setnum) { + set_vim_var_nr(VV_LNUM, lnum); + set_vim_var_nr(VV_RELNUM, relnum); + } + set_vim_var_bool(VV_WRAP, wrap); + + StlClickRecord *clickrec; + char *stc = xstrdup(wp->w_p_stc); + int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, + fillchar, maxwidth, hlrec, &clickrec, stcp); + xfree(stc); + + // Allocate and fill click def array if width has changed + if (wp->w_status_click_defs_size != (size_t)width) { + stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); + wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width, + &wp->w_statuscol_click_defs_size); + stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false); + } + + return width; +} + /// Build a string from the status line items in "fmt". /// Return length of string in screen cells. /// @@ -890,11 +917,13 @@ void draw_tabline(void) /// @param fillchar Character to use when filling empty space in the statusline /// @param maxwidth The maximum width to make the statusline /// @param hltab HL attributes (can be NULL) -/// @param tabtab Tab clicks definition (can be NULL). +/// @param tabtab Tab clicks definition (can be NULL) +/// @param stcp Status column attributes (can be NULL) /// /// @return The final width of the statusline int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_name, int opt_scope, - int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) + int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab, + statuscol_T *stcp) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; @@ -1466,8 +1495,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n } case STL_LINE: - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) - ? 0L : (long)(wp->w_cursor.lnum); + // Overload %l with v:lnum for 'statuscolumn' + num = strcmp(opt_name, "statuscolumn") == 0 ? get_vim_var_nr(VV_LNUM) + : (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum); break; case STL_NUMLINES: @@ -1565,9 +1595,14 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n case STL_ROFLAG: case STL_ROFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_p_ro) { - str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + // Overload %r with v:relnum for 'statuscolumn' + if (strcmp(opt_name, "statuscolumn") == 0) { + num = get_vim_var_nr(VV_RELNUM); + } else { + itemisflag = true; + if (wp->w_buffer->b_p_ro) { + str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + } } break; @@ -1579,6 +1614,33 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n } break; + case STL_FOLDCOL: // 'C' for 'statuscolumn' + case STL_SIGNCOL: { // 's' for 'statuscolumn' + if (stcp == NULL) { + break; + } + + bool fold = opt == STL_FOLDCOL; + *buf_tmp = NUL; + for (int i = 0; i <= 9; i++) { + char *p = fold ? stcp->fold_text : stcp->sign_text[i]; + if ((!p || !*p) && *buf_tmp == NUL) { + break; + } + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p + strlen(buf_tmp); + stl_items[curitem].minwid = !p || (fold && i) ? 0 : fold ? stcp->fold_attr + : stcp->sign_attr[i]; + curitem++; + if (!p || (fold && i)) { + str = buf_tmp; + break; + } + STRCAT(buf_tmp, p); + } + break; + } + case STL_FILETYPE: // Copy the filetype if it is not null and the formatted string will fit // in the temporary buffer @@ -1850,6 +1912,10 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n // What follows is post-processing to handle alignment and highlighting. int width = vim_strsize(out); + // Return truncated width for 'statuscolumn' + if (stcp != NULL && width > maxwidth) { + stcp->truncate = width - maxwidth; + } if (maxwidth > 0 && width > maxwidth) { // Result is too long, must truncate somewhere. int item_idx = 0; diff --git a/src/nvim/window.c b/src/nvim/window.c index 4e569ce11f..e836fa50fb 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5094,6 +5094,9 @@ static void win_free(win_T *wp, tabpage_T *tp) stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); xfree(wp->w_winbar_click_defs); + stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); + xfree(wp->w_statuscol_click_defs); + // Remove the window from the b_wininfo lists, it may happen that the // freed memory is re-used for another window. FOR_ALL_BUFFERS(buf) { diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua index 3969a7a478..18b13a8959 100644 --- a/test/functional/lua/ffi_spec.lua +++ b/test/functional/lua/ffi_spec.lua @@ -29,6 +29,7 @@ describe('ffi.cdef', function() typedef struct window_S win_T; typedef struct {} stl_hlrec_t; typedef struct {} StlClickRecord; + typedef struct {} statuscol_T; typedef struct {} Error; win_T *find_window_by_handle(int Window, Error *err); @@ -43,7 +44,8 @@ describe('ffi.cdef', function() int fillchar, int maxwidth, stl_hlrec_t **hltab, - StlClickRecord **tabtab + StlClickRecord **tabtab, + statuscol_T *scp ); ]] @@ -57,6 +59,7 @@ describe('ffi.cdef', function() 0, 0, nil, + nil, nil ) ]=]) diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua new file mode 100644 index 0000000000..1ee96ed625 --- /dev/null +++ b/test/functional/ui/statuscolumn_spec.lua @@ -0,0 +1,273 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local eval = helpers.eval +local meths = helpers.meths +local pcall_err = helpers.pcall_err + +describe('statuscolumn', function() + local screen + before_each(function() + clear('--cmd', 'set number nuw=1 | call setline(1, repeat(["aaaaa"], 16)) | norm GM') + screen = Screen.new() + screen:attach() + end) + + it('fails with invalid \'statuscolumn\'', function() + command('set stc=%{v:relnum?v:relnum:(v:lnum==5?invalid:v:lnum)}\\ ') + screen:expect([[ + 4 aaaaa | + 3 aaaaa | + 2 aaaaa | + 1 aaaaa | + 8 ^aaaaa | + 1 aaaaa | + 2 aaaaa | + 3 aaaaa | + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + 8 aaaaa | + | + ]]) + command('norm 5G') + eq('Vim(redraw):E121: Undefined variable: invalid', pcall_err(command, 'redraw!')) + eq('', eval('&statuscolumn')) + end) + + it('widens with irregular \'statuscolumn\' width', function() + command('set stc=%{v:relnum?v:relnum:(v:lnum==5?\'bbbbb\':v:lnum)}') + command('norm 5G | redraw!') + screen:expect([[ + 1 aaaaa | + bbbbba^eaaa | + 1 aaaaa | + 2 aaaaa | + 3 aaaaa | + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + 8 aaaaa | + 9 aaaaa | + 10 aaaaa | + 11 aaaaa | + | + ]]) + end) + + it('works with \'statuscolumn\'', function() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + screen:expect([[ + 4 │aaaaa | + 5 │aaaaa | + 6 │aaaaa | + 7 │aaaaa | + 8 │^aaaaa | + 9 │aaaaa | + 10│aaaaa | + 11│aaaaa | + 12│aaaaa | + 13│aaaaa | + 14│aaaaa | + 15│aaaaa | + 16│aaaaa | + | + ]]) + command('set relativenumber') + screen:expect([[ + 4 4│aaaaa | + 5 3│aaaaa | + 6 2│aaaaa | + 7 1│aaaaa | + 8 0│^aaaaa | + 9 1│aaaaa | + 10 2│aaaaa | + 11 3│aaaaa | + 12 4│aaaaa | + 13 5│aaaaa | + 14 6│aaaaa | + 15 7│aaaaa | + 16 8│aaaaa | + | + ]]) + command('norm 12GH') + screen:expect([[ + 4 0│^aaaaa | + 5 1│aaaaa | + 6 2│aaaaa | + 7 3│aaaaa | + 8 4│aaaaa | + 9 5│aaaaa | + 10 6│aaaaa | + 11 7│aaaaa | + 12 8│aaaaa | + 13 9│aaaaa | + 14 10│aaaaa | + 15 11│aaaaa | + 16 12│aaaaa | + | + ]]) + end) + + it('works with highlighted \'statuscolumn\'', function() + command([[set stc=%#NonText#%{&nu?v:lnum:''}]] .. + [[%=%{&rnu&&(v:lnum%2)?'\ '.v:relnum:''}]] .. + [[%#LineNr#%{&rnu&&!(v:lnum%2)?'\ '.v:relnum:''}│]]) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {foreground = Screen.colors.Brown}, + }) + screen:expect([[ + {0:4 }{1:│}aaaaa | + {0:5 }{1:│}aaaaa | + {0:6 }{1:│}aaaaa | + {0:7 }{1:│}aaaaa | + {0:8 }{1:│}^aaaaa | + {0:9 }{1:│}aaaaa | + {0:10}{1:│}aaaaa | + {0:11}{1:│}aaaaa | + {0:12}{1:│}aaaaa | + {0:13}{1:│}aaaaa | + {0:14}{1:│}aaaaa | + {0:15}{1:│}aaaaa | + {0:16}{1:│}aaaaa | + | + ]]) + command('set relativenumber') + screen:expect([[ + {0:4 }{1: 4│}aaaaa | + {0:5 3}{1:│}aaaaa | + {0:6 }{1: 2│}aaaaa | + {0:7 1}{1:│}aaaaa | + {0:8 }{1: 0│}^aaaaa | + {0:9 1}{1:│}aaaaa | + {0:10}{1: 2│}aaaaa | + {0:11 3}{1:│}aaaaa | + {0:12}{1: 4│}aaaaa | + {0:13 5}{1:│}aaaaa | + {0:14}{1: 6│}aaaaa | + {0:15 7}{1:│}aaaaa | + {0:16}{1: 8│}aaaaa | + | + ]]) + command('set nonumber') + screen:expect([[ + {1:4│}aaaaa | + {0:3}{1:│}aaaaa | + {1:2│}aaaaa | + {0:1}{1:│}aaaaa | + {1:0│}^aaaaa | + {0:1}{1:│}aaaaa | + {1:2│}aaaaa | + {0:3}{1:│}aaaaa | + {1:4│}aaaaa | + {0:5}{1:│}aaaaa | + {1:6│}aaaaa | + {0:7}{1:│}aaaaa | + {1:8│}aaaaa | + | + ]]) + end) + + it('works with wrapped lines, signs and folds', function() + command("set stc=%C%s%=%{v:wrap?'':v:lnum}│\\ ") + command("call setline(1,repeat([repeat('aaaaa',10)],16))") + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {foreground = Screen.colors.Brown}, + [2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGrey}, + [3] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, + }) + screen:expect([[ + {1: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{0:@@@}| + | + ]]) + command('set signcolumn=auto:2 foldcolumn=auto') + command('sign define piet1 text=>> texthl=LineNr') + command('sign define piet2 text=>! texthl=NonText') + command('sign place 1 line=4 name=piet1 buffer=1') + command('sign place 2 line=5 name=piet2 buffer=1') + command('sign place 3 line=6 name=piet1 buffer=1') + command('sign place 4 line=6 name=piet2 buffer=1') + screen:expect([[ + {1:>> 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }aaaaa | + {0:>!}{1: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }aaaaa | + {1:>>}{0:>!}{1: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }aaaaa | + {1: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }aaaaa | + {1: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }aaaaa | + {1: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }aaaaa | + {1: 10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{0:@@@}| + | + ]]) + command('norm zf$') + -- Check that alignment works properly with signs after %= + command("set stc=%C%=%{v:wrap?'':v:lnum}│%s\\ ") + screen:expect([[ + {2: }{1: 4│>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaaa | + {2: }{1: 5│}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaaa | + {2: }{1: 6│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaaa | + {2: }{1: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaaa | + {2:+}{1: 8│ }{3:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaaa | + {2: }{1:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaaa | + | + ]]) + end) + + it('works with \'statuscolumn\' clicks', function() + command('set mousemodel=extend') + command([[ + function! MyClickFunc(minwid, clicks, button, mods) + let g:testvar = printf("%d %d %s %d", a:minwid, a:clicks, a:button, getmousepos().line) + if a:mods !=# ' ' + let g:testvar ..= '(' .. a:mods .. ')' + endif + endfunction + set stc=%0@MyClickFunc@%=%l%T + ]]) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 1 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 2 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 3 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 4 l 4', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 1 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 2 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 3 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 4 r 7', eval("g:testvar")) + end) +end) diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 4f8a27de09..a54ea8c656 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -238,6 +238,7 @@ describe('buffer functions', function() fillchar, maximum_cell_count, NULL, + NULL, NULL) end |