diff --git a/lua/opencode/cli/server.lua b/lua/opencode/cli/server.lua index f3a950d..b61bf6c 100644 --- a/lua/opencode/cli/server.lua +++ b/lua/opencode/cli/server.lua @@ -157,6 +157,15 @@ local function find_server_in_nvim_cwd() end end end + + -- Fallback: try provider-specific discovery + if not found_server then + local provider = require("opencode.config").provider + if provider and provider.find_server then + found_server = provider:find_server() + end + end + if not found_server then error("No `opencode` servers in Neovim's CWD", 0) end @@ -198,8 +207,9 @@ end ---Attempt to get the `opencode` server's port. Tries, in order: ---1. A process responding on `opts.port`. ----2. Any `opencode` process running in Neovim's CWD. Prioritizes embedded. ----3. Calling `opts.provider.start` and polling for the port. +---2. Any `opencode` process running inside Neovim's CWD. Prioritizes embedded. +---3. Provider-specific discovery (e.g., tmux sibling panes). +---4. Calling `opts.provider.start` and polling for the port. --- ---@param launch boolean? Whether to launch a new server if none found. Defaults to true. ---@return Promise diff --git a/lua/opencode/config.lua b/lua/opencode/config.lua index ca4ed22..851bdd6 100644 --- a/lua/opencode/config.lua +++ b/lua/opencode/config.lua @@ -162,6 +162,7 @@ local defaults = { -- Disables allow-passthrough in the tmux split -- preventing OSC escape sequences from leaking into the nvim buffer allow_passthrough = false, + auto_close = true, -- Auto-close the tmux pane when opencode exits }, }, } diff --git a/lua/opencode/provider/init.lua b/lua/opencode/provider/init.lua index 74359a7..2118e07 100644 --- a/lua/opencode/provider/init.lua +++ b/lua/opencode/provider/init.lua @@ -1,8 +1,8 @@ ---@module 'snacks.terminal' ---Provide an integrated `opencode`. ----Providers should ignore manually-started `opencode` instances, ----operating only on those they start themselves. +---`start`/`stop`/`toggle` should only operate on provider-managed instances. +---`find_server` may attach to any existing instance for connection purposes. ---@class opencode.Provider --- ---The name of the provider. @@ -33,6 +33,11 @@ ---Should return `true` if the provider is available, ---else a reason string and optional advice (for `vim.health.warn`). ---@field health? fun(): boolean|string, ...string|string[] +--- +---Find an existing `opencode` server via provider-specific discovery. +---Unlike other methods, may return servers not started by the provider. +---Called as a fallback when CWD-based discovery fails. +---@field find_server? fun(self: opencode.Provider): opencode.cli.server.Server|nil ---Configure and enable built-in providers. ---@class opencode.provider.Opts diff --git a/lua/opencode/provider/tmux.lua b/lua/opencode/provider/tmux.lua index bf4d443..8a51baf 100644 --- a/lua/opencode/provider/tmux.lua +++ b/lua/opencode/provider/tmux.lua @@ -16,6 +16,9 @@ Tmux.name = "tmux" --- ---Focus the opencode pane when created. Default: `false` ---@field focus? boolean +--- +---Auto-close the tmux pane when opencode exits. Default: `true` +---@field auto_close? boolean -- ---Allow `allow-passthrough` on the opencode pane. -- When enabled, opencode.nvim will use your configured tmux `allow-passthrough` option on its pane. @@ -107,10 +110,46 @@ end ---Kill the `opencode` pane. function Tmux:stop() local pane_id = self:get_pane_id() - if pane_id then + if pane_id and self.opts.auto_close ~= false then vim.fn.system("tmux kill-pane -t " .. pane_id) self.pane_id = nil end end +---Find an `opencode` server running in a sibling pane of the current tmux window. +---@return opencode.cli.server.Server|nil +function Tmux:find_server() + if self.health() ~= true then + return nil + end + + local session_window = vim.fn.system("tmux display-message -p '#{session_name}:#{window_index}'"):gsub("\n", "") + local panes_output = vim.fn.system(string.format("tmux list-panes -t '%s' -F '#{pane_tty}'", session_window)) + + for tty in panes_output:gmatch("[^\r\n]+") do + local tty_short = tty:gsub("^/dev/", "") + local ps_output = vim.fn.system(string.format("ps -t %s -o pid,command", tty_short)) + + for line in ps_output:gmatch("[^\r\n]+") do + local pid = line:match("^%s*(%d+).*opencode") + if pid then + local lsof = vim.fn.system(string.format("lsof -w -iTCP -sTCP:LISTEN -P -n -a -p %s", pid)) + local port = lsof:match(":(%d+) %(LISTEN%)") + if port then + local ok, path = pcall(require("opencode.cli.client").get_path, tonumber(port)) + if ok then + return { + pid = tonumber(pid), + port = tonumber(port), + cwd = path.directory or path.worktree, + } + end + end + end + end + end + + return nil +end + return Tmux