local util = require("git.util") local M = {} local SENTINEL = "__NVIM_GIT_EDIT__" local SCRIPT = string.format( [=[set -eu flag="${TMPDIR:-/tmp}/nvim-git-editor-$$.done" trap 'rm -f "$flag"' EXIT abs=$(realpath "$1") printf '%s\t%%s\t%%s\n' "$flag" "$abs" >&2 while [ ! -e "$flag" ]; do sleep 0.05 done ]=], SENTINEL ) ---@param s string ---@return string local function shq(s) return "'" .. s:gsub("'", "'\\''") .. "'" end local GIT_EDITOR = "sh -c " .. shq(SCRIPT) .. " --" ---@param on_open fun(file_path: string, done: fun()) ---@return fun(err: string?, data: string?), fun(result: vim.SystemCompleted) local function build_stderr_handler(on_open) local pending = "" local stderr_buf = {} local function dispatch(flag_path, abs_path) vim.schedule(function() local fired = false local function done() if fired then return end fired = true local fw = io.open(flag_path, "w") if fw then fw:close() end end local ok, err = pcall(on_open, abs_path, done) if not ok then util.error("git.editor on_open failed: %s", tostring(err)) done() end end) end local pattern = "^" .. SENTINEL .. "\t(.-)\t(.+)$" local function on_stderr(_, data) if not data or data == "" then return end pending = pending .. data while true do local nl = pending:find("\n", 1, true) if not nl then break end local line = pending:sub(1, nl - 1) pending = pending:sub(nl + 1) local flag, abs = line:match(pattern) if flag then dispatch(flag, abs) else table.insert(stderr_buf, line) table.insert(stderr_buf, "\n") end end end local function finalize(result) if pending ~= "" then table.insert(stderr_buf, pending) end result.stderr = table.concat(stderr_buf) end return on_stderr, finalize end ---@param cmd string[] ---@param opts? { cwd?: string, env?: table } ---@param on_open fun(file_path: string, done: fun()) ---@param on_exit fun(result: vim.SystemCompleted) function M.run(cmd, opts, on_open, on_exit) opts = opts or {} local on_stderr, finalize = build_stderr_handler(on_open) local env = vim.tbl_extend("force", opts.env or {}, { GIT_EDITOR = GIT_EDITOR, GIT_SEQUENCE_EDITOR = GIT_EDITOR, }) vim.system( cmd, { cwd = opts.cwd, text = true, env = env, stderr = on_stderr, }, vim.schedule_wrap(function(result) finalize(result) on_exit(result) end) ) end return M