refactor(git): make the git module self-contained under git.util
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
local M = {}
|
||||
|
||||
---@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
|
||||
|
||||
---Split a string on newlines, dropping the trailing empty element that an
|
||||
---input ending in `\n` produces. Convenient for slicing subprocess stdout
|
||||
---into a list of lines without a phantom blank at the 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 = assert(vim.uv.new_timer())
|
||||
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.ExecOpts
|
||||
---@field cwd string?
|
||||
---@field stdin string?
|
||||
---@field silent boolean? suppress the auto-log on non-zero exit
|
||||
---@field on_done fun(stdout: string?)? if set, run async and deliver stdout (or nil on failure) here on the main loop instead of returning sync
|
||||
|
||||
---Run a system command. Default is sync: returns stdout on success or
|
||||
---nil on failure (logging stderr unless `opts.silent`). When
|
||||
---`opts.on_done` is set, runs async via `vim.schedule_wrap` and
|
||||
---delivers the same stdout-or-nil value to that callback instead.
|
||||
---
|
||||
---Async mode returns nil immediately. Callers that need access to the
|
||||
---raw stderr / exit code in the failure path should opt out of this
|
||||
---helper and use `vim.system` directly.
|
||||
---@param cmd string[]
|
||||
---@param opts ow.Git.ExecOpts?
|
||||
---@return string?
|
||||
function M.exec(cmd, opts)
|
||||
opts = opts or {}
|
||||
local sys_opts = { cwd = opts.cwd, stdin = opts.stdin, text = true }
|
||||
|
||||
local function handle(result)
|
||||
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
|
||||
|
||||
if opts.on_done then
|
||||
vim.system(
|
||||
cmd,
|
||||
sys_opts,
|
||||
vim.schedule_wrap(function(result)
|
||||
opts.on_done(handle(result))
|
||||
end)
|
||||
)
|
||||
return nil
|
||||
end
|
||||
return handle(vim.system(cmd, sys_opts):wait())
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user