refactor(git): remove blame gutter
This commit is contained in:
@@ -235,7 +235,6 @@ vim.keymap.set("n", "<leader>gc", "<Plug>(git-commit)")
|
|||||||
vim.keymap.set("n", "<leader>ga", "<Plug>(git-commit-amend)")
|
vim.keymap.set("n", "<leader>ga", "<Plug>(git-commit-amend)")
|
||||||
vim.keymap.set("n", "<leader>gl", "<Plug>(git-log)")
|
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-popup)")
|
||||||
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>gv", "<Plug>(git-hunk-select)")
|
||||||
vim.keymap.set("n", "<leader>gs", "<Plug>(git-hunk-stage-toggle)")
|
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", "<leader>gr", "<Plug>(git-hunk-reset)")
|
||||||
|
|||||||
+3
-263
@@ -8,15 +8,6 @@ local NS_INLINE = vim.api.nvim_create_namespace("ow.git.blame.inline")
|
|||||||
local NS_POPUP = vim.api.nvim_create_namespace("ow.git.blame.popup")
|
local NS_POPUP = vim.api.nvim_create_namespace("ow.git.blame.popup")
|
||||||
|
|
||||||
local ZERO_SHA = string.rep("0", 40)
|
local ZERO_SHA = string.rep("0", 40)
|
||||||
local BLAME_EXPR = "%{%v:lua.require('git.blame').statuscolumn()%}"
|
|
||||||
|
|
||||||
-- Neovim collapses the gutter once the statuscolumn passes 47 cells.
|
|
||||||
local STATUSCOLUMN_MAX = 47
|
|
||||||
local GAP = " "
|
|
||||||
local SHA_WIDTH = 8
|
|
||||||
local AUTHOR_MAX = 16
|
|
||||||
local DATE_WIDTH = 10
|
|
||||||
local PREFERRED_WIDTH = SHA_WIDTH + AUTHOR_MAX + DATE_WIDTH + 3 * #GAP
|
|
||||||
|
|
||||||
---@class ow.Git.Blame.Commit
|
---@class ow.Git.Blame.Commit
|
||||||
---@field sha string
|
---@field sha string
|
||||||
@@ -41,14 +32,10 @@ local PREFERRED_WIDTH = SHA_WIDTH + AUTHOR_MAX + DATE_WIDTH + 3 * #GAP
|
|||||||
---@field revision string? nil = working tree, else the blamed revision
|
---@field revision string? nil = working tree, else the blamed revision
|
||||||
---@field commits table<string, ow.Git.Blame.Commit>
|
---@field commits table<string, ow.Git.Blame.Commit>
|
||||||
---@field line_sha table<integer, string>
|
---@field line_sha table<integer, string>
|
||||||
---@field blame_text table<integer, string>?
|
|
||||||
---@field blame_width integer?
|
|
||||||
---@field blame_blank string?
|
|
||||||
---@field tick integer?
|
---@field tick integer?
|
||||||
---@field epoch integer
|
---@field epoch integer
|
||||||
---@field pending fun()[]
|
---@field pending fun()[]
|
||||||
---@field inline boolean
|
---@field inline boolean
|
||||||
---@field gutter boolean
|
|
||||||
---@field autocmds integer[]
|
---@field autocmds integer[]
|
||||||
|
|
||||||
---@type table<integer, ow.Git.Blame.BufState>
|
---@type table<integer, ow.Git.Blame.BufState>
|
||||||
@@ -116,17 +103,6 @@ local function format_author_time(ts, tz)
|
|||||||
return os.date("!%Y-%m-%d %T ", ts + offset) .. tz
|
return os.date("!%Y-%m-%d %T ", ts + offset) .. tz
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param s string
|
|
||||||
---@param width integer
|
|
||||||
---@return string
|
|
||||||
local function pad(s, width)
|
|
||||||
local w = vim.api.nvim_strwidth(s)
|
|
||||||
if w > width then
|
|
||||||
return vim.fn.strcharpart(s, 0, width)
|
|
||||||
end
|
|
||||||
return s .. string.rep(" ", width - w)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param stdout string
|
---@param stdout string
|
||||||
---@return ow.Git.Blame.Result
|
---@return ow.Git.Blame.Result
|
||||||
local function parse_porcelain(stdout)
|
local function parse_porcelain(stdout)
|
||||||
@@ -284,14 +260,10 @@ local function ensure_state(buf)
|
|||||||
revision = src.revision,
|
revision = src.revision,
|
||||||
commits = {},
|
commits = {},
|
||||||
line_sha = {},
|
line_sha = {},
|
||||||
blame_text = nil,
|
|
||||||
blame_width = nil,
|
|
||||||
blame_blank = nil,
|
|
||||||
tick = nil,
|
tick = nil,
|
||||||
epoch = 0,
|
epoch = 0,
|
||||||
pending = {},
|
pending = {},
|
||||||
inline = false,
|
inline = false,
|
||||||
gutter = false,
|
|
||||||
autocmds = {},
|
autocmds = {},
|
||||||
}
|
}
|
||||||
states[buf] = state
|
states[buf] = state
|
||||||
@@ -340,7 +312,6 @@ local function run_blame(state, buf, done)
|
|||||||
or synth_uncommitted(vim.api.nvim_buf_line_count(buf))
|
or synth_uncommitted(vim.api.nvim_buf_line_count(buf))
|
||||||
state.commits = data.commits
|
state.commits = data.commits
|
||||||
state.line_sha = data.line_sha
|
state.line_sha = data.line_sha
|
||||||
state.blame_text = nil
|
|
||||||
state.tick = tick
|
state.tick = tick
|
||||||
local pending = state.pending
|
local pending = state.pending
|
||||||
state.pending = {}
|
state.pending = {}
|
||||||
@@ -390,196 +361,14 @@ local function render_inline(buf)
|
|||||||
})
|
})
|
||||||
end
|
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
|
|
||||||
---gutter itself has already inflated.
|
|
||||||
---@param win integer
|
|
||||||
---@return integer
|
|
||||||
local function native_width(win)
|
|
||||||
local wo = vim.wo[win]
|
|
||||||
local width = 0
|
|
||||||
if wo.number or wo.relativenumber then
|
|
||||||
local buf = vim.api.nvim_win_get_buf(win)
|
|
||||||
local digits = #tostring(vim.api.nvim_buf_line_count(buf)) + 1
|
|
||||||
width = math.max(wo.numberwidth, digits)
|
|
||||||
end
|
|
||||||
local sc = wo.signcolumn
|
|
||||||
if sc:find("^yes") or sc:find("^auto") then
|
|
||||||
width = width + 2 * math.floor(tonumber(sc:match("(%d)$")) or 1)
|
|
||||||
end
|
|
||||||
local fc = tonumber(wo.foldcolumn:match("(%d)$"))
|
|
||||||
if fc then
|
|
||||||
width = width + math.floor(fc)
|
|
||||||
elseif wo.foldcolumn:find("^auto") then
|
|
||||||
width = width + 1
|
|
||||||
end
|
|
||||||
return width
|
|
||||||
end
|
|
||||||
|
|
||||||
---Split a `budget` of display cells across the blame fields. The author
|
|
||||||
---absorbs the squeeze first, then the date, then the sha.
|
|
||||||
---@param budget integer
|
|
||||||
---@return integer sha_w
|
|
||||||
---@return integer author_w
|
|
||||||
---@return integer date_w
|
|
||||||
local function layout(budget)
|
|
||||||
local body = budget - 3 * #GAP
|
|
||||||
if body <= 0 then
|
|
||||||
return 0, 0, 0
|
|
||||||
end
|
|
||||||
local sha_w = math.min(body, SHA_WIDTH)
|
|
||||||
local rest = body - sha_w
|
|
||||||
local date_w = math.min(rest, DATE_WIDTH)
|
|
||||||
return sha_w, rest - date_w, date_w
|
|
||||||
end
|
|
||||||
|
|
||||||
---Precompute the per-line blame segment so the statuscolumn expression
|
|
||||||
---stays a table lookup. Blame shares Neovim's 47-cell statuscolumn cap
|
|
||||||
---with the native columns, so it is budgeted into what they leave free.
|
|
||||||
---@param state ow.Git.Blame.BufState
|
|
||||||
---@param win integer
|
|
||||||
local function build_blame_text(state, win)
|
|
||||||
local total = math.max(
|
|
||||||
0,
|
|
||||||
math.min(STATUSCOLUMN_MAX - native_width(win), PREFERRED_WIDTH)
|
|
||||||
)
|
|
||||||
local sha_w, author_w, date_w = layout(total)
|
|
||||||
local blank = string.rep(" ", total)
|
|
||||||
---@type table<integer, string>
|
|
||||||
local text = {}
|
|
||||||
for lnum, sha in pairs(state.line_sha) do
|
|
||||||
local commit = state.commits[sha]
|
|
||||||
if commit then
|
|
||||||
if sha_w == 0 then
|
|
||||||
text[lnum] = blank
|
|
||||||
else
|
|
||||||
local author, date
|
|
||||||
if util.is_zero_sha(sha) then
|
|
||||||
author, date = "Uncommitted", ""
|
|
||||||
else
|
|
||||||
author = commit.author
|
|
||||||
date = os.date("%Y-%m-%d", commit.author_time)
|
|
||||||
end
|
|
||||||
text[lnum] = "%#GitBlameSha#"
|
|
||||||
.. sha:sub(1, sha_w)
|
|
||||||
.. "%#GitBlameAuthor#"
|
|
||||||
.. GAP
|
|
||||||
.. (pad(author, author_w):gsub("%%", "%%%%"))
|
|
||||||
.. "%#GitBlameDate#"
|
|
||||||
.. GAP
|
|
||||||
.. pad(date, date_w)
|
|
||||||
.. GAP
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
state.blame_text = text
|
|
||||||
state.blame_width = total
|
|
||||||
state.blame_blank = blank
|
|
||||||
end
|
|
||||||
|
|
||||||
---Render the blame segment for one screen line. Wired into the window's
|
|
||||||
---`'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
|
|
||||||
---@param virtnum integer
|
|
||||||
---@return string
|
|
||||||
local function gutter(win, lnum, virtnum)
|
|
||||||
if not win or win == 0 or not vim.api.nvim_win_is_valid(win) then
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
local state = states[vim.api.nvim_win_get_buf(win)]
|
|
||||||
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 ""
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.statuscolumn()
|
|
||||||
local ok, result =
|
|
||||||
pcall(gutter, vim.api.nvim_get_current_win(), vim.v.lnum, vim.v.virtnum)
|
|
||||||
return ok and result or ""
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type table<integer, string>
|
|
||||||
local saved_statuscolumn = {}
|
|
||||||
|
|
||||||
---Set a window's `'statuscolumn'` with `scope = "local"`, so the global
|
|
||||||
---value stays clean and splits and new windows do not inherit the gutter.
|
|
||||||
---@param win integer
|
|
||||||
---@param value string
|
|
||||||
local function set_statuscolumn(win, value)
|
|
||||||
vim.api.nvim_set_option_value(
|
|
||||||
"statuscolumn",
|
|
||||||
value,
|
|
||||||
{ win = win, scope = "local" }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
---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_gutter_columns()
|
|
||||||
for win in pairs(saved_statuscolumn) do
|
|
||||||
if not vim.api.nvim_win_is_valid(win) then
|
|
||||||
saved_statuscolumn[win] = nil
|
|
||||||
end
|
|
||||||
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.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]
|
|
||||||
or vim.wo[win].statuscolumn
|
|
||||||
set_statuscolumn(
|
|
||||||
win,
|
|
||||||
BLAME_EXPR .. "%C%s%l" .. (native_width(win) > 0 and " " or "")
|
|
||||||
)
|
|
||||||
elseif not on and has_blame then
|
|
||||||
set_statuscolumn(
|
|
||||||
win,
|
|
||||||
saved_statuscolumn[win] or vim.go.statuscolumn
|
|
||||||
)
|
|
||||||
saved_statuscolumn[win] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf integer
|
|
||||||
local function render(buf)
|
|
||||||
render_inline(buf)
|
|
||||||
local state = states[buf]
|
|
||||||
if not state or not state.gutter then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
-- Rebuild against the current native column widths, then re-set
|
|
||||||
-- `'statuscolumn'` so the redraw picks up the new text instead of the
|
|
||||||
-- cached gutter.
|
|
||||||
local built = false
|
|
||||||
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
|
||||||
if vim.api.nvim_win_get_buf(win) == buf then
|
|
||||||
if not built then
|
|
||||||
build_blame_text(state, win)
|
|
||||||
built = true
|
|
||||||
end
|
|
||||||
set_statuscolumn(
|
|
||||||
win,
|
|
||||||
BLAME_EXPR .. "%C%s%l" .. (native_width(win) > 0 and " " or "")
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
local function reblame(buf)
|
local function reblame(buf)
|
||||||
local state = states[buf]
|
local state = states[buf]
|
||||||
if not state or (not state.inline and not state.gutter) then
|
if not state or not state.inline then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
run_blame(state, buf, function()
|
run_blame(state, buf, function()
|
||||||
render(buf)
|
render_inline(buf)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -648,28 +437,6 @@ function M.toggle_inline(buf)
|
|||||||
if vim.api.nvim_buf_is_valid(buf) then
|
if vim.api.nvim_buf_is_valid(buf) then
|
||||||
vim.api.nvim_buf_clear_namespace(buf, NS_INLINE, 0, -1)
|
vim.api.nvim_buf_clear_namespace(buf, NS_INLINE, 0, -1)
|
||||||
end
|
end
|
||||||
if not state.gutter then
|
|
||||||
detach_autocmds(buf, state)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf integer?
|
|
||||||
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.gutter = not state.gutter
|
|
||||||
refresh_gutter_columns()
|
|
||||||
if state.gutter then
|
|
||||||
attach_autocmds(buf, state)
|
|
||||||
run_blame(state, buf, function()
|
|
||||||
render(buf)
|
|
||||||
end)
|
|
||||||
elseif not state.inline then
|
|
||||||
detach_autocmds(buf, state)
|
detach_autocmds(buf, state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -920,35 +687,8 @@ function M.detach(buf)
|
|||||||
detach_autocmds(buf, state)
|
detach_autocmds(buf, state)
|
||||||
state.epoch = state.epoch + 1
|
state.epoch = state.epoch + 1
|
||||||
states[buf] = nil
|
states[buf] = nil
|
||||||
refresh_gutter_columns()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local augroup = vim.api.nvim_create_augroup("ow.git.blame", { clear = true })
|
|
||||||
vim.api.nvim_create_autocmd("BufWinEnter", {
|
|
||||||
group = augroup,
|
|
||||||
callback = refresh_gutter_columns,
|
|
||||||
})
|
|
||||||
|
|
||||||
-- The blame budget depends on the gutter option widths, so re-render an
|
|
||||||
-- gutter buffer when one of them changes.
|
|
||||||
vim.api.nvim_create_autocmd("OptionSet", {
|
|
||||||
group = augroup,
|
|
||||||
pattern = {
|
|
||||||
"number",
|
|
||||||
"relativenumber",
|
|
||||||
"numberwidth",
|
|
||||||
"signcolumn",
|
|
||||||
"foldcolumn",
|
|
||||||
},
|
|
||||||
callback = function()
|
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
local state = states[buf]
|
|
||||||
if state and state.gutter then
|
|
||||||
render(buf)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
-- The blame cache is keyed by `changedtick`, which a commit / checkout /
|
-- The blame cache is keyed by `changedtick`, which a commit / checkout /
|
||||||
-- rebase does not bump. Drop the cache for affected worktree buffers on
|
-- rebase does not bump. Drop the cache for affected worktree buffers on
|
||||||
-- a repo change so the next blame re-fetches; re-blame eagerly if a mode
|
-- a repo change so the next blame re-fetches; re-blame eagerly if a mode
|
||||||
@@ -961,7 +701,7 @@ repo.on("change", function(r, change)
|
|||||||
and (change.paths[state.rel] or change.branch_changed)
|
and (change.paths[state.rel] or change.branch_changed)
|
||||||
then
|
then
|
||||||
state.tick = nil
|
state.tick = nil
|
||||||
if state.inline or state.gutter then
|
if state.inline then
|
||||||
schedule(buf)
|
schedule(buf)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -322,9 +322,6 @@ vim.api.nvim_create_user_command("GitDiffOverlay", function()
|
|||||||
require("git.hunks").toggle_overlay()
|
require("git.hunks").toggle_overlay()
|
||||||
end, { desc = "Toggle the git diff overlay in the current buffer" })
|
end, { desc = "Toggle the git diff overlay in the current buffer" })
|
||||||
|
|
||||||
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()
|
vim.keymap.set("n", "<Plug>(git-blame-line)", function()
|
||||||
require("git.blame").toggle_inline()
|
require("git.blame").toggle_inline()
|
||||||
end, { silent = true, desc = "Toggle inline git blame" })
|
end, { silent = true, desc = "Toggle inline git blame" })
|
||||||
@@ -344,9 +341,6 @@ end, {
|
|||||||
desc = "Open this file at the parent of the line's commit",
|
desc = "Open this file at the parent of the line's commit",
|
||||||
})
|
})
|
||||||
|
|
||||||
vim.api.nvim_create_user_command("GitBlame", function()
|
|
||||||
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()
|
vim.api.nvim_create_user_command("GitBlameLine", function()
|
||||||
require("git.blame").toggle_inline()
|
require("git.blame").toggle_inline()
|
||||||
end, { desc = "Toggle inline git blame in the current buffer" })
|
end, { desc = "Toggle inline git blame in the current buffer" })
|
||||||
|
|||||||
@@ -197,7 +197,6 @@ t.test("blame actions are no-ops off a worktree", function()
|
|||||||
t.quietly(function()
|
t.quietly(function()
|
||||||
blame.line_popup(buf)
|
blame.line_popup(buf)
|
||||||
blame.toggle_inline(buf)
|
blame.toggle_inline(buf)
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
end)
|
end)
|
||||||
t.eq(blame.state(buf), nil, "no state created for a non-worktree buffer")
|
t.eq(blame.state(buf), nil, "no state created for a non-worktree buffer")
|
||||||
end)
|
end)
|
||||||
@@ -301,204 +300,6 @@ t.test("inline annotation follows the cursor", function()
|
|||||||
t.eq(assert(inline_marks(buf)[1])[2], 2, "annotation moved to line 3")
|
t.eq(assert(inline_marks(buf)[1])[2], 2, "annotation moved to line 3")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
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_gutter(buf)
|
|
||||||
t.truthy(
|
|
||||||
vim.wo[win].statuscolumn ~= "",
|
|
||||||
"the gutter sets the window statuscolumn"
|
|
||||||
)
|
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
t.eq(vim.wo[win].statuscolumn, "", "toggling off clears it")
|
|
||||||
end)
|
|
||||||
|
|
||||||
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()
|
|
||||||
t.defer(function()
|
|
||||||
if vim.api.nvim_win_is_valid(win) then
|
|
||||||
vim.wo[win].statuscolumn = ""
|
|
||||||
vim.wo[win].signcolumn = "auto"
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
vim.wo[win].statuscolumn = "%l custom"
|
|
||||||
vim.wo[win].signcolumn = "yes:2"
|
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
t.truthy(
|
|
||||||
vim.wo[win].statuscolumn ~= "%l custom",
|
|
||||||
"the gutter overrides a custom statuscolumn"
|
|
||||||
)
|
|
||||||
t.eq(
|
|
||||||
vim.wo[win].signcolumn,
|
|
||||||
"yes:2",
|
|
||||||
"the gutter leaves signcolumn untouched"
|
|
||||||
)
|
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
t.eq(vim.wo[win].statuscolumn, "%l custom", "statuscolumn restored")
|
|
||||||
end)
|
|
||||||
|
|
||||||
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()
|
|
||||||
t.defer(function()
|
|
||||||
if vim.api.nvim_win_is_valid(win) then
|
|
||||||
vim.wo[win].statuscolumn = ""
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
vim.wo[win].number = false
|
|
||||||
vim.wo[win].relativenumber = false
|
|
||||||
vim.wo[win].signcolumn = "no"
|
|
||||||
vim.wo[win].foldcolumn = "0"
|
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
t.wait_for(function()
|
|
||||||
local s = blame.state(buf)
|
|
||||||
return s ~= nil and s.blame_width ~= nil
|
|
||||||
end, "the gutter blame to render")
|
|
||||||
t.eq(
|
|
||||||
assert(blame.state(buf)).blame_width,
|
|
||||||
40,
|
|
||||||
"with no native columns the blame takes its full preferred width"
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
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()
|
|
||||||
t.defer(function()
|
|
||||||
if vim.api.nvim_win_is_valid(win) then
|
|
||||||
vim.wo[win].statuscolumn = ""
|
|
||||||
vim.wo[win].signcolumn = "auto"
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
vim.wo[win].number = false
|
|
||||||
vim.wo[win].relativenumber = false
|
|
||||||
vim.wo[win].foldcolumn = "0"
|
|
||||||
vim.wo[win].signcolumn = "yes:9"
|
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
t.wait_for(function()
|
|
||||||
local s = blame.state(buf)
|
|
||||||
return s ~= nil and s.blame_width ~= nil
|
|
||||||
end, "the gutter blame to render")
|
|
||||||
local width = assert(assert(blame.state(buf)).blame_width)
|
|
||||||
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)
|
|
||||||
|
|
||||||
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()
|
|
||||||
t.defer(function()
|
|
||||||
if vim.api.nvim_win_is_valid(win) then
|
|
||||||
vim.wo[win].statuscolumn = ""
|
|
||||||
vim.wo[win].signcolumn = "auto"
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
vim.wo[win].number = false
|
|
||||||
vim.wo[win].relativenumber = false
|
|
||||||
vim.wo[win].foldcolumn = "0"
|
|
||||||
vim.wo[win].signcolumn = "no"
|
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
t.wait_for(function()
|
|
||||||
local s = blame.state(buf)
|
|
||||||
return s ~= nil and s.blame_width ~= nil
|
|
||||||
end, "the gutter blame to render")
|
|
||||||
t.eq(
|
|
||||||
assert(blame.state(buf)).blame_width,
|
|
||||||
40,
|
|
||||||
"a clear gutter leaves the full preferred width"
|
|
||||||
)
|
|
||||||
|
|
||||||
vim.wo[win].signcolumn = "yes:9"
|
|
||||||
t.wait_for(function()
|
|
||||||
return assert(blame.state(buf)).blame_width == 47 - 18
|
|
||||||
end, "the blame to re-budget for the widened signcolumn")
|
|
||||||
end)
|
|
||||||
|
|
||||||
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_gutter(buf)
|
|
||||||
t.wait_for(function()
|
|
||||||
local s = blame.state(buf)
|
|
||||||
return s ~= nil and s.blame_width ~= nil
|
|
||||||
end, "the gutter to render")
|
|
||||||
t.falsy(
|
|
||||||
vim.go.statuscolumn:find("git.blame", 1, true),
|
|
||||||
"the gutter leaves the global statuscolumn untouched"
|
|
||||||
)
|
|
||||||
|
|
||||||
vim.cmd("new")
|
|
||||||
local plain = vim.api.nvim_get_current_win()
|
|
||||||
t.defer(function()
|
|
||||||
if vim.api.nvim_win_is_valid(plain) then
|
|
||||||
vim.api.nvim_win_close(plain, true)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
t.falsy(
|
|
||||||
vim.wo[plain].statuscolumn:find("git.blame", 1, true),
|
|
||||||
"a new window does not inherit the blame gutter"
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
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 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(
|
|
||||||
g:match("%d%d%d%d%-%d%d%-%d%d"),
|
|
||||||
"the gutter shows a YYYY-MM-DD date"
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
t.test("gutter is blank on virtual lines", function()
|
|
||||||
local _, buf = setup("a\nb\nc\n")
|
|
||||||
vim.api.nvim_set_current_buf(buf)
|
|
||||||
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 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()
|
|
||||||
local dir, buf = setup("a\nb\nc\n")
|
|
||||||
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_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 rendered = vim.api.nvim_eval_statusline(
|
|
||||||
"%{%v:lua.require('git.blame').statuscolumn()%}",
|
|
||||||
{ winid = win, use_statuscol_lnum = 1 }
|
|
||||||
)
|
|
||||||
t.truthy(
|
|
||||||
rendered.str:find(sha:sub(1, 8), 1, true),
|
|
||||||
"the statuscolumn renders the commit's short sha"
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
t.test("open_commit opens the commit that last touched the line", function()
|
t.test("open_commit opens the commit that last touched the line", function()
|
||||||
local _, buf = setup("alpha\nbeta\ngamma\n")
|
local _, buf = setup("alpha\nbeta\ngamma\n")
|
||||||
vim.api.nvim_set_current_buf(buf)
|
vim.api.nvim_set_current_buf(buf)
|
||||||
@@ -559,12 +360,8 @@ end)
|
|||||||
t.test("detach clears blame state and annotations", function()
|
t.test("detach clears blame state and annotations", function()
|
||||||
local _, buf = setup("a\nb\nc\n")
|
local _, buf = setup("a\nb\nc\n")
|
||||||
vim.api.nvim_set_current_buf(buf)
|
vim.api.nvim_set_current_buf(buf)
|
||||||
local win = vim.api.nvim_get_current_win()
|
|
||||||
enable_blame(buf)
|
enable_blame(buf)
|
||||||
blame.toggle_gutter(buf)
|
|
||||||
t.truthy(vim.wo[win].statuscolumn ~= "", "the gutter statuscolumn set")
|
|
||||||
blame.detach(buf)
|
blame.detach(buf)
|
||||||
t.eq(blame.state(buf), nil, "state dropped on detach")
|
t.eq(blame.state(buf), nil, "state dropped on detach")
|
||||||
t.eq(#inline_marks(buf), 0, "inline annotation cleared")
|
t.eq(#inline_marks(buf), 0, "inline annotation cleared")
|
||||||
t.eq(vim.wo[win].statuscolumn, "", "gutter statuscolumn cleared")
|
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user