feat(git/cmd): make :G diff output navigable

This commit is contained in:
2026-05-07 23:18:17 +02:00
parent 93c9b6500a
commit c543f0a7ba
5 changed files with 291 additions and 48 deletions
+62 -39
View File
@@ -221,7 +221,7 @@ function M.read_uri(buf)
local state = r:state(buf) --[[@as -nil]]
vim.bo[buf].swapfile = false
vim.bo[buf].bufhidden = "hide"
vim.bo[buf].bufhidden = "delete"
local rev_sha = r:rev_parse(rev:format(), true)
if not rev_sha then
@@ -284,42 +284,53 @@ local function refresh(buf, r)
end
---@param r ow.Git.Repo
---@param blob string?
---@param ref string? -- nil = worktree, ":" = index, else commit/sha
---@param path string
---@param sha string
---@param blob string? -- diff section blob hash; if zero, side has no content
---@return integer?
local function blob_buf(r, blob, path, sha)
if is_zero(blob) then
local function side_buf(r, ref, path, blob)
if blob and is_zero(blob) then
return nil
end
return M.buf_for(r, Revision.new({ base = sha, path = path }))
if ref == nil then
local p = vim.fs.joinpath(r.worktree, path)
if not vim.uv.fs_stat(p) then
return nil
end
local buf = vim.fn.bufadd(p)
vim.fn.bufload(buf)
return buf
end
local rev = ref == ":" and Revision.new({ stage = 0, path = path })
or Revision.new({ base = ref, path = path })
return M.buf_for(r, rev)
end
---@param r ow.Git.Repo
---@param blob string?
---@param ref string?
---@param path string
---@param sha string
local function load_blob(r, blob, path, sha)
local buf = blob_buf(r, blob, path, sha)
---@param blob string?
local function load_side(r, ref, path, blob)
local buf = side_buf(r, ref, path, blob)
if not buf then
util.error("no content for %s at %s", path, sha)
util.error("no content for %s", path)
return
end
vim.cmd.normal({ "m'", bang = true })
vim.api.nvim_set_current_buf(buf)
end
---@param s ow.Git.Repo.BufState
---@param r ow.Git.Repo
---@param left_ref string?
---@param right_ref string?
---@param section ow.Git.DiffSection
local function open_section(s, section)
local function open_section(r, left_ref, right_ref, section)
if not section.blob_a or not section.blob_b then
util.error("no index line, cannot determine blob SHAs")
return
end
local parent = s.parent_sha or "0"
local left = blob_buf(s.repo, section.blob_a, section.path_a, parent)
local right =
blob_buf(s.repo, section.blob_b, section.path_b, s.sha --[[@as string]])
local left = side_buf(r, left_ref, section.path_a, section.blob_a)
local right = side_buf(r, right_ref, section.path_b, section.blob_b)
if left and right then
require("git.diff").open(left, right, true)
return
@@ -359,56 +370,68 @@ end
---@return boolean dispatched
function M.open_under_cursor()
local s = repo.state()
if not s or not s.sha then
if not s then
return false
end
local line = vim.api.nvim_get_current_line()
local r = s.repo
local sha = line:match("^commit (%x+)$")
or line:match("^parent (%x+)$")
or line:match("^tree (%x+)$")
or line:match("^object (%x+)$")
if sha then
M.open(r, sha, { split = false })
return true
if s.sha and not s.left_ref then
local sha = line:match("^commit (%x+)$")
or line:match("^parent (%x+)$")
or line:match("^tree (%x+)$")
or line:match("^object (%x+)$")
if sha then
M.open(r, sha, { split = false })
return true
end
local entry_type, entry_sha, entry_name =
line:match("^%d+ (%w+) (%x+)\t(.+)$")
if entry_sha then
local nav_rev = entry_type == "blob"
and Revision.new({ base = s.sha, path = entry_name }):format()
or entry_sha
M.open(r, nav_rev, { split = false })
return true
end
end
local entry_type, entry_sha, entry_name =
line:match("^%d+ (%w+) (%x+)\t(.+)$")
if entry_sha then
local nav_rev = entry_type == "blob"
and Revision.new({ base = s.sha, path = entry_name }):format()
or entry_sha
M.open(r, nav_rev, { split = false })
return true
local left_ref, right_ref
if s.left_ref then
left_ref = s.left_ref
right_ref = s.right_ref
elseif s.sha then
left_ref = s.parent_sha or "0"
right_ref = s.sha
else
return false
end
local section = diff_section()
if not section then
return false
end
local parent = s.parent_sha or "0"
if line:match("^diff %-%-git ") then
open_section(s, section)
open_section(r, left_ref, right_ref, section)
return true
end
if line:match("^%-%-%- ") then
load_blob(r, section.blob_a, section.path_a, parent)
load_side(r, left_ref, section.path_a, section.blob_a)
return true
end
if line:match("^%+%+%+ ") then
load_blob(r, section.blob_b, section.path_b, s.sha --[[@as string]])
load_side(r, right_ref, section.path_b, section.blob_b)
return true
end
local prefix = line:sub(1, 1)
if prefix == "+" then
load_blob(r, section.blob_b, section.path_b, s.sha --[[@as string]])
load_side(r, right_ref, section.path_b, section.blob_b)
return true
elseif prefix == "-" then
load_blob(r, section.blob_a, section.path_a, parent)
load_side(r, left_ref, section.path_a, section.blob_a)
return true
end
return false