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 = {}
|
local M = {}
|
||||||
|
|
||||||
---@class ow.Git.Diffsplit.OpenOpts
|
---@class ow.Git.Diffsplit.OpenOpts
|
||||||
---@field target string?
|
---@field other string?
|
||||||
|
---@field layout ("vertical"|"horizontal")?
|
||||||
---@field mods vim.api.keyset.cmd.mods?
|
---@field mods vim.api.keyset.cmd.mods?
|
||||||
|
---@field focus ("current"|"other")?
|
||||||
|
|
||||||
---@param cur_buf integer
|
---@param cur_buf integer
|
||||||
---@return string? target
|
---@return string? other
|
||||||
---@return string? err
|
---@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_name = vim.api.nvim_buf_get_name(cur_buf)
|
||||||
local cur_rev = object.parse_uri(cur_name)
|
local cur_rev = object.parse_uri(cur_name)
|
||||||
if cur_rev then
|
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
|
return object.format_uri(Revision.new({ stage = 0, path = rel })), nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param target string
|
---@param other string
|
||||||
---@param cur_buf integer
|
---@param cur_buf integer
|
||||||
---@return string? resolved
|
---@return string? resolved
|
||||||
---@return string? err
|
---@return string? err
|
||||||
local function resolve_target(target, cur_buf)
|
local function resolve_other(other, cur_buf)
|
||||||
if vim.startswith(target, object.URI_PREFIX) then
|
if vim.startswith(other, object.URI_PREFIX) then
|
||||||
return target, nil
|
return other, nil
|
||||||
end
|
end
|
||||||
if vim.fn.filereadable(target) == 1 then
|
if vim.fn.filereadable(other) == 1 then
|
||||||
return target, nil
|
return other, nil
|
||||||
end
|
end
|
||||||
local cur_name = vim.api.nvim_buf_get_name(cur_buf)
|
local cur_name = vim.api.nvim_buf_get_name(cur_buf)
|
||||||
local cur_rev = object.parse_uri(cur_name)
|
local cur_rev = object.parse_uri(cur_name)
|
||||||
@@ -78,57 +80,141 @@ local function resolve_target(target, cur_buf)
|
|||||||
if not rel then
|
if not rel then
|
||||||
return nil, "current buffer has no path"
|
return nil, "current buffer has no path"
|
||||||
end
|
end
|
||||||
if not r:rev_parse(target, true) then
|
if not r:rev_parse(other, true) then
|
||||||
return nil, "invalid rev: " .. target
|
return nil, "invalid rev: " .. other
|
||||||
end
|
end
|
||||||
return object.format_uri(Revision.new({ base = target, path = rel })), nil
|
return object.format_uri(Revision.new({ base = other, path = rel })), nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param cur_buf integer
|
---@param cur_buf integer
|
||||||
---@param target string
|
---@param other string
|
||||||
---@return 'aboveleft'|'belowright'|nil
|
---@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 cur_rev = object.parse_uri(vim.api.nvim_buf_get_name(cur_buf))
|
||||||
local target_rev = object.parse_uri(target)
|
local other_rev = object.parse_uri(other)
|
||||||
if not cur_rev and target_rev then
|
if not cur_rev and other_rev then
|
||||||
return "aboveleft"
|
return "aboveleft"
|
||||||
end
|
end
|
||||||
if cur_rev and not target_rev then
|
if cur_rev and not other_rev then
|
||||||
return "belowright"
|
return "belowright"
|
||||||
end
|
end
|
||||||
if cur_rev and target_rev then
|
if cur_rev and other_rev then
|
||||||
if cur_rev.stage == 0 and target_rev.base then
|
if cur_rev.stage == 0 and other_rev.base then
|
||||||
return "aboveleft"
|
return "aboveleft"
|
||||||
end
|
end
|
||||||
if cur_rev.base and target_rev.stage == 0 then
|
if cur_rev.base and other_rev.stage == 0 then
|
||||||
return "belowright"
|
return "belowright"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
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
|
---@param opts? ow.Git.Diffsplit.OpenOpts
|
||||||
function M.open(opts)
|
function M.open(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local cur_buf = vim.api.nvim_get_current_buf()
|
local cur_buf = vim.api.nvim_get_current_buf()
|
||||||
local target, err
|
local other, err
|
||||||
if opts.target then
|
if opts.other then
|
||||||
target, err = resolve_target(opts.target, cur_buf)
|
other, err = resolve_other(opts.other, cur_buf)
|
||||||
else
|
else
|
||||||
target, err = infer_target(cur_buf)
|
other, err = infer_other(cur_buf)
|
||||||
end
|
end
|
||||||
if not target then
|
if not other then
|
||||||
util.error("%s", err or "no diff target")
|
util.error("%s", err or "no diff side")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local mods = opts.mods
|
local mods = layout_mods(opts.mods, opts.layout)
|
||||||
if not mods or mods.split == nil then
|
if mods.split == nil then
|
||||||
local placement = default_split(cur_buf, target)
|
local placement = default_split(cur_buf, other)
|
||||||
if placement then
|
if placement then
|
||||||
mods = vim.tbl_extend("force", mods or {}, { split = placement })
|
mods = vim.tbl_extend("force", mods or {}, { split = placement })
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
return M
|
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 left = side(r, commit, true, section.blob_a, section.path_a)
|
||||||
local right = side(r, commit, false, section.blob_b, section.path_b)
|
local right = side(r, commit, false, section.blob_b, section.path_b)
|
||||||
if left and right then
|
if left and right then
|
||||||
vim.cmd.normal({ "m'", bang = true })
|
require("git.diffsplit").open_pair(left, right, {
|
||||||
vim.api.nvim_set_current_buf(right)
|
layout = "vertical",
|
||||||
require("git.diffsplit").open({
|
focus = "new",
|
||||||
target = vim.api.nvim_buf_get_name(left),
|
|
||||||
mods = { vertical = true },
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -391,17 +391,11 @@ local function view_row(s, row, focus_left)
|
|||||||
---@cast left ow.Git.StatusView.Pane
|
---@cast left ow.Git.StatusView.Pane
|
||||||
---@cast right 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
|
local left_win
|
||||||
vim.api.nvim_win_call(target, function()
|
vim.api.nvim_win_call(target, function()
|
||||||
diffsplit.open({
|
diffsplit.open_pair(left.buf, right.buf, {
|
||||||
target = older,
|
layout = "vertical",
|
||||||
mods = { vertical = true },
|
focus = "old",
|
||||||
})
|
})
|
||||||
left_win = vim.api.nvim_get_current_win()
|
left_win = vim.api.nvim_get_current_win()
|
||||||
vim.api.nvim_win_set_cursor(left_win, { 1, 0 })
|
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)
|
vim.api.nvim_create_user_command("Gdiffsplit", function(opts)
|
||||||
local fargs = opts.fargs
|
local fargs = opts.fargs
|
||||||
local mods = nil
|
local layout = nil
|
||||||
local rev_idx = 1
|
local rev_idx = 1
|
||||||
if fargs[1] == "vertical" then
|
if fargs[1] == "vertical" then
|
||||||
mods = { vertical = true }
|
layout = "vertical"
|
||||||
rev_idx = 2
|
rev_idx = 2
|
||||||
elseif fargs[1] == "horizontal" then
|
elseif fargs[1] == "horizontal" then
|
||||||
mods = { vertical = false }
|
layout = "horizontal"
|
||||||
rev_idx = 2
|
rev_idx = 2
|
||||||
end
|
end
|
||||||
require("git.diffsplit").open({ target = fargs[rev_idx], mods = mods })
|
require("git.diffsplit").open({ other = fargs[rev_idx], layout = layout })
|
||||||
end, {
|
end, {
|
||||||
nargs = "*",
|
nargs = "*",
|
||||||
complete = function(arg_lead, cmd_line, _)
|
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" })
|
end, { silent = true, desc = "Edit a git object" })
|
||||||
|
|
||||||
vim.keymap.set("n", "<Plug>(git-diffsplit-vertical)", function()
|
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)" })
|
end, { silent = true, desc = "Open a diff split against index (vertical)" })
|
||||||
vim.keymap.set("n", "<Plug>(git-diffsplit-horizontal)", function()
|
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)" })
|
end, { silent = true, desc = "Open a diff split against index (horizontal)" })
|
||||||
vim.keymap.set("n", "<Plug>(git-diffsplit-vertical-head)", function()
|
vim.keymap.set("n", "<Plug>(git-diffsplit-vertical-head)", function()
|
||||||
require("git.diffsplit").open({
|
require("git.diffsplit").open({
|
||||||
target = "HEAD",
|
other = "HEAD",
|
||||||
mods = { vertical = true },
|
layout = "vertical",
|
||||||
})
|
})
|
||||||
end, { silent = true, desc = "Open a diff split against HEAD (vertical)" })
|
end, { silent = true, desc = "Open a diff split against HEAD (vertical)" })
|
||||||
vim.keymap.set("n", "<Plug>(git-diffsplit-horizontal-head)", function()
|
vim.keymap.set("n", "<Plug>(git-diffsplit-horizontal-head)", function()
|
||||||
require("git.diffsplit").open({
|
require("git.diffsplit").open({
|
||||||
target = "HEAD",
|
other = "HEAD",
|
||||||
mods = { vertical = false },
|
layout = "horizontal",
|
||||||
})
|
})
|
||||||
end, { silent = true, desc = "Open a diff split against HEAD (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")
|
error("a non-sidebar window should remain after close")
|
||||||
end
|
end
|
||||||
vim.api.nvim_set_current_win(remaining)
|
vim.api.nvim_set_current_win(remaining)
|
||||||
require("git.diffsplit").open({ mods = { vertical = true } })
|
require("git.diffsplit").open({ layout = "vertical" })
|
||||||
t.wait_for(function()
|
t.wait_for(function()
|
||||||
local count = 0
|
local count = 0
|
||||||
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||||
|
|||||||
Reference in New Issue
Block a user