aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/userregs.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2025-04-09 23:46:29 +0000
committerJosh Rahm <joshuarahm@gmail.com>2025-04-16 17:41:19 +0000
commit2034a8419e1c5675592cdd0d0ffeaadfda58001a (patch)
tree4ba185d58c2ea2b8893aad66aa96f6e5efaec1ef /runtime/lua/vim/userregs.lua
parentf068386c9f709c586f44169f4566b4e31ce973de (diff)
downloadrneovim-2034a8419e1c5675592cdd0d0ffeaadfda58001a.tar.gz
rneovim-2034a8419e1c5675592cdd0d0ffeaadfda58001a.tar.bz2
rneovim-2034a8419e1c5675592cdd0d0ffeaadfda58001a.zip
feat(userregfunc): programmable user-defined registers with multibyte support
This patch introduces a new global option `userregfunc`, allowing users to define custom behavior for registers not handled by Neovim internally. This enables programmable registers using any Unicode character — including multibyte characters. - A new register slot `USER_REGISTER` is introduced. Any register not matching the standard set (`0-9a-zA-Z"+-*%#/:.=`, etc.) is routed through this system. - When such a register is accessed, the function defined in `userregfunc` is called with three arguments: 1. `{action}` (string): either `"yank"` or `"put"` 2. `{register}` (string): UTF-8 character name of the register 3. `{content}`: - If `action == "yank"`: a dictionary with these keys: - `lines` (list of strings): the yanked text - `type` (string): one of `"v"` (charwise), `"V"` (linewise), or `"b"` (blockwise) - `width` (number, optional): present if `type == "b"` - `additional_data` (dict, optional): user-extensible metadata - If `action == "put"`: this is always `v:null` - The function may return either: - A **string** (used as a charwise register), or - A **dictionary** matching the structure above - Internally, `read_userregister()` and `write_userregister()` convert between `yankreg_T` and typval dictionaries. - Messages and internal logic fully support multibyte register names via UTF-8. - A new `USER_REGISTER` slot is used for logical separation in the register table. Included in this patch is an extensible Lua framework (`vim.userregs`) for defining user register handlers in Lua. It provides per-register handlers via `register_handler(registers, handler)` The global function `_G.def_userreg_func` is registered as the default implementation of `'userregfunc'`, enabling seamless integration with the Lua framework. - Register `λ` dynamically inserts the current date - Register `&` reads and writes from a "global register" file under `stdpath("run")` - Register `?` returns the result of a shell command - Registers that auto-adjust based on filetype, cursor context, or Treesitter nodes This change expands the register model into a programmable abstraction — fully scriptable and extensible — without breaking compatibility.
Diffstat (limited to 'runtime/lua/vim/userregs.lua')
-rw-r--r--runtime/lua/vim/userregs.lua64
1 files changed, 64 insertions, 0 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