Files
nvim/lua/git/log_view.lua
T

176 lines
4.3 KiB
Lua

local repo = require("git.core.repo")
local util = require("git.core.util")
local M = {}
M.URI_PREFIX = "gitlog://"
local LOG_FORMAT = "%h %ad {%an}%d %s"
local cr = vim.api.nvim_replace_termcodes("<CR>", true, false, true)
---@param buf integer
local function attach_dispatch(buf)
vim.keymap.set("n", "<CR>", function()
local r = repo.resolve(buf)
-- Anchor past the leading graph chars (matches the leading sha column,
-- not any hex word that happens to appear later in the subject).
local sha = r
and vim.api
.nvim_get_current_line()
:match("^[*|/\\_ ]*(%x%x%x%x%x%x%x+)")
if sha then
---@cast r -nil
require("git.object").open(r, sha, { split = false })
else
vim.api.nvim_feedkeys(cr, "n", false)
end
end, { buffer = buf, silent = true, desc = "Open commit" })
end
---@param worktree string
---@param max_count integer?
---@return string?
local function fetch(worktree, max_count)
local args = {
"log",
"--graph",
"--all",
"--decorate",
"--date=short",
"--format=format:" .. LOG_FORMAT,
}
if max_count then
table.insert(args, "--max-count=" .. max_count)
end
return util.git(args, { cwd = worktree })
end
---@type table<string, integer> -- worktree -> max_count
local max_counts = {}
---@param buf integer
---@param r ow.Git.Repo
local function populate(buf, r)
local stdout = fetch(r.worktree, max_counts[r.worktree])
if not stdout then
return
end
local new_lines = util.split_lines(stdout)
local old_str = table.concat(
vim.api.nvim_buf_get_lines(buf, 0, -1, false),
"\n"
) .. "\n"
local new_str = table.concat(new_lines, "\n") .. "\n"
local hunks = vim.text.diff(old_str, new_str, {
result_type = "indices",
algorithm = "histogram",
})
---@cast hunks [integer, integer, integer, integer][]
if #hunks == 0 then
return
end
for i = #hunks, 1, -1 do
local sa, ca, sb, cb = unpack(hunks[i])
local start = ca == 0 and sa or sa - 1
util.set_buf_lines(
buf,
start,
start + ca,
vim.list_slice(new_lines, sb, sb + cb - 1)
)
end
end
---@param buf integer
function M.read_uri(buf)
local name = vim.api.nvim_buf_get_name(buf)
local worktree = name:sub(#M.URI_PREFIX + 1)
if worktree == "" then
return
end
local r = repo.resolve(worktree)
if not r then
return
end
repo.bind(buf, r)
util.setup_scratch(buf, { bufhidden = "hide" })
vim.bo[buf].filetype = "gitlog"
attach_dispatch(buf)
populate(buf, r)
end
---@class ow.Git.Log.OpenOpts
---@field max_count integer?
---@type table<string, fun(s: string): any>
M.opt_parsers = {
max_count = tonumber,
}
---@param opts ow.Git.Log.OpenOpts?
function M.open(opts)
opts = opts or {}
local r = repo.resolve()
if not r then
util.error("not in a git repository")
return
end
max_counts[r.worktree] = opts.max_count
local buf = vim.fn.bufadd(M.URI_PREFIX .. r.worktree)
local was_loaded = vim.api.nvim_buf_is_loaded(buf)
local win = vim.fn.bufwinid(buf)
if win == -1 then
util.place_buf(buf, nil)
else
vim.api.nvim_set_current_win(win)
end
if was_loaded then
populate(buf, r)
end
end
---@param cmd_opts table
function M.run_glog(cmd_opts)
local parsed = { max_count = 1000 }
for _, a in ipairs(cmd_opts.fargs) do
local k, v = a:match("^([%w_]+)=(.*)$")
if not k then
util.error("invalid argument: %s", a)
return
end
---@cast v -nil
local parser = M.opt_parsers[k]
if parser then
local value = parser(v)
if value ~= nil then
parsed[k] = value
end
end
end
M.open(parsed)
end
---@param arg_lead string
---@return string[]
function M.complete_glog(arg_lead)
local matches = {}
for k in pairs(M.opt_parsers) do
local prefix = k .. "="
if prefix:sub(1, #arg_lead) == arg_lead then
table.insert(matches, prefix)
end
end
table.sort(matches)
return matches
end
repo.on_uri_refresh(M.URI_PREFIX, populate)
return M