Compare commits

...

5 Commits

5 changed files with 63 additions and 21 deletions
+30 -6
View File
@@ -42,15 +42,34 @@ local function resolve_buf(buf)
return buf and buf ~= 0 and buf or vim.api.nvim_get_current_buf() return buf and buf ~= 0 and buf or vim.api.nvim_get_current_buf()
end end
---Mirror the hunk-affecting parts of the user's 'diffopt' so the gutter
---lines up with what `:diffsplit` shows.
---@return table
local function diff_opts()
local opts = { result_type = "indices", algorithm = "myers" }
for _, item in ipairs(vim.split(vim.o.diffopt, ",", { plain = true })) do
if item == "indent-heuristic" then
opts.indent_heuristic = true
else
local algorithm = item:match("^algorithm:(.+)$")
if algorithm then
opts.algorithm = algorithm
end
local linematch = item:match("^linematch:(%d+)$")
if linematch then
opts.linematch = tonumber(linematch)
end
end
end
return opts
end
---@param state ow.Git.Hunks.BufState ---@param state ow.Git.Hunks.BufState
---@param new_lines string[] ---@param new_lines string[]
local function compute_hunks(state, new_lines) local function compute_hunks(state, new_lines)
local old = table.concat(state.index or {}, "\n") local old = table.concat(state.index or {}, "\n")
local new = table.concat(new_lines, "\n") local new = table.concat(new_lines, "\n")
local raw = vim.text.diff(old, new, { local raw = vim.text.diff(old, new, diff_opts())
result_type = "indices",
algorithm = "histogram",
})
---@type ow.Git.Hunks.Hunk[] ---@type ow.Git.Hunks.Hunk[]
local hunks = {} local hunks = {}
if type(raw) ~= "table" then if type(raw) ~= "table" then
@@ -127,11 +146,11 @@ local function render_signs(buf)
end end
pcall(vim.api.nvim_buf_set_extmark, buf, NS_SIGNS, row, 0, { pcall(vim.api.nvim_buf_set_extmark, buf, NS_SIGNS, row, 0, {
sign_text = signs.delete, sign_text = signs.delete,
sign_hl_group = "GitHunkDelete", sign_hl_group = "GitHunkRemoved",
priority = 100, priority = 100,
}) })
else else
local hl = h.type == "add" and "GitHunkAdd" or "GitHunkChange" local hl = h.type == "add" and "GitHunkAdded" or "GitHunkChanged"
local sign = h.type == "add" and signs.add or signs.change local sign = h.type == "add" and signs.add or signs.change
for r = h.new_start, h.new_start + h.new_count - 1 do for r = h.new_start, h.new_start + h.new_count - 1 do
local row = r - 1 local row = r - 1
@@ -380,6 +399,11 @@ end
local schedule, sched_handle = util.keyed_debounce(recompute, 100) local schedule, sched_handle = util.keyed_debounce(recompute, 100)
---@param buf integer
function M._flush(buf)
sched_handle.flush(buf)
end
---@param buf integer ---@param buf integer
function M.attach(buf) function M.attach(buf)
if states[buf] then if states[buf] then
+1 -2
View File
@@ -184,8 +184,7 @@ local function populate(buf, r, rev, state, rev_sha)
local patch = util.git({ local patch = util.git({
"diff-tree", "diff-tree",
"-p", "-p",
"-m", "--diff-merges=first-parent",
"--first-parent",
"--root", "--root",
"--no-commit-id", "--no-commit-id",
commit_sha, commit_sha,
+3 -3
View File
@@ -35,9 +35,9 @@ local DEFAULT_HIGHLIGHTS = {
GitUnmergedDeletedByThem = "GitUnmerged", GitUnmergedDeletedByThem = "GitUnmerged",
GitUnmergedDeletedByUs = "GitUnmerged", GitUnmergedDeletedByUs = "GitUnmerged",
GitHunkAdd = "Added", GitHunkAdded = "Added",
GitHunkChange = "Changed", GitHunkChanged = "Changed",
GitHunkDelete = "Removed", GitHunkRemoved = "Removed",
GitHunkAddLine = "DiffAdd", GitHunkAddLine = "DiffAdd",
GitHunkDeleteLine = "DiffDelete", GitHunkDeleteLine = "DiffDelete",
} }
+12 -10
View File
@@ -15,6 +15,7 @@ local function setup(committed, worktree, file)
vim.cmd.edit(dir .. "/" .. file) vim.cmd.edit(dir .. "/" .. file)
local buf = vim.api.nvim_get_current_buf() local buf = vim.api.nvim_get_current_buf()
hunks.attach(buf) hunks.attach(buf)
hunks._flush(buf)
t.wait_for(function() t.wait_for(function()
local s = hunks.state(buf) local s = hunks.state(buf)
return s ~= nil and s.index ~= nil return s ~= nil and s.index ~= nil
@@ -69,8 +70,8 @@ t.test("pure add: hunk shape and add signs", function()
t.eq(hk.new_start, 2) t.eq(hk.new_start, 2)
t.eq(hk.new_count, 2) t.eq(hk.new_count, 2)
t.eq(sign_marks(buf), { t.eq(sign_marks(buf), {
{ row = 1, sign = "", hl = "GitHunkAdd" }, { row = 1, sign = "", hl = "GitHunkAdded" },
{ row = 2, sign = "", hl = "GitHunkAdd" }, { row = 2, sign = "", hl = "GitHunkAdded" },
}) })
end) end)
@@ -82,7 +83,7 @@ t.test("pure delete (middle): hunk shape and delete sign", function()
t.eq(hk.new_count, 0) t.eq(hk.new_count, 0)
t.eq(hk.old_lines, { "b" }) t.eq(hk.old_lines, { "b" })
t.eq(sign_marks(buf), { t.eq(sign_marks(buf), {
{ row = 0, sign = "", hl = "GitHunkDelete" }, { row = 0, sign = "", hl = "GitHunkRemoved" },
}) })
end) end)
@@ -94,7 +95,7 @@ t.test("top-of-file delete: sign anchors on line 1", function()
t.eq(hk.new_start, 0) t.eq(hk.new_start, 0)
t.eq(hk.old_lines, { "a" }) t.eq(hk.old_lines, { "a" })
t.eq(sign_marks(buf), { t.eq(sign_marks(buf), {
{ row = 0, sign = "", hl = "GitHunkDelete" }, { row = 0, sign = "", hl = "GitHunkRemoved" },
}) })
end) end)
@@ -110,8 +111,8 @@ t.test("change of N lines: hunk shape and change signs", function()
t.eq(hk.old_lines, { "b", "c" }) t.eq(hk.old_lines, { "b", "c" })
t.eq(hk.new_lines, { "B", "C" }) t.eq(hk.new_lines, { "B", "C" })
t.eq(sign_marks(buf), { t.eq(sign_marks(buf), {
{ row = 1, sign = "", hl = "GitHunkChange" }, { row = 1, sign = "", hl = "GitHunkChanged" },
{ row = 2, sign = "", hl = "GitHunkChange" }, { row = 2, sign = "", hl = "GitHunkChanged" },
}) })
end) end)
@@ -119,8 +120,8 @@ t.test("multi-hunk file: two separate change hunks", function()
local _, buf, state = setup("a\nb\nc\nd\ne\n", "A\nb\nc\nd\nE\n") local _, buf, state = setup("a\nb\nc\nd\ne\n", "A\nb\nc\nd\nE\n")
t.eq(#state.hunks, 2, "two hunks for two disjoint changes") t.eq(#state.hunks, 2, "two hunks for two disjoint changes")
t.eq(sign_marks(buf), { t.eq(sign_marks(buf), {
{ row = 0, sign = "", hl = "GitHunkChange" }, { row = 0, sign = "", hl = "GitHunkChanged" },
{ row = 4, sign = "", hl = "GitHunkChange" }, { row = 4, sign = "", hl = "GitHunkChanged" },
}) })
end) end)
@@ -135,6 +136,7 @@ t.test("editing the buffer refreshes signs", function()
t.eq(#state.hunks, 0) t.eq(#state.hunks, 0)
vim.api.nvim_buf_set_lines(buf, 1, 2, false, { "CHANGED" }) vim.api.nvim_buf_set_lines(buf, 1, 2, false, { "CHANGED" })
vim.api.nvim_exec_autocmds("TextChanged", { buffer = buf }) vim.api.nvim_exec_autocmds("TextChanged", { buffer = buf })
hunks._flush(buf)
t.wait_for(function() t.wait_for(function()
local s = assert(hunks.state(buf)) local s = assert(hunks.state(buf))
return #s.hunks == 1 return #s.hunks == 1
@@ -327,7 +329,7 @@ t.test("git_hunk_signs overrides the sign character per kind", function()
end) end)
local _, buf = setup("a\nb\nc\n", "a\nB\nc\n") local _, buf = setup("a\nb\nc\n", "a\nB\nc\n")
t.eq(sign_marks(buf), { t.eq(sign_marks(buf), {
{ row = 1, sign = "C", hl = "GitHunkChange" }, { row = 1, sign = "C", hl = "GitHunkChanged" },
}) })
end) end)
@@ -339,7 +341,7 @@ t.test("git_hunk_signs falls back to the default for unset kinds", function()
end) end)
local _, buf = setup("a\nb\nc\n", "a\nB\nc\n") local _, buf = setup("a\nb\nc\n", "a\nB\nc\n")
t.eq(sign_marks(buf), { t.eq(sign_marks(buf), {
{ row = 1, sign = "", hl = "GitHunkChange" }, { row = 1, sign = "", hl = "GitHunkChanged" },
}) })
end) end)
+17
View File
@@ -86,6 +86,23 @@ t.test("M.open(HEAD:<path>) loads file content at HEAD", function()
) )
end) end)
t.test("M.open on a merge commit diffs against the first parent only", function()
local dir = h.make_repo({ ["a.txt"] = "one\n" })
t.write(dir, "a.txt", "two\n")
h.git(dir, "stash")
local stash = h.git(dir, "rev-parse", "stash@{0}").stdout
local r = assert(require("git.core.repo").resolve(dir))
object.open(r, stash, { split = false })
local count = 0
for _, l in ipairs(vim.api.nvim_buf_get_lines(0, 0, -1, false)) do
if l:match("^diff %-%-git ") then
count = count + 1
end
end
t.eq(count, 1, "the stashed file's diff appears once, not per-parent")
end)
t.test("M.open errors on a bogus base, no buffer is opened", function() t.test("M.open errors on a bogus base, no buffer is opened", function()
local dir = h.make_repo({ a = "first\n" }) local dir = h.make_repo({ a = "first\n" })
local r = assert(require("git.core.repo").resolve(dir)) local r = assert(require("git.core.repo").resolve(dir))