refactor(git): rebuild status_view diff layout per selection

This commit is contained in:
2026-05-19 11:40:02 +02:00
parent ab9b70c70a
commit 8a8c73ca8b
2 changed files with 57 additions and 134 deletions
+42 -130
View File
@@ -39,11 +39,7 @@ end
---@field placement ow.Git.StatusView.Placement
---@field lines table<integer, ow.Git.StatusView.Item>
---@field win integer?
---@field invocation_win integer?
---@field diff_left_win integer?
---@field diff_right_win integer?
---@field unsubscribe fun()?
---@field last_shown_key string?
---@type table<integer, ow.Git.StatusView.State>
local state = {}
@@ -165,7 +161,6 @@ local function refresh(bufnr)
if not s or not vim.api.nvim_buf_is_valid(bufnr) then
return
end
s.last_shown_key = nil
render(bufnr, s.repo.status)
end
@@ -264,62 +259,6 @@ local function reset_diff_win(win)
end)
end
---@param s ow.Git.StatusView.State
---@return integer?
local function invocation_win_for(s)
local win = s.invocation_win
if not win or not vim.api.nvim_win_is_valid(win) then
return nil
end
if win == s.win then
return nil
end
if
vim.api.nvim_win_get_tabpage(win)
~= vim.api.nvim_get_current_tabpage()
then
return nil
end
return win
end
---@param s ow.Git.StatusView.State
---@param status_win integer
---@return integer? left
---@return integer? right
local function adopt_diff_wins(s, status_win)
local left = valid_in_current_tab(s.diff_left_win) and s.diff_left_win
or nil
local right = valid_in_current_tab(s.diff_right_win) and s.diff_right_win
or nil
if left and right then
return left, right
end
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
if win ~= status_win then
local role = vim.w[win].git_diff_role
if role == "left" and not left then
left = win
elseif role == "right" and not right then
right = win
end
end
end
return left, right
end
---@param row ow.Git.Status.Row
---@return string
local function row_key(row)
local entry = row.entry
local orig
if entry.kind == "changed" then
---@cast entry ow.Git.Status.ChangedEntry
orig = entry.orig
end
return row.section .. "|" .. entry.path .. "|" .. (orig or "")
end
---@param target_win integer
---@param dir "left"|"right"
---@return integer
@@ -333,23 +272,32 @@ local function vsplit_at(target_win, dir)
return win
end
---@param s ow.Git.StatusView.State
---@param status_win integer
---@param right_win integer?
---@return integer
local function ensure_right_win(s, status_win, right_win)
if right_win then
return right_win
---@return integer?
local function previous_target_win(status_win)
local n = vim.fn.winnr("#")
if n == 0 then
return nil
end
local target = invocation_win_for(s)
if target then
right_win = target
else
right_win = vsplit_at(status_win, "right")
vim.api.nvim_win_set_width(status_win, WINDOW_WIDTH)
local win = vim.fn.win_getid(n)
if win == 0 or win == status_win or not valid_in_current_tab(win) then
return nil
end
local cfg = vim.api.nvim_win_get_config(win)
if cfg.relative and cfg.relative ~= "" then
return nil
end
return win
end
---@param status_win integer
---@param keep integer
local function close_other_diff_wins(status_win, keep)
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
if win ~= status_win and win ~= keep and vim.wo[win].diff then
pcall(vim.api.nvim_win_close, win, false)
end
end
reset_diff_win(right_win)
return right_win
end
---@param s ow.Git.StatusView.State
@@ -384,68 +332,35 @@ local function view_row(s, row, focus_left)
return
end
local key = row_key(row)
local left_win, right_win = adopt_diff_wins(s, status_win)
local want_pair = left and right
if s.last_shown_key == key then
local intact = (want_pair and left_win and right_win)
or (not want_pair and right_win and not left_win)
if intact then
local target = focus_left and (left_win or right_win) or status_win
vim.api.nvim_set_current_win(target)
return
end
local target = previous_target_win(status_win)
if not target then
target = vsplit_at(status_win, "right")
end
close_other_diff_wins(status_win, target)
vim.api.nvim_win_set_width(status_win, WINDOW_WIDTH)
reset_diff_win(target)
diff.set_diff(target, false)
if not want_pair then
if left_win and vim.api.nvim_win_is_valid(left_win) then
pcall(vim.api.nvim_win_close, left_win, false)
left_win = nil
s.diff_left_win = nil
end
right_win = ensure_right_win(s, status_win, right_win)
s.diff_right_win = right_win
vim.w[right_win].git_diff_role = "right"
local side = left or right
if not (left and right) then
local side = right or left
---@cast side ow.Git.Diff.Side
diff.set_diff(right_win, false)
vim.api.nvim_win_set_buf(right_win, side.buf)
vim.api.nvim_win_set_buf(target, side.buf)
if side.name then
util.set_buf_name(side.buf, side.name)
end
s.last_shown_key = key
vim.api.nvim_set_current_win(focus_left and right_win or status_win)
vim.api.nvim_set_current_win(focus_left and target or status_win)
return
end
---@cast left ow.Git.Diff.Side
---@cast right ow.Git.Diff.Side
if left_win and not right_win then
right_win = vsplit_at(left_win, "right")
reset_diff_win(right_win)
elseif right_win and not left_win then
left_win = vsplit_at(right_win, "left")
reset_diff_win(left_win)
elseif not (left_win or right_win) then
right_win = ensure_right_win(s, status_win, nil)
left_win = vsplit_at(right_win, "left")
reset_diff_win(left_win)
local combined = vim.api.nvim_win_get_width(left_win)
+ vim.api.nvim_win_get_width(right_win)
vim.api.nvim_win_set_width(left_win, math.floor(combined / 2))
end
---@cast left_win -nil
---@cast right_win -nil
vim.w[left_win].git_diff_role = "left"
vim.w[right_win].git_diff_role = "right"
s.diff_left_win = left_win
s.diff_right_win = right_win
diff.update_pair(left_win, right_win, { left = left, right = right })
s.last_shown_key = key
local left_win = vsplit_at(target, "left")
reset_diff_win(left_win)
local combined = vim.api.nvim_win_get_width(left_win)
+ vim.api.nvim_win_get_width(target)
vim.api.nvim_win_set_width(left_win, math.floor(combined / 2))
diff.update_pair(left_win, target, { left = left, right = right })
vim.api.nvim_set_current_win(focus_left and left_win or status_win)
end
@@ -640,14 +555,12 @@ end
---@param r ow.Git.Repo
---@param placement ow.Git.StatusView.Placement
---@param win integer?
---@param invocation_win integer?
local function setup_buffer(bufnr, r, placement, win, invocation_win)
local function setup_buffer(bufnr, r, placement, win)
state[bufnr] = {
repo = r,
placement = placement,
lines = {},
win = win,
invocation_win = invocation_win,
}
local function k(lhs, rhs, desc)
@@ -743,7 +656,6 @@ function M.open(opts)
local s = state[buf]
if s then
s.win = win
s.invocation_win = previous_win
s.placement = placement
end
set_keymaps(buf, placement)
@@ -805,7 +717,7 @@ function M.read_uri(buf)
win = nil
end
if not state[buf] then
setup_buffer(buf, r, "current", win, nil)
setup_buffer(buf, r, "current", win)
else
state[buf].win = win
end
+15 -4
View File
@@ -39,16 +39,28 @@ local function find_sidebar()
end
end
---Find a diff-role window in the given tabpage (or current).
---Find a diff window in the given tabpage (or current). "left" / "right"
---is determined by column position: the layout is [sidebar | left | right],
---so the leftmost &diff window is the left pane and the rightmost is the
---right pane.
---@param role "left"|"right"
---@param tab integer?
---@return integer?
local function find_diff_win(role, tab)
local diffs = {}
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(tab or 0)) do
if vim.w[w].git_diff_role == role then
return w
if vim.wo[w].diff then
table.insert(diffs, w)
end
end
table.sort(diffs, function(a, b)
return vim.api.nvim_win_get_position(a)[2]
< vim.api.nvim_win_get_position(b)[2]
end)
if role == "left" then
return diffs[1]
end
return diffs[#diffs]
end
---@param file_path string
@@ -161,7 +173,6 @@ t.test(
setup_sidebar_with_unstaged_file("foo.txt", "v1\n", "v2\n")
local tab1 = vim.api.nvim_get_current_tabpage()
-- First show diff in tab1, so state.diff_*_win point at tab1.
vim.api.nvim_set_current_win(sidebar_win)
vim.api.nvim_win_set_cursor(sidebar_win, { line, 0 })
t.press("<Tab>")