Skip to content
Open
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A Neovim Lua plugin that bridges Neovim and the `opencode` CLI (external binary)

## Entrypoints

- **Public API**: `lua/opencode.lua` — exports `ask()`, `select()`, `prompt()`, `command()`, `operator()`, `format()`, `statusline`
- **Public API**: `lua/opencode.lua` — exports `ask()`, `select()`, `prompt()`, `command()`, `operator()`, `rename_session()`, `format()`, `statusline`
- **Config**: `vim.g.opencode_opts` global (not a `setup()` call); merged with defaults from `lua/opencode/config.lua`
- **Plugin files**: `plugin/highlights.lua` sets highlight groups; `plugin/events/` registers four autocmd groups (`OpencodeReload`, `OpencodeStatus`, `OpencodePermissions`, `OpencodeEdits`) that listen for `OpencodeEvent:*` User events to reload edited buffers, update statusline, display permission requests, and diff edit proposals

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,13 @@ Prompt OpenCode.

Wraps Prompt as an operator, supporting ranges and dot-repeat.

### Rename Session — `require("opencode").rename_session()`

Rename the current session.

- Prompts for a new title, pre-filled with the current one.
- Supports [snacks.input](https://github.com/folke/snacks.nvim/blob/main/docs/input.md).

### Command — `require("opencode").command()`

Command OpenCode:
Expand All @@ -314,11 +321,14 @@ Command OpenCode:
| `session.new` | Start new session |
| `session.page.up` | Scroll messages up one page |
| `session.page.down` | Scroll messages down one page |
| `session.rename` | Rename session |
| `session.select` | Select session |
| `session.share` | Share current session |
| `session.redo` | Redo last undone action in current session |
| `session.undo` | Undo last action in current session |

`session.rename` and `session.select` aren't OpenCode TUI commands — they're handled by opencode.nvim itself, opening the rename input and session picker respectively.

## 👀 Events

opencode.nvim forwards the connected OpenCode's Server-Sent-Events as an `OpencodeEvent` autocmd:
Expand Down
14 changes: 14 additions & 0 deletions lua/opencode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,20 @@ function M.operator(prompt)
return "g@"
end

---Rename the current session.
---
--- - Prompts for a new title pre-filled with the current one.
---
---@param opts? opencode.rename_session.Opts Override configured options for this call.
function M.rename_session(opts)
require("opencode.server.discovery")
.get()
:next(function(server)
return require("opencode.ui.rename_session").rename_session(server, opts)
end)
:catch(on_error)
end

M.format = require("opencode.context").format

return M
7 changes: 7 additions & 0 deletions lua/opencode/api/command.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ local M = {}
---@param server opencode.server.Server
---@return Promise<any>
function M.command(command, server)
if command == "session.rename" then
return require("opencode.ui.rename_session").rename_session(server)
elseif command == "session.select" then
return require("opencode.ui.select_session").select_session(server):next(function(session)
return server:select_session(session.id)
end)
end
return server:tui_execute_command(command):next(function()
if command == "session.interrupt" then
-- Evidently OpenCode only uses this command for their "double-tap Esc to interrupt" user keybind.
Expand Down
2 changes: 2 additions & 0 deletions lua/opencode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
---@field server? opencode.server.Opts OpenCode server connection options.
---@field contexts? table<string, fun(context: opencode.context.Context): string?> Context placeholders and their builders.
---@field ask? opencode.ask.Opts Options for `ask()`. Supports [snacks.input](https://github.com/folke/snacks.nvim/blob/main/docs/input.md).
---@field rename_session? opencode.rename_session.Opts Options for `rename_session()`. Supports [snacks.input](https://github.com/folke/snacks.nvim/blob/main/docs/input.md).
---@field select? opencode.select.Opts Options and items for `select()`. Supports [snacks.picker](https://github.com/folke/snacks.nvim/blob/main/docs/picker.md).
---@field events? opencode.events.Opts Options for handling OpenCode events.

Expand Down Expand Up @@ -88,6 +89,7 @@ local defaults = {
["session.interrupt"] = "Interrupt current session",
["session.new"] = "Start new session",
["session.redo"] = "Redo last undone action in current session",
["session.rename"] = "Rename session",
["session.select"] = "Select session",
["session.undo"] = "Undo last action in current session",
},
Expand Down
17 changes: 16 additions & 1 deletion lua/opencode/server/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
---@field url string
---@field cwd string
---@field title string
---@field session_id? string The session currently active in the TUI.
---@field subagents opencode.server.Agent[]
---@field subscription_job_id? number
---@field heartbeat_timer? uv.uv_timer_t
Expand Down Expand Up @@ -61,6 +62,7 @@ Server.__index = Server
---| { type: "server.instance.disposed" }
---| { type: "session.status", properties: { status: { type: "idle" | "busy" | "error" } } }
---| { type: "tui.command.execute", properties: { command: string } }
---| { type: "tui.session.select", properties: { sessionID: string } }
---| { type: string, properties: table }

---Attempt to connect to an OpenCode server and fetch its health and details.
Expand Down Expand Up @@ -92,6 +94,7 @@ function Server.new(url)
function(results) ---@param results { [1]: { directory: string, worktree: string }, [2]: opencode.server.Session[], [3]: opencode.server.Agent }
self.cwd = results[1].directory or results[1].worktree
self.title = results[2][1] and results[2][1].title or "<No sessions>"
self.session_id = results[2][1] and results[2][1].id
self.subagents = vim.tbl_filter(function(agent) ---@param agent opencode.server.Agent
return agent.mode == "subagent"
end, results[3])
Expand All @@ -110,7 +113,7 @@ function Server:display_name()
end

---@param path string
---@param method "GET" | "POST"
---@param method "GET" | "POST" | "PATCH"
---@param body table?
---@param on_success fun(response: table)
---@param on_error fun(msg: string, code: number, status: number?)
Expand Down Expand Up @@ -293,11 +296,21 @@ end
---@param session_id string
---@return Promise<any>
function Server:select_session(session_id)
self.session_id = session_id
return require("opencode.promise").new(function(resolve, reject)
self:curl("/tui/select-session", "POST", { sessionID = session_id }, resolve, reject)
end)
end

---@param session_id string
---@param title string New title for the session.
---@return Promise<any>
function Server:update_session(session_id, title)
return require("opencode.promise").new(function(resolve, reject)
self:curl("/session/" .. session_id, "PATCH", { title = title }, resolve, reject)
end)
end

---@return Promise<{ directory: string, worktree: string }>
function Server:get_path()
return require("opencode.promise").new(function(resolve, reject)
Expand Down Expand Up @@ -352,6 +365,8 @@ function Server:connect()
resolve(self)
elseif response.type == "server.instance.disposed" then
self:disconnect()
elseif response.type == "tui.session.select" then
self.session_id = response.properties.sessionID
end

require("opencode.events").emit(response, self)
Expand Down
48 changes: 48 additions & 0 deletions lua/opencode/ui/rename_session.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---@class opencode.rename_session.Opts
---@field prompt? string Text of the prompt.
---@field snacks? snacks.input.Opts Options for [snacks.input](https://github.com/folke/snacks.nvim/blob/main/docs/input.md).

local M = {}

---@param server opencode.server.Server
---@param opts? opencode.rename_session.Opts Override configured options for this call.
---@return Promise<any>
function M.rename_session(server, opts)
local config = require("opencode.config")
---@type snacks.input.Opts
local input_opts = {
prompt = "Rename session to: ",
}
input_opts = vim.tbl_deep_extend("keep", config.opts.rename_session or {}, input_opts)
input_opts = vim.tbl_deep_extend("force", input_opts, opts or {})

local snacks_ok, snacks = pcall(require, "snacks")
if snacks_ok and snacks.config.get("input", {}).enabled then
input_opts = vim.tbl_deep_extend("keep", input_opts, input_opts.snacks or {})
end
input_opts.snacks = nil

return server:get_sessions():next(function(sessions)
local current
for _, session in ipairs(sessions) do
if session.id == server.session_id then
current = session
break
end
end
current = current or sessions[1]
if not current then
return require("opencode.promise").resolve(nil)
end

input_opts.default = current.title
return require("opencode.promise.ui").input(input_opts):next(function(title)
if not title or title == "" then
return require("opencode.promise").resolve(nil)
end
return server:update_session(current.id, title)
end)
end)
end

return M
8 changes: 1 addition & 7 deletions lua/opencode/ui/select.lua
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,7 @@ function M.select(context, opts)
if choice.__type == "prompt" then
return require("opencode.api.prompt").prompt(choice.text, context)
elseif choice.__type == "command" then
if choice.name == "session.select" then
return require("opencode.ui.select_session").select_session(context.server):next(function(session)
return context.server:select_session(session.id)
end)
else
return require("opencode.api.command").command(choice.name, context.server)
end
return require("opencode.api.command").command(choice.name, context.server)
elseif choice.__type == "server" then
if choice.name == "server.connect" then
return require("opencode.server.discovery")
Expand Down
Loading