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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,25 @@ listen_on unix:/tmp/kitty

</details>

<details>
<summary><a href="https://wezterm.org/">wezterm</a></summary>

```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,
}
}
}
```

</details>

<details>
<summary><a href="https://github.com/tmux/tmux">tmux</a></summary>

Expand Down
5 changes: 4 additions & 1 deletion lua/opencode/provider/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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
Expand Down
138 changes: 138 additions & 0 deletions lua/opencode/provider/wezterm.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---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

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
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

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
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

table.insert(cmd_parts, "--")
table.insert(cmd_parts, self.cmd)

local result = vim.fn.system(table.concat(cmd_parts, " "))
focus_pane(vim.env.WEZTERM_PANE)

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
--- 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