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