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 placement ow.Git.StatusView.Placement
---@field lines table<integer, ow.Git.StatusView.Item> ---@field lines table<integer, ow.Git.StatusView.Item>
---@field win integer? ---@field win integer?
---@field invocation_win integer?
---@field diff_left_win integer?
---@field diff_right_win integer?
---@field unsubscribe fun()? ---@field unsubscribe fun()?
---@field last_shown_key string?
---@type table<integer, ow.Git.StatusView.State> ---@type table<integer, ow.Git.StatusView.State>
local state = {} local state = {}
@@ -165,7 +161,6 @@ local function refresh(bufnr)
if not s or not vim.api.nvim_buf_is_valid(bufnr) then if not s or not vim.api.nvim_buf_is_valid(bufnr) then
return return
end end
s.last_shown_key = nil
render(bufnr, s.repo.status) render(bufnr, s.repo.status)
end end
@@ -264,62 +259,6 @@ local function reset_diff_win(win)
end) end)
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 target_win integer
---@param dir "left"|"right" ---@param dir "left"|"right"
---@return integer ---@return integer
@@ -333,23 +272,32 @@ local function vsplit_at(target_win, dir)
return win return win
end end
---@param s ow.Git.StatusView.State
---@param status_win integer ---@param status_win integer
---@param right_win integer? ---@return integer?
---@return integer local function previous_target_win(status_win)
local function ensure_right_win(s, status_win, right_win) local n = vim.fn.winnr("#")
if right_win then if n == 0 then
return right_win return nil
end end
local target = invocation_win_for(s) local win = vim.fn.win_getid(n)
if target then if win == 0 or win == status_win or not valid_in_current_tab(win) then
right_win = target return nil
else end
right_win = vsplit_at(status_win, "right") local cfg = vim.api.nvim_win_get_config(win)
vim.api.nvim_win_set_width(status_win, WINDOW_WIDTH) 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 end
reset_diff_win(right_win)
return right_win
end end
---@param s ow.Git.StatusView.State ---@param s ow.Git.StatusView.State
@@ -384,68 +332,35 @@ local function view_row(s, row, focus_left)
return return
end end
local key = row_key(row) local target = previous_target_win(status_win)
local left_win, right_win = adopt_diff_wins(s, status_win) if not target then
local want_pair = left and right target = vsplit_at(status_win, "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
end 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 not (left and right) then
if left_win and vim.api.nvim_win_is_valid(left_win) then local side = right or left
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
---@cast side ow.Git.Diff.Side ---@cast side ow.Git.Diff.Side
diff.set_diff(right_win, false) vim.api.nvim_win_set_buf(target, side.buf)
vim.api.nvim_win_set_buf(right_win, side.buf)
if side.name then if side.name then
util.set_buf_name(side.buf, side.name) util.set_buf_name(side.buf, side.name)
end end
s.last_shown_key = key vim.api.nvim_set_current_win(focus_left and target or status_win)
vim.api.nvim_set_current_win(focus_left and right_win or status_win)
return return
end end
---@cast left ow.Git.Diff.Side ---@cast left ow.Git.Diff.Side
---@cast right ow.Git.Diff.Side ---@cast right ow.Git.Diff.Side
if left_win and not right_win then local left_win = vsplit_at(target, "left")
right_win = vsplit_at(left_win, "right") reset_diff_win(left_win)
reset_diff_win(right_win) local combined = vim.api.nvim_win_get_width(left_win)
elseif right_win and not left_win then + vim.api.nvim_win_get_width(target)
left_win = vsplit_at(right_win, "left") vim.api.nvim_win_set_width(left_win, math.floor(combined / 2))
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
diff.update_pair(left_win, target, { left = left, right = right })
vim.api.nvim_set_current_win(focus_left and left_win or status_win) vim.api.nvim_set_current_win(focus_left and left_win or status_win)
end end
@@ -640,14 +555,12 @@ end
---@param r ow.Git.Repo ---@param r ow.Git.Repo
---@param placement ow.Git.StatusView.Placement ---@param placement ow.Git.StatusView.Placement
---@param win integer? ---@param win integer?
---@param invocation_win integer? local function setup_buffer(bufnr, r, placement, win)
local function setup_buffer(bufnr, r, placement, win, invocation_win)
state[bufnr] = { state[bufnr] = {
repo = r, repo = r,
placement = placement, placement = placement,
lines = {}, lines = {},
win = win, win = win,
invocation_win = invocation_win,
} }
local function k(lhs, rhs, desc) local function k(lhs, rhs, desc)
@@ -743,7 +656,6 @@ function M.open(opts)
local s = state[buf] local s = state[buf]
if s then if s then
s.win = win s.win = win
s.invocation_win = previous_win
s.placement = placement s.placement = placement
end end
set_keymaps(buf, placement) set_keymaps(buf, placement)
@@ -805,7 +717,7 @@ function M.read_uri(buf)
win = nil win = nil
end end
if not state[buf] then if not state[buf] then
setup_buffer(buf, r, "current", win, nil) setup_buffer(buf, r, "current", win)
else else
state[buf].win = win state[buf].win = win
end end
+15 -4
View File
@@ -39,16 +39,28 @@ local function find_sidebar()
end end
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 role "left"|"right"
---@param tab integer? ---@param tab integer?
---@return integer? ---@return integer?
local function find_diff_win(role, tab) local function find_diff_win(role, tab)
local diffs = {}
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(tab or 0)) do for _, w in ipairs(vim.api.nvim_tabpage_list_wins(tab or 0)) do
if vim.w[w].git_diff_role == role then if vim.wo[w].diff then
return w table.insert(diffs, w)
end end
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 end
---@param file_path string ---@param file_path string
@@ -161,7 +173,6 @@ t.test(
setup_sidebar_with_unstaged_file("foo.txt", "v1\n", "v2\n") setup_sidebar_with_unstaged_file("foo.txt", "v1\n", "v2\n")
local tab1 = vim.api.nvim_get_current_tabpage() 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_set_current_win(sidebar_win)
vim.api.nvim_win_set_cursor(sidebar_win, { line, 0 }) vim.api.nvim_win_set_cursor(sidebar_win, { line, 0 })
t.press("<Tab>") t.press("<Tab>")