Skip to content

Commit 4994d4a

Browse files
committed
feat: render session.status message in loading footer
1 parent 73fea61 commit 4994d4a

2 files changed

Lines changed: 126 additions & 1 deletion

File tree

lua/opencode/event_manager.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ local log = require('opencode.log')
5858
--- @field type "session.error"
5959
--- @field properties {sessionID: string, error: table}
6060

61+
--- @class EventSessionStatus
62+
--- @field type "session.status"
63+
--- @field properties {
64+
--- sessionID: string,
65+
--- status: {
66+
--- type: string,
67+
--- message?: string,
68+
--- attempt?: number,
69+
--- next?: number
70+
--- }
71+
--- }
72+
6173
--- @class OpencodePermission
6274
--- @field id string
6375
--- @field type string
@@ -146,6 +158,7 @@ local log = require('opencode.log')
146158
--- | "session.updated"
147159
--- | "session.deleted"
148160
--- | "session.error"
161+
--- | "session.status"
149162
--- | "permission.updated"
150163
--- | "permission.asked"
151164
--- | "permission.replied"
@@ -208,6 +221,7 @@ end
208221
--- @overload fun(self: EventManager, event_name: "session.updated", callback: fun(data: EventSessionUpdated['properties']): nil)
209222
--- @overload fun(self: EventManager, event_name: "session.deleted", callback: fun(data: EventSessionDeleted['properties']): nil)
210223
--- @overload fun(self: EventManager, event_name: "session.error", callback: fun(data: EventSessionError['properties']): nil)
224+
--- @overload fun(self: EventManager, event_name: "session.status", callback: fun(data: EventSessionStatus['properties']): nil)
211225
--- @overload fun(self: EventManager, event_name: "permission.updated", callback: fun(data: EventPermissionUpdated['properties']): nil)
212226
--- @overload fun(self: EventManager, event_name: "permission.replied", callback: fun(data: EventPermissionReplied['properties']): nil)
213227
--- @overload fun(self: EventManager, event_name: "file.edited", callback: fun(data: EventFileEdited['properties']): nil)
@@ -249,6 +263,7 @@ end
249263
--- @overload fun(self: EventManager, event_name: "session.updated", callback: fun(data: EventSessionUpdated['properties']): nil)
250264
--- @overload fun(self: EventManager, event_name: "session.deleted", callback: fun(data: EventSessionDeleted['properties']): nil)
251265
--- @overload fun(self: EventManager, event_name: "session.error", callback: fun(data: EventSessionError['properties']): nil)
266+
--- @overload fun(self: EventManager, event_name: "session.status", callback: fun(data: EventSessionStatus['properties']): nil)
252267
--- @overload fun(self: EventManager, event_name: "permission.updated", callback: fun(data: EventPermissionUpdated['properties']): nil)
253268
--- @overload fun(self: EventManager, event_name: "permission.replied", callback: fun(data: EventPermissionReplied['properties']): nil)
254269
--- @overload fun(self: EventManager, event_name: "file.edited", callback: fun(data: EventFileEdited['properties']): nil)

lua/opencode/ui/loading_animation.lua

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,115 @@ local M = {}
77
M._animation = {
88
frames = nil,
99
text = 'Thinking... ',
10+
status_data = nil,
1011
current_frame = 1,
1112
timer = nil,
1213
fps = 10,
1314
extmark_id = nil,
1415
ns_id = vim.api.nvim_create_namespace('opencode_loading_animation'),
16+
status_event_manager = nil,
1517
}
1618

