From d2325e040e2a4c2ea9d0d8c57af57bc13f7aeae7 Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Thu, 10 Apr 2025 00:20:28 -0600 Subject: Add some more user-defined registers: "| - file contents register " - visual selection register " - timestamp register " - uuid register " - dirname register --- lua/vim/userregs/impl/basename.lua | 28 ++++++++++++ lua/vim/userregs/impl/buffercontents.lua | 39 +++++++++++++++++ lua/vim/userregs/impl/dirname.lua | 27 ++++++++++++ lua/vim/userregs/impl/fileregister.lua | 73 +++++++++++++++++++++++++++++++ lua/vim/userregs/impl/timestamp.lua | 22 ++++++++++ lua/vim/userregs/impl/uuid_register.lua | 18 ++++++++ lua/vim/userregs/impl/visualselection.lua | 51 +++++++++++++++++++++ plugin/basename.lua | 26 ----------- plugin/fileregister.lua | 71 ------------------------------ plugin/loader.vim | 7 +++ 10 files changed, 265 insertions(+), 97 deletions(-) create mode 100644 lua/vim/userregs/impl/basename.lua create mode 100644 lua/vim/userregs/impl/buffercontents.lua create mode 100644 lua/vim/userregs/impl/dirname.lua create mode 100644 lua/vim/userregs/impl/fileregister.lua create mode 100644 lua/vim/userregs/impl/timestamp.lua create mode 100644 lua/vim/userregs/impl/uuid_register.lua create mode 100644 lua/vim/userregs/impl/visualselection.lua delete mode 100644 plugin/basename.lua delete mode 100644 plugin/fileregister.lua create mode 100644 plugin/loader.vim diff --git a/lua/vim/userregs/impl/basename.lua b/lua/vim/userregs/impl/basename.lua new file mode 100644 index 0000000..a46a381 --- /dev/null +++ b/lua/vim/userregs/impl/basename.lua @@ -0,0 +1,28 @@ +--- Basename Register: returns the current filename without path and extension. +--- +--- This is similar to the "%" register (full filename), but returns only +--- the basename with one extension removed. Useful, for example, when referencing +--- Java class names based on the file name. +--- +--- This register is read-only and assigned to the character '$'. + +local userregs = require("vim.userregs") +local api = vim.api + +---@type table +local handler = {} + +--- Return the current buffer's basename without extension. +--- Equivalent to `expand('%:t:r')` +function handler.put(regname) + return vim.fn.expand('%:t:r') +end + +-- Make this register read-only by omitting the yank handler. +handler.yank = nil + +-- Register this handler under the user register '$'. +userregs.register_handler('$', handler) + + +return {} diff --git a/lua/vim/userregs/impl/buffercontents.lua b/lua/vim/userregs/impl/buffercontents.lua new file mode 100644 index 0000000..016b4e5 --- /dev/null +++ b/lua/vim/userregs/impl/buffercontents.lua @@ -0,0 +1,39 @@ +local userregs = require("vim.userregs") + +-- Register name: "|" +-- This register represents the full contents of the current buffer. +-- Yank into it → saves the buffer. +-- Put from it → replaces the buffer contents. + +local regname = "|" + +--- Reads the full contents of the current buffer as a list of lines. +--- This is returned in the format expected by 'userregfunc'. +--- @return table a 'put' dictionary with `lines`, `type`, and `additional_data` +local function read_current_buffer() + local bufnr = vim.api.nvim_get_current_buf() + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + + return { + lines = lines, + type = "V", -- linewise register + additional_data = {}, + } +end + +--- Replaces the entire contents of the current buffer with the given content. +--- Used when yanking into the "|" register. +--- @param _ string -- register name (ignored here) +--- @param content table -- dictionary with `lines` from yankreg_T +local function write_current_buffer(_, content) + local bufnr = vim.api.nvim_get_current_buf() + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, content.lines or {}) +end + +-- Register the "|" register with the userregs framework +userregs.register_handler(regname, { + put = function(_) return read_current_buffer() end, + yank = write_current_buffer, +}) + diff --git a/lua/vim/userregs/impl/dirname.lua b/lua/vim/userregs/impl/dirname.lua new file mode 100644 index 0000000..503601d --- /dev/null +++ b/lua/vim/userregs/impl/dirname.lua @@ -0,0 +1,27 @@ +--- Dirname Register: returns the direction for the filename. +--- +--- This is similar to the "%" register (full filename), but returns only +--- the the directory. +--- +--- This register is read-only and assigned to the character ''. + +local userregs = require("vim.userregs") +local api = vim.api + +---@type table +local handler = {} + +--- Return the current buffer's basename without extension. +--- Equivalent to `expand('%:t:r')` +function handler.put(regname) + return vim.fn.expand('%:p:h') .. '/' +end + +-- Make this register read-only by omitting the yank handler. +handler.yank = nil + +-- Register this handler under the user register ''. +userregs.register_handler('\4', handler) + + +return {} diff --git a/lua/vim/userregs/impl/fileregister.lua b/lua/vim/userregs/impl/fileregister.lua new file mode 100644 index 0000000..0ec1715 --- /dev/null +++ b/lua/vim/userregs/impl/fileregister.lua @@ -0,0 +1,73 @@ +--- Fileregister: maps the '&' register to an external file. +--- +--- This register reads from or writes to a file defined by `g:fileregister_filename` +--- or defaults to `${RUN_DIRECTORY}/fileregister`. +--- +--- On yank, the content is saved as JSON. +--- On put, the content is decoded from JSON (if valid) or interpreted as raw lines. + +local userregs = require("vim.userregs") +local api = vim.api + +local handler = {} + +--- Get the filename used for this register. +--- Defaults to /tmp/.vim.$USER.fileregister +---@return string +local function get_file_name() + return vim.g.fileregister_filename + or (vim.fn.stdpath("run") .. "/fileregister") +end + +--- Write the yanked content into the fileregister as JSON. +---@param _ string register name (ignored) +---@param content table content from 'userregfunc' +function handler.yank(_, content) + local filename = get_file_name() + local ok, encoded = pcall(vim.json.encode, content) + if not ok then + vim.notify("[fileregister] Failed to encode content to JSON", vim.log.levels.ERROR) + return + end + + local f = io.open(filename, "w") + if not f then + vim.notify("[fileregister] Could not open file for writing: " .. filename, vim.log.levels.ERROR) + return + end + + f:write(encoded) + f:close() +end + +--- Read from the file. If it's valid JSON, decode as register content. +--- Otherwise, interpret it as a list of lines. +---@param _ string register name (ignored) +---@return table|string +function handler.put(_) + local filename = get_file_name() + local f = io.open(filename, "r") + + if not f then + return {} + end + + local content = f:read("*a") + f:close() + + if content:sub(1, 1) == "{" then + local ok, decoded = pcall(vim.json.decode, content) + if ok then return decoded end + end + + local lines = {} + for line in content:gmatch("[^\r\n]+") do + table.insert(lines, line) + end + return lines +end + +-- Register the '&' fileregister +userregs.register_handler('&', handler) + +return {} diff --git a/lua/vim/userregs/impl/timestamp.lua b/lua/vim/userregs/impl/timestamp.lua new file mode 100644 index 0000000..517dd43 --- /dev/null +++ b/lua/vim/userregs/impl/timestamp.lua @@ -0,0 +1,22 @@ +local userregs = require("vim.userregs") + +-- Get format from g:timestamp_register_format or use fallback +local function get_timestamp_format() + return vim.g.timestamp_register_format or "%Y-%m-%d %H:%M:%S" +end + +local function generate_timestamp() + local ok, result = pcall(os.date, get_timestamp_format()) + if not ok then + vim.notify("[userregs] Invalid timestamp format", vim.log.levels.WARN) + return os.date("%Y-%m-%d %H:%M:%S") + end + return result +end + +userregs.register_handler('\20', { + put = function() + return generate_timestamp() + end, + yank = nil, -- read-only +}) diff --git a/lua/vim/userregs/impl/uuid_register.lua b/lua/vim/userregs/impl/uuid_register.lua new file mode 100644 index 0000000..de45de1 --- /dev/null +++ b/lua/vim/userregs/impl/uuid_register.lua @@ -0,0 +1,18 @@ +local userregs = require("vim.userregs") + +local function generate_uuid() + local random = math.random + local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + return string.gsub(template, "[xy]", function(c) + local v = (c == "x") and random(0, 0xf) or random(8, 0xb) + return string.format("%x", v) + end) +end + +userregs.register_handler("\21", { -- Ctrl+U + put = function() + return generate_uuid() + end, + yank = nil, -- read-only +}) + diff --git a/lua/vim/userregs/impl/visualselection.lua b/lua/vim/userregs/impl/visualselection.lua new file mode 100644 index 0000000..93cb370 --- /dev/null +++ b/lua/vim/userregs/impl/visualselection.lua @@ -0,0 +1,51 @@ +local userregs = require("vim.userregs") + +-- ASCII-safe register name +local regname = "\22" + +-- Return the contents of the last visual selection +local function read_last_visual() + local bufnr = vim.api.nvim_get_current_buf() + + local start_pos = vim.fn.getpos("'<") + local end_pos = vim.fn.getpos("'>") + + if not start_pos or not end_pos then + vim.notify("[userregs] No visual selection", vim.log.levels.WARN) + return { lines = {}, type = "V" } + end + + local start_lnum = start_pos[2] - 1 -- 0-indexed + local end_lnum = end_pos[2] -- end line is exclusive + + local lines = vim.api.nvim_buf_get_lines(bufnr, start_lnum, end_lnum, false) + return { + lines = lines, + type = "V", -- Assume linewise for now + additional_data = {}, + } +end + +-- Replace the last visual selection with new content +local function write_last_visual(_, content) + local bufnr = vim.api.nvim_get_current_buf() + + local start_pos = vim.fn.getpos("'<") + local end_pos = vim.fn.getpos("'>") + + if not start_pos or not end_pos then + vim.notify("[userregs] No visual selection to write into", vim.log.levels.ERROR) + return + end + + local start_lnum = start_pos[2] - 1 + local end_lnum = end_pos[2] + + -- Replace the range with the new content + vim.api.nvim_buf_set_lines(bufnr, start_lnum, end_lnum, false, content.lines or {}) +end + +userregs.register_handler(regname, { + put = function() return read_last_visual() end, + yank = write_last_visual, +}) diff --git a/plugin/basename.lua b/plugin/basename.lua deleted file mode 100644 index 84d2991..0000000 --- a/plugin/basename.lua +++ /dev/null @@ -1,26 +0,0 @@ ---- Basename Register: returns the current filename without path and extension. ---- ---- This is similar to the "%" register (full filename), but returns only ---- the basename with one extension removed. Useful, for example, when referencing ---- Java class names based on the file name. ---- ---- This register is read-only and assigned to the character '$'. - -local userregs = require("vim.userregs") -local api = vim.api - ----@type table -local handler = {} - ---- Return the current buffer's basename without extension. ---- Equivalent to `expand('%:t:r')` -function handler.put(regname) - return vim.fn.expand('%:t:r') -end - --- Make this register read-only by omitting the yank handler. -handler.yank = nil - --- Register this handler under the user register '$'. -userregs.register_handler('$', handler) - diff --git a/plugin/fileregister.lua b/plugin/fileregister.lua deleted file mode 100644 index 08bdc5a..0000000 --- a/plugin/fileregister.lua +++ /dev/null @@ -1,71 +0,0 @@ ---- Fileregister: maps the '&' register to an external file. ---- ---- This register reads from or writes to a file defined by `g:fileregister_filename` ---- or defaults to `${RUN_DIRECTORY}/fileregister`. ---- ---- On yank, the content is saved as JSON. ---- On put, the content is decoded from JSON (if valid) or interpreted as raw lines. - -local userregs = require("vim.userregs") -local api = vim.api - -local handler = {} - ---- Get the filename used for this register. ---- Defaults to /tmp/.vim.$USER.fileregister ----@return string -local function get_file_name() - return vim.g.fileregister_filename - or (vim.fn.stdpath("run") .. "/fileregister") -end - ---- Write the yanked content into the fileregister as JSON. ----@param _ string register name (ignored) ----@param content table content from 'userregfunc' -function handler.yank(_, content) - local filename = get_file_name() - local ok, encoded = pcall(vim.json.encode, content) - if not ok then - vim.notify("[fileregister] Failed to encode content to JSON", vim.log.levels.ERROR) - return - end - - local f = io.open(filename, "w") - if not f then - vim.notify("[fileregister] Could not open file for writing: " .. filename, vim.log.levels.ERROR) - return - end - - f:write(encoded) - f:close() -end - ---- Read from the file. If it's valid JSON, decode as register content. ---- Otherwise, interpret it as a list of lines. ----@param _ string register name (ignored) ----@return table|string -function handler.put(_) - local filename = get_file_name() - local f = io.open(filename, "r") - - if not f then - return {} - end - - local content = f:read("*a") - f:close() - - if content:sub(1, 1) == "{" then - local ok, decoded = pcall(vim.json.decode, content) - if ok then return decoded end - end - - local lines = {} - for line in content:gmatch("[^\r\n]+") do - table.insert(lines, line) - end - return lines -end - --- Register the '&' fileregister -userregs.register_handler('&', handler) diff --git a/plugin/loader.vim b/plugin/loader.vim new file mode 100644 index 0000000..a694db1 --- /dev/null +++ b/plugin/loader.vim @@ -0,0 +1,7 @@ +lua require('vim.userregs.impl.fileregister') +lua require('vim.userregs.impl.uuid_register') +lua require('vim.userregs.impl.basename') +lua require('vim.userregs.impl.dirname') +lua require('vim.userregs.impl.timestamp') +lua require('vim.userregs.impl.buffercontents') +lua require('vim.userregs.impl.visualselection') -- cgit