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