feat(git): add in-house git blame
This commit is contained in:
@@ -0,0 +1,572 @@
|
||||
local blame = require("git.blame")
|
||||
local h = require("test.git.helpers")
|
||||
local t = require("test")
|
||||
|
||||
---@param sha string
|
||||
---@return boolean
|
||||
local function is_zero(sha)
|
||||
return sha:match("^0+$") ~= nil
|
||||
end
|
||||
|
||||
---@param committed string
|
||||
---@param worktree string?
|
||||
---@param file string?
|
||||
---@return string dir
|
||||
---@return integer buf
|
||||
local function setup(committed, worktree, file)
|
||||
file = file or "a.txt"
|
||||
local dir = h.make_repo({ [file] = committed })
|
||||
if worktree then
|
||||
t.write(dir, file, worktree)
|
||||
end
|
||||
vim.cmd.edit(dir .. "/" .. file)
|
||||
return dir, vim.api.nvim_get_current_buf()
|
||||
end
|
||||
|
||||
---@param buf integer
|
||||
---@return ow.Git.Blame.BufState
|
||||
local function enable_blame(buf)
|
||||
blame.toggle_inline(buf)
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.tick ~= nil
|
||||
end, "blame to populate the buffer state")
|
||||
local s = assert(blame.state(buf))
|
||||
return s
|
||||
end
|
||||
|
||||
---@param buf integer
|
||||
---@param ns_name string
|
||||
---@return vim.api.keyset.get_extmark_item[]
|
||||
local function marks(buf, ns_name)
|
||||
local ns = vim.api.nvim_get_namespaces()[ns_name]
|
||||
return vim.api.nvim_buf_get_extmarks(buf, ns, 0, -1, { details = true })
|
||||
end
|
||||
|
||||
---@param buf integer
|
||||
---@return vim.api.keyset.get_extmark_item[]
|
||||
local function inline_marks(buf)
|
||||
return marks(buf, "ow.git.blame.inline")
|
||||
end
|
||||
|
||||
---@return integer?
|
||||
local function find_float()
|
||||
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||
if vim.api.nvim_win_get_config(w).relative ~= "" then
|
||||
return w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@return integer float
|
||||
local function wait_float()
|
||||
local float ---@type integer?
|
||||
t.wait_for(function()
|
||||
float = find_float()
|
||||
return float ~= nil
|
||||
end, "blame popup float to open")
|
||||
local found = assert(float)
|
||||
return found
|
||||
end
|
||||
|
||||
---@param pat string
|
||||
local function wait_buf_name(pat)
|
||||
t.wait_for(function()
|
||||
return vim.api.nvim_buf_get_name(0):match(pat) ~= nil
|
||||
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")
|
||||
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)
|
||||
t.eq(vim.tbl_count(state.commits), 1, "one commit")
|
||||
local sha = state.line_sha[1]
|
||||
t.eq(state.line_sha[2], sha, "line 2 shares the commit")
|
||||
t.eq(state.line_sha[3], sha, "line 3 shares the commit")
|
||||
local commit = state.commits[sha]
|
||||
t.eq(commit.author, "t", "author parsed from the porcelain")
|
||||
t.eq(commit.summary, "init", "summary parsed from the porcelain")
|
||||
t.truthy(#sha >= 40, "the full sha is recorded")
|
||||
t.truthy(commit.author_time > 0, "author time parsed")
|
||||
end)
|
||||
|
||||
t.test("multiple line groups reuse one commit entry", function()
|
||||
local dir = h.make_repo({ ["a.txt"] = "a\nb\nc\n" })
|
||||
t.write(dir, "a.txt", "a\nB\nc\n")
|
||||
h.git(dir, "add", "a.txt")
|
||||
h.git(dir, "commit", "-q", "-m", "change middle")
|
||||
vim.cmd.edit(dir .. "/a.txt")
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local state = enable_blame(buf)
|
||||
t.eq(vim.tbl_count(state.commits), 2, "two distinct commits")
|
||||
t.eq(
|
||||
state.line_sha[1],
|
||||
state.line_sha[3],
|
||||
"lines 1 and 3 share the original commit"
|
||||
)
|
||||
t.truthy(
|
||||
state.line_sha[1] ~= state.line_sha[2],
|
||||
"line 2 is a different commit"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("an edited line blames as the zero sha", function()
|
||||
local _, buf = setup("a\nb\nc\n")
|
||||
local state = enable_blame(buf)
|
||||
t.falsy(is_zero(state.line_sha[2]), "line 2 starts committed")
|
||||
vim.api.nvim_buf_set_lines(buf, 1, 2, false, { "EDITED" })
|
||||
vim.api.nvim_exec_autocmds("TextChanged", { buffer = buf })
|
||||
blame._flush(buf)
|
||||
t.wait_for(function()
|
||||
local s = assert(blame.state(buf))
|
||||
return s.line_sha[2] ~= nil and is_zero(s.line_sha[2])
|
||||
end, "the edited line to blame as uncommitted")
|
||||
end)
|
||||
|
||||
t.test("blame refreshes after a git event", function()
|
||||
local dir, buf = setup("original\n")
|
||||
local state = enable_blame(buf)
|
||||
local sha1 = state.line_sha[1]
|
||||
h.git(dir, "commit", "--amend", "-m", "amended")
|
||||
local sha2 = h.git(dir, "rev-parse", "HEAD").stdout
|
||||
t.truthy(sha1 ~= sha2, "the amend produced a new commit")
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.line_sha[1] == sha2
|
||||
end, "blame to pick up the amended commit", 2000)
|
||||
end)
|
||||
|
||||
t.test("an untracked file blames every line as uncommitted", function()
|
||||
local dir = h.make_repo({ ["tracked.txt"] = "x\n" })
|
||||
t.write(dir, "new.txt", "one\ntwo\nthree\n")
|
||||
vim.cmd.edit(dir .. "/new.txt")
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local state = enable_blame(buf)
|
||||
for i = 1, 3 do
|
||||
t.truthy(is_zero(state.line_sha[i]), "line " .. i .. " is uncommitted")
|
||||
end
|
||||
t.eq(vim.tbl_count(state.commits), 1, "one synthesized commit")
|
||||
end)
|
||||
|
||||
t.test("blame actions are no-ops off a worktree", function()
|
||||
local buf = vim.api.nvim_create_buf(true, false)
|
||||
t.defer(function()
|
||||
pcall(vim.api.nvim_buf_delete, buf, { force = true })
|
||||
end)
|
||||
t.quietly(function()
|
||||
blame.line_popup(buf)
|
||||
blame.toggle_inline(buf)
|
||||
blame.toggle_overlay(buf)
|
||||
end)
|
||||
t.eq(blame.state(buf), nil, "no state created for a non-worktree buffer")
|
||||
end)
|
||||
|
||||
t.test("line popup shows the commit for the cursor line", function()
|
||||
local dir, buf = setup("alpha\nbeta\ngamma\n")
|
||||
local sha = h.git(dir, "rev-parse", "HEAD").stdout
|
||||
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(
|
||||
vim.startswith(lines[1] or "", sha:sub(1, 8)),
|
||||
"first line starts with the short sha"
|
||||
)
|
||||
t.truthy((lines[1] or ""):find("t", 1, true), "author shown")
|
||||
end)
|
||||
|
||||
t.test("re-invoking the line popup focuses the open float", 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)
|
||||
t.truthy(
|
||||
vim.api.nvim_get_current_win() ~= float,
|
||||
"the float opens unfocused"
|
||||
)
|
||||
blame.line_popup(buf)
|
||||
t.eq(
|
||||
vim.api.nvim_get_current_win(),
|
||||
float,
|
||||
"re-invoking focuses the existing float"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("line popup works in a git:// object buffer", function()
|
||||
local dir = h.make_repo({ ["a.txt"] = "alpha\nbeta\ngamma\n" })
|
||||
local sha = h.git(dir, "rev-parse", "HEAD").stdout
|
||||
local r = assert(require("git.core.repo").resolve(dir))
|
||||
local gbuf = require("git.object").buf_for(
|
||||
r,
|
||||
require("git.core.revision").new({ base = sha, path = "a.txt" })
|
||||
)
|
||||
vim.api.nvim_set_current_buf(gbuf)
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
blame.line_popup(gbuf)
|
||||
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(
|
||||
vim.startswith(lines[1] or "", sha:sub(1, 8)),
|
||||
"the popup blames the commit even in a git:// buffer"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("inline toggle adds and removes the annotation", 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")
|
||||
t.eq(assert(inline_marks(buf)[1])[2], 0, "annotation on line 1")
|
||||
blame.toggle_inline(buf)
|
||||
t.eq(#inline_marks(buf), 0, "annotation cleared when toggled off")
|
||||
end)
|
||||
|
||||
t.test("inline annotation follows the cursor", 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, "the initial annotation")
|
||||
t.eq(assert(inline_marks(buf)[1])[2], 0)
|
||||
vim.api.nvim_win_set_cursor(0, { 3, 0 })
|
||||
vim.api.nvim_exec_autocmds("CursorMoved", { buffer = buf })
|
||||
t.eq(#inline_marks(buf), 1, "still one annotation")
|
||||
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()
|
||||
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)
|
||||
t.truthy(
|
||||
vim.wo[win].statuscolumn ~= "",
|
||||
"the overlay sets the window statuscolumn"
|
||||
)
|
||||
blame.toggle_overlay(buf)
|
||||
t.eq(vim.wo[win].statuscolumn, "", "toggling off clears it")
|
||||
end)
|
||||
|
||||
t.test("overlay 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_overlay(buf)
|
||||
t.truthy(
|
||||
vim.wo[win].statuscolumn ~= "%l custom",
|
||||
"the overlay overrides a custom statuscolumn"
|
||||
)
|
||||
t.eq(
|
||||
vim.wo[win].signcolumn,
|
||||
"yes:2",
|
||||
"the overlay leaves signcolumn untouched"
|
||||
)
|
||||
blame.toggle_overlay(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()
|
||||
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_overlay(buf)
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.blame_width ~= nil
|
||||
end, "the overlay 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("overlay 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_overlay(buf)
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.blame_width ~= nil
|
||||
end, "the overlay 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")
|
||||
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("overlay 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_overlay(buf)
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.blame_width ~= nil
|
||||
end, "the overlay 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("overlay 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)
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.tick ~= nil
|
||||
end, "the overlay 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")
|
||||
t.truthy(
|
||||
g:match("%d%d%d%d%-%d%d%-%d%d"),
|
||||
"the gutter shows a YYYY-MM-DD date"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("overlay 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)
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.tick ~= nil
|
||||
end, "the overlay 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)
|
||||
|
||||
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_overlay(buf)
|
||||
t.wait_for(function()
|
||||
local s = blame.state(buf)
|
||||
return s ~= nil and s.tick ~= nil
|
||||
end, "the overlay 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()
|
||||
local _, buf = setup("alpha\nbeta\ngamma\n")
|
||||
vim.api.nvim_set_current_buf(buf)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||
blame.open_commit()
|
||||
wait_buf_name("^git://%x+$")
|
||||
end)
|
||||
|
||||
t.test("open_file opens the file at the line's commit", function()
|
||||
local _, buf = setup("alpha\nbeta\ngamma\n")
|
||||
vim.api.nvim_set_current_buf(buf)
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
blame.open_file()
|
||||
wait_buf_name("^git://%x+:a%.txt$")
|
||||
end)
|
||||
|
||||
t.test("open_file_parent opens the file at the parent commit", function()
|
||||
local dir = h.make_repo({ ["a.txt"] = "a\nb\nc\n" })
|
||||
local root = h.git(dir, "rev-parse", "HEAD").stdout
|
||||
t.write(dir, "a.txt", "a\nB\nc\n")
|
||||
h.git(dir, "add", "a.txt")
|
||||
h.git(dir, "commit", "-q", "-m", "change middle")
|
||||
vim.cmd.edit(dir .. "/a.txt")
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
blame.open_file_parent()
|
||||
t.wait_for(function()
|
||||
return vim.api.nvim_buf_get_name(0) == "git://" .. root .. ":a.txt"
|
||||
end, "the file at the parent commit to open")
|
||||
end)
|
||||
|
||||
t.test("the drill actions refuse an uncommitted line", function()
|
||||
local _, buf = setup("a\nb\nc\n")
|
||||
vim.api.nvim_set_current_buf(buf)
|
||||
vim.api.nvim_buf_set_lines(buf, 1, 2, false, { "EDITED" })
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
t.quietly(function()
|
||||
blame.open_commit()
|
||||
vim.wait(200)
|
||||
end)
|
||||
t.eq(
|
||||
vim.api.nvim_get_current_buf(),
|
||||
buf,
|
||||
"no commit opened for an uncommitted line"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("drilling chains through git:// buffers", function()
|
||||
local _, buf = setup("alpha\nbeta\ngamma\n")
|
||||
vim.api.nvim_set_current_buf(buf)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||
blame.open_file()
|
||||
wait_buf_name("^git://%x+:a%.txt$")
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||
blame.open_commit()
|
||||
wait_buf_name("^git://%x+$")
|
||||
end)
|
||||
|
||||
t.test("detach clears blame state and annotations", function()
|
||||
local _, buf = setup("a\nb\nc\n")
|
||||
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.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")
|
||||
end)
|
||||
Reference in New Issue
Block a user