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
1 change: 1 addition & 0 deletions lua/opencode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local M = {}

M.ask = require("opencode.ui.ask").ask
M.select = require("opencode.ui.select").select
M.select_session = require("opencode.ui.select_session").select_session

M.prompt = require("opencode.api.prompt").prompt
M.operator = require("opencode.api.operator").operator
Expand Down
25 changes: 25 additions & 0 deletions lua/opencode/cli/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,31 @@ function M.get_commands(port, callback)
M.call(port, "/command", "GET", nil, callback)
end

---@class opencode.cli.client.SessionTime
---@field created integer
---@field updated integer

---@class opencode.cli.client.Session
---@field id string
---@field title string
---@field time opencode.cli.client.SessionTime

---Get sessions from `opencode`.
---
---@param port number
---@param callback fun(sessions: opencode.cli.client.Session[])
function M.get_sessions(port, callback)
M.call(port, "/session", "GET", nil, callback)
end

---Select session in `opencode`.
---
---@param port number
---@param session_id number
function M.select_session(port, session_id)
M.call(port, "/tui/select-session", "POST", { sessionID = session_id }, nil)
end

---@class opencode.cli.client.PathResponse
---@field directory string
---@field worktree string
Expand Down
1 change: 1 addition & 0 deletions lua/opencode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ local defaults = {
prompts = true,
commands = {
["session.new"] = "Start a new session",
["session.select"] = "Select a session",
["session.share"] = "Share the current session",
["session.interrupt"] = "Interrupt the current session",
["session.compact"] = "Compact the current session (reduce context size)",
Expand Down
6 changes: 5 additions & 1 deletion lua/opencode/ui/select.lua
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,11 @@ function M.select(opts)
require("opencode").prompt(prompt.prompt, prompt)
end
elseif choice.__type == "command" then
require("opencode").command(choice.name)
if choice.name == "session.select" then
require("opencode").select_session()
else
require("opencode").command(choice.name)
end
elseif choice.__type == "provider" then
if choice.name == "toggle" then
require("opencode").toggle()
Expand Down
61 changes: 61 additions & 0 deletions lua/opencode/ui/select_session.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
local M = {}

function M.select_session()
require("opencode.cli.server")
.get_port()
:next(function(port)
return require("opencode.promise").new(function(resolve)
require("opencode.cli.client").get_sessions(port, function(sessions)
resolve({ sessions = sessions, port = port })
end)
end)
end)
:next(function(session_data)
local sessions = {}
for _, session in ipairs(session_data.sessions) do
---@type opencode.cli.client.Session
local item = {
id = session.id,
title = session.title,
time = {
created = math.floor(session.time.created),
updated = math.floor(session.time.updated),
},
}
table.insert(sessions, item)
end

table.sort(sessions, function(a, b)
return a.time.updated > b.time.updated
end)

vim.ui.select(sessions, {
prompt = "Select session (recently updated first):",
format_item = function(item)
local title_length = 60
local updated = os.date("%b %d, %Y %H:%M:%S", item.time.updated / 1000)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timestamp is being divided by 1000, suggesting it's in milliseconds, but os.date expects Unix time in seconds. This division should be applied consistently or the timestamp should already be in seconds from the API.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to keep it in milliseconds since this is what opencode returns.

local title = M.ellipsize(item.title, title_length)
return ("%s%s%s"):format(title, string.rep(" ", title_length - #title), updated)
end,
}, function(choice)
if choice then
require("opencode.cli.client").select_session(session_data.port, choice.id)
end
end)
end)
:catch(function(err)
vim.notify(err, vim.log.levels.ERROR)
end)
end

function M.ellipsize(s, max_len)
if vim.fn.strdisplaywidth(s) <= max_len then
return s
end
local truncated = vim.fn.strcharpart(s, 0, max_len - 3)
truncated = truncated:gsub("%s+%S*$", "")

return truncated .. "..."
end

return M