fix(git): resolve sidebar and diff-split rendering bugs
This commit is contained in:
+65
-41
@@ -59,36 +59,10 @@ local function attach_index_writer(buf, worktree, path)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---Run `git show <revspec>` asynchronously and call `callback(lines?)` on the
|
---Internal builder: create a scratch buffer and fill it with the output of
|
||||||
---main loop. `lines` is nil on failure (the error is logged).
|
---`git show <revspec>`. Synchronous so the buffer is ready by the time the
|
||||||
---@param worktree string
|
---caller wires up windows / `:diffthis`. An empty buffer briefly visible
|
||||||
---@param revspec string anything `git show` accepts (e.g. `HEAD:foo`, `:foo`, blob SHA)
|
---to the diff engine produces a spurious whole-file diff.
|
||||||
---@param callback fun(lines: string[]?)
|
|
||||||
local function read_show_async(worktree, revspec, callback)
|
|
||||||
vim.system(
|
|
||||||
{ "git", "show", revspec },
|
|
||||||
{ cwd = worktree, text = true },
|
|
||||||
vim.schedule_wrap(function(result)
|
|
||||||
if result.code ~= 0 then
|
|
||||||
log.error(
|
|
||||||
"git show %s failed: %s",
|
|
||||||
revspec,
|
|
||||||
vim.trim(result.stderr or "")
|
|
||||||
)
|
|
||||||
callback(nil)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
callback(util.split_lines(result.stdout or ""))
|
|
||||||
end)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Internal builder: create a scratch buffer immediately and asynchronously
|
|
||||||
---fill it with the content of `git show <revspec>`. Returning the buffer
|
|
||||||
---synchronously lets callers wire up windows / `:diffthis` right away; the
|
|
||||||
---diff updates when the content arrives. The buffer starts non-modifiable
|
|
||||||
---and the index `BufWriteCmd` is only attached after a successful load, so
|
|
||||||
---a premature `:w` can't blow away the index entry with empty content.
|
|
||||||
---@param worktree string
|
---@param worktree string
|
||||||
---@param revspec string
|
---@param revspec string
|
||||||
---@param is_index boolean
|
---@param is_index boolean
|
||||||
@@ -99,14 +73,28 @@ local function build_show_buf(worktree, revspec, is_index, index_path)
|
|||||||
vim.bo[buf].buftype = "nofile"
|
vim.bo[buf].buftype = "nofile"
|
||||||
vim.bo[buf].bufhidden = "wipe"
|
vim.bo[buf].bufhidden = "wipe"
|
||||||
vim.bo[buf].swapfile = false
|
vim.bo[buf].swapfile = false
|
||||||
vim.bo[buf].modifiable = false
|
|
||||||
vim.bo[buf].modified = false
|
local result = vim.system(
|
||||||
read_show_async(worktree, revspec, function(lines)
|
{ "git", "show", revspec },
|
||||||
if not vim.api.nvim_buf_is_valid(buf) then
|
{ cwd = worktree, text = true }
|
||||||
return
|
)
|
||||||
|
:wait()
|
||||||
|
if result.code ~= 0 then
|
||||||
|
log.error(
|
||||||
|
"git show %s failed: %s",
|
||||||
|
revspec,
|
||||||
|
vim.trim(result.stderr or "")
|
||||||
|
)
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_lines(
|
||||||
|
buf,
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false,
|
||||||
|
util.split_lines(result.stdout or "")
|
||||||
|
)
|
||||||
end
|
end
|
||||||
vim.bo[buf].modifiable = true
|
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines or {})
|
|
||||||
if is_index then
|
if is_index then
|
||||||
vim.bo[buf].buftype = "acwrite"
|
vim.bo[buf].buftype = "acwrite"
|
||||||
attach_index_writer(buf, worktree, assert(index_path))
|
attach_index_writer(buf, worktree, assert(index_path))
|
||||||
@@ -114,7 +102,6 @@ local function build_show_buf(worktree, revspec, is_index, index_path)
|
|||||||
vim.bo[buf].modifiable = false
|
vim.bo[buf].modifiable = false
|
||||||
end
|
end
|
||||||
vim.bo[buf].modified = false
|
vim.bo[buf].modified = false
|
||||||
end)
|
|
||||||
return buf
|
return buf
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -179,6 +166,28 @@ function M.set_buf_name_and_filetype(buf, name)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Apply (or remove) the window-local options that `:diffthis` would
|
||||||
|
---normally set. Used by both `M.split` here and the sidebar so the two
|
||||||
|
---diff entry points behave consistently. Vim is supposed to save and
|
||||||
|
---restore these around the `'diff'` flag flip, but that round-trip is
|
||||||
|
---fragile when buffers are swapped under an already-diff window.
|
||||||
|
---@param win integer
|
||||||
|
---@param enabled boolean
|
||||||
|
function M.set_diff(win, enabled)
|
||||||
|
vim.wo[win].diff = enabled
|
||||||
|
if enabled then
|
||||||
|
vim.wo[win].foldmethod = "diff"
|
||||||
|
vim.wo[win].foldenable = true
|
||||||
|
vim.wo[win].foldlevel = 0
|
||||||
|
vim.wo[win].scrollbind = true
|
||||||
|
vim.wo[win].cursorbind = true
|
||||||
|
vim.wo[win].wrap = false
|
||||||
|
else
|
||||||
|
vim.wo[win].scrollbind = false
|
||||||
|
vim.wo[win].cursorbind = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@class ow.Git.SplitOpts
|
---@class ow.Git.SplitOpts
|
||||||
---@field ref string '' for index, 'HEAD' for HEAD
|
---@field ref string '' for index, 'HEAD' for HEAD
|
||||||
---@field vertical boolean
|
---@field vertical boolean
|
||||||
@@ -191,6 +200,10 @@ function M.split(opts)
|
|||||||
log.warning("no file in current buffer")
|
log.warning("no file in current buffer")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if vim.bo[cur_buf].buftype ~= "" then
|
||||||
|
log.warning("cannot diff this buffer (not a worktree file)")
|
||||||
|
return
|
||||||
|
end
|
||||||
local _, worktree = repo.resolve(cur_path)
|
local _, worktree = repo.resolve(cur_path)
|
||||||
if not worktree then
|
if not worktree then
|
||||||
log.warning("not in a git repository")
|
log.warning("not in a git repository")
|
||||||
@@ -212,9 +225,20 @@ function M.split(opts)
|
|||||||
split = opts.vertical and "left" or "above",
|
split = opts.vertical and "left" or "above",
|
||||||
win = cur_win,
|
win = cur_win,
|
||||||
})
|
})
|
||||||
vim.wo[other_win].diff = true
|
|
||||||
vim.api.nvim_set_current_win(cur_win)
|
-- The synthetic index/HEAD buffer can't run BufRead, so its filetype
|
||||||
vim.wo[cur_win].diff = true
|
-- 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)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
+45
-35
@@ -34,6 +34,7 @@ local SIDEBAR_WIDTH = 50
|
|||||||
---@field worktree string
|
---@field worktree string
|
||||||
---@field lines table<integer, ow.Git.SidebarEntry>
|
---@field lines table<integer, ow.Git.SidebarEntry>
|
||||||
---@field sidebar_win integer?
|
---@field sidebar_win integer?
|
||||||
|
---@field invocation_win integer? window focused when the sidebar opened. The first diff repurposes it as the right pane
|
||||||
---@field diff_left_win integer?
|
---@field diff_left_win integer?
|
||||||
---@field diff_right_win integer?
|
---@field diff_right_win integer?
|
||||||
---@field user_aucmd integer?
|
---@field user_aucmd integer?
|
||||||
@@ -575,39 +576,30 @@ local function reset_diff_win(win)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param sidebar_win integer
|
---Validate the window the user was in when the sidebar opened. The first
|
||||||
|
---diff repurposes it as the right pane, regardless of whether it holds an
|
||||||
|
---empty buffer or a real file. Returns nil if the user closed it, moved
|
||||||
|
---to another tabpage, or it's somehow the sidebar itself.
|
||||||
|
---@param s ow.Git.SidebarState
|
||||||
---@return integer?
|
---@return integer?
|
||||||
local function find_default_main_win(sidebar_win)
|
local function invocation_win_for(s)
|
||||||
local non_sidebar = {}
|
local win = s.invocation_win
|
||||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
if not win or not vim.api.nvim_win_is_valid(win) then
|
||||||
if win ~= sidebar_win then
|
|
||||||
table.insert(non_sidebar, win)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #non_sidebar ~= 1 then
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local buf = vim.api.nvim_win_get_buf(non_sidebar[1])
|
if win == s.sidebar_win then
|
||||||
if
|
return nil
|
||||||
vim.api.nvim_buf_get_name(buf) == ""
|
|
||||||
and vim.bo[buf].buftype == ""
|
|
||||||
and not vim.bo[buf].modified
|
|
||||||
and vim.api.nvim_buf_line_count(buf) == 1
|
|
||||||
and vim.api.nvim_buf_get_lines(buf, 0, 1, false)[1] == ""
|
|
||||||
then
|
|
||||||
return non_sidebar[1]
|
|
||||||
end
|
end
|
||||||
|
if
|
||||||
|
vim.api.nvim_win_get_tabpage(win)
|
||||||
|
~= vim.api.nvim_get_current_tabpage()
|
||||||
|
then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return win
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param win integer
|
local set_diff = diff.set_diff
|
||||||
---@param enabled boolean
|
|
||||||
local function set_diff(win, enabled)
|
|
||||||
vim.wo[win].diff = enabled
|
|
||||||
if enabled then
|
|
||||||
vim.wo[win].foldenable = true
|
|
||||||
vim.wo[win].foldlevel = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param s ow.Git.SidebarState
|
---@param s ow.Git.SidebarState
|
||||||
---@param sidebar_win integer
|
---@param sidebar_win integer
|
||||||
@@ -696,18 +688,19 @@ local function show_diff(s, entry, focus_left)
|
|||||||
left_win = vsplit_at(right_win, "left")
|
left_win = vsplit_at(right_win, "left")
|
||||||
reset_diff_win(left_win)
|
reset_diff_win(left_win)
|
||||||
elseif not (left_win or right_win) then
|
elseif not (left_win or right_win) then
|
||||||
local default_main = find_default_main_win(sidebar_win)
|
local target = invocation_win_for(s)
|
||||||
if default_main then
|
if target then
|
||||||
right_win = default_main
|
right_win = target
|
||||||
reset_diff_win(right_win)
|
reset_diff_win(right_win)
|
||||||
left_win = vsplit_at(right_win, "left")
|
left_win = vsplit_at(right_win, "left")
|
||||||
reset_diff_win(left_win)
|
reset_diff_win(left_win)
|
||||||
else
|
else
|
||||||
-- No reusable default-empty window. Open the diff pair by
|
-- Invocation window is gone (closed or in another tabpage).
|
||||||
-- splitting from the sidebar. winfixwidth keeps the sidebar at 50
|
-- Open the diff pair by splitting from the sidebar. winfixwidth
|
||||||
-- when there are other windows to absorb the split; if the
|
-- keeps the sidebar at 50 when there are other windows to
|
||||||
-- sidebar is the only window in the tab, the split has to take
|
-- absorb the split; if the sidebar is the only window in the
|
||||||
-- from the sidebar itself, so restore the width explicitly.
|
-- tab, the split has to take from the sidebar itself, so
|
||||||
|
-- restore the width explicitly.
|
||||||
right_win = vsplit_at(sidebar_win, "right")
|
right_win = vsplit_at(sidebar_win, "right")
|
||||||
reset_diff_win(right_win)
|
reset_diff_win(right_win)
|
||||||
left_win = vsplit_at(right_win, "left")
|
left_win = vsplit_at(right_win, "left")
|
||||||
@@ -725,6 +718,13 @@ local function show_diff(s, entry, focus_left)
|
|||||||
s.diff_left_win = left_win
|
s.diff_left_win = left_win
|
||||||
s.diff_right_win = right_win
|
s.diff_right_win = right_win
|
||||||
|
|
||||||
|
-- Toggle diff off around the buffer swap so Vim tears down the old
|
||||||
|
-- diff group and re-establishes a fresh one against the new pair.
|
||||||
|
-- nvim_win_set_buf swaps the buffer pointer without invalidating
|
||||||
|
-- cached diff state, and :diffupdate alone doesn't reliably force a
|
||||||
|
-- recompute when no buffer contents have actually changed.
|
||||||
|
set_diff(left_win, false)
|
||||||
|
set_diff(right_win, false)
|
||||||
vim.api.nvim_win_set_buf(left_win, pair.left.buf)
|
vim.api.nvim_win_set_buf(left_win, pair.left.buf)
|
||||||
vim.api.nvim_win_set_buf(right_win, pair.right.buf)
|
vim.api.nvim_win_set_buf(right_win, pair.right.buf)
|
||||||
for _, side in ipairs({ pair.left, pair.right }) do
|
for _, side in ipairs({ pair.left, pair.right }) do
|
||||||
@@ -732,6 +732,15 @@ local function show_diff(s, entry, focus_left)
|
|||||||
diff.set_buf_name_and_filetype(side.buf, side.name)
|
diff.set_buf_name_and_filetype(side.buf, side.name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- Synthetic index/HEAD buffers never run BufRead, so modeline-only
|
||||||
|
-- filetypes aren't detected. Copy from the side that did resolve.
|
||||||
|
local left_ft = vim.bo[pair.left.buf].filetype
|
||||||
|
local right_ft = vim.bo[pair.right.buf].filetype
|
||||||
|
if left_ft == "" and right_ft ~= "" then
|
||||||
|
vim.bo[pair.left.buf].filetype = right_ft
|
||||||
|
elseif right_ft == "" and left_ft ~= "" then
|
||||||
|
vim.bo[pair.right.buf].filetype = left_ft
|
||||||
|
end
|
||||||
set_diff(left_win, true)
|
set_diff(left_win, true)
|
||||||
set_diff(right_win, true)
|
set_diff(right_win, true)
|
||||||
s.last_shown_key = key
|
s.last_shown_key = key
|
||||||
@@ -897,6 +906,7 @@ local function open(worktree)
|
|||||||
worktree = worktree,
|
worktree = worktree,
|
||||||
lines = {},
|
lines = {},
|
||||||
sidebar_win = win,
|
sidebar_win = win,
|
||||||
|
invocation_win = previous_win,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function k(lhs, rhs, desc)
|
local function k(lhs, rhs, desc)
|
||||||
|
|||||||
Reference in New Issue
Block a user