aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluukvbaal <31730729+luukvbaal@users.noreply.github.com>2023-01-09 18:12:06 +0100
committerGitHub <noreply@github.com>2023-01-09 17:12:06 +0000
commit364b131f42509326c912c9b0fef5dfc94ed23b41 (patch)
tree654f24fde95819257002aa9b36990efd6a80535c
parent50f03773f4b9f4638489ccfd0503dc9e39e5de78 (diff)
downloadrneovim-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.txt17
-rw-r--r--runtime/doc/news.txt5
-rw-r--r--runtime/doc/options.txt46
-rw-r--r--runtime/doc/quickref.txt1
-rw-r--r--runtime/optwin.vim3
-rw-r--r--src/nvim/api/vim.c1
-rw-r--r--src/nvim/buffer.c4
-rw-r--r--src/nvim/buffer_defs.h28
-rw-r--r--src/nvim/drawline.c138
-rw-r--r--src/nvim/drawscreen.c16
-rw-r--r--src/nvim/eval.c2
-rw-r--r--src/nvim/eval.h2
-rw-r--r--src/nvim/mouse.c26
-rw-r--r--src/nvim/mouse.h15
-rw-r--r--src/nvim/move.c3
-rw-r--r--src/nvim/option.c13
-rw-r--r--src/nvim/option.h2
-rw-r--r--src/nvim/option_defs.h8
-rw-r--r--src/nvim/options.lua9
-rw-r--r--src/nvim/optionstr.c9
-rw-r--r--src/nvim/screen.c10
-rw-r--r--src/nvim/statusline.c84
-rw-r--r--src/nvim/window.c3
-rw-r--r--test/functional/lua/ffi_spec.lua5
-rw-r--r--test/functional/ui/statuscolumn_spec.lua273
-rw-r--r--test/unit/buffer_spec.lua1
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