Skip to content

Commit b7540bd

Browse files
committed
refactor(renderer): move data lookup in specialized class
1 parent fcacf5f commit b7540bd

File tree

9 files changed

+611
-114
lines changed

9 files changed

+611
-114
lines changed

lua/opencode/api_client.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ end
193193
--- List messages for a session
194194
--- @param id string Session ID (required)
195195
--- @param directory string|nil Directory path
196-
--- @return Promise<{info: Message, parts: MessagePart[]}[]>
196+
--- @return Promise<OpencodeMessage[]>
197197
function OpencodeApiClient:list_messages(id, directory)
198198
return self:_call('/session/' .. id .. '/message', 'GET', nil, { directory = directory })
199199
end
@@ -202,7 +202,7 @@ end
202202
--- @param id string Session ID (required)
203203
--- @param message_data {messageID?: string, model?: {providerID: string, modelID: string}, agent?: string, system?: string, tools?: table<string, boolean>, parts: Part[]} Message creation data
204204
--- @param directory string|nil Directory path
205-
--- @return Promise<{info: Message, parts: MessagePart[]}>
205+
--- @return Promise<{info: MessageInfo, parts: MessagePart[]}>
206206
function OpencodeApiClient:create_message(id, message_data, directory)
207207
return self:_call('/session/' .. id .. '/message', 'POST', message_data, { directory = directory })
208208
end
@@ -211,7 +211,7 @@ end
211211
--- @param id string Session ID (required)
212212
--- @param messageID string Message ID (required)
213213
--- @param directory string|nil Directory path
214-
--- @return Promise<{info: Message, parts: MessagePart[]}>
214+
--- @return Promise<OpencodeMessage>
215215
function OpencodeApiClient:get_message(id, messageID, directory)
216216
return self:_call('/session/' .. id .. '/message/' .. messageID, 'GET', nil, { directory = directory })
217217
end
@@ -220,7 +220,7 @@ end
220220
--- @param id string Session ID (required)
221221
--- @param command_data {messageID?: string, agent?: string, model?: string, arguments: string, command: string} Command data
222222
--- @param directory string|nil Directory path
223-
--- @return Promise<{info: Message, parts: MessagePart[]}>
223+
--- @return Promise<OpencodeMessage>
224224
function OpencodeApiClient:send_command(id, command_data, directory)
225225
return self:_call('/session/' .. id .. '/command', 'POST', command_data, { directory = directory })
226226
end
@@ -229,7 +229,7 @@ end
229229
--- @param id string Session ID (required)
230230
--- @param shell_data {agent?: string, command: string} Shell command data
231231
--- @param directory string|nil Directory path
232-
--- @return Promise<Message>
232+
--- @return Promise<MessageInfo>
233233
function OpencodeApiClient:run_shell(id, shell_data, directory)
234234
return self:_call('/session/' .. id .. '/shell', 'POST', shell_data, { directory = directory })
235235
end

lua/opencode/event_manager.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ local state = require('opencode.state')
1010

1111
--- @class EventMessageUpdated
1212
--- @field type "message.updated"
13-
--- @field properties {info: Message}
13+
--- @field properties {info: MessageInfo}
1414

1515
--- @class EventMessageRemoved
1616
--- @field type "message.removed"

lua/opencode/session.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ end
150150

151151
---Get messages for a session
152152
---@param session Session
153-
---@return Promise<{info: Message, parts: MessagePart[]}[]?>
153+
---@return Promise<{info: MessageInfo, parts: MessagePart[]}[]?>
154154
function M.get_messages(session)
155155
local state = require('opencode.state')
156156
if not session then
@@ -161,7 +161,7 @@ function M.get_messages(session)
161161
end
162162

163163
---Get snapshot IDs from a message's parts
164-
---@param message Message
164+
---@param message OpencodeMessage
165165
---@return string[]|nil
166166
function M.get_message_snapshot_ids(message)
167167
if not message then

