Skip to content

Commit b885ccc

Browse files
committed
feat(timeline-picker): Implement basic timeline picker
This is the first commit, the fork action is not yet implemented
1 parent aad6305 commit b885ccc

File tree

8 files changed

+474
-50
lines changed

8 files changed

+474
-50
lines changed

README.md

Lines changed: 54 additions & 49 deletions
Large diffs are not rendered by default.

lua/opencode/api.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,26 @@ function M.undo(messageId)
680680
end)
681681
end
682682

683+
function M.timeline()
684+
local user_messages = {}
685+
for _, msg in ipairs(state.messages or {}) do
686+
if msg.info.role == 'user' then
687+
table.insert(user_messages, msg)
688+
end
689+
end
690+
if #user_messages == 0 then
691+
vim.notify('No user messages in the current session', vim.log.levels.WARN)
692+
return
693+
end
694+
695+
local timeline_picker = require('opencode.ui.timeline_picker')
696+
timeline_picker.pick(user_messages, function(selected_msg)
697+
if selected_msg then
698+
require('opencode.ui.navigation').goto_message_by_id(selected_msg.info.id)
699+
end
700+
end)
701+
end
702+
683703
-- Returns the ID of the next user message after the current undo point
684704
-- This is a port of the opencode tui logic
685705
-- https://github.com/sst/opencode/blob/dev/packages/tui/internal/components/chat/messages.go#L1199
@@ -1074,6 +1094,11 @@ M.commands = {
10741094
end
10751095
end,
10761096
},
1097+
1098+
timeline = {
1099+
desc = 'Open timeline picker to navigate/undo/redo/fork to message',
1100+
fn = M.timeline,
1101+
},
10771102
}
10781103

10791104
M.slash_commands_map = {
@@ -1089,6 +1114,7 @@ M.slash_commands_map = {
10891114
['/redo'] = { fn = M.redo, desc = 'Redo last action' },
10901115
['/sessions'] = { fn = M.select_session, desc = 'Select session' },
10911116
['/share'] = { fn = M.share, desc = 'Share current session' },
1117+
['/timeline'] = { fn = M.timeline, desc = 'Open timeline picker' },
10921118
['/undo'] = { fn = M.undo, desc = 'Undo last action' },
10931119
['/unshare'] = { fn = M.unshare, desc = 'Unshare current session' },
10941120
}

lua/opencode/config.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ M.defaults = {
7575
delete_session = { '<C-d>' },
7676
new_session = { '<C-n>' },
7777
},
78+
timeline_picker = {
79+
undo = { '<C-u>', mode = { 'i', 'n' } },
80+
fork = { '<C-f>', mode = { 'i', 'n' } },
81+
},
7882
},
7983
ui = {
8084
position = 'right',

lua/opencode/types.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@
7777
---@field delete_session OpencodeKeymapEntry
7878
---@field new_session OpencodeKeymapEntry
7979

80+
---@class OpencodeTimelinePickerKeymap
81+
---@field undo OpencodeKeymapEntry
82+
---@field fork OpencodeKeymapEntry
83+
8084
---@class OpencodeCompletionFileSourcesConfig
8185
---@field enabled boolean
8286
---@field preferred_cli_tool 'server'|'fd'|'fdfind'|'rg'|'git'

lua/opencode/ui/navigation.lua

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@ local function is_message_header(details)
2020
return first_virt_text[1] == header_user_icon or first_virt_text[1] == header_assistant_icon
2121
end
2222

23+
function M.goto_message_by_id(message_id)
24+
require('opencode.ui.ui').focus_output()
25+
local windows = state.windows or {}
26+
local win = windows.output_win
27+
local buf = windows.output_buf
28+
29+
if not win or not buf then
30+
return
31+
end
32+
33+
local rendered_msg = require('opencode.ui.renderer').get_rendered_message(message_id)
34+
if not rendered_msg or not rendered_msg.line_start then
35+
return
36+
end
37+
local sep_offset = 2
38+
vim.api.nvim_win_set_cursor(win, { rendered_msg.line_start + sep_offset, 0 })
39+
end
40+
2341
function M.goto_next_message()
2442
require('opencode.ui.ui').focus_output()
2543
local windows = state.windows or {}

lua/opencode/ui/renderer.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,4 +962,15 @@ function M._update_stats_from_message(message)
962962
end
963963
end
964964

965+
---Get rendered message by ID
966+
---@param message_id string Message ID
967+
---@return RenderedMessage|nil Rendered message or nil if not found
968+
function M.get_rendered_message(message_id)
969+
local rendered_msg = M._render_state:get_message(message_id)
970+
if rendered_msg then
971+
return rendered_msg
972+
end
973+
return nil
974+
end
975+
965976
return M

lua/opencode/ui/session_picker.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local M = {}
22
local picker = require('opencode.ui.picker')
3+
local config = require('lua.opencode.config')
34

45
local picker_title = function()
56
local config = require('opencode.config') --[[@as OpencodeConfig]]
@@ -37,7 +38,9 @@ local function format_session(session)
3738
table.insert(parts, modified)
3839
end
3940

40-
table.insert(parts, 'ID: ' .. (session.id or 'N/A'))
41+
if config.debug then
42+
table.insert(parts, 'ID: ' .. (session.id or 'N/A'))
43+
end
4144
return table.concat(parts, ' ~ ')
4245
end
4346

0 commit comments

Comments
 (0)