refactor(git): rework blame highlights and rename overlay to gutter

This commit is contained in:
2026-05-26 14:52:59 +02:00
parent a0a8d723d6
commit c560f62fb2
5 changed files with 157 additions and 109 deletions
+2 -2
View File
@@ -235,11 +235,11 @@ vim.keymap.set("n", "<leader>gc", "<Plug>(git-commit)")
vim.keymap.set("n", "<leader>ga", "<Plug>(git-commit-amend)")
vim.keymap.set("n", "<leader>gl", "<Plug>(git-log)")
vim.keymap.set("n", "<leader>gb", "<Plug>(git-blame-popup)")
vim.keymap.set("n", "<leader>gB", "<Plug>(git-blame)")
vim.keymap.set("n", "<leader>gB", "<Plug>(git-blame-gutter)")
vim.keymap.set("n", "<leader>gv", "<Plug>(git-hunk-select)")
vim.keymap.set("n", "<leader>gs", "<Plug>(git-hunk-stage-toggle)")
vim.keymap.set("n", "<leader>gr", "<Plug>(git-hunk-reset)")
vim.keymap.set("n", "<C-w>g", "<Plug>(git-hunk-preview)")
vim.keymap.set("n", "<leader>go", "<Plug>(git-diff-overlay)")
vim.keymap.set("n", "<leader>go", "<Plug>(git-hunk-overlay-toggle)")
vim.keymap.set({ "n", "x" }, "]g", "<Plug>(git-hunk-next)")
vim.keymap.set({ "n", "x" }, "[g", "<Plug>(git-hunk-prev)")
+70 -52
View File
@@ -41,14 +41,14 @@ local PREFERRED_WIDTH = SHA_WIDTH + AUTHOR_MAX + DATE_WIDTH + 3 * #GAP
---@field revision string? nil = working tree, else the blamed revision
---@field commits table<string, ow.Git.Blame.Commit>
---@field line_sha table<integer, string>
---@field blame_text table<integer, string>? cached overlay gutter text
---@field blame_width integer? display width of each cached segment
---@field blame_blank string? a blank segment of that width
---@field blame_text table<integer, string>?
---@field blame_width integer?
---@field blame_blank string?
---@field tick integer?
---@field epoch integer
---@field pending fun()[]
---@field inline boolean
---@field overlay boolean
---@field gutter boolean
---@field autocmds integer[]
---@type table<integer, ow.Git.Blame.BufState>
@@ -66,13 +66,6 @@ function M.state(buf)
return states[buf]
end
---@param n integer
---@param unit string
---@return string
local function plural(n, unit)
return string.format("%d %s%s ago", n, unit, n == 1 and "" or "s")
end
---@param unix_ts integer
---@return string
local function relative_time(unix_ts)
@@ -80,34 +73,47 @@ local function relative_time(unix_ts)
if diff < 0 then
diff = 0
end
local function fmt(n, unit)
return string.format("%d %s%s ago", n, unit, n == 1 and "" or "s")
end
if diff < 45 then
return "just now"
elseif diff < 90 then
return "a minute ago"
elseif diff < 45 * 60 then
return plural(math.floor(diff / 60 + 0.5), "minute")
return fmt(math.floor(diff / 60 + 0.5), "minute")
elseif diff < 90 * 60 then
return "an hour ago"
elseif diff < 22 * 3600 then
return plural(math.floor(diff / 3600 + 0.5), "hour")
return fmt(math.floor(diff / 3600 + 0.5), "hour")
elseif diff < 36 * 3600 then
return "a day ago"
elseif diff < 7 * 86400 then
return plural(math.floor(diff / 86400 + 0.5), "day")
return fmt(math.floor(diff / 86400 + 0.5), "day")
elseif diff < 30 * 86400 then
return plural(math.floor(diff / (7 * 86400) + 0.5), "week")
return fmt(math.floor(diff / (7 * 86400) + 0.5), "week")
elseif diff < 365 * 86400 then
return plural(math.floor(diff / (30 * 86400) + 0.5), "month")
return fmt(math.floor(diff / (30 * 86400) + 0.5), "month")
end
return plural(math.floor(diff / (365 * 86400) + 0.5), "year")
return fmt(math.floor(diff / (365 * 86400) + 0.5), "year")
end
M.relative_time = relative_time
---@param ts integer
---@param tz string
---@return string
local function format_date(ts)
return os.date("%Y-%m-%d", ts) --[[@as string]]
local function format_author_time(ts, tz)
local sign, hh, mm = tz:match("^([+-])(%d%d)(%d%d)$")
---@type number
local offset = 0
if sign then
local h = tonumber(hh) or 0
local m = tonumber(mm) or 0
offset = (h * 3600 + m * 60) * (sign == "-" and -1 or 1)
end
return os.date("!%Y-%m-%d %T ", ts + offset) .. tz
end
---@param s string
@@ -285,7 +291,7 @@ local function ensure_state(buf)
epoch = 0,
pending = {},
inline = false,
overlay = false,
gutter = false,
autocmds = {},
}
states[buf] = state
@@ -378,7 +384,7 @@ local function render_inline(buf)
)
end
pcall(vim.api.nvim_buf_set_extmark, buf, NS_INLINE, lnum - 1, 0, {
virt_text = { { text, "GitBlame" } },
virt_text = { { text, "GitBlameInline" } },
virt_text_pos = "eol",
hl_mode = "combine",
})
@@ -406,7 +412,7 @@ 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
---overlay itself has already inflated.
---gutter itself has already inflated.
---@param win integer
---@return integer
local function native_width(win)
@@ -472,13 +478,14 @@ local function build_blame_text(state, win)
author, date = "Uncommitted", ""
else
author = commit.author
date = format_date(commit.author_time)
date = os.date("%Y-%m-%d", commit.author_time)
end
text[lnum] = "%#GitBlameSha#"
.. sha:sub(1, sha_w)
.. "%#GitBlame#"
.. "%#GitBlameAuthor#"
.. GAP
.. (pad(author, author_w):gsub("%%", "%%%%"))
.. "%#GitBlameDate#"
.. GAP
.. pad(date, date_w)
.. GAP
@@ -491,7 +498,7 @@ local function build_blame_text(state, win)
end
---Render the blame segment for one screen line. Wired into the window's
---`'statuscolumn'` while the overlay is on, so the cursor never enters
---`'statuscolumn'` while the gutter is on, so the cursor never enters
---it - it lives outside the text area, unlike inline virtual text.
---@param win integer?
---@param lnum integer
@@ -502,7 +509,7 @@ local function gutter(win, lnum, virtnum)
return ""
end
local state = states[vim.api.nvim_win_get_buf(win)]
if not state or not state.overlay or not state.blame_text then
if not state or not state.gutter or not state.blame_text then
return ""
end
return (virtnum == 0 and state.blame_text[lnum]) or state.blame_blank or ""
@@ -533,10 +540,10 @@ local function set_statuscolumn(win, value)
)
end
---Reconcile every window's `'statuscolumn'` with the overlay state.
---Overlay windows get the blame statuscolumn, and a window that has it
---Reconcile every window's `'statuscolumn'` with the gutter state.
---Gutter windows get the blame statuscolumn, and a window that has it
---but should not (a split inherits window options) is restored.
local function refresh_overlay_columns()
local function refresh_gutter_columns()
for win in pairs(saved_statuscolumn) do
if not vim.api.nvim_win_is_valid(win) then
saved_statuscolumn[win] = nil
@@ -544,7 +551,7 @@ local function refresh_overlay_columns()
end
for _, win in ipairs(vim.api.nvim_list_wins()) do
local state = states[vim.api.nvim_win_get_buf(win)]
local on = state ~= nil and state.overlay
local on = state ~= nil and state.gutter
local has_blame = vim.startswith(vim.wo[win].statuscolumn, BLAME_EXPR)
if on and not has_blame then
saved_statuscolumn[win] = saved_statuscolumn[win]
@@ -564,7 +571,7 @@ end
local function render(buf)
render_inline(buf)
local state = states[buf]
if not state or not state.overlay then
if not state or not state.gutter then
return
end
-- Rebuild against the current native column widths, then re-set
@@ -585,7 +592,7 @@ end
---@param buf integer
local function reblame(buf)
local state = states[buf]
if not state or (not state.inline and not state.overlay) then
if not state or (not state.inline and not state.gutter) then
return
end
run_blame(state, buf, function()
@@ -658,23 +665,23 @@ function M.toggle_inline(buf)
if vim.api.nvim_buf_is_valid(buf) then
vim.api.nvim_buf_clear_namespace(buf, NS_INLINE, 0, -1)
end
if not state.overlay then
if not state.gutter then
detach_autocmds(buf, state)
end
end
end
---@param buf integer?
function M.toggle_overlay(buf)
function M.toggle_gutter(buf)
buf = resolve_buf(buf)
local state = ensure_state(buf)
if not state then
util.warning("git blame: nothing to blame in this buffer")
return
end
state.overlay = not state.overlay
refresh_overlay_columns()
if state.overlay then
state.gutter = not state.gutter
refresh_gutter_columns()
if state.gutter then
attach_autocmds(buf, state)
run_blame(state, buf, function()
render(buf)
@@ -714,7 +721,8 @@ end
---@param head string[]
---@param body string[]?
---@param sha_len integer?
local function apply_popup(pbuf, win, head, body, sha_len)
---@param date_col integer?
local function apply_popup(pbuf, win, head, body, sha_len, date_col)
local lines = {}
vim.list_extend(lines, head)
if body then
@@ -729,9 +737,17 @@ local function apply_popup(pbuf, win, head, body, sha_len)
end_col = sha_len,
hl_group = "GitBlameSha",
})
pcall(vim.api.nvim_buf_set_extmark, pbuf, NS_POPUP, 1, 0, {
end_col = #(head[2] or ""),
hl_group = "GitBlame",
end
if sha_len and date_col then
pcall(vim.api.nvim_buf_set_extmark, pbuf, NS_POPUP, 0, sha_len + 2, {
end_col = date_col - 2,
hl_group = "GitBlameAuthor",
})
end
if date_col then
pcall(vim.api.nvim_buf_set_extmark, pbuf, NS_POPUP, 0, date_col, {
end_col = #(head[1] or ""),
hl_group = "GitBlameDate",
})
end
local width, height = size_for(lines)
@@ -780,14 +796,16 @@ local function open_popup(r, commits, line_sha, lnum, watch_buf)
end
local head ---@type string[]
local sha_len ---@type integer?
local date_col ---@type integer?
if util.is_zero_sha(sha) then
head = { "Not Committed Yet" }
else
local short = sha:sub(1, 8)
local date = format_author_time(commit.author_time, commit.author_tz)
sha_len = #short
date_col = sha_len + 2 + #commit.author + 2
head = {
short .. " " .. commit.author,
commit.author_mail .. " " .. relative_time(commit.author_time),
short .. " " .. commit.author .. " " .. date,
"",
}
end
@@ -809,7 +827,7 @@ local function open_popup(r, commits, line_sha, lnum, watch_buf)
style = "minimal",
})
popup_win = win
apply_popup(pbuf, win, head, body, sha_len)
apply_popup(pbuf, win, head, body, sha_len, date_col)
setup_popup_autocmds(watch_buf, pbuf, win)
if not sha_len then
return
@@ -828,7 +846,7 @@ local function open_popup(r, commits, line_sha, lnum, watch_buf)
end
local msg = util.split_lines(res.stdout or "")
if #msg > 0 then
apply_popup(pbuf, win, head, msg, sha_len)
apply_popup(pbuf, win, head, msg, sha_len, date_col)
end
end,
})
@@ -919,17 +937,17 @@ function M.detach(buf)
detach_autocmds(buf, state)
state.epoch = state.epoch + 1
states[buf] = nil
refresh_overlay_columns()
refresh_gutter_columns()
end
local augroup = vim.api.nvim_create_augroup("ow.git.blame", { clear = true })
vim.api.nvim_create_autocmd("BufWinEnter", {
group = augroup,
callback = refresh_overlay_columns,
callback = refresh_gutter_columns,
})
-- The blame budget depends on the gutter option widths, so re-render an
-- overlay buffer when one of them changes.
-- gutter buffer when one of them changes.
vim.api.nvim_create_autocmd("OptionSet", {
group = augroup,
pattern = {
@@ -942,7 +960,7 @@ vim.api.nvim_create_autocmd("OptionSet", {
callback = function()
local buf = vim.api.nvim_get_current_buf()
local state = states[buf]
if state and state.overlay then
if state and state.gutter then
render(buf)
end
end,
@@ -960,7 +978,7 @@ repo.on("change", function(r, change)
and (change.paths[state.rel] or change.branch_changed)
then
state.tick = nil
if state.inline or state.overlay then
if state.inline or state.gutter then
schedule(buf)
end
end
+10 -6
View File
@@ -4,6 +4,8 @@ end
vim.g.loaded_git = 1
local DEFAULT_HIGHLIGHTS = {
GitAuthor = "String",
GitDate = "Number",
GitIgnored = "Comment",
GitSha = "Identifier",
GitStaged = "Constant",
@@ -41,7 +43,9 @@ local DEFAULT_HIGHLIGHTS = {
GitHunkAddLine = "DiffAdd",
GitHunkDeleteLine = "DiffDelete",
GitBlame = "Comment",
GitBlameAuthor = "GitAuthor",
GitBlameDate = "GitDate",
GitBlameInline = "Comment",
GitBlameSha = "GitSha",
}
local STAGED_HUNK_HL = {
@@ -318,9 +322,9 @@ vim.api.nvim_create_user_command("GitDiffOverlay", function()
require("git.hunks").toggle_overlay()
end, { desc = "Toggle the git diff overlay in the current buffer" })
vim.keymap.set("n", "<Plug>(git-blame)", function()
require("git.blame").toggle_overlay()
end, { silent = true, desc = "Toggle the full-file git blame overlay" })
vim.keymap.set("n", "<Plug>(git-blame-gutter)", function()
require("git.blame").toggle_gutter()
end, { silent = true, desc = "Toggle the full-file git blame gutter" })
vim.keymap.set("n", "<Plug>(git-blame-line)", function()
require("git.blame").toggle_inline()
end, { silent = true, desc = "Toggle inline git blame" })
@@ -341,8 +345,8 @@ end, {
})
vim.api.nvim_create_user_command("GitBlame", function()
require("git.blame").toggle_overlay()
end, { desc = "Toggle the full-file git blame overlay in the current buffer" })
require("git.blame").toggle_gutter()
end, { desc = "Toggle the full-file git blame gutter in the current buffer" })
vim.api.nvim_create_user_command("GitBlameLine", function()
require("git.blame").toggle_inline()
end, { desc = "Toggle inline git blame in the current buffer" })
+2 -2
View File
@@ -19,8 +19,8 @@ syntax match gitlogGraphLine /^[*|\\\/_ ]\+$/
highlight default link gitlogGraph Comment
highlight default link gitlogHash GitSha
highlight default link gitlogDate Number
highlight default link gitlogAuthor String
highlight default link gitlogDate GitDate
highlight default link gitlogAuthor GitAuthor
highlight default link gitlogRef Constant
let b:current_syntax = "gitlog"
+73 -47
View File
@@ -76,19 +76,45 @@ local function wait_buf_name(pat)
end, "current buffer name to match " .. pat)
end
t.test("relative_time buckets", function()
local now = os.time()
t.eq(blame.relative_time(now), "just now")
t.eq(blame.relative_time(now - 10), "just now")
t.eq(blame.relative_time(now - 60), "a minute ago")
t.eq(blame.relative_time(now - 5 * 60), "5 minutes ago")
t.eq(blame.relative_time(now - 60 * 60), "an hour ago")
t.eq(blame.relative_time(now - 3 * 3600), "3 hours ago")
t.eq(blame.relative_time(now - 26 * 3600), "a day ago")
t.eq(blame.relative_time(now - 3 * 86400), "3 days ago")
t.eq(blame.relative_time(now - 14 * 86400), "2 weeks ago")
t.eq(blame.relative_time(now - 60 * 86400), "2 months ago")
t.eq(blame.relative_time(now - 400 * 86400), "1 year ago")
t.test("inline annotation includes the relative time", function()
local _, buf = setup("alpha\nbeta\ngamma\n")
vim.api.nvim_set_current_buf(buf)
vim.api.nvim_win_set_cursor(0, { 1, 0 })
enable_blame(buf)
t.wait_for(function()
return #inline_marks(buf) == 1
end, "an inline annotation on the current line")
local mark = assert(inline_marks(buf)[1])
local details = assert(mark[4])
local virt_text = assert(details.virt_text)
local chunk = assert(virt_text[1])
t.truthy(
chunk[1]:find("just now", 1, true),
"a fresh commit reads as 'just now' in the annotation"
)
end)
t.test("line popup formats the datetime in the author timezone", function()
local _, buf = setup("alpha\nbeta\n")
vim.api.nvim_set_current_buf(buf)
vim.api.nvim_win_set_cursor(0, { 1, 0 })
blame.line_popup(buf)
local float = wait_float()
t.defer(function()
pcall(vim.api.nvim_win_close, float, true)
end)
local lines = vim.api.nvim_buf_get_lines(
vim.api.nvim_win_get_buf(float),
0,
-1,
false
)
t.truthy(
(lines[1] or ""):match(
"%d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d [+%-]%d%d%d%d$"
),
"the head line ends with an ISO datetime and numeric timezone"
)
end)
t.test("blame layout squeezes the author before date and sha", function()
@@ -225,7 +251,7 @@ t.test("blame actions are no-ops off a worktree", function()
t.quietly(function()
blame.line_popup(buf)
blame.toggle_inline(buf)
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
end)
t.eq(blame.state(buf), nil, "no state created for a non-worktree buffer")
end)
@@ -329,20 +355,20 @@ t.test("inline annotation follows the cursor", function()
t.eq(assert(inline_marks(buf)[1])[2], 2, "annotation moved to line 3")
end)
t.test("overlay toggle sets and clears the statuscolumn", function()
t.test("gutter toggle sets and clears the statuscolumn", function()
local _, buf = setup("a\nb\nc\nd\n")
vim.api.nvim_set_current_buf(buf)
local win = vim.api.nvim_get_current_win()
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.truthy(
vim.wo[win].statuscolumn ~= "",
"the overlay sets the window statuscolumn"
"the gutter sets the window statuscolumn"
)
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.eq(vim.wo[win].statuscolumn, "", "toggling off clears it")
end)
t.test("overlay saves and restores the statuscolumn", function()
t.test("gutter saves and restores the statuscolumn", function()
local _, buf = setup("a\nb\nc\n")
vim.api.nvim_set_current_buf(buf)
local win = vim.api.nvim_get_current_win()
@@ -354,21 +380,21 @@ t.test("overlay saves and restores the statuscolumn", function()
end)
vim.wo[win].statuscolumn = "%l custom"
vim.wo[win].signcolumn = "yes:2"
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.truthy(
vim.wo[win].statuscolumn ~= "%l custom",
"the overlay overrides a custom statuscolumn"
"the gutter overrides a custom statuscolumn"
)
t.eq(
vim.wo[win].signcolumn,
"yes:2",
"the overlay leaves signcolumn untouched"
"the gutter leaves signcolumn untouched"
)
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.eq(vim.wo[win].statuscolumn, "%l custom", "statuscolumn restored")
end)
t.test("overlay gutter uses the full preferred width when it can", function()
t.test("gutter uses the full preferred width when it can", function()
local _, buf = setup("a\nb\nc\n")
vim.api.nvim_set_current_buf(buf)
local win = vim.api.nvim_get_current_win()
@@ -381,11 +407,11 @@ t.test("overlay gutter uses the full preferred width when it can", function()
vim.wo[win].relativenumber = false
vim.wo[win].signcolumn = "no"
vim.wo[win].foldcolumn = "0"
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.wait_for(function()
local s = blame.state(buf)
return s ~= nil and s.blame_width ~= nil
end, "the overlay blame to render")
end, "the gutter blame to render")
t.eq(
assert(blame.state(buf)).blame_width,
40,
@@ -393,7 +419,7 @@ t.test("overlay gutter uses the full preferred width when it can", function()
)
end)
t.test("overlay gutter is budgeted under the 47-cell cap", function()
t.test("gutter is budgeted under the 47-cell cap", function()
local _, buf = setup("a\nb\nc\n")
vim.api.nvim_set_current_buf(buf)
local win = vim.api.nvim_get_current_win()
@@ -407,11 +433,11 @@ t.test("overlay gutter is budgeted under the 47-cell cap", function()
vim.wo[win].relativenumber = false
vim.wo[win].foldcolumn = "0"
vim.wo[win].signcolumn = "yes:9"
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.wait_for(function()
local s = blame.state(buf)
return s ~= nil and s.blame_width ~= nil
end, "the overlay blame to render")
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")
@@ -419,7 +445,7 @@ t.test("overlay gutter is budgeted under the 47-cell cap", function()
t.truthy(width + native <= 47, "blame plus native columns fits the cap")
end)
t.test("overlay re-budgets when a gutter option changes", function()
t.test("gutter re-budgets when a gutter option changes", function()
local _, buf = setup("a\nb\nc\n")
vim.api.nvim_set_current_buf(buf)
local win = vim.api.nvim_get_current_win()
@@ -433,11 +459,11 @@ t.test("overlay re-budgets when a gutter option changes", function()
vim.wo[win].relativenumber = false
vim.wo[win].foldcolumn = "0"
vim.wo[win].signcolumn = "no"
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.wait_for(function()
local s = blame.state(buf)
return s ~= nil and s.blame_width ~= nil
end, "the overlay blame to render")
end, "the gutter blame to render")
t.eq(
assert(blame.state(buf)).blame_width,
40,
@@ -450,17 +476,17 @@ t.test("overlay re-budgets when a gutter option changes", function()
end, "the blame to re-budget for the widened signcolumn")
end)
t.test("the overlay statuscolumn does not leak into other windows", function()
t.test("the gutter statuscolumn does not leak into other windows", function()
local _, buf = setup("a\nb\nc\n")
vim.api.nvim_set_current_buf(buf)
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.wait_for(function()
local s = blame.state(buf)
return s ~= nil and s.blame_width ~= nil
end, "the overlay to render")
end, "the gutter to render")
t.falsy(
vim.go.statuscolumn:find("git.blame", 1, true),
"the overlay leaves the global statuscolumn untouched"
"the gutter leaves the global statuscolumn untouched"
)
vim.cmd("new")
@@ -476,15 +502,15 @@ t.test("the overlay statuscolumn does not leak into other windows", function()
)
end)
t.test("overlay gutter shows sha, author and an absolute date", function()
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_overlay(buf)
blame.toggle_gutter(buf)
t.wait_for(function()
local s = blame.state(buf)
return s ~= nil and s.tick ~= nil
end, "the overlay blame to populate")
end, "the gutter blame to populate")
local g = blame._gutter(win, 1, 0)
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")
@@ -494,15 +520,15 @@ t.test("overlay gutter shows sha, author and an absolute date", function()
)
end)
t.test("overlay gutter is blank on virtual lines", function()
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_overlay(buf)
blame.toggle_gutter(buf)
t.wait_for(function()
local s = blame.state(buf)
return s ~= nil and s.tick ~= nil
end, "the overlay blame to populate")
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")
end)
@@ -512,11 +538,11 @@ t.test("the statuscolumn expression renders the blame gutter", function()
local sha = h.git(dir, "rev-parse", "HEAD").stdout
vim.api.nvim_set_current_buf(buf)
local win = vim.api.nvim_get_current_win()
blame.toggle_overlay(buf)
blame.toggle_gutter(buf)
t.wait_for(function()
local s = blame.state(buf)
return s ~= nil and s.tick ~= nil
end, "the overlay blame to populate")
end, "the gutter blame to populate")
local rendered = vim.api.nvim_eval_statusline(
"%{%v:lua.require('git.blame').statuscolumn()%}",
{ winid = win, use_statuscol_lnum = 1 }
@@ -589,10 +615,10 @@ t.test("detach clears blame state and annotations", function()
vim.api.nvim_set_current_buf(buf)
local win = vim.api.nvim_get_current_win()
enable_blame(buf)
blame.toggle_overlay(buf)
t.truthy(vim.wo[win].statuscolumn ~= "", "the overlay statuscolumn set")
blame.toggle_gutter(buf)
t.truthy(vim.wo[win].statuscolumn ~= "", "the gutter statuscolumn set")
blame.detach(buf)
t.eq(blame.state(buf), nil, "state dropped on detach")
t.eq(#inline_marks(buf), 0, "inline annotation cleared")
t.eq(vim.wo[win].statuscolumn, "", "overlay statuscolumn cleared")
t.eq(vim.wo[win].statuscolumn, "", "gutter statuscolumn cleared")
end)