lua/opencode/state.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ local config = require('opencode.config')
2323
---@field restore_points table<string, any>
2424
---@field current_model string|nil
2525
---@field current_model_info table|nil
26-
---@field messages {info: Message, parts: MessagePart[]}[]|nil
27-
---@field current_message Message|nil
28-
---@field last_user_message Message|nil
26+
---@field messages OpencodeMessage[]|nil
27+
---@field current_message OpencodeMessage|nil
28+
---@field last_user_message MessageInfo|nil
2929
---@field current_permission OpencodePermission|nil
3030
---@field cost number
3131
---@field tokens_count number

lua/opencode/types.lua

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,14 @@
239239

240240
---@alias OutputExtmark vim.api.keyset.set_extmark|fun():vim.api.keyset.set_extmark
241241

242-
---@class Message
242+
---@class OpencodeMessage
243+
---@field info MessageInfo Metadata about the message
244+
---@field parts MessagePart[] Parts that make up the message
245+
246+
---@class MessageInfo
243247
---@field id string Unique message identifier
244248
---@field sessionID string Unique session identifier
245249
---@field tokens MessageTokenCount Token usage statistics
246-
---@field parts MessagePart[] Array of message parts
247250
---@field system string[] System messages
248251
---@field time { created: number, completed: number } Timestamps
249252
---@field cost number Cost of the message

lua/opencode/ui/formatter.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ function M._format_permission_request()
118118
end
119119

120120
---@param line number Buffer line number
121-
---@return {message: Message, part: MessagePart, msg_idx: number, part_idx: number}|nil
121+
---@return {message: MessageInfo, part: MessagePart, msg_idx: number, part_idx: number}|nil
122122
function M.get_message_at_line(line)
123123
local metadata = M.output:get_nearest_metadata(line)
124124
if metadata and metadata.msg_idx and metadata.part_idx then
@@ -145,7 +145,7 @@ function M.get_lines()
145145
end
146146

147147
---Calculate statistics for reverted messages and tool calls
148-
---@param messages {info: Message, parts: MessagePart[]}[] All messages in the session
148+
---@param messages {info: MessageInfo, parts: MessagePart[]}[] All messages in the session
149149
---@param revert_index number Index of the message where revert occurred
150150
---@param revert_info SessionRevertInfo Revert information
151151
---@return {messages: number, tool_calls: number, files: table<string, {additions: number, deletions: number}>}
@@ -303,13 +303,13 @@ function M._format_patch(part)
303303
end
304304
end
305305

306-
---@param message Message
306+
---@param message MessageInfo
307307
function M._format_error(message)
308308
M.output:add_empty_line()
309309
M._format_callout('ERROR', vim.inspect(message.error))
310310
end
311311

312-
---@param message Message
312+
---@param message MessageInfo
313313
---@param msg_idx number Message index in the session
314314
function M._format_message_header(message, msg_idx)
315315
local role = message.role or 'unknown'

