refactor(git): load git:// URI buffers via BufReadCmd
This commit is contained in:
+79
-63
@@ -59,33 +59,58 @@ local function attach_index_writer(buf, worktree, path)
|
||||
})
|
||||
end
|
||||
|
||||
---Internal builder: create a scratch buffer and fill it with the output of
|
||||
---`git show <revspec>`. Synchronous so the buffer is ready by the time the
|
||||
---caller wires up windows / `:diffthis`. An empty buffer briefly visible
|
||||
---to the diff engine produces a spurious whole-file diff.
|
||||
---Return a buffer holding the content at `<ref>:<path>`. `ref` is
|
||||
---"index", "HEAD", or a commit revspec.
|
||||
---@param worktree string
|
||||
---@param revspec string
|
||||
---@param is_index boolean
|
||||
---@param index_path string? required when is_index is true
|
||||
---@param ref string
|
||||
---@param path string
|
||||
---@return integer
|
||||
local function build_show_buf(worktree, revspec, is_index, index_path)
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
vim.bo[buf].bufhidden = "wipe"
|
||||
vim.bo[buf].swapfile = false
|
||||
function M.git_show_buf(worktree, ref, path)
|
||||
local name = "git://" .. ref .. "//" .. path
|
||||
local buf = vim.fn.bufadd(name)
|
||||
vim.b[buf].git_worktree = worktree
|
||||
vim.fn.bufload(buf)
|
||||
return buf
|
||||
end
|
||||
|
||||
---BufReadCmd handler for `git://<ref>//<path>` URIs. Worktree comes from
|
||||
---`vim.b[buf].git_worktree` if set, else from cwd. Ref of "index" maps
|
||||
---to `git show :<path>`; "worktree" leaves the buffer empty (placeholder
|
||||
---for missing files); anything else is a revspec.
|
||||
---@param buf integer
|
||||
function M.read_uri(buf)
|
||||
local name = vim.api.nvim_buf_get_name(buf)
|
||||
local ref, path = name:match("^git://(.-)//(.*)$")
|
||||
if not ref or path == "" then
|
||||
return
|
||||
end
|
||||
---@cast path -nil
|
||||
|
||||
local worktree = vim.b[buf].git_worktree or select(2, repo.resolve_cwd())
|
||||
if not worktree then
|
||||
log.error("git BufReadCmd %s: cannot resolve worktree", name)
|
||||
return
|
||||
end
|
||||
vim.b[buf].git_worktree = worktree
|
||||
|
||||
vim.bo[buf].swapfile = false
|
||||
vim.bo[buf].bufhidden = "wipe"
|
||||
|
||||
if ref == "worktree" then
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
vim.bo[buf].modifiable = false
|
||||
vim.bo[buf].modified = false
|
||||
vim.api.nvim_exec_autocmds("BufReadPost", { buffer = buf })
|
||||
return
|
||||
end
|
||||
|
||||
local revspec = ref == "index" and (":" .. path) or (ref .. ":" .. path)
|
||||
local result = vim.system(
|
||||
{ "git", "show", revspec },
|
||||
{ cwd = worktree, text = true }
|
||||
)
|
||||
:wait()
|
||||
if result.code ~= 0 then
|
||||
log.error(
|
||||
"git show %s failed: %s",
|
||||
revspec,
|
||||
vim.trim(result.stderr or "")
|
||||
)
|
||||
else
|
||||
if result.code == 0 then
|
||||
vim.api.nvim_buf_set_lines(
|
||||
buf,
|
||||
0,
|
||||
@@ -93,45 +118,51 @@ local function build_show_buf(worktree, revspec, is_index, index_path)
|
||||
false,
|
||||
util.split_lines(result.stdout or "")
|
||||
)
|
||||
else
|
||||
log.error(
|
||||
"git show %s failed: %s",
|
||||
revspec,
|
||||
vim.trim(result.stderr or "")
|
||||
)
|
||||
end
|
||||
|
||||
if is_index then
|
||||
if ref == "index" then
|
||||
vim.bo[buf].buftype = "acwrite"
|
||||
attach_index_writer(buf, worktree, assert(index_path))
|
||||
-- Re-running BufReadCmd (e.g. on `:edit`) would otherwise stack
|
||||
-- another BufWriteCmd on the same buffer, so each `:w` runs
|
||||
-- hash-object + update-index N times.
|
||||
if not vim.b[buf].git_index_writer then
|
||||
attach_index_writer(buf, worktree, path)
|
||||
vim.b[buf].git_index_writer = true
|
||||
end
|
||||
else
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
vim.bo[buf].modifiable = false
|
||||
end
|
||||
vim.bo[buf].modified = false
|
||||
return buf
|
||||
end
|
||||
|
||||
---@param worktree string
|
||||
---@param ref string '' for index, 'HEAD' or a sha for committed refs
|
||||
---@param path string
|
||||
---@param is_index boolean? true to hook :w to update the git index
|
||||
---@return integer
|
||||
function M.git_show_buf(worktree, ref, path, is_index)
|
||||
return build_show_buf(worktree, ref .. ":" .. path, is_index or false, path)
|
||||
end
|
||||
|
||||
---@param worktree string
|
||||
---@param blob string the blob SHA (full or abbreviated)
|
||||
---@return integer
|
||||
function M.git_show_blob(worktree, blob)
|
||||
return build_show_buf(worktree, blob, false, nil)
|
||||
-- BufReadCmd suppresses the normal BufReadPost dispatch, so filetype
|
||||
-- detection and modeline parsing don't run unless we fire it ourselves.
|
||||
vim.api.nvim_exec_autocmds("BufReadPost", { buffer = buf })
|
||||
end
|
||||
|
||||
---@class ow.Git.EmptyBufOpts
|
||||
---@field name string?
|
||||
---@field bufhidden ("hide"|"wipe")? defaults to "wipe"
|
||||
|
||||
---Build a read-only scratch buffer, optionally naming it via
|
||||
---`nvim_buf_set_name` (silently no-op if a buffer with that name
|
||||
---already exists).
|
||||
---Build a read-only scratch buffer, optionally naming it. When `opts.name`
|
||||
---is set and a loaded buffer with that name already exists, returns it
|
||||
---instead of creating a duplicate.
|
||||
---@param opts ow.Git.EmptyBufOpts?
|
||||
---@return integer
|
||||
function M.empty_buf(opts)
|
||||
opts = opts or {}
|
||||
if opts.name then
|
||||
local existing = vim.fn.bufnr(opts.name)
|
||||
if existing ~= -1 and vim.api.nvim_buf_is_loaded(existing) then
|
||||
return existing
|
||||
end
|
||||
end
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
vim.bo[buf].bufhidden = opts.bufhidden or "wipe"
|
||||
@@ -215,30 +246,15 @@ function M.split(opts)
|
||||
return
|
||||
end
|
||||
|
||||
local is_index = opts.ref == ""
|
||||
local other = M.git_show_buf(worktree, opts.ref, rel, is_index)
|
||||
local label = is_index and "index" or opts.ref
|
||||
M.set_buf_name_and_filetype(other, "git://" .. label .. "/" .. rel)
|
||||
local label = opts.ref == "" and "index" or opts.ref
|
||||
local uri = "git://" .. label .. "//" .. rel
|
||||
-- Stash the worktree on the buffer so the BufReadCmd handler doesn't
|
||||
-- fall back to cwd resolution (wrong when cwd != worktree).
|
||||
local buf = vim.fn.bufadd(uri)
|
||||
vim.b[buf].git_worktree = worktree
|
||||
|
||||
local cur_win = vim.api.nvim_get_current_win()
|
||||
local other_win = vim.api.nvim_open_win(other, true, {
|
||||
split = opts.vertical and "left" or "above",
|
||||
win = cur_win,
|
||||
})
|
||||
|
||||
-- The synthetic index/HEAD buffer can't run BufRead, so its filetype
|
||||
-- detection in `set_buf_name_and_filetype` only catches
|
||||
-- filename-pattern matches. Mirror cur_buf's filetype, since this is
|
||||
-- the same logical file at a different version. Done after the
|
||||
-- window opens so any BufWinEnter / BufEnter autocmds that fire on
|
||||
-- nvim_open_win can't undo it.
|
||||
local cur_ft = vim.bo[cur_buf].filetype
|
||||
if cur_ft ~= "" then
|
||||
vim.bo[other].filetype = cur_ft
|
||||
end
|
||||
|
||||
M.set_diff(cur_win, true)
|
||||
M.set_diff(other_win, true)
|
||||
local prefix = opts.vertical and "leftabove vert " or "leftabove "
|
||||
vim.cmd(prefix .. "diffsplit " .. vim.fn.fnameescape(uri))
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user