Diffview auto-refresh, yank file path key bindings

This commit is contained in:
2026-02-13 00:21:41 -05:00
parent ccebe78d4e
commit 7a00a8d47b
7 changed files with 289 additions and 21 deletions

View File

@@ -0,0 +1,96 @@
-- Custom directory watcher using Neovim's built-in libuv bindings
-- Source: https://github.com/richardgill/nix/blob/ebdd8260f770d242db7b6163158cfe9ad9784c41/modules/home-manager/dot-files/nvim/lua/custom/directory-watcher.lua
local M = {}
local uv = vim.uv
local watcher = nil
local debounce_timer = nil
local on_change_handlers = {}
-- Debounce helper to prevent callback storms
local debounce = function(fn, delay)
return function(...)
local args = { ... }
if debounce_timer then
debounce_timer:close()
end
debounce_timer = vim.defer_fn(function()
debounce_timer = nil
fn(unpack(args))
end, delay)
end
end
-- Register a named handler to be called when files change
-- If a handler with the same name exists, it will be replaced
-- Note: Named handlers are required to support Lua hotreload - when a file is reloaded,
-- it re-registers its handler with the same name, replacing the old one instead of
-- creating duplicates
M.registerOnChangeHandler = function(name, handler)
on_change_handlers[name] = handler
end
-- Start watching a directory for file changes
M.setup = function(opts)
opts = opts or {}
local path = opts.path
local debounce_delay = opts.debounce or 100 -- ms
if not path then
return false
end
-- Stop existing watcher if any
if watcher then
M.stop()
end
-- Create fs_event handle
local fs_event = uv.new_fs_event()
if not fs_event then
return false
end
-- Debounced callback for file changes
local on_change = debounce(function(err, filename, events)
if err then
M.stop()
return
end
if filename then
local full_path = path .. "/" .. filename
-- Call all registered handlers
for _, handler in pairs(on_change_handlers) do
pcall(handler, full_path, events)
end
end
end, debounce_delay)
-- Start watching (wrapped for thread safety)
local ok, err = fs_event:start(path, { recursive = false }, vim.schedule_wrap(on_change))
if ok ~= 0 then
return false
end
watcher = fs_event
return true
end
-- Stop the watcher and clean up resources
M.stop = function()
if watcher then
watcher:stop()
watcher = nil
end
if debounce_timer then
debounce_timer:close()
debounce_timer = nil
end
end
return M

92
lua/custom/yank.lua Normal file
View File

@@ -0,0 +1,92 @@
-- Custom yank functions for copying file paths and visual selections with file paths.
-- From: https://richardgill.org/blog/configuring-neovim-coding-agents
-- Source: https://github.com/richardgill/nix/blob/ebdd8260f770d242db7b6163158cfe9ad9784c41/modules/home-manager/dot-files/nvim/lua/custom/yank.lua
local M = {}
M.get_buffer_absolute = function()
return vim.fn.expand("%:p")
end
M.get_buffer_cwd_relative = function()
return vim.fn.expand("%:.")
end
M.get_visual_bounds = function()
local mode = vim.fn.mode()
if mode ~= "v" and mode ~= "V" then
error("get_visual_bounds must be called in visual or visual-line mode (current mode: " .. vim.inspect(mode) .. ")")
end
local is_visual_line_mode = mode == "V"
local start_pos = vim.fn.getpos("v")
local end_pos = vim.fn.getpos(".")
return {
start_line = math.min(start_pos[2], end_pos[2]),
end_line = math.max(start_pos[2], end_pos[2]),
start_col = is_visual_line_mode and 0 or math.min(start_pos[3], end_pos[3]) - 1,
end_col = is_visual_line_mode and -1 or math.max(start_pos[3], end_pos[3]),
mode = mode,
start_pos = start_pos,
end_pos = end_pos,
}
end
M.format_line_range = function(start_line, end_line)
return start_line == end_line and tostring(start_line) or start_line .. "-" .. end_line
end
M.simulate_yank_highlight = function()
local bounds = M.get_visual_bounds()
local ns = vim.api.nvim_create_namespace("simulate_yank_highlight")
vim.highlight.range(
0,
ns,
"IncSearch",
{ bounds.start_line - 1, bounds.start_col },
{ bounds.end_line - 1, bounds.end_col },
{ priority = 200 }
)
vim.defer_fn(function()
vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
end, 150)
end
M.exit_visual_mode = function()
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "n", false)
end
M.yank_path = function(path, label)
vim.fn.setreg("+", path)
print("Yanked " .. label .. " path: " .. path)
end
M.yank_visual_with_path = function(path, label)
local bounds = M.get_visual_bounds()
local selected_lines = vim.fn.getregion(bounds.start_pos, bounds.end_pos, { type = bounds.mode })
local selected_text = table.concat(selected_lines, "\n")
local line_range = M.format_line_range(bounds.start_line, bounds.end_line)
local path_with_lines = path .. ":" .. line_range
local result = path_with_lines .. "\n\n" .. selected_text
vim.fn.setreg("+", result)
M.simulate_yank_highlight()
M.exit_visual_mode()
print("Yanked " .. label .. " with lines " .. line_range)
end
vim.keymap.set("v", "<leader>ya", function()
M.yank_visual_with_path(M.get_buffer_absolute(), "absolute")
end, { desc = "[Y]ank selection with [A]bsolute path" })
vim.keymap.set("v", "<leader>yr", function()
M.yank_visual_with_path(M.get_buffer_cwd_relative(), "relative")
end, { desc = "[Y]ank selection with [R]elative path" })
return M