aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-01-14 23:19:41 -0700
committerJosh Rahm <joshuarahm@gmail.com>2023-01-14 23:19:41 -0700
commit3e0218e7a0c47e2ce0aad11b635944cdd9b75c65 (patch)
tree9f3367296555c22f355bf5250b0d7d1924cec5f7
parent7a6e249a32f79331bfb5e29bc0cabcf89a026081 (diff)
parent396c48d54ef313ca02e2e97849e51721094400cd (diff)
downloadrneovim-3e0218e7a0c47e2ce0aad11b635944cdd9b75c65.tar.gz
rneovim-3e0218e7a0c47e2ce0aad11b635944cdd9b75c65.tar.bz2
rneovim-3e0218e7a0c47e2ce0aad11b635944cdd9b75c65.zip
Merge branch 'userreg' into HEAD
-rw-r--r--runtime/autoload/userreg.vim7
-rw-r--r--runtime/doc/options.txt51
-rw-r--r--runtime/lua/vim/userreg.lua51
-rw-r--r--runtime/plugin/userreg.vim1
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/eval.c10
-rw-r--r--src/nvim/eval/funcs.c1
-rw-r--r--src/nvim/eval/vars.c14
-rw-r--r--src/nvim/map.c1
-rw-r--r--src/nvim/map.h1
-rw-r--r--src/nvim/ops.c408
-rw-r--r--src/nvim/ops.h13
-rw-r--r--src/nvim/option.c4
-rw-r--r--src/nvim/option_defs.h2
-rw-r--r--src/nvim/options.lua7
-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.c4
-rw-r--r--src/nvim/shada.c4
-rw-r--r--src/nvim/yankmap.c42
-rw-r--r--src/nvim/yankmap.h24
23 files changed, 580 insertions, 74 deletions
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 4498dda300..0571b6589f 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -6817,6 +6817,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/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/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 de0d969f3b..e2c93f481d 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -664,6 +664,7 @@ struct file_buffer {
char *b_p_tfu; ///< 'tagfunc'
Callback b_tfu_cb; ///< 'tagfunc' callback
char *b_p_umf; ///< 'usermarkfunc'
+ char *b_p_urf; ///< 'userregfunc'
int b_p_eof; ///< 'endoffile'
int b_p_eol; ///< 'endofline'
int b_p_fixeol; ///< 'fixendofline'
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index f9825496a5..af52e87728 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3060,12 +3060,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_u**) 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;
@@ -4173,7 +4171,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;
@@ -4182,7 +4180,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 d6b9ca20fc..0a02ba5a03 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3152,6 +3152,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"winaltkeys",
"writebackup",
"nvim",
+ "userreg",
};
// XXX: eval_has_provider() may shell out :(
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 206df03381..ab3bc2b2c3 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("+-*/%", *op) != NULL) {
semsg(_(e_letwrong), op);
} else if (endchars != NULL
- && vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) {
+ && vim_strchr(endchars, *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,8 @@ 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/map.c b/src/nvim/map.c
index 24066659a8..b94e0dd8c6 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -162,6 +162,7 @@ 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)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 6a548ad30b..9b91d3c7a9 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -41,6 +41,7 @@
//
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/ops.c b/src/nvim/ops.c
index 34432c58db..cfe23d2ce9 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -25,6 +25,8 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
@@ -63,8 +65,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 +800,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 +834,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((char *) curbuf->b_p_urf) == 0)
+ return;
+
+
+ typval_T* ret = xmalloc(sizeof(typval_T));
+
+ if (call_userreg_put((const char*) 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 +1017,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 +1089,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 +1114,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 +1192,9 @@ static int stuff_yank(int regname, char *p)
reg->y_type = kMTCharWise;
}
reg->timestamp = os_time();
+ if (get_userreg(regname) != -1) {
+ eval_yank_userreg(curbuf->b_p_urf, regname, reg);
+ }
return OK;
}
@@ -1306,6 +1489,89 @@ 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((ptrdiff_t) yankreg->y_size);
+
+ size_t i;
+ for (i = 0; i < yankreg->y_size; ++ i) {
+ tv_list_append_string(
+ lines, yankreg->y_array[i], (ssize_t) 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 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);
+
+ char *dup_ufn = strdup((char *)ufn);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+
+ typval_T out;
+ call_func(
+ dup_ufn,
+ -1,
+ &out,
+ /* argcount_in = */ 3,
+ args,
+ &funcexe);
+ xfree(dup_ufn);
+ return (int) out.vval.v_number;
+}
+
/// If "regname" is a special register, return true and store a pointer to its
/// value in "argp".
///
@@ -1392,6 +1658,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;
@@ -1438,14 +1707,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.
@@ -1541,7 +1810,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;
}
@@ -2553,13 +2822,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));
}
}
@@ -2603,6 +2881,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);
@@ -2783,7 +3069,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
@@ -2853,6 +3143,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.
@@ -2874,13 +3165,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.
@@ -3095,6 +3388,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;
@@ -3757,7 +4054,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.
@@ -3796,10 +4093,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);
@@ -5006,6 +5303,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);
@@ -6445,7 +6746,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;
}
@@ -6462,10 +6763,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;
}
@@ -6781,11 +7082,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`.
@@ -6797,30 +7098,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
@@ -6828,8 +7130,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++;
}
}
@@ -6849,11 +7151,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;
}
@@ -6869,7 +7171,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
@@ -6884,7 +7186,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 c44ed036f8..97a1c04572 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3920,6 +3920,8 @@ static char_u *get_varp(vimoption_T *p)
return (char_u *)&(curbuf->b_p_cfu);
case PV_OFU:
return (char_u *)&(curbuf->b_p_ofu);
+ case PV_URF:
+ return (char_u *)&(curbuf->b_p_urf);
case PV_EOF:
return (char_u *)&(curbuf->b_p_eof);
case PV_EOL:
@@ -4332,6 +4334,8 @@ 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);
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 8ad1b8b2d7..32733d0c4b 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -816,6 +816,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; // 'userregfunc'
EXTERN int p_force_on; ///< options that cannot be turned off.
EXTERN int p_force_off; ///< options that cannot be turned on.
@@ -915,6 +916,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 bfbfdd938e..7d2f9075f6 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -2636,6 +2636,13 @@ return {
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 7d6af4d2cf..f011c29b11 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 b521e37177..05802f387e 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 b18bf7ed6a..da34cced31 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -672,8 +672,8 @@ static void recording_mode(int attr)
{
msg_puts_attr(_("recording"), attr);
if (!shortmess(SHM_RECORDING)) {
- char s[4];
- snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
+ char s[7] = { ' ', '@', 0, 0, 0, 0, 0 };
+ utf_char2bytes(reg_recording, s + 2);
msg_puts_attr(s, attr);
}
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 754139a147..02538d2dd2 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -2396,7 +2396,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;
@@ -2427,7 +2427,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/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