From 7a00a8d47bd2dd1b2c48848058de23e113e2dc17 Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Fri, 13 Feb 2026 00:21:41 -0500 Subject: [PATCH] Diffview auto-refresh, yank file path key bindings --- lazy-lock.json | 35 ++++++------ lua/config/options.lua | 16 ++++++ lua/custom/directory-watcher.lua | 96 ++++++++++++++++++++++++++++++++ lua/custom/yank.lua | 92 ++++++++++++++++++++++++++++++ lua/plugins/diffview.lua | 56 +++++++++++++++++-- lua/plugins/yank-path.lua | 6 ++ lua/plugins/yank.lua | 9 +++ 7 files changed, 289 insertions(+), 21 deletions(-) create mode 100644 lua/custom/directory-watcher.lua create mode 100644 lua/custom/yank.lua create mode 100644 lua/plugins/yank-path.lua create mode 100644 lua/plugins/yank.lua diff --git a/lazy-lock.json b/lazy-lock.json index 7f37bd7..6418914 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -1,6 +1,6 @@ { "LazyVim": { "branch": "main", "commit": "28db03f958d58dfff3c647ce28fdc1cb88ac158d" }, - "SchemaStore.nvim": { "branch": "main", "commit": "b850ab25279ba04ada90e8b696ef5d0624af103d" }, + "SchemaStore.nvim": { "branch": "main", "commit": "2abb594d69a43c0a48a11b30bb0ef17ad90dceea" }, "blink-cmp-avante": { "branch": "master", "commit": "4f494c6e124acbe31a8f5d58effa0c14aa38a6d5" }, "blink-copilot": { "branch": "main", "commit": "7ad8209b2f880a2840c94cdcd80ab4dc511d4f39" }, "blink-emoji.nvim": { "branch": "master", "commit": "066013e4c98a9318408ee3f1ca2dbcb6fa3e4c06" }, @@ -8,7 +8,7 @@ "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, "catppuccin": { "branch": "main", "commit": "beaf41a30c26fd7d6c386d383155cbd65dd554cd" }, "clangd_extensions.nvim": { "branch": "main", "commit": "e84eaea7ca79610d7047fb1c9d74f15bb1c9db34" }, - "cmake-tools.nvim": { "branch": "master", "commit": "75a3589cf5ff19ede2a0c0f34da5c37554a44221" }, + "cmake-tools.nvim": { "branch": "master", "commit": "24502aec9166fd6b851762c5930ff316083acd85" }, "codesnap.nvim": { "branch": "main", "commit": "31b5dbc353ce0823086273abb6e82f3dcbb9e4f8" }, "conform.nvim": { "branch": "master", "commit": "c2526f1cde528a66e086ab1668e996d162c75f4f" }, "copilot.lua": { "branch": "master", "commit": "3faffefbd6ddeb52578535ec6b730e0b72d7fd1a" }, @@ -24,14 +24,14 @@ "fold-preview.nvim": { "branch": "main", "commit": "b7920cb0aba2b48a6b679bff45f98c3ebc0f0b89" }, "friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" }, "fzf": { "branch": "master", "commit": "0e859a18ed399dc4d9d15be6e462f6c4b4e02358" }, - "fzf-lua": { "branch": "main", "commit": "fb8c50ba62a0daa433b7ac2b78834f318322b879" }, + "fzf-lua": { "branch": "main", "commit": "739330f48ca64b4283f5228e9dc9c0c8f9a37110" }, "gh.nvim": { "branch": "main", "commit": "6f367b2ab8f9d4a0a23df2b703a3f91137618387" }, - "gitsigns.nvim": { "branch": "main", "commit": "f7cc6850517b8db59a41314b83bb7994eae33cdd" }, + "gitsigns.nvim": { "branch": "main", "commit": "31217271a7314c343606acb4072a94a039a19fb5" }, "goto-preview": { "branch": "main", "commit": "d2d6923c9b9e0e43f0b9b566f261a8b1ae016540" }, "grug-far.nvim": { "branch": "main", "commit": "275dbedc96e61a6b8d1dfb28ba51586ddd233dcf" }, "gruvbox.nvim": { "branch": "main", "commit": "a472496e1a4465a2dd574389dcf6cdb29af9bf1b" }, "gui-font-resize.nvim": { "branch": "main", "commit": "8b3c7d632c798a93fa7321162a3e018ecdd733a7" }, - "hybrid.nvim": { "branch": "master", "commit": "74dfee0d5084a3db5e2ad0a78a67ee45e93a64bf" }, + "hybrid.nvim": { "branch": "master", "commit": "bd52073c4b2e357be0c5ad0d3bd0d07ca48773f7" }, "inc-rename.nvim": { "branch": "main", "commit": "2597bccb57d1b570fbdbd4adf88b955f7ade715b" }, "kanagawa.nvim": { "branch": "master", "commit": "aef7f5cec0a40dbe7f3304214850c472e2264b10" }, "key-analyzer.nvim": { "branch": "main", "commit": "4e4bef34498e821bcbd5203f44db8b67e4f10e04" }, @@ -44,7 +44,7 @@ "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, "lush.nvim": { "branch": "main", "commit": "9c60ec2279d62487d942ce095e49006af28eed6e" }, "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" }, - "mason-lspconfig.nvim": { "branch": "main", "commit": "ae609525ddf01c153c39305730b1791800ffe4fe" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "21c2a84ce368e99b18f52ab348c4c02c32c02fcf" }, "mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" }, "mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" }, "miasma.nvim": { "branch": "main", "commit": "627f2e1cac91de0d1d4dd7472b506a30f41b2b7d" }, @@ -57,8 +57,8 @@ "mini.surround": { "branch": "main", "commit": "d648a5601e1c48f175b07d10eba141da338a0a2a" }, "monochrome.nvim": { "branch": "main", "commit": "2de78d9688ea4a177bcd9be554ab9192337d35ff" }, "moonbow.nvim": { "branch": "master", "commit": "baf62d035785910c5a368cbde5872190a56afb30" }, - "neo-tree.nvim": { "branch": "main", "commit": "ef57a6f68a47e9bed218d948558c3671c0977f60" }, - "neoconf.nvim": { "branch": "main", "commit": "5e41e649b1f4459ffd95237e0b272795535ed358" }, + "neo-tree.nvim": { "branch": "main", "commit": "dfe9d8b520baf34340e9fc80dc1534401db2cb3a" }, + "neoconf.nvim": { "branch": "main", "commit": "7b84ac37a9ffb5cacbaf43c03cf868c18523c224" }, "neogen": { "branch": "main", "commit": "23e7e9f883d01289ebd90e98025acc860ea26366" }, "neotest": { "branch": "master", "commit": "deadfb1af5ce458742671ad3a013acb9a6b41178" }, "neotest-mocha": { "branch": "main", "commit": "bf24f5ba9bbb35ba5156b9642bccb80b8cc80eb0" }, @@ -71,17 +71,17 @@ "numb.nvim": { "branch": "master", "commit": "12ef3913dea8727d4632c6f2ed47957a993de627" }, "nvim-bqf": { "branch": "main", "commit": "f65fba733268ffcf9c5b8ac381287eca7c223422" }, "nvim-cmp": { "branch": "main", "commit": "85bbfad83f804f11688d1ab9486b459e699292d6" }, - "nvim-dap": { "branch": "master", "commit": "e47878dcf1ccc30136b30d19ab19fe76946d61cd" }, + "nvim-dap": { "branch": "master", "commit": "db321947bb289a2d4d76a32e76e4d2bd6103d7df" }, "nvim-dap-python": { "branch": "master", "commit": "1808458eba2b18f178f990e01376941a42c7f93b" }, "nvim-dap-ruby": { "branch": "main", "commit": "ba36f9905ca9c6d89e5af5467a52fceeb2bbbf6d" }, "nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" }, "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, - "nvim-jdtls": { "branch": "master", "commit": "291dad797b5427ca0c9b7e0dd261279c7c3823e1" }, + "nvim-jdtls": { "branch": "master", "commit": "77ccaeb422f8c81b647605da5ddb4a7f725cda90" }, "nvim-lint": { "branch": "master", "commit": "bcd1a44edbea8cd473af7e7582d3f7ffc60d8e81" }, - "nvim-lspconfig": { "branch": "master", "commit": "66fd02ad1c7ea31616d3ca678fa04e6d0b360824" }, + "nvim-lspconfig": { "branch": "master", "commit": "44acfe887d4056f704ccc4f17513ed41c9e2b2e6" }, "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, "nvim-pqf": { "branch": "main", "commit": "148ee2ca8b06d83fd9bf6f9b9497724ad39a07d6" }, - "nvim-treesitter": { "branch": "main", "commit": "45a07f869b0cffba342276f2c77ba7c116d35db8" }, + "nvim-treesitter": { "branch": "main", "commit": "9f2dad22ef8bb14fd1e0a3aa8859cdc88170668b" }, "nvim-treesitter-context": { "branch": "master", "commit": "64dd4cf3f6fd0ab17622c5ce15c91fc539c3f24a" }, "nvim-treesitter-textobjects": { "branch": "main", "commit": "a0e182ae21fda68c59d1f36c9ed45600aef50311" }, "nvim-ts-autotag": { "branch": "main", "commit": "8e1c0a389f20bf7f5b0dd0e00306c1247bda2595" }, @@ -91,7 +91,7 @@ "octo.nvim": { "branch": "master", "commit": "5ae580df72589f25b775ff2bdacfd7f7be8d63bd" }, "oil.nvim": { "branch": "master", "commit": "f55b25e493a7df76371cfadd0ded5004cb9cd48a" }, "onedarker.nvim": { "branch": "master", "commit": "b4f92f073ed7cdf0358ad005cee0484411232b1b" }, - "overseer.nvim": { "branch": "master", "commit": "5828bdbd86677497613033c142f0a8624489216f" }, + "overseer.nvim": { "branch": "master", "commit": "392093e610333c0aea89bf43de7362e25783eada" }, "oxocarbon.nvim": { "branch": "main", "commit": "9f85f6090322f39b11ae04a343d4eb9d12a86897" }, "persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" }, "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, @@ -117,7 +117,7 @@ "venv-selector.nvim": { "branch": "main", "commit": "26a6da0348dc2d0a3787e095bd4e68d976304bb2" }, "vim-dadbod": { "branch": "master", "commit": "6d1d41da4873a445c5605f2005ad2c68c99d8770" }, "vim-dadbod-completion": { "branch": "master", "commit": "a8dac0b3cf6132c80dc9b18bef36d4cf7a9e1fe6" }, - "vim-dadbod-ui": { "branch": "master", "commit": "48c4f271da13d380592f4907e2d1d5558044e4e5" }, + "vim-dadbod-ui": { "branch": "master", "commit": "07e92e22114cc5b1ba4938d99897d85b58e20475" }, "vim-eunuch": { "branch": "master", "commit": "e86bb794a1c10a2edac130feb0ea590a00d03f1e" }, "vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" }, "vim-repeat": { "branch": "master", "commit": "65846025c15494983dafe5e3b46c8f88ab2e9635" }, @@ -125,9 +125,10 @@ "vim-sleuth": { "branch": "master", "commit": "be69bff86754b1aa5adcbb527d7fcd1635a84080" }, "vim-startuptime": { "branch": "master", "commit": "b6f0d93f6b8cf6eee0b4c94450198ba2d6a05ff6" }, "vim-unimpaired": { "branch": "master", "commit": "db65482581a28e4ccf355be297f1864a4e66985c" }, - "vimtex": { "branch": "master", "commit": "f707368022cdb851716be0d2970b90599c84a6a6" }, - "wezterm-types": { "branch": "main", "commit": "a57cc58e9b92c9629cca9c5efb78b72df1796c45" }, + "vimtex": { "branch": "master", "commit": "95b93a24740f7b89dd8331326b41bdd1337d79f6" }, + "wezterm-types": { "branch": "main", "commit": "4918fc9631ac4ff775230861a20f72777a806fc8" }, "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }, + "yank-path.nvim": { "branch": "main", "commit": "e660248de1e4c91a760f510fc165c172a19cc1d5" }, "yanky.nvim": { "branch": "main", "commit": "9d3caea67cf183639d4e510d34f2d58b6109abdd" }, - "zenbones.nvim": { "branch": "main", "commit": "4635a3f46d1066975d1074cd9f61f93cb1e32f64" } + "zenbones.nvim": { "branch": "main", "commit": "22b7fb75593412e0dc81b4bdefae718e9e84aa82" } } diff --git a/lua/config/options.lua b/lua/config/options.lua index a9569fa..c372bc8 100644 --- a/lua/config/options.lua +++ b/lua/config/options.lua @@ -20,3 +20,19 @@ vim.opt.titlestring = "%{substitute(getcwd(),$HOME,'~','')} - Neovim" vim.opt.fillchars:append({ diff = "╱" }) vim.opt.mousescroll = "ver:6,hor:6" -- better mouse scroll in hidpi mode + +-- WSL2 clipboard support via win32yank +if vim.fn.has("wsl") == 1 then + vim.g.clipboard = { + name = "win32yank-wsl", + copy = { + ["+"] = "win32yank.exe -i --crlf", + ["*"] = "win32yank.exe -i --crlf", + }, + paste = { + ["+"] = "win32yank.exe -o --lf", + ["*"] = "win32yank.exe -o --lf", + }, + cache_enabled = true, + } +end diff --git a/lua/custom/directory-watcher.lua b/lua/custom/directory-watcher.lua new file mode 100644 index 0000000..fe281a3 --- /dev/null +++ b/lua/custom/directory-watcher.lua @@ -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 diff --git a/lua/custom/yank.lua b/lua/custom/yank.lua new file mode 100644 index 0000000..dfd4ae1 --- /dev/null +++ b/lua/custom/yank.lua @@ -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("", 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", "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", "yr", function() + M.yank_visual_with_path(M.get_buffer_cwd_relative(), "relative") +end, { desc = "[Y]ank selection with [R]elative path" }) + +return M diff --git a/lua/plugins/diffview.lua b/lua/plugins/diffview.lua index 517847a..17b77c7 100644 --- a/lua/plugins/diffview.lua +++ b/lua/plugins/diffview.lua @@ -1,3 +1,6 @@ +-- Custom Diffview auto-refresh config from https://richardgill.org/blog/configuring-neovim-coding-agents +-- Source: https://github.com/richardgill/nix/blob/ebdd8260f770d242db7b6163158cfe9ad9784c41/modules/home-manager/dot-files/nvim/lua/plugins/git-diff_diffview.lua + return { "sindrets/diffview.nvim", cmd = { "DiffviewOpen", "DiffviewClose", "DiffviewToggleFiles", "DiffviewFocusFiles" }, @@ -5,13 +8,13 @@ return { enhanced_diff_hl = true, keymaps = { view = { - ["gq"] = "DiffviewClose", + { "n", "gq", "DiffviewClose", { desc = "Close diffview" } }, }, file_panel = { - ["gq"] = "DiffviewClose", + { "n", "gq", "DiffviewClose", { desc = "Close diffview" } }, }, file_history_panel = { - ["gq"] = "DiffviewClose", + { "n", "gq", "DiffviewClose", { desc = "Close diffview" } }, }, }, }, @@ -19,6 +22,51 @@ return { { "gl", ":'<,'>DiffviewFileHistory", mode = "v", desc = "Toggle git log of selected lines" }, { "gL", "DiffviewFileHistory", desc = "Toggle git log of current branch" }, { "gl", "DiffviewFileHistory %", desc = "Toggle git log of current file history" }, - { "gd", "DiffviewOpen", desc = "Git diff current file against the index" }, + { "gv", "DiffviewOpen", desc = "Open [g]it Diff[v]iew tab" }, }, + config = function(_, opts) + require("diffview").setup(opts) + + local watcher = require("custom.directory-watcher") + + local is_git_ignored = function(filepath) + vim.fn.system("git check-ignore -q " .. vim.fn.shellescape(filepath)) + return vim.v.shell_error == 0 + end + + local update_left_pane = function() + pcall(function() + local lib = require("diffview.lib") + local view = lib.get_current_view() + if view then + view:update_files() + end + end) + end + + -- Register handler for file changes in watched directory + watcher.registerOnChangeHandler("diffview", function(filepath, events) + local is_in_dot_git_dir = filepath:match("/%.git/") or filepath:match("^%.git/") + + if is_in_dot_git_dir or not is_git_ignored(filepath) then + vim.notify("[diffview] File changed: " .. vim.fn.fnamemodify(filepath, ":t"), vim.log.levels.INFO) + update_left_pane() + end + end) + + -- Start watching the current working directory + watcher.setup({ path = vim.fn.getcwd() }) + + vim.api.nvim_create_autocmd("FocusGained", { + callback = update_left_pane, + }) + + vim.api.nvim_create_autocmd("User", { + pattern = "DiffviewViewLeave", + callback = function() + watcher.stop() + vim.cmd(":DiffviewClose") + end, + }) + end, } diff --git a/lua/plugins/yank-path.lua b/lua/plugins/yank-path.lua new file mode 100644 index 0000000..ba0e9b2 --- /dev/null +++ b/lua/plugins/yank-path.lua @@ -0,0 +1,6 @@ +return { + "ywpkwon/yank-path.nvim", + config = function() + require("yank-path").setup() + end, +} diff --git a/lua/plugins/yank.lua b/lua/plugins/yank.lua new file mode 100644 index 0000000..576459b --- /dev/null +++ b/lua/plugins/yank.lua @@ -0,0 +1,9 @@ +-- Load custom yank keymaps for copying file paths + visual selections +-- See lua/custom/yank.lua for implementation +return { + dir = vim.fn.stdpath("config"), + name = "custom-yank", + config = function() + require("custom.yank") + end, +}