From 69ae6efc09152c7dae8e4214d8092a531dcfa0e3 Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Sat, 29 Aug 2026 19:04:06 -0500 Subject: [PATCH] removed plugin --- nvim/lua/darcula/autocmds.lua | 2 +- nvim/lua/darcula/git/blame.lua | 429 ++++++++++++++++++ nvim/lua/darcula/keymaps.lua | 8 +- nvim/lua/darcula/plugins.lua | 1 - .../{500_gitsigns.lua => 500_git_blame.lua} | 26 +- 5 files changed, 441 insertions(+), 25 deletions(-) create mode 100644 nvim/lua/darcula/git/blame.lua rename nvim/lua/darcula/setup/{500_gitsigns.lua => 500_git_blame.lua} (72%) diff --git a/nvim/lua/darcula/autocmds.lua b/nvim/lua/darcula/autocmds.lua index 9e27562..e53e78f 100644 --- a/nvim/lua/darcula/autocmds.lua +++ b/nvim/lua/darcula/autocmds.lua @@ -112,7 +112,7 @@ utils.autocmd( utils.autocmd( group, "User", - {"FugitiveChanged", "GitSignsUpdate"}, + {"DarculaGitUpdate", "FugitiveChanged"}, function() local api = require("nvim-tree.api") if api.tree.is_visible() then diff --git a/nvim/lua/darcula/git/blame.lua b/nvim/lua/darcula/git/blame.lua new file mode 100644 index 0000000..e9632f4 --- /dev/null +++ b/nvim/lua/darcula/git/blame.lua @@ -0,0 +1,429 @@ +-- The MIT License (MIT) +-- +-- Copyright © 2026 Scott E. Graves +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software +-- and associated documentation files (the “Software”), to deal in the Software without restriction, +-- including without limitation the rights to use, copy, modify, merge, publish, distribute, +-- sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all copies or +-- substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +-- BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-- +local M = {} + +local blame_namespace = vim.api.nvim_create_namespace("DarculaGitBlame") +local popup_namespace = vim.api.nvim_create_namespace("DarculaGitBlamePopup") +local timers = {} +local revisions = {} +local attached = {} + +local config = { + delay = 1000, + enabled = false, + highlight = "GitSignsCurrentLineBlame", + on_attach = nil, + symbol = "" +} + +local function normalize_bufnr(bufnr) + if bufnr == nil or bufnr == 0 then + return vim.api.nvim_get_current_buf() + end + return bufnr +end + +local function cancel_timer(bufnr) + local timer = timers[bufnr] + timers[bufnr] = nil + if timer and not timer:is_closing() then + timer:stop() + timer:close() + end +end + +local function clear(bufnr) + bufnr = normalize_bufnr(bufnr) + revisions[bufnr] = (revisions[bufnr] or 0) + 1 + cancel_timer(bufnr) + if vim.api.nvim_buf_is_valid(bufnr) then + vim.api.nvim_buf_clear_namespace(bufnr, blame_namespace, 0, -1) + end +end + +local function git_context(bufnr) + if not vim.api.nvim_buf_is_valid(bufnr) or vim.bo[bufnr].buftype ~= "" then + return nil + end + + local filename = vim.api.nvim_buf_get_name(bufnr) + if filename == "" then + return nil + end + + local root = vim.fs.root(filename, ".git") + if not root then + return nil + end + + local relative = vim.fs.relpath(root, filename) + if not relative then + return nil + end + + return root, relative +end + +local function parse_blame(output) + local info = {} + for index, line in ipairs(vim.split(output or "", "\n", {plain = true})) do + if index == 1 then + info.sha = line:match("^(%x+)") + elseif line:sub(1, 1) ~= "\t" then + local key, value = line:match("^(%S+)%s(.*)$") + if key then + info[key:gsub("-", "_")] = value + end + end + end + + if not info.sha or not info.author then + return nil + end + info.author_time = tonumber(info.author_time) + info.committed = not info.sha:match("^0+$") + return info +end + +local function request_blame(bufnr, line, callback) + local root, relative = git_context(bufnr) + if not root then + callback(nil) + return + end + + vim.system( + { + "git", + "-C", + root, + "blame", + "--line-porcelain", + "-L", + line .. "," .. line, + "--", + relative + }, + {text = true}, + vim.schedule_wrap( + function(result) + callback(result.code == 0 and parse_blame(result.stdout) or nil) + end + ) + ) +end + +local function render(bufnr, line, info) + if not vim.api.nvim_buf_is_valid(bufnr) then + return + end + + local text + if info.committed then + local date = info.author_time and os.date("%Y-%m-%d", info.author_time) or "" + text = config.symbol .. " " .. info.author .. ", " .. date .. + " - " .. (info.summary or "") + else + text = " " .. info.author + end + + vim.api.nvim_buf_clear_namespace(bufnr, blame_namespace, 0, -1) + vim.api.nvim_buf_set_extmark( + bufnr, + blame_namespace, + math.max(0, math.min(line - 1, vim.api.nvim_buf_line_count(bufnr) - 1)), + 0, + { + hl_mode = "combine", + priority = 100, + virt_text = {{text, config.highlight}}, + virt_text_pos = "eol" + } + ) +end + +local function update(bufnr) + bufnr = normalize_bufnr(bufnr) + if + not config.enabled or + bufnr ~= vim.api.nvim_get_current_buf() or + vim.api.nvim_get_mode().mode == "i" + then + return + end + + local line = vim.api.nvim_win_get_cursor(0)[1] + local changedtick = vim.api.nvim_buf_get_changedtick(bufnr) + local revision = revisions[bufnr] or 0 + request_blame( + bufnr, + line, + function(info) + if + not info or + revisions[bufnr] ~= revision or + not vim.api.nvim_buf_is_valid(bufnr) or + vim.api.nvim_get_current_buf() ~= bufnr or + vim.api.nvim_win_get_cursor(0)[1] ~= line or + vim.api.nvim_buf_get_changedtick(bufnr) ~= changedtick + then + return + end + render(bufnr, line, info) + end + ) +end + +function M.schedule(args) + local bufnr = normalize_bufnr(args and args.buf) + clear(bufnr) + if not config.enabled then + return + end + + local revision = revisions[bufnr] + timers[bufnr] = vim.defer_fn( + function() + timers[bufnr] = nil + if revisions[bufnr] == revision then + update(bufnr) + end + end, + config.delay + ) +end + +local function close_popup() + for _, win in ipairs(vim.api.nvim_list_wins()) do + if vim.w[win].darcula_blame_popup then + pcall(vim.api.nvim_win_close, win, true) + end + end +end + +local function show_popup(info) + close_popup() + local source_win = vim.api.nvim_get_current_win() + local source_cursor = vim.api.nvim_win_get_cursor(source_win) + + local lines + if not info.committed then + lines = {info.author} + else + local abbrev = info.sha:sub(1, 8) + local date = info.author_time and + os.date("%Y-%m-%d %H:%M", info.author_time) or + "" + lines = { + abbrev .. " " .. info.author .. " (" .. date .. "):", + info.summary or "" + } + end + + local width = 1 + for _, line in ipairs(lines) do + width = math.max(width, vim.fn.strdisplaywidth(line) + 1) + end + width = math.min(width, math.max(1, vim.api.nvim_win_get_width(0) - 2)) + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.bo[bufnr].modifiable = false + vim.bo[bufnr].bufhidden = "wipe" + + local win = vim.api.nvim_open_win( + bufnr, + false, + { + border = "rounded", + col = 1, + height = #lines, + relative = "cursor", + row = 0, + style = "minimal", + width = width + } + ) + vim.w[win].darcula_blame_popup = true + vim.wo[win].signcolumn = "no" + vim.keymap.set("n", "q", "quit!", {buffer = bufnr, silent = true}) + + local popup_group = + vim.api.nvim_create_augroup("DarculaGitBlamePopup", {clear = true}) + vim.api.nvim_create_autocmd( + {"CursorMoved", "CursorMovedI"}, + { + callback = function() + if + not vim.api.nvim_win_is_valid(win) or + vim.api.nvim_get_current_win() == win or + not vim.api.nvim_win_is_valid(source_win) + then + return + end + + local cursor = vim.api.nvim_win_get_cursor(source_win) + if cursor[1] ~= source_cursor[1] or cursor[2] ~= source_cursor[2] then + pcall(vim.api.nvim_win_close, win, true) + end + end, + group = popup_group + } + ) + + if info.committed then + local abbrev_end = 8 + local author_start = abbrev_end + 1 + local author_end = author_start + #info.author + vim.api.nvim_buf_set_extmark( + bufnr, + popup_namespace, + 0, + 0, + {end_col = abbrev_end, hl_group = "Directory"} + ) + vim.api.nvim_buf_set_extmark( + bufnr, + popup_namespace, + 0, + author_start, + {end_col = author_end, hl_group = "MoreMsg"} + ) + vim.api.nvim_buf_set_extmark( + bufnr, + popup_namespace, + 0, + author_end + 1, + {end_col = #lines[1] - 1, hl_group = "Label"} + ) + else + vim.api.nvim_buf_set_extmark( + bufnr, + popup_namespace, + 0, + 0, + {end_col = #lines[1], hl_group = "Label"} + ) + end +end + +function M.blame_line() + for _, win in ipairs(vim.api.nvim_list_wins()) do + if vim.w[win].darcula_blame_popup then + vim.api.nvim_set_current_win(win) + return + end + end + + local bufnr = vim.api.nvim_get_current_buf() + local line = vim.api.nvim_win_get_cursor(0)[1] + local changedtick = vim.api.nvim_buf_get_changedtick(bufnr) + request_blame( + bufnr, + line, + function(info) + if + not info or + not vim.api.nvim_buf_is_valid(bufnr) or + vim.api.nvim_get_current_buf() ~= bufnr or + vim.api.nvim_win_get_cursor(0)[1] ~= line or + vim.api.nvim_buf_get_changedtick(bufnr) ~= changedtick + then + return + end + show_popup(info) + end + ) +end + +function M.toggle(value) + config.enabled = value == nil and not config.enabled or value + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + clear(bufnr) + end + if config.enabled then + M.schedule({buf = vim.api.nvim_get_current_buf()}) + end + return config.enabled +end + +local function attach(bufnr) + if attached[bufnr] or not git_context(bufnr) then + return + end + attached[bufnr] = true + if config.on_attach then + config.on_attach(bufnr) + end +end + +function M.setup(opts) + config = vim.tbl_deep_extend("force", config, opts or {}) + local group = vim.api.nvim_create_augroup("DarculaGitBlame", {clear = true}) + + vim.api.nvim_create_autocmd( + {"BufReadPost", "BufNewFile"}, + { + callback = function(args) + attach(args.buf) + end, + group = group + } + ) + vim.api.nvim_create_autocmd( + {"BufEnter", "CursorMoved", "CursorMovedI", "FocusGained", "WinResized"}, + {callback = M.schedule, group = group} + ) + vim.api.nvim_create_autocmd( + {"BufLeave", "FocusLost", "InsertEnter"}, + {callback = function(args) clear(args.buf) end, group = group} + ) + vim.api.nvim_create_autocmd( + "BufWritePost", + { + callback = function(args) + M.schedule(args) + vim.api.nvim_exec_autocmds( + "User", + {pattern = "DarculaGitUpdate"} + ) + end, + group = group + } + ) + vim.api.nvim_create_autocmd( + "BufWipeout", + { + callback = function(args) + clear(args.buf) + attached[args.buf] = nil + revisions[args.buf] = nil + end, + group = group + } + ) + + attach(vim.api.nvim_get_current_buf()) + if config.enabled then + M.schedule({buf = vim.api.nvim_get_current_buf()}) + end +end + +return M diff --git a/nvim/lua/darcula/keymaps.lua b/nvim/lua/darcula/keymaps.lua index a08ba10..f6c9a23 100644 --- a/nvim/lua/darcula/keymaps.lua +++ b/nvim/lua/darcula/keymaps.lua @@ -512,14 +512,14 @@ function M.flutter() end end -function M.gitsigns(bufnr) +function M.git_blame(bufnr) local opts = {remap = false, silent = true, buffer = bufnr} km.nmap( km.leader2 "tb", function() NV_DARCULA_ENABLE_GIT_LINE_BLAME = not NV_DARCULA_ENABLE_GIT_LINE_BLAME - require("gitsigns").toggle_current_line_blame( + require("darcula.git.blame").toggle( NV_DARCULA_ENABLE_GIT_LINE_BLAME ) utils.notify_setting_enabled( @@ -532,9 +532,7 @@ function M.gitsigns(bufnr) km.nmap( km.leader "bl", - function() - vim.cmd("Gitsigns blame_line") - end, + require("darcula.git.blame").blame_line, opts ) end diff --git a/nvim/lua/darcula/plugins.lua b/nvim/lua/darcula/plugins.lua index 7c2c76c..66489bb 100644 --- a/nvim/lua/darcula/plugins.lua +++ b/nvim/lua/darcula/plugins.lua @@ -96,7 +96,6 @@ vim.list_extend( {src = "https://github.com/dstein64/nvim-scrollview"}, {src = "https://github.com/kyazdani42/nvim-tree.lua"}, {src = "https://github.com/leoluz/nvim-dap-go"}, - {src = "https://github.com/lewis6991/gitsigns.nvim"}, {src = "https://github.com/mfussenegger/nvim-dap"}, {src = "https://github.com/mhartington/formatter.nvim"}, {src = "https://github.com/nvim-mini/mini.nvim"}, diff --git a/nvim/lua/darcula/setup/500_gitsigns.lua b/nvim/lua/darcula/setup/500_git_blame.lua similarity index 72% rename from nvim/lua/darcula/setup/500_gitsigns.lua rename to nvim/lua/darcula/setup/500_git_blame.lua index dc903ea..987d844 100644 --- a/nvim/lua/darcula/setup/500_gitsigns.lua +++ b/nvim/lua/darcula/setup/500_git_blame.lua @@ -20,22 +20,12 @@ local keymaps = require("darcula.keymaps") local symbols = require("darcula.utils.symbols") -require("gitsigns").setup { - current_line_blame = NV_DARCULA_ENABLE_GIT_LINE_BLAME, - current_line_blame_opts = { - virt_text = true, - virt_text_pos = "eol", +require("darcula.git.blame").setup( + { delay = 1000, - ignore_whitespace = false - }, - current_line_blame_formatter = symbols.git.blame .. - " , - ", - linehl = false, - numhl = false, - preview_config = {border = "rounded"}, - signcolumn = false, - word_diff = false, - on_attach = function(bufnr) - keymaps.gitsigns(bufnr) - end -} + enabled = NV_DARCULA_ENABLE_GIT_LINE_BLAME, + highlight = "GitSignsCurrentLineBlame", + on_attach = keymaps.git_blame, + symbol = symbols.git.blame + } +)