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 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..50e87cd --- /dev/null +++ b/lua/opencode/provider/wezterm.lua @@ -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