Files
nvim/lua/git/diffsplit.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