aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/usermark.vim7
-rw-r--r--runtime/autoload/userreg.vim7
-rw-r--r--runtime/doc/options.txt56
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua6
-rw-r--r--runtime/lua/vim/usermark.lua68
-rw-r--r--runtime/lua/vim/userreg.lua51
-rw-r--r--runtime/plugin/usermark.vim1
-rw-r--r--runtime/plugin/userreg.vim1
-rw-r--r--src/nvim/buffer_defs.h20
-rw-r--r--src/nvim/drawline.c30
-rw-r--r--src/nvim/drawscreen.c13
-rw-r--r--src/nvim/eval.c10
-rw-r--r--src/nvim/eval/funcs.c3
-rw-r--r--src/nvim/eval/userfunc.c6
-rw-r--r--src/nvim/eval/vars.c15
-rw-r--r--src/nvim/ex_docmd.c8
-rw-r--r--src/nvim/map.c2
-rw-r--r--src/nvim/map.h2
-rw-r--r--src/nvim/mark.c158
-rw-r--r--src/nvim/ops.c408
-rw-r--r--src/nvim/ops.h13
-rw-r--r--src/nvim/option.c8
-rw-r--r--src/nvim/option_defs.h4
-rw-r--r--src/nvim/options.lua14
-rw-r--r--src/nvim/po/da.po2
-rw-r--r--src/nvim/po/fr.po2
-rw-r--r--src/nvim/po/tr.po2
-rw-r--r--src/nvim/po/uk.po2
-rw-r--r--src/nvim/screen.c6
-rw-r--r--src/nvim/shada.c4
-rw-r--r--src/nvim/state.c14
-rw-r--r--src/nvim/window.c110
-rw-r--r--src/nvim/yankmap.c42
-rw-r--r--src/nvim/yankmap.h24
-rw-r--r--test/functional/ui/highlight_spec.lua7
35 files changed, 1011 insertions, 115 deletions
diff --git a/runtime/autoload/usermark.vim b/runtime/autoload/usermark.vim
new file mode 100644
index 0000000000..b1b4113d1a
--- /dev/null
+++ b/runtime/autoload/usermark.vim
@@ -0,0 +1,7 @@
+" This is used for the default userreg function.
+
+lua vim.usermark = require('vim.usermark')
+
+function! usermark#func(action, mark) abort
+ return v:lua.vim.usermark.fn(a:action, a:mark)
+endfunction
diff --git a/runtime/autoload/userreg.vim b/runtime/autoload/userreg.vim
new file mode 100644
index 0000000000..fd026a12e6
--- /dev/null
+++ b/runtime/autoload/userreg.vim
@@ -0,0 +1,7 @@
+" This is used for the default userreg function.
+
+lua vim.userreg = require('vim.userreg')
+
+function! userreg#func(action, register, content) abort
+ return v:lua.vim.userreg.fn(a:action, a:register, a:content)
+endfunction
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index b1af90a604..2bd976fa43 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1371,7 +1371,8 @@ A jump table for the options with a short description can be found at |Q_op|.
'colorcolumn' 'cc' string (default "")
local to window
'colorcolumn' is a comma-separated list of screen columns that are
- highlighted with ColorColumn |hl-ColorColumn|. Useful to align
+ highlighted with ColorColumn |hl-ColorColumn| and drawn using the
+ "colorcol" option from 'fillchars'. Useful to align
text. Will make screen redrawing slower.
The screen column can be an absolute number, or a number preceded with
'+' or '-', which is added to or subtracted from 'textwidth'. >
@@ -2501,6 +2502,7 @@ A jump table for the options with a short description can be found at |Q_op|.
diff '-' deleted lines of the 'diff' option
msgsep ' ' message separator 'display'
eob '~' empty lines at the end of a buffer
+ colorcol ' ' character to display in the colorcolumn
lastline '@' 'display' contains lastline/truncate
Any one that is omitted will fall back to the default. For "stl" and
@@ -2539,6 +2541,7 @@ A jump table for the options with a short description can be found at |Q_op|.
fold Folded |hl-Folded|
diff DiffDelete |hl-DiffDelete|
eob EndOfBuffer |hl-EndOfBuffer|
+ colorcol:c ColorColumn |hl-ColorColumn|
lastline NonText |hl-NonText|
*'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'*
@@ -6828,6 +6831,57 @@ A jump table for the options with a short description can be found at |Q_op|.
written to disk (see |crash-recovery|). Also used for the
|CursorHold| autocommand event.
+ *'userregfunc'* *'urf'*
+'userregfunc' 'urf' string (default "")
+ global
+ The option specifies a function to be used to handle any registers
+ that Neovim does not natively handle. This option unlocks all
+ characters to be used as registers by the user.
+
+ The 'userregfunc' function is called each time a user register is read
+ from or written to.
+
+ The 'userregfunc' function must take the following parameters:
+
+ {action} The action being done on this register (either 'yank'
+ or 'put'
+
+ {register} The string holding the name of the register. This
+ is always a single character, though multi-byte
+ characters are allowed.
+
+ {content} If the action is 'yank' this is the content being
+ yanked into the register. The content is a dictionary
+ with the following items:
+
+ {lines} The lines being yanked, as a list.
+
+ {type} The type of yank, either "line", "char", or
+ "block"
+
+ {width} The width in case of "block" mode.
+
+ {additional_data} Additional data. (can be returned in
+ put mode).
+
+ In case the action is 'put', the 'userregfunc' function should return
+ the content to place in that location. The content can either be a
+ string, in which case "char" mode is inferred, or it can return a
+ dictionary of the same template that populates 'content'.
+
+ A very simple example of a 'userregfunc' function that behaves exactly
+ like traditional registers would look like: >
+
+ let s:contents = {}
+ function! MyUserregFunction(action, register, content) abort
+ if a:action == "put"
+ return get(s:contents, a:register, "")
+ else
+ let s:contents[a:register] = a:content
+ endif
+ endfunction
+ set userregfunc=MyUserregFunction
+<
*'varsofttabstop'* *'vsts'*
'varsofttabstop' 'vsts' string (default "")
local to buffer
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index b1bc48dac6..a9d3d0cbd6 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -330,7 +330,7 @@ function STHighlighter:process_response(response, client, version)
end
vim.list_extend(tokens, old_tokens, idx)
else
- tokens = response.data
+ tokens = response.data or {}
end
-- Update the state with the new results
@@ -378,7 +378,7 @@ function STHighlighter:on_win(topline, botline)
--
-- Instead, we have to use normal extmarks that can attach to locations
-- in the buffer and are persisted between redraws.
- local highlights = current_result.highlights
+ local highlights = current_result.highlights or {}
local idx = binary_search(highlights, topline)
for i = idx, #highlights do
@@ -612,7 +612,7 @@ function M.get_at_pos(bufnr, row, col)
local tokens = {}
for client_id, client in pairs(highlighter.client_state) do
- local highlights = client.current_result.highlights
+ local highlights = client.current_result.highlights or {}
if highlights then
local idx = binary_search(highlights, row)
for i = idx, #highlights do
diff --git a/runtime/lua/vim/usermark.lua b/runtime/lua/vim/usermark.lua
new file mode 100644
index 0000000000..0d1ec0ae0f
--- /dev/null
+++ b/runtime/lua/vim/usermark.lua
@@ -0,0 +1,68 @@
+-- Defualt implementation of the usermarkfunc. This default implementation is
+-- extensible and allows other plugins to register handlers for different
+-- registers.
+--
+-- The default handler behaves just as a normal register would.
+
+local vim = assert(vim)
+local usermark = {}
+
+-- Returns a "default handler" which behaves like normal global marks. When a
+-- call to set() is made, it stores the current line and col of the cursor and
+-- the filename of the current file.
+function usermark._default_handler()
+ local d = {}
+
+ -- Called when a mark is recalled using the "'" command. Just returns what was
+ -- stored before or nothing if it was never set before.
+ function d.get(self, mark)
+ return self.content or {}
+ end
+
+ -- Called when a mark is set using the "m" command. Stores the current cursor
+ -- position to be recalled at a later time.
+ function d.set(self, mark)
+ local r,c = unpack(vim.api.nvim_win_get_cursor(0))
+ local file = vim.fn.expand("%:p")
+
+ self.content = {
+ line = r;
+ col = c;
+ }
+
+ if file ~= '' then
+ self.content.file = file
+ end
+ end
+
+ return d
+end
+
+-- The store for register default handler
+usermark._marktable = {}
+
+-- Function for the 'usermarkfunc'. Will defer to the handler associated with
+-- the provided mark.
+--
+-- If not handler is registered to a given mark, the default handler is used,
+-- which is a re-implementation of standard mark behavior.
+function usermark.fn(action, mark)
+ if not usermark._marktable[mark] then
+ usermark._marktable[mark] = usermark._default_handler()
+ end
+
+ if action == "get" then
+ return usermark._marktable[mark]:get(mark)
+ else
+ usermark._marktable[mark]:set(mark)
+ return nil
+ end
+end
+
+-- Registers a handler with a mark. Gets and sets will then defer to this
+-- handler when determining the mark's behavior.
+function usermark.register_handler(mark, handler)
+ usermark._marktable[mark] = handler
+end
+
+return usermark
diff --git a/runtime/lua/vim/userreg.lua b/runtime/lua/vim/userreg.lua
new file mode 100644
index 0000000000..5abcff0407
--- /dev/null
+++ b/runtime/lua/vim/userreg.lua
@@ -0,0 +1,51 @@
+-- Defualt implementation of the userregfunc. This default implementation is
+-- extensible and allows other plugins to register handlers for different
+-- registers.
+--
+-- The default handler behaves just as a normal register would.
+
+local userreg = {}
+
+-- Returns a "default handler" which behaves exactly like the builtin registers
+-- in Vim. Simply stores whatever was yanked and returns the last thing that was
+-- yanked.
+function userreg._default_handler()
+ local d = {}
+
+ function d.do_yank(self, content)
+ self.content = content
+ end
+
+ function d.do_put(self)
+ return self.content or {}
+ end
+
+ return d
+end
+
+-- The store for registers default handler
+userreg._regtable = {}
+
+-- Function for the userreg. This function will defer to the handler registered
+-- to the given register. If no handler is registered to the given register, the
+-- default handler is used.
+function userreg.fn(action, register, content)
+ if not userreg._regtable[register] then
+ userreg._regtable[register] = userreg._default_handler()
+ end
+
+ if action == "yank" then
+ userreg._regtable[register]:do_yank(content)
+ return nil
+ else
+ return userreg._regtable[register]:do_put()
+ end
+end
+
+-- Registers a handler with a register. Future yanks and puts will defer to the
+-- handler when determining the content to put/yank.
+function userreg.register_handler(register, handler)
+ userreg._regtable[register] = handler
+end
+
+return userreg
diff --git a/runtime/plugin/usermark.vim b/runtime/plugin/usermark.vim
new file mode 100644
index 0000000000..917e7510f1
--- /dev/null
+++ b/runtime/plugin/usermark.vim
@@ -0,0 +1 @@
+set usermarkfunc=usermark#func
diff --git a/runtime/plugin/userreg.vim b/runtime/plugin/userreg.vim
new file mode 100644
index 0000000000..099e7c65cb
--- /dev/null
+++ b/runtime/plugin/userreg.vim
@@ -0,0 +1 @@
+set userregfunc=userreg#func
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 4c99191170..28c3374c0c 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -93,6 +93,22 @@ typedef uint64_t disptick_T; // display tick type
#include "nvim/syntax_defs.h"
#include "nvim/terminal.h"
+typedef enum {
+ kColorcolBehind = 1,
+ kColorcolForeground = 2,
+} colorcol_flags_T;
+
+// Structure to define data associated with a colorcolumn.
+typedef struct {
+ int col; // The column number to highlight.
+ int ch; // The character to draw in the column.
+
+ char* syn_name; // The highlight group name. Must be free'd.
+ int syn_attr; // The attribute. Will be set before a redraw.
+
+ int flags; // Additional flags
+} colorcol_T;
+
// The taggy struct is used to store the information about a :tag command.
typedef struct taggy {
char *tagname; // tag name
@@ -657,12 +673,14 @@ struct file_buffer {
#ifdef BACKSLASH_IN_FILENAME
char *b_p_csl; ///< 'completeslash'
#endif
+ char *b_p_umf; ///< 'usermarkfunc'
char *b_p_cfu; ///< 'completefunc'
Callback b_cfu_cb; ///< 'completefunc' callback
char *b_p_ofu; ///< 'omnifunc'
Callback b_ofu_cb; ///< 'omnifunc' callback
char *b_p_tfu; ///< 'tagfunc'
Callback b_tfu_cb; ///< 'tagfunc' callback
+ char *b_p_urf; ///< 'userregfunc'
int b_p_eof; ///< 'endoffile'
int b_p_eol; ///< 'endofline'
int b_p_fixeol; ///< 'fixendofline'
@@ -1335,7 +1353,7 @@ struct window_S {
uint32_t w_p_wbr_flags; // flags for 'winbar'
uint32_t w_p_fde_flags; // flags for 'foldexpr'
uint32_t w_p_fdt_flags; // flags for 'foldtext'
- int *w_p_cc_cols; // array of columns to highlight or NULL
+ colorcol_T *w_p_cc_cols; // array of columns to highlight or NULL
uint8_t w_p_culopt_flags; // flags for cursorline highlighting
long w_p_siso; // 'sidescrolloff' local value
long w_p_so; // 'scrolloff' local value
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 01ff207c2b..83aa68194c 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -84,12 +84,12 @@ typedef struct {
/// Advance **color_cols
///
/// @return true when there are columns to draw.
-static bool advance_color_col(int vcol, int **color_cols)
+static bool advance_color_col(int vcol, colorcol_T **color_cols)
{
- while (**color_cols >= 0 && vcol > **color_cols) {
+ while ((*color_cols)->col >= 0 && vcol > (*color_cols)->col) {
(*color_cols)++;
}
- return **color_cols >= 0;
+ return (*color_cols)->col >= 0;
}
/// Used when 'cursorlineopt' contains "screenline": compute the margins between
@@ -671,7 +671,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int save_did_emsg;
int eol_hl_off = 0; // 1 if highlighted char after EOL
bool draw_color_col = false; // highlight colorcolumn
- int *color_cols = NULL; // pointer to according columns array
+ colorcol_T *color_cols = NULL; // pointer to according columns array
bool has_spell = false; // this buffer has spell checking
#define SPWORDLEN 150
char nextline[SPWORDLEN * 2]; // text with start of the next line
@@ -2494,15 +2494,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (draw_color_col) {
// determine rightmost colorcolumn to possibly draw
- for (i = 0; color_cols[i] >= 0; i++) {
- if (rightmost_vcol < color_cols[i]) {
- rightmost_vcol = color_cols[i];
+ for (i = 0; color_cols[i].col >= 0; i++) {
+ if (rightmost_vcol < color_cols[i].col) {
+ rightmost_vcol = color_cols[i].col;
}
}
}
int cuc_attr = win_hl_attr(wp, HLF_CUC);
- int mc_attr = win_hl_attr(wp, HLF_MC);
int diff_attr = 0;
if (diff_hlf == HLF_TXD) {
@@ -2530,8 +2529,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) {
col_attr = cuc_attr;
- } else if (draw_color_col && VCOL_HLC == *color_cols) {
- col_attr = mc_attr;
+ } else if (draw_color_col && VCOL_HLC == color_cols->col) {
+ col_attr = color_cols->syn_attr;
+ c = color_cols->ch;
+ schar_from_char(linebuf_char[off], c);
}
col_attr = hl_combine_attr(col_attr, line_attr);
@@ -2618,9 +2619,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& lnum != wp->w_cursor.lnum) {
vcol_save_attr = char_attr;
char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr);
- } else if (draw_color_col && VCOL_HLC == *color_cols) {
+ } else if (draw_color_col && VCOL_HLC == color_cols->col) {
vcol_save_attr = char_attr;
- char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr);
+
+ if (color_cols->flags & kColorcolForeground) {
+ char_attr = hl_combine_attr(char_attr, color_cols->syn_attr);
+ } else if (!(color_cols->flags & kColorcolBehind)) {
+ char_attr = hl_combine_attr(color_cols->syn_attr, char_attr);
+ }
}
}
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 04c342e068..f5f72b711f 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -1016,6 +1016,19 @@ static void win_update(win_T *wp, DecorProviders *providers)
return;
}
+ // Link colorcolumn syn_attrs to syn_names. Needs to be done at a redraw
+ // as the syn names are volitile and can change.
+ if (wp->w_p_cc_cols) {
+ for (int i = 0; wp->w_p_cc_cols[i].col >= 0; ++ i) {
+ const char* syn_name = wp->w_p_cc_cols[i].syn_name;
+ if (syn_name == NULL) {
+ wp->w_p_cc_cols[i].syn_attr = win_hl_attr(wp, HLF_MC);
+ } else {
+ wp->w_p_cc_cols[i].syn_attr = syn_name2attr(syn_name);
+ }
+ }
+ }
+
buf_T *buf = wp->w_buffer;
// reset got_int, otherwise regexp won't work
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 4392ea306f..ffab9a02b5 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -2976,12 +2976,10 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// Register contents: @r.
case '@':
(*arg)++;
+ int regname = mb_cptr2char_adv((const char**) arg);
if (evaluate) {
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = get_reg_contents(**arg, kGRegExprSrc);
- }
- if (**arg != NUL) {
- (*arg)++;
+ rettv->vval.v_string = get_reg_contents(regname, kGRegExprSrc);
}
break;
@@ -4175,7 +4173,7 @@ bool garbage_collect(bool testing)
// registers (ShaDa additional data)
{
- const void *reg_iter = NULL;
+ iter_register_T reg_iter = ITER_REGISTER_NULL;
do {
yankreg_T reg;
char name = NUL;
@@ -4184,7 +4182,7 @@ bool garbage_collect(bool testing)
if (name != NUL) {
ABORTING(set_ref_dict)(reg.additional_data, copyID);
}
- } while (reg_iter != NULL);
+ } while (reg_iter != ITER_REGISTER_NULL);
}
// global marks (ShaDa additional data)
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 48f3cd4293..569c1462d1 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3134,6 +3134,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"title",
"user-commands", // was accidentally included in 5.4
"user_commands",
+ "usermarks",
"vartabs",
"vertsplit",
"vimscript-1",
@@ -3147,6 +3148,8 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"winaltkeys",
"writebackup",
"nvim",
+ "rneovim",
+ "fancycolorcol",
};
// XXX: eval_has_provider() may shell out :(
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 6c6dc3fa43..ab81c190b0 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -854,6 +854,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
int started_profiling = false;
bool did_save_redo = false;
save_redo_T save_redo;
+ char_u* saved_repeat_cmdline = NULL;
// If depth of calling is getting too high, don't execute the function
if (depth >= p_mfd) {
@@ -866,6 +867,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// Save search patterns and redo buffer.
save_search_patterns();
if (!ins_compl_active()) {
+ if (repeat_cmdline) {
+ saved_repeat_cmdline = xstrdup(repeat_cmdline);
+ }
saveRedobuff(&save_redo);
did_save_redo = true;
}
@@ -1215,6 +1219,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// restore search patterns and redo buffer
if (did_save_redo) {
restoreRedobuff(&save_redo);
+ xfree(repeat_cmdline);
+ repeat_cmdline = saved_repeat_cmdline;
}
restore_search_patterns();
}
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 9ed245d6c4..f38dc90e9d 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -408,7 +408,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon)
static const char *skip_var_one(const char *arg)
{
if (*arg == '@' && arg[1] != NUL) {
- return arg + 2;
+ return arg + 1 + utfc_ptr2len(arg + 1);
}
return (char *)find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
@@ -716,10 +716,14 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
return NULL;
}
arg++;
+
+ int regname = utf_ptr2char(arg);
+ int mblen = utf_ptr2len(arg);
+
if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
semsg(_(e_letwrong), op);
} else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + mblen))) == NULL) {
emsg(_(e_letunexp));
} else {
char *s;
@@ -727,7 +731,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
char *ptofree = NULL;
const char *p = tv_get_string_chk(tv);
if (p != NULL && op != NULL && *op == '.') {
- s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
+ s = get_reg_contents(*arg == '@' ? '"' : regname, kGRegExprSrc);
if (s != NULL) {
ptofree = concat_str(s, p);
p = (const char *)ptofree;
@@ -735,8 +739,9 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
}
}
if (p != NULL) {
- write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
- arg_end = arg + 1;
+ write_reg_contents(*arg == '@' ? '"' : regname,
+ p, (ssize_t)strlen(p), false);
+ arg_end = arg + mblen;
}
xfree(ptofree);
}
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a24e8458a6..5d271e4ef6 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -3195,7 +3195,7 @@ char *skip_range(const char *cmd, int *ctx)
}
}
if (*cmd != NUL) {
- cmd++;
+ cmd += utf_ptr2len(cmd);
}
}
@@ -3338,13 +3338,13 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
goto error;
}
if (skip) {
- cmd++;
+ cmd += utfc_ptr2len(cmd);
} else {
// Only accept a mark in another file when it is
// used by itself: ":'M".
MarkGet flag = to_other_file && cmd[1] == NUL ? kMarkAll : kMarkBufLocal;
- fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd);
- cmd++;
+ fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, utf_ptr2char(cmd));
+ cmd += utf_ptr2len(cmd);
if (fm != NULL && fm->fnum != curbuf->handle) {
// Jumped to another file.
lnum = curwin->w_cursor.lnum;
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 191a459863..b94e0dd8c6 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -162,6 +162,8 @@ static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2)
return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
}
+MAP_IMPL(ptr_t, int, DEFAULT_INITIALIZER)
+MAP_IMPL(int, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(int, int, DEFAULT_INITIALIZER)
MAP_IMPL(int, cstr_t, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 92f0b32255..9b91d3c7a9 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -39,7 +39,9 @@
//
// NOTE: Keys AND values must be allocated! khash.h does not make a copy.
//
+MAP_DECLS(int, ptr_t)
MAP_DECLS(int, int)
+MAP_DECLS(ptr_t, int)
MAP_DECLS(int, cstr_t)
MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(cstr_t, int)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index f1a1f25e6c..0c7f5efd89 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -26,6 +26,7 @@
#include "nvim/globals.h"
#include "nvim/highlight_defs.h"
#include "nvim/mark.h"
+#include "nvim/mark_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -42,6 +43,8 @@
#include "nvim/textobject.h"
#include "nvim/undo_defs.h"
#include "nvim/vim.h"
+#include "nvim/map.h"
+#include "nvim/eval/userfunc.h"
// This file contains routines to maintain and manipulate marks.
@@ -54,6 +57,32 @@
/// Global marks (marks with file number or name)
static xfmark_T namedfm[NGLOBALMARKS];
+static struct {
+ Map(int, ptr_t) named;
+} usermarks;
+bool usermarks_init;
+
+static xfmark_T* lookup_user_mark(int mark)
+{
+ if (!usermarks_init) {
+ map_init(int, ptr_t, &usermarks.named);
+ usermarks_init = 1;
+ }
+
+ xfmark_T **ret =
+ (xfmark_T**) map_ref(int, ptr_t)(&usermarks.named, mark, true);
+
+ if (ret) {
+ if (! (*ret)) {
+ *ret = xcalloc(sizeof(xfmark_T), 1);
+ }
+
+ return *ret;
+ }
+
+ return NULL;
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.c.generated.h"
#endif
@@ -160,7 +189,8 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL);
return OK;
}
- return FAIL;
+
+ return mark_set_user(buf, c);
}
// Set the previous context mark to the current position and add it to the
@@ -341,6 +371,15 @@ fmark_T *mark_get(buf_T *buf, win_T *win, fmark_T *fmp, MarkGet flag, int name)
// Local Marks
fm = mark_get_local(buf, win, name);
}
+
+ if (!fm) {
+ // Get usermark.
+ xfmark_T* xm = mark_get_user(buf, name);
+ if (xm) {
+ fm = &xm->fmark;
+ }
+ }
+
if (fmp != NULL && fm != NULL) {
*fmp = *fm;
return fmp;
@@ -429,6 +468,123 @@ fmark_T *mark_get_local(buf_T *buf, win_T *win, int name)
return mark;
}
+/// Loads the mark 'out' with the results from calling the usermarkfunc.
+///
+/// @param umf String for the usermarkfunc
+/// @param name name for the mark
+/// @param[out] out the mark to write the results to.
+static int call_umf(
+ const char* umf, int name, typval_T* out, const char* get_or_set_a)
+{
+ char markname_str[5];
+ char get_or_set[4];
+ int len;
+
+ strncpy(get_or_set, get_or_set_a, sizeof(get_or_set));
+ get_or_set[3] = 0;
+
+ len = (*utf_char2len)(name);
+ markname_str[len] = 0;
+ utf_char2bytes(name, markname_str);
+
+ typval_T args[3];
+ args[0].v_type = VAR_STRING;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_UNKNOWN;
+
+ args[0].vval.v_string = get_or_set;
+ args[1].vval.v_string = markname_str;
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+
+ return call_func(umf, -1, out, 2, args, &funcexe);
+}
+
+static int typval_to_xfmark(buf_T* buf, xfmark_T* out, typval_T* in)
+{
+ varnumber_T line;
+ varnumber_T col;
+ char* filename = NULL;
+
+ switch (in->v_type) {
+ case VAR_DICT:
+ line = tv_dict_get_number(in->vval.v_dict, "line");
+ col = tv_dict_get_number(in->vval.v_dict, "col");
+ filename = tv_dict_get_string(in->vval.v_dict, "file", true);
+ break;
+
+ case VAR_NUMBER:
+ line = in->vval.v_number;
+ col = 1;
+ break;
+
+ default:
+ return -1;
+ }
+
+ free_xfmark(*out);
+ memset(out, 0, sizeof(*out));
+
+ out->fname = filename;
+ out->fmark.mark.col = (int) col;
+ out->fmark.mark.lnum = (int) line;
+ out->fmark.fnum = 0;
+ out->fmark.timestamp = os_time();
+
+ return 0;
+}
+
+/// Gets marks that are defined by the user.
+///
+/// @param buf the buffer
+/// @param name name fo the mark
+xfmark_T *mark_get_user(buf_T* buf, int name)
+{
+ const char* umf = (const char*) buf->b_p_umf;
+
+ if (!umf) {
+ return NULL;
+ }
+
+ xfmark_T* mark = lookup_user_mark(name);
+ if (mark) {
+ typval_T* typval = xcalloc(sizeof(typval_T), 1);
+ call_umf(umf, name, typval, "get");
+ typval_to_xfmark(buf, mark, typval);
+ tv_free(typval);
+
+ if (mark->fname) {
+ buf_T* buffer =
+ buflist_new(
+ mark->fname, NULL, mark->fmark.mark.lnum, BLN_CURBUF | BLN_LISTED);
+
+ if (buffer) {
+ mark->fmark.fnum = buffer->b_fnum;
+ }
+ } else {
+ mark->fmark.fnum = buf->b_fnum;
+ }
+ }
+
+ return mark;
+}
+
+int mark_set_user(buf_T* buf, int name)
+{
+ const char* umf = (const char*) buf->b_p_umf;
+
+ if (!umf) {
+ return FAIL;
+ }
+
+ typval_T* out = xcalloc(sizeof(typval_T), 1);
+ call_umf(umf, name, out, "set");
+ tv_free(out);
+
+ return OK;
+}
+
/// Get marks that are actually motions but return them as marks
///
/// Gets the following motions as marks: '{', '}', '(', ')'
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index cb56cb7027..86e317b7f6 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -25,6 +25,7 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
@@ -63,8 +64,24 @@
#include "nvim/undo.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/yankmap.h"
-static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
+struct yank_registers {
+ yankmap_T inner;
+};
+
+yank_registers_T y_regs;
+
+static yankreg_T *get_reg(yank_registers_T *regs, int idx)
+{
+ return yankmap_get(&regs->inner, idx);
+
+}
+
+static yankreg_T *get_global_reg(int idx)
+{
+ return get_reg(&y_regs, idx);
+}
static yankreg_T *y_previous = NULL; // ptr to last written yankreg
@@ -782,6 +799,24 @@ char *get_expr_line_src(void)
return xstrdup(expr_line);
}
+
+int get_userreg(int regname)
+{
+ if ((regname >= 'a' && regname <= 'z')
+ || (regname >= 'A' && regname <= 'Z')
+ || (regname >= '0' && regname <= '9')
+ || (regname <= 127 && strchr("\"-:.%#=*+_/", regname))
+ || regname == Ctrl_F
+ || regname == Ctrl_P
+ || regname == Ctrl_W
+ || regname == Ctrl_A
+ || (regname + USER_REGISTERS_START) < regname) {
+ return -1;
+ }
+
+ return regname + USER_REGISTERS_START;
+}
+
/// @return whether `regname` is a valid name of a yank register.
///
/// @note: There is no check for 0 (default register), caller should do this.
@@ -798,12 +833,152 @@ bool valid_yank_reg(int regname, bool writing)
|| regname == '-'
|| regname == '_'
|| regname == '*'
- || regname == '+') {
+ || regname == '+'
+ || get_userreg(regname) != -1) {
return true;
}
return false;
}
+static int call_userreg_put(const char* urf, int regname, typval_T* out)
+{
+ char regname_str[5];
+ int len;
+
+ len = (*utf_char2len)(regname);
+ regname_str[len] = 0;
+ utf_char2bytes(regname, regname_str);
+
+ typval_T args[3];
+ args[0].v_type = VAR_STRING;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_NUMBER;
+
+ args[0].vval.v_string = "put";
+ args[1].vval.v_string = regname_str;
+ args[2].vval.v_number = 0;
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+
+ return call_func(
+ urf,
+ -1,
+ out,
+ /* argcount_in = */ 3,
+ args,
+ &funcexe);
+}
+
+// Converts a typval returned from the userregfunction to a register.
+static void typval_to_yankreg(yankreg_T* yankreg, typval_T* val)
+{
+ if (!yankreg || !val) return;
+
+ char* type;
+ dict_T* dict;
+ typval_T tv;
+ size_t i;
+ size_t sz;
+
+ free_register(yankreg);
+ memset(yankreg, 0, sizeof(*yankreg));
+
+ switch (val->v_type) {
+
+ case VAR_DICT:
+ dict = val->vval.v_dict;
+ type = tv_dict_get_string(dict, "type", false);
+
+ if (!strcmp(type, "block")) {
+ yankreg->y_width = (int) tv_dict_get_number(dict, "width");
+ yankreg->y_type = kMTBlockWise;
+ } else if (!strcmp(type, "line")) {
+ yankreg->y_type = kMTLineWise;
+ } else {
+ yankreg->y_type = kMTCharWise;
+ }
+
+ if (tv_dict_get_tv(dict, "lines", &tv) == OK) {
+ if (tv.v_type == VAR_STRING) {
+ yankreg->y_array = xcalloc(sizeof(char*), 1);
+ yankreg->y_array[0] = strdup(tv.vval.v_string);
+ } else if (tv.v_type == VAR_LIST) {
+ yankreg->y_array =
+ xcalloc(sizeof(char*), (size_t) tv_list_len(tv.vval.v_list));
+
+ i = 0;
+ TV_LIST_ITER_CONST(tv.vval.v_list, li, {
+ if (li->li_tv.v_type == VAR_STRING) {
+ yankreg->y_array[i] = strdup(tv_get_string(&li->li_tv));
+ } else {
+ yankreg->y_array[i] = NULL;
+ }
+ ++ i;
+ });
+
+ yankreg->y_size = i;
+ }
+ } else {
+ yankreg->y_array = NULL;
+ }
+
+ if (tv_dict_get_tv(dict, "additional_data", &tv) == OK) {
+ if (tv.v_type == VAR_DICT) {
+ yankreg->additional_data = tv.vval.v_dict;
+ }
+ }
+ break;
+
+ case VAR_LIST:
+ yankreg->y_type = kMTLineWise;
+ sz = (size_t) tv_list_len(val->vval.v_list);
+ yankreg->y_array = xcalloc(sizeof(char*), sz);
+ yankreg->y_size = sz;
+ i = 0;
+ TV_LIST_ITER_CONST(val->vval.v_list, li, {
+ yankreg->y_array[i] = strdup(tv_get_string(&li->li_tv));
+ i ++;
+ });
+ break;
+
+ default:
+ yankreg->y_type = kMTCharWise;
+ yankreg->y_size = 1;
+
+ if (val->vval.v_string) {
+ yankreg->y_array = xcalloc(sizeof(char*), 1);
+ yankreg->y_array[0] = strdup(tv_get_string(val));
+ } else {
+ yankreg->y_array = NULL;
+ }
+
+ break;
+
+ }
+
+ yankreg->timestamp = os_time();
+}
+
+static void copy_userreg(yankreg_T* into, int regname)
+{
+ if (!into) return;
+
+ if (!curbuf->b_p_urf || strlen(curbuf->b_p_urf) == 0)
+ return;
+
+
+ typval_T* ret = xmalloc(sizeof(typval_T));
+
+ if (call_userreg_put(curbuf->b_p_urf, regname, ret) == FAIL) {
+ return;
+ }
+
+ typval_to_yankreg(into, ret);
+
+ tv_free(ret);
+}
+
/// @return yankreg_T to use, according to the value of `regname`.
/// Cannot handle the '_' (black hole) register.
/// Must only be called with a valid register name!
@@ -841,7 +1016,11 @@ yankreg_T *get_yank_register(int regname, int mode)
if (i == -1) {
i = 0;
}
- reg = &y_regs[i];
+ reg = get_global_reg(i);
+ if (get_userreg(regname) != -1 && mode != YREG_YANK) {
+ // If the mode is not yank, copy the userreg data to the reg.
+ copy_userreg(reg, regname);
+ }
if (mode == YREG_YANK) {
// remember the written register for unnamed paste
@@ -909,8 +1088,7 @@ int do_record(int c)
if (reg_recording == 0) {
// start recording
- // registers 0-9, a-z and " are allowed
- if (c < 0 || (!ASCII_ISALNUM(c) && c != '"')) {
+ if (c < 0) {
retval = FAIL;
} else {
reg_recording = c;
@@ -935,9 +1113,10 @@ int do_record(int c)
}
// Name of requested register, or empty string for unnamed operation.
- char buf[NUMBUFLEN + 2];
- buf[0] = (char)regname;
- buf[1] = NUL;
+ char buf[NUMBUFLEN + 5];
+ int len = (*utf_char2len)(regname);
+ utf_char2bytes(regname, buf);
+ buf[len] = NUL;
(void)tv_dict_add_str(dict, S_LEN("regname"), buf);
tv_dict_set_keys_readonly(dict);
@@ -1012,6 +1191,9 @@ static int stuff_yank(int regname, char *p)
reg->y_type = kMTCharWise;
}
reg->timestamp = os_time();
+ if (get_userreg(regname) != -1) {
+ return eval_yank_userreg(curbuf->b_p_urf, regname, reg);
+ }
return OK;
}
@@ -1308,6 +1490,90 @@ int insert_reg(int regname, bool literally_arg)
return retval;
}
+/// Converts a yankreg to a dict which can be used as an argument to the
+// userregfunc.
+static dict_T* yankreg_to_dict(yankreg_T* yankreg) {
+ dict_T *const dict = tv_dict_alloc();
+ dict->dv_refcount = 1;
+ tv_dict_add_nr(dict, S_LEN("width"), yankreg->y_width);
+
+ const char* type;
+
+ switch(yankreg->y_type) {
+ case kMTLineWise:
+ type = "line";
+ break;
+ case kMTCharWise:
+ type = "char";
+ break;
+ case kMTBlockWise:
+ type = "block";
+ break;
+ default:
+ type = "unknown";
+ }
+
+ tv_dict_add_str(dict, S_LEN("type"), type);
+ if (yankreg->additional_data) {
+ tv_dict_add_dict(dict, S_LEN("additional_data"), yankreg->additional_data);
+ }
+
+ list_T *const lines = tv_list_alloc((long)yankreg->y_size);
+
+ size_t i;
+ for (i = 0; i < yankreg->y_size; ++ i) {
+ tv_list_append_string(
+ lines, yankreg->y_array[i], (long)strlen(yankreg->y_array[i]));
+ }
+
+ tv_dict_add_list(dict, S_LEN("lines"), lines);
+
+ return dict;
+}
+
+/*
+ * Executes the yank() function on a user-defined register to set the contents
+ * of that register.
+ */
+static int eval_yank_userreg(const char *ufn, int regname, yankreg_T *reg)
+{
+ if (!reg)
+ return -1;
+
+ int ret, len;
+ char regname_str[5];
+
+ len = (*utf_char2len)(regname);
+ regname_str[len] = 0;
+ utf_char2bytes(regname, regname_str);
+
+ typval_T args[4];
+ args[0].v_type = VAR_STRING;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_DICT;
+ args[3].v_type = VAR_UNKNOWN;
+
+ args[0].vval.v_string = "yank";
+ args[1].vval.v_string = regname_str;
+ args[2].vval.v_dict = yankreg_to_dict(reg);
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+
+ typval_T* out = xmalloc(sizeof(typval_T));
+ return call_func(
+ ufn,
+ -1,
+ out,
+ /* argcount_in = */ 3,
+ args,
+ &funcexe
+ );
+
+ tv_free(out);
+ return ret;
+}
+
/// If "regname" is a special register, return true and store a pointer to its
/// value in "argp".
///
@@ -1393,6 +1659,9 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
case '_': // black hole: always empty
*argp = "";
return true;
+
+ default:
+ break;
}
return false;
@@ -1439,14 +1708,14 @@ bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr)
/// Shift the delete registers: "9 is cleared, "8 becomes "9, etc.
static void shift_delete_registers(bool y_append)
{
- free_register(&y_regs[9]); // free register "9
+ free_register(get_global_reg(9)); // free register "9
for (int n = 9; n > 1; n--) {
- y_regs[n] = y_regs[n - 1];
+ *get_global_reg(n) = *get_global_reg(n - 1);
}
if (!y_append) {
- y_previous = &y_regs[1];
+ y_previous = get_global_reg(1);
}
- y_regs[1].y_array = NULL; // set register "1 to empty
+ get_global_reg(1)->y_array = NULL; // set register "1 to empty
}
/// Handle a delete operation.
@@ -1542,7 +1811,7 @@ int op_delete(oparg_T *oap)
if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) {
shift_delete_registers(is_append_register(oap->regname));
- reg = &y_regs[1];
+ reg = get_global_reg(1);
op_yank_reg(oap, false, reg, false);
did_yank = true;
}
@@ -2556,13 +2825,22 @@ int op_change(oparg_T *oap)
return retval;
}
+
+/*
+ * set all the yank registers to empty (called from main())
+ */
+void init_yank(void)
+{
+ init_yankmap(&y_regs.inner);
+}
+
#if defined(EXITFREE)
void clear_registers(void)
{
int i;
for (i = 0; i < NUM_REGISTERS; i++) {
- free_register(&y_regs[i]);
+ free_register(get_global_reg(i));
}
}
@@ -2608,6 +2886,14 @@ bool op_yank(oparg_T *oap, bool message)
yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
op_yank_reg(oap, message, reg, is_append_register(oap->regname));
+
+ if (get_userreg(oap->regname) != -1) {
+ if (eval_yank_userreg(curbuf->b_p_urf, oap->regname, reg) == -1) {
+ beep_flush();
+ return false;
+ }
+ }
+
set_clipboard(oap->regname, reg);
do_autocmd_textyankpost(oap, reg);
@@ -2788,7 +3074,11 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
if (oap->regname == NUL) {
*namebuf = NUL;
} else {
- vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%c"), oap->regname);
+ char buf[5];
+ int len = (*utf_char2len) (oap->regname);
+ utf_char2bytes(oap->regname, buf);
+ buf[len] = 0;
+ vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%s"), buf);
}
// redisplay now, so message is not deleted
@@ -2858,6 +3148,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
FUNC_ATTR_NONNULL_ALL
{
static bool recursive = false;
+ int len;
if (recursive || !has_event(EVENT_TEXTYANKPOST)) {
// No autocommand was defined, or we yanked from this autocommand.
@@ -2879,13 +3170,15 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
(void)tv_dict_add_list(dict, S_LEN("regcontents"), list);
// Register type.
- char buf[NUMBUFLEN + 2];
+ char buf[NUMBUFLEN + 6];
format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf));
(void)tv_dict_add_str(dict, S_LEN("regtype"), buf);
// Name of requested register, or empty string for unnamed operation.
- buf[0] = (char)oap->regname;
- buf[1] = NUL;
+ len = (*utf_char2len)(oap->regname);
+ buf[len] = 0;
+ utf_char2bytes(oap->regname, buf);
+ recursive = true;
(void)tv_dict_add_str(dict, S_LEN("regname"), buf);
// Motion type: inclusive or exclusive.
@@ -3150,6 +3443,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
reg = get_yank_register(regname, YREG_PASTE);
}
+ if (get_userreg(regname) != -1) {
+ copy_userreg(reg, regname);
+ }
+
y_type = reg->y_type;
y_width = reg->y_width;
y_size = reg->y_size;
@@ -3817,7 +4114,7 @@ int get_register_name(int num)
/// @return the index of the register "" points to.
int get_unname_register(void)
{
- return y_previous == NULL ? -1 : (int)(y_previous - &y_regs[0]);
+ return yankmap_find(&y_regs.inner, y_previous);
}
/// ":dis" and ":registers": Display the contents of the yank registers.
@@ -3856,10 +4153,10 @@ void ex_display(exarg_T *eap)
if (y_previous != NULL) {
yb = y_previous;
} else {
- yb = &(y_regs[0]);
+ yb = get_global_reg(0);
}
} else {
- yb = &(y_regs[i]);
+ yb = get_global_reg(i);
}
get_clipboard(name, &yb, true);
@@ -5068,6 +5365,10 @@ static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must
static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous)
{
+ if (get_userreg(name) != -1) {
+ eval_yank_userreg(curbuf->b_p_urf, name, reg);
+ }
+
// Send text of clipboard register to the clipboard.
set_clipboard(name, reg);
@@ -6507,7 +6808,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
}
if (explicit_cb_reg) {
- target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
+ target = get_global_reg(*name == '*' ? STAR_REGISTER : PLUS_REGISTER);
if (writing && (cb_flags & (*name == '*' ? CB_UNNAMED : CB_UNNAMEDPLUS))) {
clipboard_needs_update = false;
}
@@ -6524,10 +6825,10 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
if (cb_flags & CB_UNNAMEDPLUS) {
*name = (cb_flags & CB_UNNAMED && writing) ? '"': '+';
- target = &y_regs[PLUS_REGISTER];
+ target = get_global_reg(PLUS_REGISTER);
} else {
*name = '*';
- target = &y_regs[STAR_REGISTER];
+ target = get_global_reg(STAR_REGISTER);
}
goto end;
}
@@ -6843,11 +7144,11 @@ static inline bool reg_empty(const yankreg_T *const reg)
/// Iterate over global registers.
///
/// @see op_register_iter
-const void *op_global_reg_iter(const void *const iter, char *const name, yankreg_T *const reg,
- bool *is_unnamed)
+iter_register_T op_global_reg_iter(iter_register_T iter, char *const name,
+ yankreg_T *const reg, bool *is_unnamed)
FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
{
- return op_reg_iter(iter, y_regs, name, reg, is_unnamed);
+ return op_reg_iter(iter, &y_regs, name, reg, is_unnamed);
}
/// Iterate over registers `regs`.
@@ -6859,30 +7160,31 @@ const void *op_global_reg_iter(const void *const iter, char *const name, yankreg
///
/// @return Pointer that must be passed to next `op_register_iter` call or
/// NULL if iteration is over.
-const void *op_reg_iter(const void *const iter, const yankreg_T *const regs, char *const name,
- yankreg_T *const reg, bool *is_unnamed)
+iter_register_T op_reg_iter(iter_register_T iter, yank_registers_T *regs,
+ char *const name, yankreg_T *const reg,
+ bool *is_unnamed)
FUNC_ATTR_NONNULL_ARG(3, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
*name = NUL;
- const yankreg_T *iter_reg = (iter == NULL
- ? &(regs[0])
- : (const yankreg_T *const)iter);
- while (iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
- iter_reg++;
- }
- if (iter_reg - &(regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
- return NULL;
- }
- int iter_off = (int)(iter_reg - &(regs[0]));
- *name = (char)get_register_name(iter_off);
- *reg = *iter_reg;
- *is_unnamed = (iter_reg == y_previous);
- while (++iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS) {
- if (!reg_empty(iter_reg)) {
- return (void *)iter_reg;
+ int iter_idx = (int)(iter == ITER_REGISTER_NULL ? 0 : iter - 1);
+
+ while (iter_idx < NUM_SAVED_REGISTERS && reg_empty(get_reg(regs, iter_idx)))
+ ++iter_idx;
+
+ if (iter_idx >= NUM_SAVED_REGISTERS || reg_empty(get_reg(regs, iter_idx)))
+ return ITER_REGISTER_NULL;
+
+ *reg = *get_reg(regs, iter_idx);
+ *name = (char)get_register_name((int)iter_idx);
+ *is_unnamed = (get_reg(regs, iter_idx) == y_previous);
+
+ while (++iter_idx < NUM_SAVED_REGISTERS) {
+ if (!reg_empty(get_reg(regs, iter_idx))) {
+ return (iter_register_T)(iter_idx + 1);
}
}
- return NULL;
+
+ return ITER_REGISTER_NULL;
}
/// Get a number of non-empty registers
@@ -6890,8 +7192,8 @@ size_t op_reg_amount(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t ret = 0;
- for (size_t i = 0; i < NUM_SAVED_REGISTERS; i++) {
- if (!reg_empty(y_regs + i)) {
+ for (int i = 0; i < NUM_SAVED_REGISTERS; i++) {
+ if (!reg_empty(get_global_reg(i))) {
ret++;
}
}
@@ -6911,11 +7213,11 @@ bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
if (i == -1) {
return false;
}
- free_register(&y_regs[i]);
- y_regs[i] = reg;
+ free_register(get_global_reg(i));
+ *get_global_reg(i) = reg;
if (is_unnamed) {
- y_previous = &y_regs[i];
+ y_previous = get_global_reg(i);
}
return true;
}
@@ -6931,7 +7233,7 @@ const yankreg_T *op_reg_get(const char name)
if (i == -1) {
return NULL;
}
- return &y_regs[i];
+ return get_global_reg(i);
}
/// Set the previous yank register
@@ -6946,7 +7248,7 @@ bool op_reg_set_previous(const char name)
return false;
}
- y_previous = &y_regs[i];
+ y_previous = get_global_reg(i);
return true;
}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 75ea1853a0..bffb692d00 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -25,6 +25,7 @@ typedef int (*Indenter)(void);
#define PUT_LINE_SPLIT 16 // split line for linewise register
#define PUT_LINE_FORWARD 32 // put linewise register below Visual sel.
#define PUT_BLOCK_INNER 64 // in block mode, do not add trailing spaces
+#define ITER_REGISTER_NULL 0
// Registers:
// 0 = register for latest (unnamed) yank
@@ -38,7 +39,8 @@ typedef int (*Indenter)(void);
// The following registers should not be saved in ShaDa file:
#define STAR_REGISTER 37
#define PLUS_REGISTER 38
-#define NUM_REGISTERS 39
+#define USER_REGISTERS_START 39
+#define NUM_REGISTERS USER_REGISTERS_START
// Operator IDs; The order must correspond to opchars[] in ops.c!
#define OP_NOP 0 // no pending operation
@@ -97,6 +99,8 @@ typedef enum {
YREG_YANK,
YREG_PUT,
} yreg_mode_t;
+/// Returns a reference to a user-defined register.
+int get_userreg(const int regname);
/// Convert register name into register index
///
@@ -119,10 +123,15 @@ static inline int op_reg_index(const int regname)
} else if (regname == '+') {
return PLUS_REGISTER;
} else {
- return -1;
+ return get_userreg(regname);
}
}
+struct yank_registers;
+typedef struct yank_registers yank_registers_T;
+
+typedef size_t iter_register_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
#endif
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 387b94533c..86840faa43 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3997,6 +3997,8 @@ static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return (char_u *)&(buf->b_p_cfu);
case PV_OFU:
return (char_u *)&(buf->b_p_ofu);
+ case PV_URF:
+ return (char_u *)&(buf->b_p_urf);
case PV_EOF:
return (char_u *)&(buf->b_p_eof);
case PV_EOL:
@@ -4077,6 +4079,8 @@ static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return (char_u *)&(buf->b_p_sw);
case PV_TFU:
return (char_u *)&(buf->b_p_tfu);
+ case PV_UMF:
+ return (char_u *)&(buf->b_p_umf);
case PV_TS:
return (char_u *)&(buf->b_p_ts);
case PV_TW:
@@ -4413,9 +4417,13 @@ void buf_copy_options(buf_T *buf, int flags)
set_buflocal_cfu_callback(buf);
buf->b_p_ofu = xstrdup(p_ofu);
COPY_OPT_SCTX(buf, BV_OFU);
+ buf->b_p_urf = xstrdup(p_urf);
+ COPY_OPT_SCTX(buf, BV_URF);
set_buflocal_ofu_callback(buf);
buf->b_p_tfu = xstrdup(p_tfu);
COPY_OPT_SCTX(buf, BV_TFU);
+ buf->b_p_umf = xstrdup(p_umf);
+ COPY_OPT_SCTX(buf, BV_UMF);
set_buflocal_tfu_callback(buf);
buf->b_p_sts = p_sts;
COPY_OPT_SCTX(buf, BV_STS);
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index d190fc5999..6fb4621033 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -398,6 +398,7 @@ EXTERN long p_channel; ///< 'channel'
EXTERN char *p_cink; ///< 'cinkeys'
EXTERN char *p_cinsd; ///< 'cinscopedecls'
EXTERN char *p_cinw; ///< 'cinwords'
+EXTERN char *p_umf; ///< 'usermarkfunc'
EXTERN char *p_cfu; ///< 'completefunc'
EXTERN char *p_ofu; ///< 'omnifunc'
EXTERN char *p_tsrfu; ///< 'thesaurusfunc'
@@ -816,6 +817,7 @@ EXTERN int p_wa; // 'writeany'
EXTERN int p_wb; // 'writebackup'
EXTERN long p_wd; // 'writedelay'
EXTERN int p_cdh; // 'cdhome'
+EXTERN char *p_urf; // 'userregister'
EXTERN int p_force_on; ///< options that cannot be turned off.
EXTERN int p_force_off; ///< options that cannot be turned on.
@@ -903,6 +905,7 @@ enum {
BV_SW,
BV_SWF,
BV_TFU,
+ BV_UMF,
BV_TSRFU,
BV_TAGS,
BV_TC,
@@ -914,6 +917,7 @@ enum {
BV_WM,
BV_VSTS,
BV_VTS,
+ BV_URF,
BV_COUNT, // must be the last one
};
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 387ccd0888..11d0a7e92c 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -2629,6 +2629,20 @@ return {
defaults={if_true=4000}
},
{
+ full_name='usermarkfunc', abbreviation='umf',
+ short_desc=N_("function used to perform jumps/sets to user marks."),
+ type='string', scope={'buffer'},
+ varname='p_umf',
+ defaults={if_true=""},
+ },
+ {
+ full_name='userregfunc', abbreviation='urf',
+ short_desc=N_("Function used to define behavior of user-defined registers."),
+ type='string', scope={'buffer'},
+ varname='p_urf',
+ defaults={if_true=""}
+ },
+ {
full_name='varsofttabstop', abbreviation='vsts',
short_desc=N_("list of numbers of spaces that <Tab> uses while editing"),
type='string', list='comma', scope={'buffer'},
diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po
index c55918abc9..80f558aba3 100644
--- a/src/nvim/po/da.po
+++ b/src/nvim/po/da.po
@@ -4049,7 +4049,7 @@ msgid "freeing %ld lines"
msgstr "frigør %ld linjer"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " i \"%c"
#, c-format
diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po
index f4fce68ac5..b8c61a627e 100644
--- a/src/nvim/po/fr.po
+++ b/src/nvim/po/fr.po
@@ -4313,7 +4313,7 @@ msgid "freeing %ld lines"
msgstr "libration de %ld lignes"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " dans \"%c"
#, c-format
diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po
index eb7879efc4..5e049127ba 100644
--- a/src/nvim/po/tr.po
+++ b/src/nvim/po/tr.po
@@ -3960,7 +3960,7 @@ msgid "E748: No previously used register"
msgstr "E748: Daha önce kullanılan bir yazmaç yok"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " \"%c"
#, c-format
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 06f845f113..785b3909bb 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -4039,7 +4039,7 @@ msgid "E748: No previously used register"
msgstr "E748: Регістри перед цим не вживались"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " у \"%c"
#, c-format
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 05da6e0ef1..f35912849d 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -677,9 +677,9 @@ static void recording_mode(int attr)
return;
}
- char s[4];
- snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
- msg_puts_attr(s, attr);
+ char s[7] = { ' ', '@', 0, 0, 0, 0, 0 };
+ utf_char2bytes(reg_recording, s + 2);
+ msg_puts_attr(s, attr);
}
void get_trans_bufname(buf_T *buf)
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 90a01aaf97..c16f3debf9 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -2395,7 +2395,7 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse,
static inline void shada_initialize_registers(WriteMergerState *const wms, int max_reg_lines)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
{
- const void *reg_iter = NULL;
+ iter_register_T reg_iter = ITER_REGISTER_NULL;
const bool limit_reg_lines = max_reg_lines >= 0;
do {
yankreg_T reg;
@@ -2426,7 +2426,7 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, int m
}
}
};
- } while (reg_iter != NULL);
+ } while (reg_iter != ITER_REGISTER_NULL);
}
/// Replace numbered mark in WriteMergerState
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 9ba5f81776..a25cb5b76e 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -35,6 +35,8 @@
# include "state.c.generated.h" // IWYU pragma: export
#endif
+size_t hack_keyfix = 0;
+
void state_enter(VimState *s)
{
for (;;) {
@@ -85,6 +87,18 @@ getkey:
}
}
+ // Hacky "fix" for https://github.com/neovim/neovim/issues/20726
+ if (key == 3) {
+ hack_keyfix ++;
+ if (hack_keyfix == 100) {
+ got_int = 0;
+ hack_keyfix = 0;
+ goto getkey;
+ }
+ } else {
+ hack_keyfix = 0;
+ }
+
if (key == K_EVENT) {
// An event handler may use the value of reg_executing.
// Clear it if it should be cleared when getting the next character.
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 6b40ccdb84..4448b72ac0 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -5064,6 +5064,17 @@ void free_wininfo(wininfo_T *wip, buf_T *bp)
xfree(wip);
}
+// Free colorcolumns. Has syntax names.
+static void free_wp_cc_cols(colorcol_T* cc)
+{
+ if (cc) {
+ for (int i = 0; cc[i].col >= 0; i ++) {
+ xfree(cc[i].syn_name);
+ }
+ }
+ xfree(cc);
+}
+
/// Remove window 'wp' from the window list and free the structure.
///
/// @param tp tab page "win" is in, NULL for current
@@ -5157,7 +5168,7 @@ static void win_free(win_T *wp, tabpage_T *tp)
qf_free_all(wp);
- xfree(wp->w_p_cc_cols);
+ free_wp_cc_cols(wp->w_p_cc_cols);
win_free_grid(wp, false);
@@ -7399,10 +7410,21 @@ static bool frame_check_width(const frame_T *topfrp, int width)
return true;
}
-/// Simple int comparison function for use with qsort()
-static int int_cmp(const void *a, const void *b)
+/// Simple colorcol_T comparison function for use with qsort().
+/// Compares the column numbers
+static int colorcol_cmp(const void *a, const void *b)
{
- return *(const int *)a - *(const int *)b;
+ colorcol_T *ac = (colorcol_T*) a;
+ colorcol_T *bc = (colorcol_T*) b;
+ int ret = ac->col - bc->col;
+ if (ret == 0) {
+ // qsort() is not inherently stable, so to make it stable,
+ // the syn_attr field temporarily contains the original index.
+ // Comparing these will enforce stability.
+ return ac->syn_attr - bc->syn_attr;
+ } else {
+ return ret;
+ }
}
/// Handle setting 'colorcolumn' or 'textwidth' in window "wp".
@@ -7415,9 +7437,16 @@ char *check_colorcolumn(win_T *wp)
}
unsigned int count = 0;
- int color_cols[256];
+ colorcol_T color_cols[256];
+ bool do_skip = false;
+
for (char *s = wp->w_p_cc; *s != NUL && count < 255;) {
int col;
+ int ch = ' ';
+ char syn_name[256];
+ int flags = 0;
+ syn_name[0] = 0;
+
if (*s == '-' || *s == '+') {
// -N and +N: add to 'textwidth'
col = (*s == '-') ? -1 : 1;
@@ -7427,7 +7456,7 @@ char *check_colorcolumn(win_T *wp)
}
col = col * getdigits_int(&s, true, 0);
if (wp->w_buffer->b_p_tw == 0) {
- goto skip; // 'textwidth' not set, skip this item
+ do_skip = true; // 'textwidth' not set, skip this item
}
assert((col >= 0
&& wp->w_buffer->b_p_tw <= INT_MAX - col
@@ -7437,15 +7466,63 @@ char *check_colorcolumn(win_T *wp)
&& wp->w_buffer->b_p_tw + col <= INT_MAX));
col += (int)wp->w_buffer->b_p_tw;
if (col < 0) {
- goto skip;
+ do_skip = true;
}
} else if (ascii_isdigit(*s)) {
col = getdigits_int(&s, true, 0);
} else {
return e_invarg;
}
- color_cols[count++] = col - 1; // 1-based to 0-based
-skip:
+
+ // Parse the character.
+ if (*s == '/') {
+ s ++;
+ ch = mb_ptr2char_adv((const char**) &s);
+ if (!ch) {
+ return e_invarg;
+ }
+ }
+
+ // Parse the highlight group.
+ if (*s == '/') {
+ s ++;
+ size_t i = 0;
+ while(i < sizeof(syn_name) && *s && *s != '/' && *s != ',') {
+ syn_name[i ++] = *(s++);
+ }
+ syn_name[i] = 0;
+ }
+
+ // Parse extra flags
+ if (*s == '/') {
+ s ++;
+ while (*s != ',' && *s) {
+ switch (*(s ++)) {
+ case 'b':
+ flags |= kColorcolBehind;
+ break;
+
+ case 'f':
+ flags |= kColorcolForeground;
+ break;
+
+ default:
+ return e_invarg;
+ }
+ }
+ }
+
+ if (!do_skip) {
+ color_cols[count] = (colorcol_T) {
+ .col = col - 1, // 1-based to 0-based
+ .syn_attr = (int) count, // Temporarily use this for stable sorting.
+ .ch = ch,
+ .syn_name = syn_name[0] == 0 ? NULL : xstrdup(syn_name),
+ .flags = flags,
+ };
+ count ++;
+ }
+
if (*s == NUL) {
break;
}
@@ -7457,23 +7534,26 @@ skip:
}
}
- xfree(wp->w_p_cc_cols);
+ free_wp_cc_cols(wp->w_p_cc_cols);
if (count == 0) {
wp->w_p_cc_cols = NULL;
} else {
- wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1));
+ wp->w_p_cc_cols = xmalloc(sizeof(colorcol_T) * (count + 1));
// sort the columns for faster usage on screen redraw inside
// win_line()
- qsort(color_cols, count, sizeof(int), int_cmp);
+ qsort(color_cols, count, sizeof(colorcol_T), colorcol_cmp);
int j = 0;
for (unsigned int i = 0; i < count; i++) {
// skip duplicates
- if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) {
- wp->w_p_cc_cols[j++] = color_cols[i];
+ if (j == 0 || wp->w_p_cc_cols[j - 1].col != color_cols[i].col) {
+ wp->w_p_cc_cols[j] = color_cols[i];
+ // Clear syn_attr, which was used for stable sorting.
+ wp->w_p_cc_cols[j ++].syn_attr = 0;
}
}
- wp->w_p_cc_cols[j] = -1; // end marker
+ memset(&wp->w_p_cc_cols[j], 0, sizeof(wp->w_p_cc_cols[j]));
+ wp->w_p_cc_cols[j].col = -1; // end marker
}
return NULL; // no error
diff --git a/src/nvim/yankmap.c b/src/nvim/yankmap.c
new file mode 100644
index 0000000000..d9229e015d
--- /dev/null
+++ b/src/nvim/yankmap.c
@@ -0,0 +1,42 @@
+#include "nvim/yankmap.h"
+
+#include "nvim/memory.h"
+
+void init_yankmap(yankmap_T* map)
+{
+ memset(map, 0, sizeof(yankmap_T));
+
+ map_init(int, ptr_t, &map->reg_to_yankreg);
+ map_init(ptr_t, int, &map->yankreg_to_reg);
+}
+
+yankreg_T* yankmap_get(yankmap_T* yankmap, int reg)
+{
+ yankreg_T** ret =
+ (yankreg_T**) map_ref(int, ptr_t)(&yankmap->reg_to_yankreg, reg, true);
+
+ if (ret) {
+ if (! (*ret)) {
+ *ret = xcalloc(sizeof(yankreg_T), 1);
+ }
+
+ /* Add the back-reference */
+ int* ref = map_ref(ptr_t, int)(&yankmap->yankreg_to_reg, *ret, true);
+ *ref = reg;
+
+ return *ret;
+ }
+
+ return NULL;
+}
+
+int yankmap_find(yankmap_T* yankmap, yankreg_T* yankreg)
+{
+ int* ref = map_ref(ptr_t, int)(&yankmap->yankreg_to_reg, yankreg, false);
+
+ if (ref) {
+ return *ref;
+ }
+
+ return -1;
+}
diff --git a/src/nvim/yankmap.h b/src/nvim/yankmap.h
new file mode 100644
index 0000000000..da7c4dcf13
--- /dev/null
+++ b/src/nvim/yankmap.h
@@ -0,0 +1,24 @@
+#ifndef YANK_TRIE_H_
+#define YANK_TRIE_H_
+
+#include <stdbool.h>
+#include "nvim/ops.h"
+#include "nvim/map.h"
+
+typedef struct {
+ /* Register name to yank register. */
+ Map(int, ptr_t) reg_to_yankreg;
+
+ /* Yank register to register name. */
+ Map(ptr_t, int) yankreg_to_reg;
+} yankmap_T;
+
+void init_yankmap(yankmap_T* yankmap);
+
+yankreg_T* yankmap_get(yankmap_T* yankmap, int index);
+
+yankreg_T* yankmap_put(yankmap_T* yankmap, int index);
+
+int yankmap_find(yankmap_T* yankmap, yankreg_T* yankreg);
+
+#endif
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index 288c2a214f..61abe341c7 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -859,13 +859,14 @@ describe('CursorLine and CursorLineNr highlights', function()
|
]])
+ command('set fillchars=colorcol:.')
command('set colorcolumn=3')
feed('i <esc>')
screen:expect([[
- {1:{} {7: } |
+ {1:{} {7:.} |
"{2:a}{7:"} : {3:abc} {3:// 10;} |
- {1:}} {7: } |
- {5: ^ }{7: }{5: }|
+ {1:}} {7:.} |
+ {5: ^ }{7:.}{5: }|
|
]])
end)