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
39 changes: 33 additions & 6 deletions lua/opencode/commands/handlers/session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,38 @@ local function compute_target_index(current_idx, total, direction, wrap)
end

function M.actions.navigate_session_tree(direction, interaction, wrap, empty_policy)
if direction and not tree_directions[direction] and direction ~= 'forward' and direction ~= 'backward' then
empty_policy = empty_policy or 'notify'
if not state.active_session then
if empty_policy == 'notify' then
vim.notify('No active session to navigate from', vim.log.levels.WARN)
end
return
end
if interaction == 'picker' then
return session_runtime.select_session(direction)
end
return session_runtime.switch_session(direction)
end

local active = state.active_session
if not active then
if empty_policy == 'notify' then vim.notify('No active session', vim.log.levels.WARN) end
if empty_policy == 'notify' then
vim.notify('No active session', vim.log.levels.WARN)
end
return
end

local dir = tree_directions[direction]
if dir then
local target_id = dir.get_target(active)
if not target_id then
if direction == 'sibling' then return session_runtime.select_session(nil) end
if empty_policy == 'notify' then vim.notify('No ' .. direction, vim.log.levels.INFO) end
if direction == 'sibling' then
return session_runtime.select_session(nil)
end
if empty_policy == 'notify' then
vim.notify('No ' .. direction, vim.log.levels.INFO)
end
return
end
if interaction == 'picker' or not dir.allow_direct then
Expand All @@ -204,13 +224,17 @@ function M.actions.navigate_session_tree(direction, interaction, wrap, empty_pol
return Promise.async(function()
local all_sessions = session_store.get_all_workspace_sessions():await()
if not all_sessions or #all_sessions == 0 then
if empty_policy == 'notify' then vim.notify('No sessions', vim.log.levels.INFO) end
if empty_policy == 'notify' then
vim.notify('No sessions', vim.log.levels.INFO)
end
return
end

local current_idx = find_session_index(all_sessions, active.id)
if not current_idx then
if empty_policy == 'notify' then vim.notify('Session not in list', vim.log.levels.INFO) end
if empty_policy == 'notify' then
vim.notify('Session not in list', vim.log.levels.INFO)
end
return
end

Expand Down Expand Up @@ -576,8 +600,11 @@ M.command_defs = {
end,
},
navigate_session_tree = {
desc = 'Navigate session tree (parent/child/sibling/forward/backward)',
desc = 'Navigate session tree (parent/child/sibling/forward/backward) or switch to a session by ID',
execute = function(args)
if args[1] and not NAV_DIRECTIONS[args[1]] then
return M.actions.navigate_session_tree(args[1], args[2], args[3], args[4])
end
local direction, interaction, wrap, empty_policy = normalize_navigate_args(args[1], args[2], args[3], args[4])
return M.actions.navigate_session_tree(direction, interaction, wrap, empty_policy)
end,
Expand Down
18 changes: 10 additions & 8 deletions lua/opencode/ui/formatter/tools/task.lua
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,16 @@ function M.format(output, part, get_child_parts)
end

local end_line = output:get_line_count()
output:add_action({
text = '[S]elect Child Session',
type = 'navigate_session_tree',
args = { 'child', 'picker' },
key = 'S',
display_line = start_line,
range = { from = start_line + 1, to = end_line + 1 },
})
if metadata.sessionId then
output:add_action({
text = '[S] Open this Session',
type = 'navigate_session_tree',
args = { metadata.sessionId },
key = 'S',
display_line = start_line,
range = { from = start_line + 1, to = end_line + 1 },
})
end
end

---@param _ OpencodeMessagePart
Expand Down
4 changes: 2 additions & 2 deletions tests/data/explore.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -1188,10 +1188,10 @@
],
"actions": [
{
"args": ["child", "picker"],
"args": ["ses_341f3e676ffez6WUF6zpok7dUZ"],
"display_line": 9,
"type": "navigate_session_tree",
"text": "[S]elect Child Session",
"text": "[S] Open this Session",
"range": { "from": 10, "to": 79 },
"key": "S"
}
Expand Down
12 changes: 8 additions & 4 deletions tests/unit/commands_handlers_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -517,11 +517,15 @@ describe('opencode.commands.handlers', function()
end)

-- normalize_navigate_args tests via command_defs
it('normalize_navigate_args rejects invalid direction', function()
it('non-direction string is treated as session ID, notifies when no active session', function()
local session_handler = require('opencode.commands.handlers.session')
local ok, err = pcall(session_handler.command_defs.navigate_session_tree.execute, { 'up' })
assert.is_false(ok)
assert.equal('invalid_arguments', err.code)
local state = require('opencode.state')
local notify_stub = stub(vim, 'notify')
state.session.clear_active()
local ok = pcall(session_handler.command_defs.navigate_session_tree.execute, { 'up' })
assert.is_true(ok)
assert.stub(notify_stub).was_called()
notify_stub:revert()
end)

it('normalize_navigate_args rejects invalid interaction', function()
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/formatter_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -585,9 +585,9 @@ describe('formatter', function()
end)

assert.are.same({
text = '[S]elect Child Session',
text = '[S] Open this Session',
type = 'navigate_session_tree',
args = { 'child', 'picker' },
args = { 'ses_child' },
key = 'S',
display_line = 1,
range = { from = 2, to = 5 },
Expand Down
Loading