See More

local util = require('opencode.util') local safe_call = util.safe_call local Promise = require('opencode.promise') local config = require('opencode.config') --- @class OpencodeServer --- @field job any The vim.system job handle --- @field url string|nil The server URL once ready --- @field handle any Compatibility property for job.stop interface --- @field spawn_promise Promise --- @field shutdown_promise Promise local OpencodeServer = {} OpencodeServer.__index = OpencodeServer local vim_leave_setup = false local function ensure_vim_leave_autocmd() if vim_leave_setup then return end vim_leave_setup = true vim.api.nvim_create_autocmd('VimLeavePre', { group = vim.api.nvim_create_augroup('OpencodeVimLeavePre', { clear = true }), callback = function() local state = require('opencode.state') if state.opencode_server then pcall(function() state.opencode_server:shutdown():wait(2000) end) end end, }) end --- Create a new ServerJob instance --- @return OpencodeServer function OpencodeServer.new() ensure_vim_leave_autocmd() return setmetatable({ job = nil, url = nil, handle = nil, spawn_promise = Promise.new(), shutdown_promise = Promise.new(), }, OpencodeServer) end function OpencodeServer:is_running() return self.job and self.job.pid ~= nil end --- Clean up this server job --- @return Promise function OpencodeServer:shutdown() if self.shutdown_promise:is_resolved() then return self.shutdown_promise end if self.job and self.job.pid then local job = self.job self.job = nil self.url = nil self.handle = nil pcall(function() job:kill(15) -- SIGTERM end) vim.defer_fn(function() if job and job.pid then pcall(function() job:kill(9) -- SIGKILL end) end end, 500) else self.shutdown_promise:resolve(true) end return self.shutdown_promise end --- @class OpencodeServerSpawnOpts --- @field cwd? string --- @field on_ready fun(job: any, url: string) --- @field on_error fun(err: any) --- @field on_exit fun(exit_opts: vim.SystemCompleted ) --- Spawn the opencode server for this ServerJob instance. --- @param opts? OpencodeServerSpawnOpts --- @return Promise function OpencodeServer:spawn(opts) opts = opts or {} self.job = vim.system({ config.opencode_executable, 'serve', }, { cwd = opts.cwd, stdout = function(err, data) if err then safe_call(opts.on_error, err) return end if data then local url = data:match('opencode server listening on ([^%s]+)') if url then self.url = url self.spawn_promise:resolve(self) safe_call(opts.on_ready, self.job, url) end end end, stderr = function(err, data) if err then self.spawn_promise:reject(err) safe_call(opts.on_error, err) return end if data then -- Filter out INFO/WARN/DEBUG log lines (not actual errors) local log_level = data:match('^%s*(%u+)%s') if log_level and (log_level == 'INFO' or log_level == 'WARN' or log_level == 'DEBUG') then -- Ignore log lines, don't reject return end -- Only reject on actual errors self.spawn_promise:reject(data) safe_call(opts.on_error, data) end end, }, function(exit_opts) -- Clear fields if not already cleared by shutdown() self.job = nil self.url = nil self.handle = nil safe_call(opts.on_exit, exit_opts) self.shutdown_promise:resolve(true) end) self.handle = self.job and self.job.pid return self.spawn_promise end function OpencodeServer:get_shutdown_promise() return self.shutdown_promise end function OpencodeServer:get_spawn_promise() return self.spawn_promise end return OpencodeServer