135 lines
4.0 KiB
Lua
135 lines
4.0 KiB
Lua
local Revision = require("git.core.revision")
|
|
local object = require("git.object")
|
|
local repo = require("git.core.repo")
|
|
local util = require("git.core.util")
|
|
|
|
local M = {}
|
|
|
|
---@class ow.Git.Diffsplit.OpenOpts
|
|
---@field target string?
|
|
---@field mods vim.api.keyset.cmd.mods?
|
|
|
|
---@param cur_buf integer
|
|
---@return string? target
|
|
---@return string? err
|
|
local function infer_target(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
|
|
local r = repo.resolve(cur_buf)
|
|
if not r then
|
|
return nil, "git URI buffer has no worktree"
|
|
end
|
|
if not cur_rev.path then
|
|
return nil, "git URI has no path, cannot diff against worktree"
|
|
end
|
|
local worktree_path = vim.fs.joinpath(r.worktree, cur_rev.path)
|
|
if not vim.uv.fs_stat(worktree_path) then
|
|
return nil, "worktree file does not exist: " .. cur_rev.path
|
|
end
|
|
return worktree_path, nil
|
|
end
|
|
|
|
if cur_name == "" then
|
|
return nil, "no file in current buffer"
|
|
end
|
|
if vim.bo[cur_buf].buftype ~= "" then
|
|
return nil, "cannot diff this buffer (not a worktree file)"
|
|
end
|
|
local resolved = vim.fn.resolve(cur_name)
|
|
local r = repo.resolve(resolved)
|
|
if not r then
|
|
return nil, "not in a git repository"
|
|
end
|
|
local rel = vim.fs.relpath(r.worktree, resolved)
|
|
if not rel then
|
|
return nil, "current buffer is outside the worktree"
|
|
end
|
|
return object.format_uri(Revision.new({ stage = 0, path = rel })), nil
|
|
end
|
|
|
|
---@param target 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
|
|
end
|
|
if vim.fn.filereadable(target) == 1 then
|
|
return target, nil
|
|
end
|
|
local cur_name = vim.api.nvim_buf_get_name(cur_buf)
|
|
local cur_rev = object.parse_uri(cur_name)
|
|
local r, rel
|
|
if cur_rev and cur_rev.path then
|
|
r = repo.resolve(cur_buf)
|
|
rel = cur_rev.path
|
|
elseif cur_name ~= "" then
|
|
local resolved = vim.fn.resolve(cur_name)
|
|
r = repo.resolve(resolved)
|
|
if r then
|
|
rel = vim.fs.relpath(r.worktree, resolved)
|
|
end
|
|
end
|
|
if not r then
|
|
return nil, "not in a git repository"
|
|
end
|
|
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
|
|
end
|
|
return object.format_uri(Revision.new({ base = target, path = rel })), nil
|
|
end
|
|
|
|
---@param cur_buf integer
|
|
---@param target string
|
|
---@return 'aboveleft'|'belowright'|nil
|
|
local function default_split(cur_buf, target)
|
|
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
|
|
return "aboveleft"
|
|
end
|
|
if cur_rev and not target_rev then
|
|
return "belowright"
|
|
end
|
|
if cur_rev and target_rev then
|
|
if cur_rev.stage == 0 and target_rev.base then
|
|
return "aboveleft"
|
|
end
|
|
if cur_rev.base and target_rev.stage == 0 then
|
|
return "belowright"
|
|
end
|
|
end
|
|
return 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)
|
|
else
|
|
target, err = infer_target(cur_buf)
|
|
end
|
|
if not target then
|
|
util.error("%s", err or "no diff target")
|
|
return
|
|
end
|
|
local mods = opts.mods
|
|
if not mods or mods.split == nil then
|
|
local placement = default_split(cur_buf, target)
|
|
if placement then
|
|
mods = vim.tbl_extend("force", mods or {}, { split = placement })
|
|
end
|
|
end
|
|
vim.cmd.diffsplit({ args = { target }, mods = mods })
|
|
end
|
|
|
|
return M
|