aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/lua/vim/userregs.lua64
-rw-r--r--runtime/plugin/userregs.vim2
-rw-r--r--src/nvim/api/command.c10
-rw-r--r--src/nvim/drawscreen.c5
-rw-r--r--src/nvim/eval.c13
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/eval/vars.c13
-rw-r--r--src/nvim/ops.c322
-rw-r--r--src/nvim/ops.h10
-rw-r--r--src/nvim/option_vars.h1
-rw-r--r--src/nvim/options.lua61
-rw-r--r--src/nvim/viml/parser/expressions.c4
12 files changed, 456 insertions, 51 deletions
diff --git a/runtime/lua/vim/userregs.lua b/runtime/lua/vim/userregs.lua
new file mode 100644
index 0000000000..d87dcfefa9
--- /dev/null
+++ b/runtime/lua/vim/userregs.lua
@@ -0,0 +1,64 @@
+local M = {}
+
+-- Table mapping register names (strings) to handler objects
+-- Each handler must implement `yank(regname, contents)` and/or `put(regname)`
+M.handlers = {}
+
+-- Default handler: stores register content in memory
+local default_handler = {
+ registers = {}
+}
+
+--- Called when a register is yanked into (i.e., written)
+---@param regname string
+---@param contents table
+function default_handler.yank(regname, contents)
+ default_handler.registers[regname] = contents
+end
+
+--- Called when a register is put from (i.e., read)
+---@param regname string
+---@return string|table
+function default_handler.put(regname)
+ return default_handler.registers[regname] or ""
+end
+
+--- Register a handler function for one or more register names.
+--- @param register_names string|string[] A register or list of registers
+--- @param handler table A table with `yank()` and/or `put()` methods
+function M.register_handler(register_names, handler)
+ if type(register_names) == "string" then
+ M.handlers[register_names] = handler
+ elseif type(register_names) == "table" then
+ for _, reg in ipairs(register_names) do
+ M.handlers[reg] = handler
+ end
+ else
+ error("register_names must be a string or table of strings")
+ end
+end
+
+--- This is the function Neovim will call via 'userregfunc'.
+--- It dispatches based on the action and register name.
+---@param action string "yank" or "put"
+---@param regname string
+---@param contents any Only passed when action is "yank"
+---@return any Only returned when action is "put"
+function M.fn(action, regname, contents)
+ local handler = M.handlers[regname] or default_handler
+ if action == "yank" and handler.yank then
+ handler.yank(regname, contents)
+ elseif action == "put" and handler.put then
+ return handler.put(regname)
+ else
+ vim.notify(
+ ("[userregs] No valid handler for action %q on register %q"):format(action, regname),
+ vim.log.levels.WARN
+ )
+ end
+end
+
+-- Expose the function to Neovim via 'set userregfunc=v:lua.def_userreg_func'
+_G.def_userreg_func = M.fn
+
+return M
diff --git a/runtime/plugin/userregs.vim b/runtime/plugin/userregs.vim
new file mode 100644
index 0000000000..c61923334d
--- /dev/null
+++ b/runtime/plugin/userregs.vim
@@ -0,0 +1,2 @@
+lua require('vim.userregs')
+set userregfunc=v:lua.def_userreg_func
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index 4b93f09c61..b8892c9baa 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -496,18 +496,14 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
if (HAS_KEY(cmd, cmd, reg)) {
VALIDATE_MOD((ea.argt & EX_REGSTR), "register", cmd->cmd.data);
- VALIDATE_EXP((cmd->reg.size == 1),
- "reg", "single character", cmd->reg.data, {
- goto end;
- });
- char regname = cmd->reg.data[0];
+ int regname = utf_ptr2char(cmd->reg.data);
VALIDATE((regname != '='), "%s", "Cannot use register \"=", {
goto end;
});
VALIDATE(valid_yank_reg(regname,
(!IS_USER_CMDIDX(ea.cmdidx)
&& ea.cmdidx != CMD_put && ea.cmdidx != CMD_iput)),
- "Invalid register: \"%c", regname, {
+ "Invalid register: \"%s", cmd->reg.data, {
goto end;
});
ea.regname = (uint8_t)regname;
@@ -794,7 +790,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
// Command register.
if (eap->argt & EX_REGSTR && eap->regname) {
- kv_printf(cmdline, " %c", eap->regname);
+ kv_printf(cmdline, " %s", reg_to_mb(eap->regname));
}
eap->argc = argc;
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index bf2bc077e0..fcb11fd281 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -96,6 +96,7 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/normal_defs.h"
+#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/os/os_defs.h"
@@ -1184,8 +1185,8 @@ static void recording_mode(int hl_id)
}
msg_puts_hl(_("recording"), hl_id, false);
- char s[4];
- snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
+ char s[4 + MB_MAXBYTES];
+ snprintf(s, ARRAY_SIZE(s), " @%s", reg_to_mb(reg_recording));
msg_puts_hl(s, hl_id, false);
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 3f73074333..370565c441 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3446,12 +3446,10 @@ static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan
// 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;
@@ -7073,16 +7071,17 @@ void set_argv_var(char **argv, int argc)
/// Set v:register if needed.
void set_reg_var(int c)
{
- char regname;
+ int regname;
if (c == 0 || c == ' ') {
regname = '"';
} else {
- regname = (char)c;
+ regname = c;
}
+
// Avoid free/alloc when the value is already right.
if (vimvars[VV_REG].vv_str == NULL || vimvars[VV_REG].vv_str[0] != c) {
- set_vim_var_string(VV_REG, &regname, 1);
+ set_vim_var_string(VV_REG, reg_to_mb(regname), -1);
}
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 25252cdfde..8ed73947e7 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -2858,7 +2858,7 @@ static int getreg_get_regname(typval_T *argvars)
strregname = get_vim_var_str(VV_REG);
}
- return *strregname == 0 ? '"' : (uint8_t)(*strregname);
+ return *strregname == 0 ? '"' : utf_ptr2char(strregname);
}
/// "getreg()" function
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 012d23b567..8d637fcc43 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -34,6 +34,7 @@
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/macros_defs.h"
+#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/ops.h"
@@ -599,7 +600,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon, bool
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 find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
@@ -908,16 +909,18 @@ static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
char *arg_end = 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 *ptofree = NULL;
const char *p = tv_get_string_chk(tv);
if (p != NULL && op != NULL && *op == '.') {
- char *s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
+ char *s = get_reg_contents(regname == '@' ? '"' : regname, kGRegExprSrc);
if (s != NULL) {
ptofree = concat_str(s, p);
p = ptofree;
@@ -925,8 +928,8 @@ static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
}
}
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/ops.c b/src/nvim/ops.c
index 977d26890e..e89b38296c 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -29,6 +29,7 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
@@ -80,6 +81,7 @@
static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
static yankreg_T *y_previous = NULL; // ptr to last written yankreg
+static int last_userreg_name = -1;
// for behavior between start_batch_changes() and end_batch_changes())
static int batch_change_count = 0; // inside a script
@@ -134,6 +136,15 @@ static char opchars[][3] = {
{ Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
+static char regstr_mb[MB_MAXBYTES + 1];
+/// Converts a register to a multibyte-character "string". Returns a pointer to a static buffer, so
+/// this should be used quickly.
+const char* reg_to_mb(int reg)
+{
+ regstr_mb[utf_char2bytes(reg, regstr_mb)] = 0;
+ return regstr_mb;
+}
+
yankreg_T *get_y_previous(void)
{
return y_previous;
@@ -875,17 +886,11 @@ char *get_expr_line_src(void)
/// @param writing allow only writable registers
bool valid_yank_reg(int regname, bool writing)
{
- if ((regname > 0 && ASCII_ISALNUM(regname))
- || (!writing && vim_strchr("/.%:=", regname) != NULL)
- || regname == '#'
- || regname == '"'
- || regname == '-'
- || regname == '_'
- || regname == '*'
- || regname == '+') {
- return true;
+ if (writing && vim_strchr("/.%:=", regname) != NULL) {
+ return false;
}
- return false;
+
+ return true;
}
/// @return yankreg_T to use, according to the value of `regname`.
@@ -909,6 +914,8 @@ bool valid_yank_reg(int regname, bool writing)
yankreg_T *get_yank_register(int regname, int mode)
{
yankreg_T *reg;
+ int do_eval = !(mode & YREG_NOEVAL);
+ mode &= ~YREG_NOEVAL;
if ((mode == YREG_PASTE || mode == YREG_PUT)
&& get_clipboard(regname, &reg, false)) {
@@ -923,16 +930,26 @@ yankreg_T *get_yank_register(int regname, int mode)
&& (regname == 0 || regname == '"' || regname == '*' || regname == '+')
&& y_previous != NULL) {
// in case clipboard not available, paste from previous used register
+ if (do_eval && is_yankreg_user_register(y_previous)) {
+ read_userregister(last_userreg_name, y_previous);
+ }
return y_previous;
}
int i = op_reg_index(regname);
- // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
if (i == -1) {
i = 0;
}
+
reg = &y_regs[i];
+ if (i == USER_REGISTER) {
+ last_userreg_name = regname;
+ if (do_eval && (mode == YREG_PUT || mode == YREG_PASTE)) {
+ read_userregister(regname, reg);
+ }
+ }
+
if (mode == YREG_YANK) {
// remember the written register for unnamed paste
y_previous = reg;
@@ -945,6 +962,14 @@ static bool is_append_register(int regname)
return ASCII_ISUPPER(regname);
}
+static bool is_user_register(int regname) {
+ return op_reg_index(regname) == USER_REGISTER;
+}
+
+static bool is_yankreg_user_register(yankreg_T* yankreg) {
+ return yankreg == &y_regs[USER_REGISTER];
+}
+
/// @return a copy of contents in register `name` for use in do_put. Should be
/// freed by caller.
yankreg_T *copy_register(int name)
@@ -988,8 +1013,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 (!valid_yank_reg(c, true)) {
retval = FAIL;
} else {
reg_recording = c;
@@ -1038,10 +1062,12 @@ int do_record(int c)
// We don't want to change the default register here, so save and
// restore the current register name.
yankreg_T *old_y_previous = y_previous;
+ int old_last_userreg_name = last_userreg_name;
retval = stuff_yank(regname, p);
y_previous = old_y_previous;
+ last_userreg_name = old_last_userreg_name;
}
}
return retval;
@@ -1082,6 +1108,10 @@ static int stuff_yank(int regname, char *p)
reg->y_array[0] = cbuf_as_string(p, plen);
reg->y_size = 1;
reg->y_type = kMTCharWise;
+
+ if (is_user_register(regname)) {
+ write_userregister(regname, reg);
+ }
}
reg->timestamp = os_time();
return OK;
@@ -2767,6 +2797,10 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
xfree(reg->y_array);
}
+ if (is_user_register(oap->regname)) {
+ write_userregister(oap->regname, curr);
+ }
+
if (message) { // Display message about yank?
if (yank_type == kMTCharWise && yanklines == 1) {
yanklines = 0;
@@ -2778,7 +2812,7 @@ 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);
+ vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%s"), reg_to_mb(oap->regname));
}
// redisplay now, so message is not deleted
@@ -5033,7 +5067,8 @@ void *get_reg_contents(int regname, int flags)
return retval;
}
-static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must_append)
+static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, int *old_last_userreg_name,
+ bool must_append)
{
if (!valid_yank_reg(name, true)) { // check for valid reg name
emsg_invreg(name);
@@ -5042,6 +5077,7 @@ static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must
// Don't want to change the current (unnamed) register.
*old_y_previous = y_previous;
+ *old_last_userreg_name = last_userreg_name;
yankreg_T *reg = get_yank_register(name, YREG_YANK);
if (!is_append_register(name) && !must_append) {
@@ -5050,7 +5086,7 @@ static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must
return reg;
}
-static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous)
+static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous, int old_last_userreg_name)
{
// Send text of clipboard register to the clipboard.
set_clipboard(name, reg);
@@ -5058,6 +5094,11 @@ static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous
// ':let @" = "val"' should change the meaning of the "" register
if (name != '"') {
y_previous = old_y_previous;
+ last_userreg_name = old_last_userreg_name;
+ }
+
+ if (is_user_register(name)) {
+ write_userregister(name, reg);
}
}
@@ -5090,13 +5131,14 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy
}
yankreg_T *old_y_previous, *reg;
- if (!(reg = init_write_reg(name, &old_y_previous, must_append))) {
+ int old_last_userreg_name;
+ if (!(reg = init_write_reg(name, &old_y_previous, &old_last_userreg_name, must_append))) {
return;
}
str_to_reg(reg, yank_type, (char *)strings, strlen((char *)strings),
block_len, true);
- finish_write_reg(name, reg, old_y_previous);
+ finish_write_reg(name, reg, old_y_previous, old_last_userreg_name);
}
/// write_reg_contents_ex - store `str` in register `name`
@@ -5178,11 +5220,12 @@ void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_app
}
yankreg_T *old_y_previous, *reg;
- if (!(reg = init_write_reg(name, &old_y_previous, must_append))) {
+ int old_last_userreg_name;
+ if (!(reg = init_write_reg(name, &old_y_previous, &old_last_userreg_name, must_append))) {
return;
}
str_to_reg(reg, yank_type, str, (size_t)len, block_len, false);
- finish_write_reg(name, reg, old_y_previous);
+ finish_write_reg(name, reg, old_y_previous, old_last_userreg_name);
}
/// str_to_reg - Put a string into a register.
@@ -5683,6 +5726,15 @@ const char *did_set_operatorfunc(optset_T *args FUNC_ATTR_UNUSED)
return NULL;
}
+static Callback urf_cb;
+const char *did_set_userregfunc(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (option_set_callback_func(p_urf, &urf_cb) == FAIL) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
#if defined(EXITFREE)
void free_operatorfunc_option(void)
{
@@ -6779,6 +6831,221 @@ static void set_clipboard(int name, yankreg_T *reg)
eval_call_provider("clipboard", "set", args, true);
}
+dict_T *yankreg_to_dict(const yankreg_T *reg)
+{
+ if (reg == NULL) {
+ return NULL;
+ }
+
+ dict_T *d = tv_dict_alloc();
+
+ // Add "lines" field
+ list_T *l = tv_list_alloc(reg->y_size);
+ for (size_t i = 0; i < reg->y_size; ++i) {
+ String str = reg->y_array[i];
+ tv_list_append_string(l, str.data, str.size);
+ }
+ tv_dict_add_list(d, S_LEN("lines"), l);
+
+ // Add "type" field
+ const char *type_str = NULL;
+ switch (reg->y_type) {
+ case kMTCharWise:
+ type_str = "v";
+ break;
+ case kMTLineWise:
+ type_str = "V";
+ break;
+ case kMTBlockWise:
+ type_str = "b";
+ break;
+ default:
+ type_str = "v";
+ break;
+ }
+ tv_dict_add_str(d, S_LEN("type"), xstrdup(type_str));
+
+ // Add "width" field for blockwise (optional)
+ if (reg->y_type == kMTBlockWise) {
+ tv_dict_add_nr(d, S_LEN("width"), reg->y_width);
+ }
+
+ // Add "additional_data" fvield (empty by default)
+ tv_dict_add_dict(d, S_LEN("additional_data"), tv_dict_alloc());
+
+ return d;
+}
+
+int dict_to_yankreg(dict_T *dict, yankreg_T *out)
+{
+ if (dict == NULL || out == NULL) {
+ return 1;
+ }
+
+ dictitem_T *di_lines = tv_dict_find(dict, "lines", -1);
+ if (di_lines == NULL || di_lines->di_tv.v_type != VAR_LIST) {
+ return 1;
+ }
+
+ list_T *lines = di_lines->di_tv.vval.v_list;
+ if (lines == NULL) {
+ return 1;
+ }
+
+ free_register(out);
+ out->y_size = tv_list_len(lines);
+ out->y_array = xcalloc(out->y_size, sizeof(String));
+
+ int i = 0;
+ TV_LIST_ITER_CONST(lines, li, {
+ const typval_T *tv = TV_LIST_ITEM_TV(li);
+ const char *s = tv_get_string(tv);
+ if (s == NULL) {
+ s = "";
+ }
+
+ size_t len = strlen(s);
+ char* p = xstrnsave(s, len);
+ out->y_array[i++] = cbuf_to_string(p, len);
+ });
+
+ // Get "type" field
+ dictitem_T *di_type = tv_dict_find(dict, "type", -1);
+ if (di_type == NULL || di_type->di_tv.v_type != VAR_STRING) {
+ return 1;
+ }
+
+ char *type = di_type->di_tv.vval.v_string;
+ if (type == NULL || strcmp(type, "v") == 0) {
+ out->y_type = kMTCharWise;
+ } else if (strcmp(type, "V") == 0) {
+ out->y_type = kMTLineWise;
+ } else if (strcmp(type, "b") == 0) {
+ out->y_type = kMTBlockWise;
+ } else {
+ return 1;
+ }
+
+ // Get optional "width" field
+ dictitem_T *di_width = tv_dict_find(dict, "width", -1);
+ if (di_width != NULL && di_width->di_tv.v_type == VAR_NUMBER) {
+ out->y_width = (int)di_width->di_tv.vval.v_number;
+ } else {
+ out->y_width = 0;
+ }
+
+ // Additional data is optional, no-op for now
+ out->additional_data = NULL;
+
+ return true;
+}
+
+int read_userregister(int regname, yankreg_T *out)
+{
+ if (out == NULL || p_urf == NULL || *p_urf == NUL) {
+ return 1;
+ }
+
+ // Clear output
+ memset(out, 0, sizeof(*out));
+
+ typval_T rettv;
+ typval_T argv[3];
+
+ // argv[0] = "put"
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = "put";
+
+ // argv[1] = register name (UTF-8 string)
+ char regstr[MB_MAXBYTES + 1];
+ int len = utf_char2bytes(regname, (char *)regstr);
+ regstr[len] = '\0';
+
+ argv[1].v_type = VAR_STRING;
+ argv[1].vval.v_string = regstr;
+
+ // argv[2] = v:null
+ argv[2].v_type = VAR_SPECIAL;
+ argv[2].vval.v_special = kSpecialVarNull;
+
+ // Call user function
+ if (callback_call(&urf_cb, 3, argv, &rettv) == FAIL) {
+ tv_clear(&rettv);
+ return 1;
+ }
+
+ // Return is a string. Make the register a "charwise" register.
+ if (rettv.v_type == VAR_STRING) {
+ out->y_type = kMTCharWise;
+ out->y_size = 1;
+ out->y_array = xmalloc(sizeof(String));
+ const char* ret = rettv.vval.v_string != NULL ? rettv.vval.v_string : "";
+ size_t slen = strlen(ret);
+ char* saved = xstrnsave(ret, slen);
+ out->y_array[0] = cbuf_to_string(saved, slen);
+
+ tv_clear(&rettv);
+ return 0;
+ }
+
+ // Return is a dictionary. It should look like a yankreg_T.
+ if (rettv.v_type == VAR_DICT && rettv.vval.v_dict != NULL) {
+ int ec = dict_to_yankreg(rettv.vval.v_dict, out);
+ tv_clear(&rettv);
+ return ec;
+ }
+
+ // Unexpected type
+ tv_clear(&rettv);
+ return 1;
+}
+
+int write_userregister(int regname, const yankreg_T *in)
+{
+ int ec = 0;
+ typval_T argv[3] = { 0 };
+ typval_T rettv = { 0 };
+ dict_T* dict = NULL;
+
+ if (in == NULL || p_urf == NULL || *p_urf == NUL) {
+ ec = 1;
+ goto end;
+ }
+
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = "yank";
+
+ // Register name
+ char regstr[MB_MAXBYTES + 1];
+ int len = utf_char2bytes(regname, regstr);
+ regstr[len] = '\0';
+
+ argv[1].v_type = VAR_STRING;
+ argv[1].vval.v_string = regstr;
+
+ // Convert yankreg_T -> dict
+ dict = yankreg_to_dict(in);
+ if (dict == NULL) {
+ ec = 1;
+ goto end;
+ }
+
+ argv[2].v_type = VAR_DICT;
+ argv[2].vval.v_dict = tv_dict_copy(NULL, dict, false, 0);
+
+ if (callback_call(&urf_cb, 3, argv, &rettv)) {
+ ec = 1;
+ tv_clear(&rettv);
+ tv_clear(&argv[2]);
+ goto end;
+ }
+ tv_clear(&argv[2]);
+
+ // We're not expecting anything specific from the return
+end:
+ return ec;
+}
+
/// Avoid slow things (clipboard) during batch operations (while/for-loops).
void start_batch_changes(void)
{
@@ -6903,7 +7170,7 @@ size_t op_reg_amount(void)
/// @param[in] is_unnamed Whether to set the unnamed regiseter to reg
///
/// @return true on success, false on failure.
-bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
+bool op_reg_set(int name, const yankreg_T reg, bool is_unnamed)
{
int i = op_reg_index(name);
if (i == -1) {
@@ -6912,6 +7179,10 @@ bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
free_register(&y_regs[i]);
y_regs[i] = reg;
+ if (i == USER_REGISTER) {
+ write_userregister(name, &reg);
+ }
+
if (is_unnamed) {
y_previous = &y_regs[i];
}
@@ -6923,12 +7194,17 @@ bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
/// @param[in] name Register name.
///
/// @return Pointer to the register contents or NULL.
-const yankreg_T *op_reg_get(const char name)
+const yankreg_T *op_reg_get(int name)
{
int i = op_reg_index(name);
if (i == -1) {
return NULL;
}
+
+ if (i == USER_REGISTER) {
+ read_userregister(name, &y_regs[i]);
+ }
+
return &y_regs[i];
}
@@ -6937,7 +7213,7 @@ const yankreg_T *op_reg_get(const char name)
/// @param[in] name Register name.
///
/// @return true on success, false on failure.
-bool op_reg_set_previous(const char name)
+bool op_reg_set_previous(int name)
{
int i = op_reg_index(name);
if (i == -1) {
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 99b9b6182d..5f6c3595fd 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -59,7 +59,8 @@ enum {
// The following registers should not be saved in ShaDa file:
STAR_REGISTER = 37,
PLUS_REGISTER = 38,
- NUM_REGISTERS = 39,
+ USER_REGISTER = 39,
+ NUM_REGISTERS = 40,
};
/// Operator IDs; The order must correspond to opchars[] in ops.c!
@@ -118,6 +119,7 @@ typedef enum {
YREG_PASTE,
YREG_YANK,
YREG_PUT,
+ YREG_NOEVAL = 0x10,
} yreg_mode_t;
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -133,6 +135,10 @@ typedef enum {
static inline int op_reg_index(const int regname)
FUNC_ATTR_CONST
{
+ if (regname == 0 || regname == '"') {
+ return -1;
+ }
+
if (ascii_isdigit(regname)) {
return regname - '0';
} else if (ASCII_ISLOWER(regname)) {
@@ -146,7 +152,7 @@ static inline int op_reg_index(const int regname)
} else if (regname == '+') {
return PLUS_REGISTER;
} else {
- return -1;
+ return USER_REGISTER;
}
}
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index d8d3e1e124..d8a7c60c48 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -539,6 +539,7 @@ EXTERN char *p_udir; ///< 'undodir'
EXTERN int p_udf; ///< 'undofile'
EXTERN OptInt p_ul; ///< 'undolevels'
EXTERN OptInt p_ur; ///< 'undoreload'
+EXTERN char* p_urf; ///< 'userregfunc'
EXTERN OptInt p_uc; ///< 'updatecount'
EXTERN OptInt p_ut; ///< 'updatetime'
EXTERN char *p_shada; ///< 'shada'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index e687490704..1cb0a65bea 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -9678,6 +9678,63 @@ local options = {
varname = 'p_ur',
},
{
+ abbreviation = 'urf',
+ defaults = '',
+ cb = 'did_set_userregfunc',
+ desc = [=[
+ Specifies a function to handle any registers that Neovim does not natively
+ handle. This allows the user to use all characters, including multi-byte ones,
+ as custom register names.
+
+ The function is called whenever a user-defined register is accessed — either
+ when yanking to it ("yank") or putting from it ("put").
+
+ The function must have the following signature:
+
+ function({action}, {register}, {content})
+
+ Parameters:
+ {action} string "yank" or "put"
+ "yank" is called when text is yanked into the register.
+ "put" is called when the register is used for insertion.
+
+ {register} string A single-character register name. Can be multibyte.
+
+ {content} dict Only present when {action} is "yank". Contains:
+ {lines}: list of strings representing the yanked text
+ {type}: "char", "line", or "block"
+ {width}: (optional) present if type is "block"
+ {additional_data}: (optional) arbitrary user data
+
+ Return value (for "put"):
+ - A string (for "char" mode), or
+ - A dictionary with the same structure as {content}.
+
+ Example (VimL):>vim
+
+ let s:contents = {}
+
+ function! MyUserregFunction(action, register, content) abort
+ if a:register == '?' && a:action ==# 'put' then
+ return strftime("YYYY-MM-DD")
+ end
+ if a:action ==# 'put'
+ return get(s:contents, a:register, '')
+ elseif a:action ==# 'yank'
+ let s:contents[a:register] = a:content
+ endif
+ endfunction
+
+ set userregfunc=MyUserregFunction
+ <
+ ]=],
+ full_name = 'userregfunc',
+ scope = { 'global' },
+ short_desc = N_('Dynamically generate register content via VimL functions'),
+ type = 'string',
+ varname = 'p_urf',
+ },
+ {
abbreviation = 'uc',
cb = 'did_set_updatecount',
defaults = 200,
@@ -10642,13 +10699,13 @@ local function preprocess(o)
if type(o.alias) == 'string' then
o.alias = {
- o.alias --[[@as string]],
+ o.alias --[[@as string]] ,
}
end
if type(o.defaults) ~= 'table' then
o.defaults = {
- if_true = o.defaults --[[@as string|boolean|number ]],
+ if_true = o.defaults --[[@as string|boolean|number ]] ,
}
end
end
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 7fb4c62b35..5907cdc610 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -557,8 +557,8 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
case '@':
ret.type = kExprLexRegister;
if (pline.size > 1) {
- ret.len++;
- ret.data.reg.name = (uint8_t)pline.data[1];
+ ret.len += utfc_ptr2len(pline.data + 1);
+ ret.data.reg.name = utf_ptr2char(pline.data + 1);
} else {
ret.data.reg.name = -1;
}