Skip to content

Commit 33657e1

Browse files
authored
feat(session): allow session ID as direction in navigate_session_tree (#370)
* feat(session): allow session ID as direction in navigate_session_tree When direction is not a known tree direction, treat it as a session ID and switch directly. Task action now passes metadata.sessionId so S key opens the exact child session. * fix(session): validate session ID path and update tests Move session ID detection above tree-navigation active check so invalid inputs get clear feedback via empty_policy=notify. Update tests to match new behavior. * remove unnessary comment
1 parent eabad92 commit 33657e1

5 files changed

Lines changed: 55 additions & 22 deletions

File tree

lua/opencode/commands/handlers/session.lua

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,18 +180,38 @@ local function compute_target_index(current_idx, total, direction, wrap)
180180
end
181181

182182
function M.actions.navigate_session_tree(direction, interaction, wrap, empty_policy)
183+
if direction and not tree_directions[direction] and direction ~= 'forward' and direction ~= 'backward' then
184+
empty_policy = empty_policy or 'notify'
185+
if not state.active_session then
186+
if empty_policy == 'notify' then
187+
vim.notify('No active session to navigate from', vim.log.levels.WARN)
188+
end
189+
return
190+
end
191+
if interaction == 'picker' then
192+
return session_runtime.select_session(direction)
193+
end
194+
return session_runtime.switch_session(direction)
195+
end
196+
183197
local active = state.active_session
184198
if not active then
185-
if empty_policy == 'notify' then vim.notify('No active session', vim.log.levels.WARN) end
199+
if empty_policy == 'notify' then
200+
vim.notify('No active session', vim.log.levels.WARN)
201+
end
186202
return
187203
end
188204

189205
local dir = tree_directions[direction]
190206
if dir then
191207
local target_id = dir.get_target(active)
192208
if not target_id then
193-
if direction == 'sibling' then return session_runtime.select_session(nil) end
194-
if empty_policy == 'notify' then vim.notify('No ' .. direction, vim.log.levels.INFO) end
209+
if direction == 'sibling' then
210+
return session_runtime.select_session(nil)
211+
end
212+
if empty_policy == 'notify' then
213+
vim.notify('No ' .. direction, vim.log.levels.INFO)
214+
end
195215
return
196216
end
197217
if interaction == 'picker' or not dir.allow_direct then
@@ -204,13 +224,17 @@ function M.actions.navigate_session_tree(direction, interaction, wrap, empty_pol
204224
return Promise.async(function()
205225
local all_sessions = session_store.get_all_workspace_sessions():await()
206226
if not all_sessions or #all_sessions == 0 then
207-
if empty_policy == 'notify' then vim.notify('No sessions', vim.log.levels.INFO) end
227+
if empty_policy == 'notify' then
228+
vim.notify('No sessions', vim.log.levels.INFO)
229+
end
208230
return
209231
end
210232

211233
local current_idx = find_session_index(all_sessions, active.id)
212234
if not current_idx then
213-
if empty_policy == 'notify' then vim.notify('Session not in list', vim.log.levels.INFO) end
235+
if empty_policy == 'notify' then
236+
vim.notify('Session not in list', vim.log.levels.INFO)
237+
end
214238
return
215239
end
216240

@@ -576,8 +600,11 @@ M.command_defs = {
576600
end,
577601
},
578602
navigate_session_tree = {
579-
desc = 'Navigate session tree (parent/child/sibling/forward/backward)',
603+
desc = 'Navigate session tree (parent/child/sibling/forward/backward) or switch to a session by ID',
580604
execute = function(args)
605+
if args[1] and not NAV_DIRECTIONS[args[1]] then
606+
return M.actions.navigate_session_tree(args[1], args[2], args[3], args[4])
607+
end
581608
local direction, interaction, wrap, empty_policy = normalize_navigate_args(args[1], args[2], args[3], args[4])
582609
return M.actions.navigate_session_tree(direction, interaction, wrap, empty_policy)
583610
end,

lua/opencode/ui/formatter/tools/task.lua

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,16 @@ function M.format(output, part, get_child_parts)
7777
end
7878

7979
local end_line = output:get_line_count()
80-
output:add_action({
81-
text = '[S]elect Child Session',
82-
type = 'navigate_session_tree',
83-
args = { 'child', 'picker' },
84-
key = 'S',
85-
display_line = start_line,
86-
range = { from = start_line + 1, to = end_line + 1 },
87-
})
80+
if metadata.sessionId then
81+
output:add_action({
82+
text = '[S] Open this Session',
83+
type = 'navigate_session_tree',
84+
args = { metadata.sessionId },
85+
key = 'S',
86+
display_line = start_line,
87+
range = { from = start_line + 1, to = end_line + 1 },
88+
})
89+
end
8890
end
8991

9092
---@param _ OpencodeMessagePart

tests/data/explore.expected.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,10 +1188,10 @@
11881188
],
11891189
"actions": [
11901190
{
1191-
"args": ["child", "picker"],
1191+
"args": ["ses_341f3e676ffez6WUF6zpok7dUZ"],
11921192
"display_line": 9,
11931193
"type": "navigate_session_tree",
1194-
"text": "[S]elect Child Session",
1194+
"text": "[S] Open this Session",
11951195
"range": { "from": 10, "to": 79 },
11961196
"key": "S"
11971197
}

tests/unit/commands_handlers_spec.lua

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -517,11 +517,15 @@ describe('opencode.commands.handlers', function()
517517
end)
518518

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

527531
it('normalize_navigate_args rejects invalid interaction', function()

tests/unit/formatter_spec.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,9 @@ describe('formatter', function()
585585
end)
586586

587587
assert.are.same({
588-
text = '[S]elect Child Session',
588+
text = '[S] Open this Session',
589589
type = 'navigate_session_tree',
590-
args = { 'child', 'picker' },
590+
args = { 'ses_child' },
591591
key = 'S',
592592
display_line = 1,
593593
range = { from = 2, to = 5 },

0 commit comments

Comments
 (0)