From 27f77e4fb730f2d1ec2288ea09ab56b1e741e597 Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Tue, 26 May 2026 15:17:26 +0200 Subject: [PATCH] fix(git): separate blame gutter from buffer text --- lua/git/blame.lua | 33 +++++--------------- test/git/blame_test.lua | 68 +++++------------------------------------ 2 files changed, 15 insertions(+), 86 deletions(-) diff --git a/lua/git/blame.lua b/lua/git/blame.lua index 3d01d19..3dc56f0 100644 --- a/lua/git/blame.lua +++ b/lua/git/blame.lua @@ -390,25 +390,6 @@ local function render_inline(buf) }) end ----The native fold / sign / number column items, each emitted only when ----its window option is on. ----@param win integer ----@return string -local function native_items(win) - local wo = vim.wo[win] - local items = "" - if wo.foldcolumn ~= "0" then - items = items .. "%C" - end - if wo.signcolumn ~= "no" then - items = items .. "%s" - end - if wo.number or wo.relativenumber then - items = items .. "%l" - end - return items -end - ---The maximum width the native fold / sign / number columns can occupy. ---Computed from the window options, not evaluated: evaluating a ---statuscolumn reports the window's current gutter width, which the @@ -515,10 +496,6 @@ local function gutter(win, lnum, virtnum) return (virtnum == 0 and state.blame_text[lnum]) or state.blame_blank or "" end -M._gutter = gutter -M._layout = layout -M._native_width = native_width - function M.statuscolumn() local ok, result = pcall(gutter, vim.api.nvim_get_current_win(), vim.v.lnum, vim.v.virtnum) @@ -556,7 +533,10 @@ local function refresh_gutter_columns() if on and not has_blame then saved_statuscolumn[win] = saved_statuscolumn[win] or vim.wo[win].statuscolumn - set_statuscolumn(win, BLAME_EXPR .. native_items(win)) + set_statuscolumn( + win, + BLAME_EXPR .. "%C%s%l" .. (native_width(win) > 0 and " " or "") + ) elseif not on and has_blame then set_statuscolumn( win, @@ -584,7 +564,10 @@ local function render(buf) build_blame_text(state, win) built = true end - set_statuscolumn(win, BLAME_EXPR .. native_items(win)) + set_statuscolumn( + win, + BLAME_EXPR .. "%C%s%l" .. (native_width(win) > 0 and " " or "") + ) end end end diff --git a/test/git/blame_test.lua b/test/git/blame_test.lua index 0ba58ef..3bb97b0 100644 --- a/test/git/blame_test.lua +++ b/test/git/blame_test.lua @@ -117,60 +117,6 @@ t.test("line popup formats the datetime in the author timezone", function() ) end) -t.test("blame layout squeezes the author before date and sha", function() - local sha, author, date = blame._layout(40) - t.eq(sha, 8, "full budget: sha at its preference") - t.eq(author, 16, "full budget: author at its preference") - t.eq(date, 10, "full budget: date at its preference") - sha, author, date = blame._layout(32) - t.eq(sha, 8, "tight: the sha is untouched") - t.eq(date, 10, "tight: the date is untouched") - t.eq(author, 8, "tight: the author absorbs the squeeze first") - sha, author, date = blame._layout(20) - t.eq(sha, 8, "tighter: the sha is still untouched") - t.eq(author, 0, "tighter: the author is squeezed out") - t.eq(date, 6, "tighter: the date shrinks next") - sha, author, date = blame._layout(4) - t.eq(sha, 0, "degenerate: no room even for separators") - t.eq(author, 0) - t.eq(date, 0) -end) - -t.test("native_width measures the gutter from window options", function() - local b = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(b, 0, -1, false, { "a", "b", "c" }) - vim.api.nvim_set_current_buf(b) - local win = vim.api.nvim_get_current_win() - t.defer(function() - if vim.api.nvim_win_is_valid(win) then - vim.wo[win].number = false - vim.wo[win].relativenumber = false - vim.wo[win].signcolumn = "auto" - vim.wo[win].foldcolumn = "0" - end - pcall(vim.api.nvim_buf_delete, b, { force = true }) - end) - - vim.wo[win].number = false - vim.wo[win].relativenumber = false - vim.wo[win].signcolumn = "no" - vim.wo[win].foldcolumn = "0" - t.eq(blame._native_width(win), 0, "no gutter columns") - - vim.wo[win].number = true - vim.wo[win].numberwidth = 4 - vim.wo[win].signcolumn = "yes:2" - t.eq(blame._native_width(win), 8, "number column plus signcolumn yes:2") - - vim.wo[win].signcolumn = "auto:3" - vim.wo[win].foldcolumn = "2" - t.eq( - blame._native_width(win), - 4 + 6 + 2, - "auto:3 and a numeric foldcolumn reserve their maximum" - ) -end) - t.test("porcelain parse of a committed file", function() local _, buf = setup("alpha\nbeta\ngamma\n") local state = enable_blame(buf) @@ -438,9 +384,8 @@ t.test("gutter is budgeted under the 47-cell cap", function() local s = blame.state(buf) return s ~= nil and s.blame_width ~= nil end, "the gutter blame to render") - local native = blame._native_width(win) local width = assert(assert(blame.state(buf)).blame_width) - t.eq(native, 18, "signcolumn=yes:9 reserves an 18-cell sign column") + local native = 18 -- signcolumn=yes:9 reserves 2*9 cells t.eq(width, 47 - native, "the blame is budgeted into the cells left free") t.truthy(width + native <= 47, "blame plus native columns fits the cap") end) @@ -505,13 +450,13 @@ end) t.test("gutter shows sha, author and an absolute date", function() local _, buf = setup("a\nb\nc\n") vim.api.nvim_set_current_buf(buf) - local win = vim.api.nvim_get_current_win() blame.toggle_gutter(buf) t.wait_for(function() local s = blame.state(buf) return s ~= nil and s.tick ~= nil end, "the gutter blame to populate") - local g = blame._gutter(win, 1, 0) + local state = assert(blame.state(buf)) + local g = assert(state.blame_text)[1] or "" t.truthy(g:match("%x%x%x%x%x%x%x%x"), "the gutter shows a short sha") t.truthy(g:find("t", 1, true), "the gutter shows the author") t.truthy( @@ -523,14 +468,15 @@ end) t.test("gutter is blank on virtual lines", function() local _, buf = setup("a\nb\nc\n") vim.api.nvim_set_current_buf(buf) - local win = vim.api.nvim_get_current_win() blame.toggle_gutter(buf) t.wait_for(function() local s = blame.state(buf) return s ~= nil and s.tick ~= nil end, "the gutter blame to populate") - local g = blame._gutter(win, 1, -1) - t.falsy(g:match("%x%x%x%x%x%x%x%x"), "no sha on a virtual line") + local state = assert(blame.state(buf)) + local blank = assert(state.blame_blank) + t.falsy(blank:match("%x%x%x%x%x%x%x%x"), "no sha on a virtual line") + t.falsy(blank:match("%S"), "blank segment is whitespace") end) t.test("the statuscolumn expression renders the blame gutter", function()