19+
---@param status table|nil
20+
---@return string|nil
21+
function M._format_status_text(status)
22+
if type(status) ~= 'table' then
23+
return nil
24+
end
25+
26+
local status_type = status.type
27+
28+
if status_type == 'busy' then
29+
return M._animation.text
30+
end
31+
32+
if status_type == 'idle' then
33+
return nil
34+
end
35+
36+
if status_type == 'retry' then
37+
local message = status.message or 'Retrying request'
38+
local details = {}
39+
40+
if type(status.attempt) == 'number' then
41+
table.insert(details, 'retry ' .. status.attempt)
42+
end
43+
44+
if type(status.next) == 'number' then
45+
local now_ms = os.time() * 1000
46+
local seconds = math.max(0, math.ceil((status.next - now_ms) / 1000))
47+
table.insert(details, 'in ' .. seconds .. 's')
48+
end
49+
50+
if #details > 0 then
51+
return string.format('%s (%s)... ', message, table.concat(details, ', '))
52+
end
53+
54+
return message .. '... '
55+
end
56+
57+
if type(status.message) == 'string' and status.message ~= '' then
58+
return status.message .. '... '
59+
end
60+
61+
return M._animation.text
62+
end
63+
64+
local function unsubscribe_session_status_event(manager)
65+
if manager and M._animation.status_event_manager == manager then
66+
manager:unsubscribe('session.status', M.on_session_status)
67+
M._animation.status_event_manager = nil
68+
end
69+
end
70+
71+
local function subscribe_session_status_event(manager)
72+
if not manager then
73+
return
74+
end
75+
76+
if M._animation.status_event_manager and M._animation.status_event_manager ~= manager then
77+
unsubscribe_session_status_event(M._animation.status_event_manager)
78+
end
79+
80+
if M._animation.status_event_manager == manager then
81+
return
82+
end
83+
84+
manager:subscribe('session.status', M.on_session_status)
85+
M._animation.status_event_manager = manager
86+
end
87+
88+
function M.on_session_status(properties)
89+
if not properties or type(properties) ~= 'table' then
90+
return
91+
end
92+
93+
local active_session = state.active_session
94+
if active_session and active_session.id and properties.sessionID ~= active_session.id then
95+
return
96+
end
97+
98+
M._animation.status_data = properties.status
99+
M.render(state.windows)
100+
end
101+
102+
local function on_active_session_change(_, new_session, old_session)
103+
local new_id = new_session and new_session.id
104+
local old_id = old_session and old_session.id
105+
if new_id ~= old_id then
106+
M._animation.status_data = nil
107+
end
108+
end
109+
110+
local function on_event_manager_change(_, new_manager, old_manager)
111+
unsubscribe_session_status_event(old_manager)
112+
subscribe_session_status_event(new_manager)
113+
end
114+
115+
function M._get_display_text()
116+
return M._format_status_text(M._animation.status_data) or M._animation.text
117+
end
118+
17119
function M._get_frames()
18120
if M._animation.frames then
19121
return M._animation.frames
@@ -41,7 +143,7 @@ M.render = vim.schedule_wrap(function(windows)
41143
return false
42144
end
43145

44-
local loading_text = M._animation.text .. M._get_frames()[M._animation.current_frame]
146+
local loading_text = M._get_display_text() .. M._get_frames()[M._animation.current_frame]
45147

46148
M._animation.extmark_id = vim.api.nvim_buf_set_extmark(windows.footer_buf, M._animation.ns_id, 0, 0, {
47149
id = M._animation.extmark_id or nil,
@@ -97,6 +199,7 @@ end
97199
function M.stop()
98200
M._clear_animation_timer()
99201
M._animation.current_frame = 1
202+
M._animation.status_data = nil
100203
if state.windows and state.windows.footer_buf and vim.api.nvim_buf_is_valid(state.windows.footer_buf) then
101204
pcall(vim.api.nvim_buf_clear_namespace, state.windows.footer_buf, M._animation.ns_id, 0, -1)
102205
end
@@ -120,10 +223,17 @@ end
120223

121224
function M.setup()
122225
state.subscribe('job_count', on_running_change)
226+
state.subscribe('active_session', on_active_session_change)
227+
state.subscribe('event_manager', on_event_manager_change)
228+
subscribe_session_status_event(state.event_manager)
123229
end
124230

125231
function M.teardown()
126232
state.unsubscribe('job_count', on_running_change)
233+
state.unsubscribe('active_session', on_active_session_change)
234+
state.unsubscribe('event_manager', on_event_manager_change)
235+
unsubscribe_session_status_event(M._animation.status_event_manager)
236+
M._animation.status_data = nil
127237
end
128238

129239
return M

0 commit comments

Comments
 (0)