Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ require('opencode').setup({
enabled = false,
},
},
logging = {
enabled = false,
level = 'warn', -- debug, info, warn, error
outfile = nil,
},
debug = {
enabled = false, -- Enable debug messages in the output window
capture_streamed_events = false,
Expand Down
5 changes: 5 additions & 0 deletions lua/opencode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ M.defaults = {
enabled = false,
},
},
logging = {
enabled = false,
level = 'info', -- debug, info, warn, error
outfile = nil,
},
debug = {
enabled = false,
capture_streamed_events = false,
Expand Down
54 changes: 54 additions & 0 deletions lua/opencode/log.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
local M = {}

local config = require('opencode.config')
local log_path = config.logging and config.logging.outfile or vim.fn.stdpath('log') .. '/opencode.log'
local level = config.logging and config.logging.level or 'warn'

local logger = require('plenary.log').new({
plugin = 'opencode',
level = level:lower(),
use_console = false,
outfile = log_path,
})

local function get_logger()
return logger
end

function M.debug(msg, ...)
if not config.logging.enabled then
return
end
get_logger().debug(string.format(msg, ...))
end

function M.info(msg, ...)
if not config.logging.enabled then
return
end
get_logger().info(string.format(msg, ...))
end

function M.warn(msg, ...)
if not config.logging.enabled then
return
end
get_logger().warn(string.format(msg, ...))
end

function M.error(msg, ...)
if not config.logging.enabled then
return
end
get_logger().error(string.format(msg, ...))
end

--- @return string
function M.get_path()
if not config.logging.enabled then
return
end
return log_path
end

return M
50 changes: 30 additions & 20 deletions lua/opencode/opencode_server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ local function ensure_vim_leave_autocmd()
group = vim.api.nvim_create_augroup('OpencodeVimLeavePre', { clear = true }),
callback = function()
local state = require('opencode.state')
local log = require('opencode.log')
if state.opencode_server then
pcall(function()
state.opencode_server:shutdown():wait(2000)
end)
state.opencode_server:shutdown()
end
end,
})
Expand All @@ -35,6 +34,7 @@ end
--- Create a new ServerJob instance
--- @return OpencodeServer
function OpencodeServer.new()
local log = require('opencode.log')
ensure_vim_leave_autocmd()

return setmetatable({
Expand All @@ -50,35 +50,42 @@ function OpencodeServer:is_running()
return self.job and self.job.pid ~= nil
end

--- Clean up this server job
--- @return Promise<boolean>
local function kill_process(pid, signal, desc)
local log = require('opencode.log')
local ok, err = pcall(vim.uv.kill, pid, signal)
log.debug('shutdown: %s pid=%d sig=%d ok=%s err=%s', desc, pid, signal, tostring(ok), tostring(err))
return ok, err
end

function OpencodeServer:shutdown()
local log = require('opencode.log')
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
---@cast self.job vim.SystemObj
local pid = self.job.pid
local children = vim.api.nvim_get_proc_children(pid)

pcall(function()
job:kill(15) -- SIGTERM
end)
if #children > 0 then
log.debug('shutdown: process pid=%d has %d children (%s)', pid, #children, vim.inspect(children))

vim.defer_fn(function()
if job and job.pid then
pcall(function()
job:kill(9) -- SIGKILL
end)
for _, cid in ipairs(children) do
kill_process(cid, 15, 'SIGTERM child')
end
end, 500)
end

kill_process(pid, 15, 'SIGTERM')
kill_process(pid, 9, 'SIGKILL')
else
self.shutdown_promise:resolve(true)
log.debug('shutdown: no job running')
end

self.job = nil
self.url = nil
self.handle = nil
self.shutdown_promise:resolve(true)
return self.shutdown_promise
end

Expand All @@ -93,6 +100,7 @@ end
--- @return Promise<OpencodeServer>
function OpencodeServer:spawn(opts)
opts = opts or {}
local log = require('opencode.log')

self.job = vim.system({
config.opencode_executable,
Expand All @@ -110,6 +118,7 @@ function OpencodeServer:spawn(opts)
self.url = url
self.spawn_promise:resolve(self)
safe_call(opts.on_ready, self.job, url)
log.debug('spawn: server ready at url=%s', url)
end
end
end,
Expand Down Expand Up @@ -142,6 +151,7 @@ function OpencodeServer:spawn(opts)

self.handle = self.job and self.job.pid

log.debug('spawn: started job with pid=%s', tostring(self.job and self.job.pid))
return self.spawn_promise
end

Expand Down
6 changes: 6 additions & 0 deletions lua/opencode/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@
---@field default_agent? string -- Use current mode if nil
---@field instructions? string[] -- Custom instructions for quick chat

---@class OpencodeLoggingConfig
---@field enabled boolean
---@field level 'debug' | 'info' | 'warn' | 'error'
---@field outfile string|nil

---@class OpencodeConfig
---@field preferred_picker 'telescope' | 'fzf' | 'mini.pick' | 'snacks' | 'select' | nil
---@field preferred_completion 'blink' | 'nvim-cmp' | 'vim_complete' | nil -- Preferred completion strategy for mentons and commands
Expand All @@ -200,6 +205,7 @@
---@field keymap OpencodeKeymap
---@field ui OpencodeUIConfig
---@field context OpencodeContextConfig
---@field logging OpencodeLoggingConfig
---@field debug OpencodeDebugConfig
---@field prompt_guard? fun(mentioned_files: string[]): boolean
---@field hooks OpencodeHooks
Expand Down