refactor(diffsplit): clarify diff side API
This commit is contained in:
+116
-30
@@ -6,13 +6,15 @@ local util = require("git.core.util")
|
||||
local M = {}
|
||||
|
||||
---@class ow.Git.Diffsplit.OpenOpts
|
||||
---@field target string?
|
||||
---@field other string?
|
||||
---@field layout ("vertical"|"horizontal")?
|
||||
---@field mods vim.api.keyset.cmd.mods?
|
||||
---@field focus ("current"|"other")?
|
||||
|
||||
---@param cur_buf integer
|
||||
---@return string? target
|
||||
---@return string? other
|
||||
---@return string? err
|
||||
local function infer_target(cur_buf)
|
||||
local function infer_other(cur_buf)
|
||||
local cur_name = vim.api.nvim_buf_get_name(cur_buf)
|
||||
local cur_rev = object.parse_uri(cur_name)
|
||||
if cur_rev then
|
||||
@@ -48,16 +50,16 @@ local function infer_target(cur_buf)
|
||||
return object.format_uri(Revision.new({ stage = 0, path = rel })), nil
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param other string
|
||||
---@param cur_buf integer
|
||||
---@return string? resolved
|
||||
---@return string? err
|
||||
local function resolve_target(target, cur_buf)
|
||||
if vim.startswith(target, object.URI_PREFIX) then
|
||||
return target, nil
|
||||
local function resolve_other(other, cur_buf)
|
||||
if vim.startswith(other, object.URI_PREFIX) then
|
||||
return other, nil
|
||||
end
|
||||
if vim.fn.filereadable(target) == 1 then
|
||||
return target, nil
|
||||
if vim.fn.filereadable(other) == 1 then
|
||||
return other, nil
|
||||
end
|
||||
local cur_name = vim.api.nvim_buf_get_name(cur_buf)
|
||||
local cur_rev = object.parse_uri(cur_name)
|
||||
@@ -78,57 +80,141 @@ local function resolve_target(target, cur_buf)
|
||||
if not rel then
|
||||
return nil, "current buffer has no path"
|
||||
end
|
||||
if not r:rev_parse(target, true) then
|
||||
return nil, "invalid rev: " .. target
|
||||
if not r:rev_parse(other, true) then
|
||||
return nil, "invalid rev: " .. other
|
||||
end
|
||||
return object.format_uri(Revision.new({ base = target, path = rel })), nil
|
||||
return object.format_uri(Revision.new({ base = other, path = rel })), nil
|
||||
end
|
||||
|
||||
---@param cur_buf integer
|
||||
---@param target string
|
||||
---@param other string
|
||||
---@return 'aboveleft'|'belowright'|nil
|
||||
local function default_split(cur_buf, target)
|
||||
local function default_split(cur_buf, other)
|
||||
local cur_rev = object.parse_uri(vim.api.nvim_buf_get_name(cur_buf))
|
||||
local target_rev = object.parse_uri(target)
|
||||
if not cur_rev and target_rev then
|
||||
local other_rev = object.parse_uri(other)
|
||||
if not cur_rev and other_rev then
|
||||
return "aboveleft"
|
||||
end
|
||||
if cur_rev and not target_rev then
|
||||
if cur_rev and not other_rev then
|
||||
return "belowright"
|
||||
end
|
||||
if cur_rev and target_rev then
|
||||
if cur_rev.stage == 0 and target_rev.base then
|
||||
if cur_rev and other_rev then
|
||||
if cur_rev.stage == 0 and other_rev.base then
|
||||
return "aboveleft"
|
||||
end
|
||||
if cur_rev.base and target_rev.stage == 0 then
|
||||
if cur_rev.base and other_rev.stage == 0 then
|
||||
return "belowright"
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---@alias ow.Git.Diffsplit.Side string|integer
|
||||
|
||||
---@class ow.Git.Diffsplit.OpenPairOpts
|
||||
---@field layout ("vertical"|"horizontal")?
|
||||
---@field mods vim.api.keyset.cmd.mods?
|
||||
---@field focus ("old"|"new")?
|
||||
|
||||
---@param mods vim.api.keyset.cmd.mods?
|
||||
---@param layout ("vertical"|"horizontal")?
|
||||
---@return vim.api.keyset.cmd.mods
|
||||
local function layout_mods(mods, layout)
|
||||
mods = vim.tbl_extend("force", {}, mods or {})
|
||||
if mods.vertical == nil then
|
||||
mods.vertical = layout ~= "horizontal"
|
||||
end
|
||||
return mods
|
||||
end
|
||||
|
||||
---@param side ow.Git.Diffsplit.Side
|
||||
---@param cur_buf integer
|
||||
---@return string? name
|
||||
---@return integer? buf
|
||||
---@return string? err
|
||||
local function resolve_side(side, cur_buf)
|
||||
if type(side) == "number" then
|
||||
local name = vim.api.nvim_buf_get_name(side)
|
||||
if name == "" then
|
||||
return nil, nil, "diff side buffer has no name"
|
||||
end
|
||||
return name, side, nil
|
||||
end
|
||||
local name, err = resolve_other(side, cur_buf)
|
||||
return name, nil, err
|
||||
end
|
||||
|
||||
---@param side ow.Git.Diffsplit.Side
|
||||
---@param cur_buf integer
|
||||
---@return integer? buf
|
||||
---@return string? err
|
||||
local function buf_for_side(side, cur_buf)
|
||||
local name, buf, err = resolve_side(side, cur_buf)
|
||||
if not name then
|
||||
return nil, err
|
||||
end
|
||||
if buf then
|
||||
return buf, nil
|
||||
end
|
||||
buf = vim.fn.bufadd(name)
|
||||
vim.fn.bufload(buf)
|
||||
return buf, nil
|
||||
end
|
||||
|
||||
---@param opts? ow.Git.Diffsplit.OpenOpts
|
||||
function M.open(opts)
|
||||
opts = opts or {}
|
||||
local cur_buf = vim.api.nvim_get_current_buf()
|
||||
local target, err
|
||||
if opts.target then
|
||||
target, err = resolve_target(opts.target, cur_buf)
|
||||
local other, err
|
||||
if opts.other then
|
||||
other, err = resolve_other(opts.other, cur_buf)
|
||||
else
|
||||
target, err = infer_target(cur_buf)
|
||||
other, err = infer_other(cur_buf)
|
||||
end
|
||||
if not target then
|
||||
util.error("%s", err or "no diff target")
|
||||
if not other then
|
||||
util.error("%s", err or "no diff side")
|
||||
return
|
||||
end
|
||||
local mods = opts.mods
|
||||
if not mods or mods.split == nil then
|
||||
local placement = default_split(cur_buf, target)
|
||||
local mods = layout_mods(opts.mods, opts.layout)
|
||||
if mods.split == nil then
|
||||
local placement = default_split(cur_buf, other)
|
||||
if placement then
|
||||
mods = vim.tbl_extend("force", mods or {}, { split = placement })
|
||||
end
|
||||
end
|
||||
vim.cmd.diffsplit({ args = { target }, mods = mods })
|
||||
local cur_win = vim.api.nvim_get_current_win()
|
||||
vim.cmd.diffsplit({ args = { other }, mods = mods })
|
||||
if opts.focus == "current" and vim.api.nvim_win_is_valid(cur_win) then
|
||||
vim.api.nvim_set_current_win(cur_win)
|
||||
end
|
||||
end
|
||||
|
||||
---@param old ow.Git.Diffsplit.Side
|
||||
---@param new ow.Git.Diffsplit.Side
|
||||
---@param opts? ow.Git.Diffsplit.OpenPairOpts
|
||||
function M.open_pair(old, new, opts)
|
||||
opts = opts or {}
|
||||
local cur_buf = vim.api.nvim_get_current_buf()
|
||||
local new_buf, err = buf_for_side(new, cur_buf)
|
||||
if not new_buf then
|
||||
util.error("%s", err or "no new diff side")
|
||||
return
|
||||
end
|
||||
local old_name, _, old_err = resolve_side(old, cur_buf)
|
||||
if not old_name then
|
||||
util.error("%s", old_err or "no old diff side")
|
||||
return
|
||||
end
|
||||
|
||||
vim.cmd.normal({ "m'", bang = true })
|
||||
vim.api.nvim_set_current_buf(new_buf)
|
||||
|
||||
local mods = layout_mods(opts.mods, opts.layout)
|
||||
mods.split = mods.split or "aboveleft"
|
||||
vim.cmd.diffsplit({ args = { old_name }, mods = mods })
|
||||
if opts.focus ~= "old" then
|
||||
vim.cmd.wincmd("p")
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
+3
-5
@@ -395,11 +395,9 @@ local function open_section(r, commit, section)
|
||||
local left = side(r, commit, true, section.blob_a, section.path_a)
|
||||
local right = side(r, commit, false, section.blob_b, section.path_b)
|
||||
if left and right then
|
||||
vim.cmd.normal({ "m'", bang = true })
|
||||
vim.api.nvim_set_current_buf(right)
|
||||
require("git.diffsplit").open({
|
||||
target = vim.api.nvim_buf_get_name(left),
|
||||
mods = { vertical = true },
|
||||
require("git.diffsplit").open_pair(left, right, {
|
||||
layout = "vertical",
|
||||
focus = "new",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
@@ -391,17 +391,11 @@ local function view_row(s, row, focus_left)
|
||||
---@cast left ow.Git.StatusView.Pane
|
||||
---@cast right ow.Git.StatusView.Pane
|
||||
|
||||
vim.api.nvim_win_set_buf(target, right.buf)
|
||||
if right.name then
|
||||
util.set_buf_name(right.buf, right.name)
|
||||
end
|
||||
|
||||
local older = left.name or vim.api.nvim_buf_get_name(left.buf)
|
||||
local left_win
|
||||
vim.api.nvim_win_call(target, function()
|
||||
diffsplit.open({
|
||||
target = older,
|
||||
mods = { vertical = true },
|
||||
diffsplit.open_pair(left.buf, right.buf, {
|
||||
layout = "vertical",
|
||||
focus = "old",
|
||||
})
|
||||
left_win = vim.api.nvim_get_current_win()
|
||||
vim.api.nvim_win_set_cursor(left_win, { 1, 0 })
|
||||
|
||||
+10
-10
@@ -183,16 +183,16 @@ local DIFF_DIRECTIONS = { "vertical", "horizontal" }
|
||||
|
||||
vim.api.nvim_create_user_command("Gdiffsplit", function(opts)
|
||||
local fargs = opts.fargs
|
||||
local mods = nil
|
||||
local layout = nil
|
||||
local rev_idx = 1
|
||||
if fargs[1] == "vertical" then
|
||||
mods = { vertical = true }
|
||||
layout = "vertical"
|
||||
rev_idx = 2
|
||||
elseif fargs[1] == "horizontal" then
|
||||
mods = { vertical = false }
|
||||
layout = "horizontal"
|
||||
rev_idx = 2
|
||||
end
|
||||
require("git.diffsplit").open({ target = fargs[rev_idx], mods = mods })
|
||||
require("git.diffsplit").open({ other = fargs[rev_idx], layout = layout })
|
||||
end, {
|
||||
nargs = "*",
|
||||
complete = function(arg_lead, cmd_line, _)
|
||||
@@ -253,21 +253,21 @@ vim.keymap.set("n", "<Plug>(git-edit)", function()
|
||||
end, { silent = true, desc = "Edit a git object" })
|
||||
|
||||
vim.keymap.set("n", "<Plug>(git-diffsplit-vertical)", function()
|
||||
require("git.diffsplit").open({ mods = { vertical = true } })
|
||||
require("git.diffsplit").open({ layout = "vertical" })
|
||||
end, { silent = true, desc = "Open a diff split against index (vertical)" })
|
||||
vim.keymap.set("n", "<Plug>(git-diffsplit-horizontal)", function()
|
||||
require("git.diffsplit").open({ mods = { vertical = false } })
|
||||
require("git.diffsplit").open({ layout = "horizontal" })
|
||||
end, { silent = true, desc = "Open a diff split against index (horizontal)" })
|
||||
vim.keymap.set("n", "<Plug>(git-diffsplit-vertical-head)", function()
|
||||
require("git.diffsplit").open({
|
||||
target = "HEAD",
|
||||
mods = { vertical = true },
|
||||
other = "HEAD",
|
||||
layout = "vertical",
|
||||
})
|
||||
end, { silent = true, desc = "Open a diff split against HEAD (vertical)" })
|
||||
vim.keymap.set("n", "<Plug>(git-diffsplit-horizontal-head)", function()
|
||||
require("git.diffsplit").open({
|
||||
target = "HEAD",
|
||||
mods = { vertical = false },
|
||||
other = "HEAD",
|
||||
layout = "horizontal",
|
||||
})
|
||||
end, { silent = true, desc = "Open a diff split against HEAD (horizontal)" })
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
local Revision = require("git.core.revision")
|
||||
local h = require("test.git.helpers")
|
||||
local t = require("test")
|
||||
|
||||
---@return integer[]
|
||||
local function diff_wins()
|
||||
local wins = {}
|
||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||
if vim.wo[win].diff then
|
||||
table.insert(wins, win)
|
||||
end
|
||||
end
|
||||
return wins
|
||||
end
|
||||
|
||||
---@param role "left"|"right"
|
||||
---@return integer
|
||||
local function vertical_diff_win(role)
|
||||
local wins = diff_wins()
|
||||
table.sort(wins, function(a, b)
|
||||
return vim.api.nvim_win_get_position(a)[2]
|
||||
< vim.api.nvim_win_get_position(b)[2]
|
||||
end)
|
||||
local win = role == "left" and wins[1] or wins[#wins]
|
||||
if not win then
|
||||
error("diff window not found")
|
||||
end
|
||||
return win
|
||||
end
|
||||
|
||||
---@param dir string
|
||||
local function cleanup_dir_buffers(dir)
|
||||
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
||||
local name = vim.api.nvim_buf_get_name(buf)
|
||||
if name:find(dir, 1, true) then
|
||||
pcall(vim.api.nvim_buf_delete, buf, { force = true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
t.test("open from worktree focuses the index pane", function()
|
||||
local dir = h.make_repo({ ["a.txt"] = "old\n" })
|
||||
t.write(dir, "a.txt", "new\n")
|
||||
vim.cmd.edit(vim.fs.joinpath(dir, "a.txt"))
|
||||
|
||||
local current_buf = vim.api.nvim_get_current_buf()
|
||||
require("git.diffsplit").open()
|
||||
|
||||
t.wait_for(function()
|
||||
return #diff_wins() == 2
|
||||
end, "diff windows to open")
|
||||
|
||||
t.eq(vim.api.nvim_get_current_win(), vertical_diff_win("left"))
|
||||
t.truthy(
|
||||
vim.api.nvim_get_current_buf() ~= current_buf,
|
||||
"worktree buffer should lose focus"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("open from index focuses the worktree pane", function()
|
||||
local dir = h.make_repo({ ["a.txt"] = "old\n" })
|
||||
t.write(dir, "a.txt", "new\n")
|
||||
local r = assert(require("git.core.repo").resolve(dir))
|
||||
local index_buf = require("git.object").buf_for(
|
||||
r,
|
||||
Revision.new({ stage = 0, path = "a.txt" })
|
||||
)
|
||||
vim.api.nvim_set_current_buf(index_buf)
|
||||
|
||||
require("git.diffsplit").open({ layout = "vertical" })
|
||||
|
||||
t.wait_for(function()
|
||||
return #diff_wins() == 2
|
||||
end, "diff windows to open")
|
||||
|
||||
local right = vertical_diff_win("right")
|
||||
t.eq(
|
||||
vim.api.nvim_get_current_win(),
|
||||
right,
|
||||
"worktree buffer should be focused"
|
||||
)
|
||||
t.truthy(
|
||||
vim.api.nvim_get_current_buf() ~= index_buf,
|
||||
"index buffer should lose focus"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("open_pair keeps old on the left when splitright is set", function()
|
||||
local dir = vim.fn.tempname()
|
||||
vim.fn.mkdir(dir, "p")
|
||||
t.defer(function()
|
||||
cleanup_dir_buffers(dir)
|
||||
vim.fn.delete(dir, "rf")
|
||||
end)
|
||||
|
||||
local old = vim.fs.joinpath(dir, "old.txt")
|
||||
local new = vim.fs.joinpath(dir, "new.txt")
|
||||
vim.fn.writefile({ "old" }, old)
|
||||
vim.fn.writefile({ "new" }, new)
|
||||
|
||||
local prev_splitright = vim.o.splitright
|
||||
vim.o.splitright = true
|
||||
t.defer(function()
|
||||
vim.o.splitright = prev_splitright
|
||||
end)
|
||||
|
||||
local old_buf = vim.fn.bufadd(old)
|
||||
local new_buf = vim.fn.bufadd(new)
|
||||
vim.fn.bufload(old_buf)
|
||||
vim.fn.bufload(new_buf)
|
||||
|
||||
require("git.diffsplit").open_pair(old_buf, new_buf, {
|
||||
layout = "vertical",
|
||||
})
|
||||
|
||||
t.wait_for(function()
|
||||
return #diff_wins() == 2
|
||||
end, "diff windows to open")
|
||||
|
||||
local left = vertical_diff_win("left")
|
||||
local right = vertical_diff_win("right")
|
||||
t.eq(vim.api.nvim_win_get_buf(left), old_buf, "old pane should be left")
|
||||
t.eq(vim.api.nvim_win_get_buf(right), new_buf, "new pane should be right")
|
||||
t.eq(
|
||||
vim.api.nvim_get_current_win(),
|
||||
right,
|
||||
"new pane should be focused by default"
|
||||
)
|
||||
end)
|
||||
@@ -341,7 +341,7 @@ t.test(
|
||||
error("a non-sidebar window should remain after close")
|
||||
end
|
||||
vim.api.nvim_set_current_win(remaining)
|
||||
require("git.diffsplit").open({ mods = { vertical = true } })
|
||||
require("git.diffsplit").open({ layout = "vertical" })
|
||||
t.wait_for(function()
|
||||
local count = 0
|
||||
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||
|
||||
Reference in New Issue
Block a user