From 7002d4209169612fed620c1b5a9b048c5ed4d52f Mon Sep 17 00:00:00 2001 From: chuan2984 Date: Wed, 26 Nov 2025 22:46:26 -0800 Subject: [PATCH 1/4] feat(provider): add wezterm integration --- lua/opencode/provider/init.lua | 5 +- lua/opencode/provider/wezterm.lua | 133 ++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 lua/opencode/provider/wezterm.lua diff --git a/lua/opencode/provider/init.lua b/lua/opencode/provider/init.lua index c087625..806274f 100644 --- a/lua/opencode/provider/init.lua +++ b/lua/opencode/provider/init.lua @@ -40,12 +40,14 @@ ---Default order: --- - `"snacks"` if `snacks.terminal` is available and enabled --- - `"kitty"` if in a `kitty` session with remote control enabled +--- - `"wezterm"` if in a `wezterm` window --- - `"tmux"` if in a `tmux` session --- - `false` ----@field enabled? "snacks"|"kitty"|"tmux"|false +---@field enabled? "snacks"|"kitty"|"wezterm"|"tmux"|false --- ---@field snacks? opencode.provider.snacks.Opts ---@field kitty? opencode.provider.kitty.Opts +---@field wezterm? opencode.provider.wezterm.Opts ---@field tmux? opencode.provider.tmux.Opts local M = {} @@ -71,6 +73,7 @@ function M.list() return { require("opencode.provider.snacks"), require("opencode.provider.kitty"), + require("opencode.provider.wezterm"), require("opencode.provider.tmux"), } end diff --git a/lua/opencode/provider/wezterm.lua b/lua/opencode/provider/wezterm.lua new file mode 100644 index 0000000..f8c99a1 --- /dev/null +++ b/lua/opencode/provider/wezterm.lua @@ -0,0 +1,133 @@ +---Provide `opencode` in a [`wezterm`](https://wezterm.org/index.html) pane in the current window. +---@class opencode.provider.Wezterm : opencode.Provider +--- +---@field opts opencode.provider.wezterm.Opts +---@field pane_id? string The `wezterm` pane ID where `opencode` is running (internal use only). +local Wezterm = {} +Wezterm.__index = Wezterm +Wezterm.name = "wezterm" + +---`wezterm` options for creating the pane. +---Strictly mimics the options available in the `wezterm cli split-pane --help` command +---@class opencode.provider.wezterm.Opts +--- +---Direction in which the pane that runs opencode is spawn, defaults to bottom +---@field direction? "left" | "top" | "right" | "bottom" +--- +---The number of cells that the new split should have expressed as a percentage of the available +---space, default is 50% +---@field percent? number +--- +---Rather than splitting the active pane, split the entire window +---@field top_level? boolean +--- +---@param opts? opencode.provider.wezterm.Opts +---@return opencode.provider.Wezterm +function Wezterm.new(opts) + local self = setmetatable({}, Wezterm) + self.opts = opts or {} + self.pane_id = nil + return self +end + +---Check if `wezterm` is running in current terminal. +function Wezterm.health() + if vim.fn.executable("wezterm") ~= 1 then + return "`wezterm` executable not found in `$PATH`.", + { + "Install `wezterm` and ensure it's in your `$PATH`.", + } + end + + if not vim.env.WEZTERM_PANE then + return "Not running inside a `wezterm` window.", { + "Launch Neovim inside a `wezterm` window.", + } + end + + return true +end + +---Get the `wezterm` pane ID where `opencode` is running. +---@return string|nil pane_id +function Wezterm:get_pane_id() + local ok = self.health() + if ok ~= true then + error(ok) + end + + -- Find existing `opencode` pane by searching for the command in the title + local base_cmd = self.cmd:match("^%S+") or self.cmd + local result = vim.fn.system( + string.format("wezterm cli list --format json 2>&1 | jq -r '.[] | select(.title == \"%s\") | .pane_id'", base_cmd) + ) + if result and result ~= "" and not result:match("error") then + self.pane_id = result:match("^%d+") + else + self.pane_id = nil + end + + return self.pane_id +end + +---Create or kill the `opencode` pane. +function Wezterm:toggle() + local pane_id = self:get_pane_id() + if pane_id then + self:stop() + else + self:start() + end +end + +---Start `opencode` in pane. +function Wezterm:start() + local pane_id = self:get_pane_id() + if not pane_id then + -- Build the split-pane command + local cmd_parts = { "wezterm", "cli", "split-pane" } + + if self.opts.direction then + table.insert(cmd_parts, "--" .. self.opts.direction) + end + + if self.opts.percent then + table.insert(cmd_parts, "--percent") + table.insert(cmd_parts, tostring(self.opts.percent)) + end + + if self.opts.top_level then + table.insert(cmd_parts, "--top-level") + end + + -- Add the command to execute + table.insert(cmd_parts, "--") + table.insert(cmd_parts, self.cmd) + + -- Execute and capture the pane ID + local result = vim.fn.system(table.concat(cmd_parts, " ")) + self.pane_id = result:match("^%d+") + end +end + +---Kill the `opencode` pane. +function Wezterm:stop() + local pane_id = self:get_pane_id() + if pane_id then + vim.fn.system(string.format("wezterm cli kill-pane --pane-id %d", pane_id)) + self.pane_id = nil + end +end + +---Focus the `opencode` pane +function Wezterm:show() + local pane_id = self:get_pane_id() + if not pane_id then + vim.notify("No opencode instance is currently running", vim.log.levels.WARN, { title = "opencode" }) + return + end + + vim.fn.system(string.format("wezterm cli activate-pane --pane-id %d", pane_id)) +end + +return Wezterm From 5512e44f53c6aa1354d188f03092ceb4679713a2 Mon Sep 17 00:00:00 2001 From: chuan2984 Date: Fri, 28 Nov 2025 17:02:32 -0800 Subject: [PATCH 2/4] doc(provider): update readme to include the new wezterm config --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 677a274..8250f14 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,25 @@ listen_on unix:/tmp/kitty +
+wezterm + +```lua +vim.g.opencode_opts = { + provider = { + enabled = "wezterm", + -- these are defaults set by wezterm + wezterm = { + direction = "bottom", -- left/right/top/bottom + top_level = false, + percent = 50, + } + } +} +``` + +
+
tmux From 952eaf3116b4823bf3d5098caa52ca4c2dacf464 Mon Sep 17 00:00:00 2001 From: chuan2984 Date: Fri, 28 Nov 2025 18:43:52 -0800 Subject: [PATCH 3/4] fix(provider): add focus back to the neovim pane after a new `opencode` pane is spawned --- lua/opencode/provider/wezterm.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/opencode/provider/wezterm.lua b/lua/opencode/provider/wezterm.lua index f8c99a1..13ff589 100644 --- a/lua/opencode/provider/wezterm.lua +++ b/lua/opencode/provider/wezterm.lua @@ -30,6 +30,10 @@ function Wezterm.new(opts) return self end +local function focus_pane(pane_id) + vim.fn.system(string.format("wezterm cli activate-pane --pane-id %d", pane_id)) +end + ---Check if `wezterm` is running in current terminal. function Wezterm.health() if vim.fn.executable("wezterm") ~= 1 then @@ -106,6 +110,8 @@ function Wezterm:start() -- Execute and capture the pane ID local result = vim.fn.system(table.concat(cmd_parts, " ")) + focus_pane(vim.env.WEZTERM_PANE) + self.pane_id = result:match("^%d+") end end @@ -127,7 +133,7 @@ function Wezterm:show() return end - vim.fn.system(string.format("wezterm cli activate-pane --pane-id %d", pane_id)) + focus_pane(pane_id) end return Wezterm From 2231534d30b17838d2bf974bb02332b40c731817 Mon Sep 17 00:00:00 2001 From: chuan2984 Date: Sun, 30 Nov 2025 20:07:52 -0800 Subject: [PATCH 4/4] fix(provider): temporarily disable show --- lua/opencode/provider/wezterm.lua | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lua/opencode/provider/wezterm.lua b/lua/opencode/provider/wezterm.lua index 13ff589..50e87cd 100644 --- a/lua/opencode/provider/wezterm.lua +++ b/lua/opencode/provider/wezterm.lua @@ -60,7 +60,6 @@ function Wezterm:get_pane_id() error(ok) end - -- Find existing `opencode` pane by searching for the command in the title local base_cmd = self.cmd:match("^%S+") or self.cmd local result = vim.fn.system( string.format("wezterm cli list --format json 2>&1 | jq -r '.[] | select(.title == \"%s\") | .pane_id'", base_cmd) @@ -88,7 +87,6 @@ end function Wezterm:start() local pane_id = self:get_pane_id() if not pane_id then - -- Build the split-pane command local cmd_parts = { "wezterm", "cli", "split-pane" } if self.opts.direction then @@ -104,11 +102,9 @@ function Wezterm:start() table.insert(cmd_parts, "--top-level") end - -- Add the command to execute table.insert(cmd_parts, "--") table.insert(cmd_parts, self.cmd) - -- Execute and capture the pane ID local result = vim.fn.system(table.concat(cmd_parts, " ")) focus_pane(vim.env.WEZTERM_PANE) @@ -126,14 +122,17 @@ function Wezterm:stop() end ---Focus the `opencode` pane -function Wezterm:show() - local pane_id = self:get_pane_id() - if not pane_id then - vim.notify("No opencode instance is currently running", vim.log.levels.WARN, { title = "opencode" }) - return - end - - focus_pane(pane_id) -end +--- INFO: This, at the moment, introduces unexpected focusing behaviors with sending `command`s +--- therefore its disabled. +function Wezterm:show() end +-- function Wezterm:show() +-- local pane_id = self:get_pane_id() +-- if not pane_id then +-- vim.notify("No opencode instance is currently running", vim.log.levels.WARN, { title = "opencode" }) +-- return +-- end +-- +-- focus_pane(pane_id) +-- end return Wezterm