lua/opencode/ui/message_map.lua

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---@class MessageMap
2+
---@field _message_lookup table<string, integer>
3+
---@field _part_lookup table<string, {message_idx: integer, part_idx: integer}>
4+
---@field _call_id_lookup table<string, string>
5+
local MessageMap = {}
6+
MessageMap.__index = MessageMap
7+
8+
---@return MessageMap
9+
function MessageMap.new()
10+
local self = setmetatable({}, MessageMap)
11+
self:reset()
12+
return self
13+
end
14+
15+
function MessageMap:reset()
16+
self._message_lookup = {} -- message_id -> message_index
17+
self._part_lookup = {} -- part_id -> {message_idx, part_idx}
18+
self._call_id_lookup = {} -- call_id -> part_id
19+
end
20+
21+
---Hydrate lookup tables from existing messages array
22+
---@param messages OpencodeMessage[] Messages array to build lookups from
23+
function MessageMap:hydrate(messages)
24+
self:reset()
25+
26+
for msg_idx, msg_wrapper in ipairs(messages) do
27+
if msg_wrapper.info and msg_wrapper.info.id then
28+
self:add_message(msg_wrapper.info.id, msg_idx)
29+
end
30+
31+
if msg_wrapper.parts then
32+
for part_idx, part in ipairs(msg_wrapper.parts) do
33+
if part.id then
34+
self:add_part(part.id, msg_idx, part_idx, part.callID)
35+
end
36+
end
37+
end
38+
end
39+
end
40+
41+
---Add message to lookup table
42+
---@param message_id string Message ID
43+
---@param message_idx integer Message index
44+
function MessageMap:add_message(message_id, message_idx)
45+
self._message_lookup[message_id] = message_idx
46+
end
47+
48+
---Remove message from lookup table and remove from messages array automatically
49+
---Also removes all parts belonging to this message
50+
---@param message_id string Message ID
51+
---@param messages table[] Messages array to modify
52+
function MessageMap:remove_message(message_id, messages)
53+
local message_idx = self._message_lookup[message_id]
54+
if not message_idx or not messages then
55+
return
56+
end
57+
58+
local msg_wrapper = messages[message_idx]
59+
60+
if msg_wrapper and msg_wrapper.parts then
61+
for _, part in ipairs(msg_wrapper.parts) do
62+
if part.id then
63+
self._part_lookup[part.id] = nil
64+
if part.callID then
65+
self._call_id_lookup[part.callID] = nil
66+
end
67+
end
68+
end
69+
end
70+
71+
table.remove(messages, message_idx)
72+
73+
self._message_lookup[message_id] = nil
74+
75+
self:update_indices_after_removal(message_idx)
76+
end
77+
78+
---Add part to lookup tables with call_id support
79+
---@param part_id string Part ID
80+
---@param message_idx integer Message index
81+
---@param part_idx integer Part index
82+
---@param call_id? string Optional call ID for permission handling
83+
function MessageMap:add_part(part_id, message_idx, part_idx, call_id)
84+
self._part_lookup[part_id] = { message_idx = message_idx, part_idx = part_idx }
85+
if call_id then
86+
self._call_id_lookup[call_id] = part_id
87+
end
88+
end
89+
90+
---Update call ID mapping for a part
91+
---@param call_id string Call ID
92+
---@param part_id string Part ID
93+
function MessageMap:update_call_id(call_id, part_id)
94+
self._call_id_lookup[call_id] = part_id
95+
end
96+
97+
---Update existing part in messages array using lookup
98+
---@param part_id string Part ID
99+
---@param new_part table New part data
100+
---@param messages table[] Messages array to modify
101+
---@return integer? part_idx Part index if successful, nil otherwise
102+
function MessageMap:update_part(part_id, new_part, messages)
103+
local location = self._part_lookup[part_id]
104+
if not location or not messages then
105+
return nil
106+
end
107+
108+
local msg_wrapper = messages[location.message_idx]
109+
if not msg_wrapper or not msg_wrapper.parts then
110+
return nil
111+
end
112+
113+
msg_wrapper.parts[location.part_idx] = new_part
114+
115+
if new_part.callID then
116+
self._call_id_lookup[new_part.callID] = part_id
117+
end
118+
119+
return location.part_idx
120+
end
121+
122+
---Remove part from lookup tables and remove from messages array automatically
123+
---@param part_id string Part ID
124+
---@param call_id? string Optional call ID to remove
125+
---@param messages table[] Messages array to modify
126+
function MessageMap:remove_part(part_id, call_id, messages)
127+
local location = self._part_lookup[part_id]
128+
if not location or not messages then
129+
return
130+
end
131+
132+
local msg_wrapper = messages[location.message_idx]
133+
if not msg_wrapper or not msg_wrapper.parts then
134+
return
135+
end
136+
137+
table.remove(msg_wrapper.parts, location.part_idx)
138+
139+
self._part_lookup[part_id] = nil
140+
if call_id then
141+
self._call_id_lookup[call_id] = nil
142+
end
143+
144+
for other_part_id, other_location in pairs(self._part_lookup) do
145+
if other_location.message_idx == location.message_idx and other_location.part_idx > location.part_idx then
146+
other_location.part_idx = other_location.part_idx - 1
147+
end
148+
end
149+
end
150+
151+
---Update message indices after a message is removed
152+
---@param removed_idx integer Index of removed message
153+
function MessageMap:update_indices_after_removal(removed_idx)
154+
for message_id, idx in pairs(self._message_lookup) do
155+
if idx > removed_idx then
156+
self._message_lookup[message_id] = idx - 1
157+
end
158+
end
159+
160+
for part_id, location in pairs(self._part_lookup) do
161+
if location.message_idx > removed_idx then
162+
location.message_idx = location.message_idx - 1
163+
end
164+
end
165+
end
166+
167+
---Update part indices after a part is removed from a message
168+
---@param message_idx integer Message index
169+
---@param removed_part_idx integer Index of removed part
170+
---@param remaining_parts table[] Remaining parts in the message
171+
function MessageMap:update_part_indices_after_removal(message_idx, removed_part_idx, remaining_parts)
172+
for i = removed_part_idx, #remaining_parts do
173+
local remaining_part = remaining_parts[i]
174+
if remaining_part and remaining_part.id then
175+
local location = self._part_lookup[remaining_part.id]
176+
if location then
177+
location.part_idx = i
178+
end
179+
end
180+
end
181+
end
182+
183+
---Update message indices after a message is removed
184+
---@param removed_idx integer Index of removed message
185+
function MessageMap:update_message_indices_after_removal(removed_idx)
186+
return self:update_indices_after_removal(removed_idx)
187+
end
188+
189+
---Get message index by ID
190+
---@param message_id string Message ID
191+
---@return integer? message_idx Message index if found, nil otherwise
192+
function MessageMap:get_message_index(message_id)
193+
return self._message_lookup[message_id]
194+
end
195+
196+
---Get part location by ID
197+
---@param part_id string Part ID
198+
---@return {message_idx: integer, part_idx: integer}? location Part location if found, nil otherwise
199+
function MessageMap:get_part_location(part_id)
200+
return self._part_lookup[part_id]
201+
end
202+
203+
---Get part ID by call ID
204+
---@param call_id string Call ID
205+
---@return string? part_id Part ID if found, nil otherwise
206+
function MessageMap:get_part_id_by_call_id(call_id)
207+
return self._call_id_lookup[call_id]
208+
end
209+
210+
---Check if part exists in lookup
211+
---@param part_id string Part ID
212+
---@return boolean
213+
function MessageMap:has_part(part_id)
214+
return self._part_lookup[part_id] ~= nil
215+
end
216+
217+
---Get message wrapper and index by ID using lookup table
218+
---@param message_id string Message ID
219+
---@param messages table[] Array of messages
220+
---@return table? msg_wrapper, integer? msg_idx
221+
function MessageMap:get_message_by_id(message_id, messages)
222+
local msg_idx = self:get_message_index(message_id)
223+
if not msg_idx or not messages[msg_idx] then
224+
return nil, nil
225+
end
226+
return messages[msg_idx], msg_idx
227+
end
228+
229+
---Get part, message wrapper, and indices by part ID using lookup table
230+
---@param part_id string Part ID
231+
---@param messages table[] Array of messages
232+
---@return table? part, table? msg_wrapper, integer? msg_idx, integer? part_idx
233+
function MessageMap:get_part_by_id(part_id, messages)
234+
local location = self:get_part_location(part_id)
235+
if not location then
236+
return nil, nil, nil, nil
237+
end
238+
239+
local msg_wrapper = messages[location.message_idx]
240+
if not msg_wrapper or not msg_wrapper.parts or not msg_wrapper.parts[location.part_idx] then
241+
return nil, nil, nil, nil
242+
end
243+
244+
return msg_wrapper.parts[location.part_idx], msg_wrapper, location.message_idx, location.part_idx
245+
end
246+
247+
return MessageMap

0 commit comments

Comments
 (0)