288 lines
7.1 KiB
Lua
288 lines
7.1 KiB
Lua
local M = {}
|
|
|
|
---@class ow.Git.Util.ScratchOpts
|
|
---@field name string?
|
|
---@field bufhidden ("hide"|"wipe"|"delete")?
|
|
---@field buftype ("nofile"|"acwrite"|"nowrite")?
|
|
---@field modifiable boolean?
|
|
|
|
---@param buf integer
|
|
---@param opts ow.Git.Util.ScratchOpts
|
|
function M.setup_scratch(buf, opts)
|
|
vim.bo[buf].buftype = opts.buftype or "nofile"
|
|
vim.bo[buf].bufhidden = opts.bufhidden or "wipe"
|
|
vim.bo[buf].swapfile = false
|
|
vim.bo[buf].modifiable = opts.modifiable == true
|
|
vim.bo[buf].modified = false
|
|
vim.bo[buf].buflisted = false
|
|
if opts.name then
|
|
pcall(vim.api.nvim_buf_set_name, buf, opts.name)
|
|
end
|
|
end
|
|
|
|
---@param name string
|
|
---@return boolean
|
|
function M.is_uri(name)
|
|
return name:match("^%a+://") ~= nil
|
|
end
|
|
|
|
---@param buf integer
|
|
---@param name string
|
|
function M.set_buf_name(buf, name)
|
|
pcall(vim.api.nvim_buf_set_name, buf, name)
|
|
local ft = vim.filetype.match({ buf = buf })
|
|
if ft then
|
|
vim.bo[buf].filetype = ft
|
|
end
|
|
end
|
|
|
|
---@param buf integer
|
|
---@param split (false|"above"|"below"|"left"|"right")?
|
|
---@return integer win
|
|
function M.place_buf(buf, split)
|
|
if split == false then
|
|
vim.cmd.normal({ "m'", bang = true })
|
|
vim.api.nvim_set_current_buf(buf)
|
|
return vim.api.nvim_get_current_win()
|
|
end
|
|
local win = vim.api.nvim_open_win(buf, true, {
|
|
split = split or (vim.o.splitbelow and "below" or "above"),
|
|
})
|
|
vim.cmd.clearjumps()
|
|
return win
|
|
end
|
|
|
|
---@class ow.Git.Util.NewScratchOpts : ow.Git.Util.ScratchOpts
|
|
---@field split (false|"above"|"below"|"left"|"right")?
|
|
|
|
---@param opts ow.Git.Util.NewScratchOpts?
|
|
---@return integer buf
|
|
---@return integer win
|
|
function M.new_scratch(opts)
|
|
opts = opts or {}
|
|
local buf = vim.api.nvim_create_buf(false, true)
|
|
M.setup_scratch(buf, opts)
|
|
return buf, M.place_buf(buf, opts.split)
|
|
end
|
|
|
|
---@param fmt string
|
|
---@param ... any
|
|
function M.error(fmt, ...)
|
|
vim.notify(fmt:format(...), vim.log.levels.ERROR)
|
|
end
|
|
|
|
---@param fmt string
|
|
---@param ... any
|
|
function M.warning(fmt, ...)
|
|
vim.notify(fmt:format(...), vim.log.levels.WARN)
|
|
end
|
|
|
|
---@param fmt string
|
|
---@param ... any
|
|
function M.info(fmt, ...)
|
|
vim.notify(fmt:format(...), vim.log.levels.INFO)
|
|
end
|
|
|
|
---@param fmt string
|
|
---@param ... any
|
|
function M.debug(fmt, ...)
|
|
vim.notify(fmt:format(...), vim.log.levels.DEBUG)
|
|
end
|
|
|
|
---@param buf integer
|
|
---@param start integer
|
|
---@param end_ integer
|
|
---@param lines string[]
|
|
function M.set_buf_lines(buf, start, end_, lines)
|
|
if not vim.api.nvim_buf_is_loaded(buf) then
|
|
return
|
|
end
|
|
local was_modifiable = vim.bo[buf].modifiable
|
|
vim.bo[buf].modifiable = true
|
|
vim.api.nvim_buf_set_lines(buf, start, end_, true, lines)
|
|
vim.bo[buf].modifiable = was_modifiable
|
|
vim.bo[buf].modified = false
|
|
end
|
|
|
|
---@param content string
|
|
---@return string[]
|
|
function M.split_lines(content)
|
|
local lines = vim.split(content, "\n", { plain = true, trimempty = false })
|
|
if #lines > 0 and lines[#lines] == "" then
|
|
table.remove(lines)
|
|
end
|
|
return lines
|
|
end
|
|
|
|
---@class ow.Git.Util.DebounceHandle
|
|
---@field cancel fun()
|
|
---@field flush fun()
|
|
---@field pending fun(): boolean
|
|
---@field close fun()
|
|
|
|
---@generic F: fun(...)
|
|
---@param fn F
|
|
---@param delay integer
|
|
---@return F, ow.Git.Util.DebounceHandle
|
|
function M.debounce(fn, delay)
|
|
local timer, err = vim.uv.new_timer()
|
|
if not timer then
|
|
M.warning("git: failed to create timer: %s", err)
|
|
local noop = function() end
|
|
return fn,
|
|
{
|
|
cancel = noop,
|
|
flush = noop,
|
|
pending = function()
|
|
return false
|
|
end,
|
|
close = noop,
|
|
}
|
|
end
|
|
local args ---@type table?
|
|
local gen = 0
|
|
local fired_gen = 0
|
|
|
|
local cb_main = vim.schedule_wrap(function()
|
|
-- Identity check: the libuv fire may have been superseded by
|
|
-- a re-arm or a cancel between the timer firing and this
|
|
-- scheduled callback running.
|
|
if fired_gen ~= gen or args == nil then
|
|
return
|
|
end
|
|
local a = args
|
|
args = nil
|
|
fn(vim.F.unpack_len(a))
|
|
end)
|
|
|
|
local cb_uv = function()
|
|
fired_gen = gen
|
|
cb_main()
|
|
end
|
|
|
|
local function call(...)
|
|
args = vim.F.pack_len(...)
|
|
gen = gen + 1
|
|
timer:start(delay, 0, cb_uv)
|
|
end
|
|
|
|
return call,
|
|
{
|
|
cancel = function()
|
|
timer:stop()
|
|
args = nil
|
|
end,
|
|
flush = function()
|
|
if args == nil then
|
|
return
|
|
end
|
|
timer:stop()
|
|
local a = args
|
|
args = nil
|
|
fn(vim.F.unpack_len(a))
|
|
end,
|
|
pending = function()
|
|
return args ~= nil
|
|
end,
|
|
close = function()
|
|
timer:stop()
|
|
if not timer:is_closing() then
|
|
timer:close()
|
|
end
|
|
args = nil
|
|
end,
|
|
}
|
|
end
|
|
|
|
---@class ow.Git.Util.ExecOpts
|
|
---@field cwd string?
|
|
---@field stdin string?
|
|
---@field silent boolean?
|
|
---@field env table<string, string>?
|
|
---@field on_exit fun(result: vim.SystemCompleted)?
|
|
|
|
---@param cmd string[]
|
|
---@param opts ow.Git.Util.ExecOpts?
|
|
---@return string?
|
|
function M.exec(cmd, opts)
|
|
opts = opts or {}
|
|
local sys_opts = {
|
|
cwd = opts.cwd,
|
|
stdin = opts.stdin,
|
|
env = opts.env,
|
|
text = true,
|
|
}
|
|
|
|
if opts.on_exit then
|
|
vim.system(cmd, sys_opts, vim.schedule_wrap(opts.on_exit))
|
|
return nil
|
|
end
|
|
|
|
local result = vim.system(cmd, sys_opts):wait()
|
|
if result.code ~= 0 then
|
|
if not opts.silent then
|
|
local label = cmd[2] and (cmd[1] .. " " .. cmd[2]) or cmd[1] or "?"
|
|
M.error("%s failed: %s", label, vim.trim(result.stderr or ""))
|
|
end
|
|
return nil
|
|
end
|
|
return result.stdout or ""
|
|
end
|
|
|
|
M.DEFAULT_GIT_ENV = {
|
|
GIT_TERMINAL_PROMPT = "false",
|
|
}
|
|
|
|
---@param args string[]
|
|
---@param opts ow.Git.Util.ExecOpts?
|
|
---@return string?
|
|
function M.git(args, opts)
|
|
opts = opts or {}
|
|
opts.env = vim.tbl_extend("force", M.DEFAULT_GIT_ENV, opts.env or {})
|
|
local cmd = { "git" }
|
|
vim.list_extend(cmd, args)
|
|
return M.exec(cmd, opts)
|
|
end
|
|
|
|
---@class ow.Git.Util.Emitter<T>
|
|
---@field private _listeners table<T, (fun(...))[]>
|
|
local Emitter = {}
|
|
Emitter.__index = Emitter
|
|
|
|
---@return ow.Git.Util.Emitter<T>
|
|
function Emitter.new()
|
|
return setmetatable({ _listeners = {} }, Emitter)
|
|
end
|
|
|
|
---@param event T
|
|
---@param fn fun(...)
|
|
---@return fun() unsubscribe
|
|
function Emitter:on(event, fn)
|
|
local list = self._listeners[event] or {}
|
|
self._listeners[event] = list
|
|
table.insert(list, fn)
|
|
return function()
|
|
for i, f in ipairs(list) do
|
|
if f == fn then
|
|
table.remove(list, i)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param event T
|
|
function Emitter:emit(event, ...)
|
|
for _, fn in ipairs(self._listeners[event] or {}) do
|
|
fn(...)
|
|
end
|
|
end
|
|
|
|
function Emitter:clear()
|
|
self._listeners = {}
|
|
end
|
|
|
|
M.Emitter = Emitter
|
|
|
|
return M
|