--- Locate, spawn, and wait on a `hephd` daemon. Shared by optional autostart --- and by the e2e harness (so test readiness uses the same definition the --- plugin does). local uv = vim.uv or vim.loop local M = {} -- The daemon THIS nvim spawned (nil if we connected to an existing one). -- `{ handle, exited = { done }, socket, db, bin }`. M._managed = nil --- Spawn a `local`-mode hephd against `opts.db` listening on `opts.socket`. --- `opts.bin` defaults to `hephd` on PATH. Returns `{ handle, pid }`. function M.spawn(opts) local args = { "--mode", "local" } if opts.db then table.insert(args, "--db") table.insert(args, opts.db) end if opts.socket then table.insert(args, "--socket") table.insert(args, opts.socket) end local handle, pid = uv.spawn(opts.bin or "hephd", { args = args, stdio = { nil, nil, opts.stderr }, }, function(code, signal) if opts.on_exit then opts.on_exit(code, signal) end end) if not handle then error("heph: failed to spawn hephd (bin=" .. (opts.bin or "hephd") .. ")") end return { handle = handle, pid = pid } end --- Wait until `socket` both exists and accepts a real RPC (`health`). The --- existence check alone races the daemon's bind→accept, so we prove liveness --- with a round-trip on a throwaway session. Returns `true`, or `false, reason`. function M.wait_ready(socket, timeout) timeout = timeout or 5000 if not vim.wait(timeout, function() return uv.fs_stat(socket) ~= nil end, 20) then return false, "socket never appeared: " .. socket end local session = require("heph.rpc").new_session(socket) local ok = vim.wait(timeout, function() return pcall(function() session:call("health", vim.empty_dict(), { timeout = 200 }) end) end, 50) session:close() if not ok then return false, "socket present but not accepting rpc: " .. socket end return true end --- Ensure a daemon is reachable at `opts.socket`. If one is already serving the --- socket (any mode — local/server/client), connect to it and do NOT spawn. Else --- if `opts.autostart`, spawn a local hephd we own (and manage its lifecycle). --- Returns `reachable, spawned_by_us`. function M.ensure(opts) -- Already serving? A quick probe respects a daemon someone else started. if M.wait_ready(opts.socket, opts.probe_ms or 400) then return true, false end if not opts.autostart then return false, false end local exited = { done = false } local d = M.spawn({ bin = opts.bin, socket = opts.socket, db = opts.db, on_exit = function() exited.done = true end, }) local ok, reason = M.wait_ready(opts.socket, opts.ready_ms or 5000) if not ok then pcall(function() if not d.handle:is_closing() then d.handle:kill("sigterm") end end) error("heph: spawned hephd but it never became ready: " .. tostring(reason)) end M._managed = { handle = d.handle, exited = exited, socket = opts.socket, db = opts.db, bin = opts.bin, } return true, true end --- True if this nvim currently owns a live spawned daemon. function M.is_managed() return M._managed ~= nil and not M._managed.exited.done end --- Stop the daemon this nvim spawned (no-op if we connected to an existing one). function M.stop_spawned() local m = M._managed if not m then return end M._managed = nil if m.handle and not m.exited.done then pcall(function() m.handle:kill("sigterm") end) vim.wait(2000, function() return m.exited.done end, 20) end pcall(function() if m.handle and not m.handle:is_closing() then m.handle:close() end end) end return M