@@ -7,6 +7,7 @@ M._part_cache = {}
77M ._namespace = vim .api .nvim_create_namespace (' opencode_stream' )
88M ._prev_line_count = 0
99
10+ --- Reset streaming renderer state
1011function M .reset ()
1112 M ._part_cache = {}
1213 M ._prev_line_count = 0
@@ -18,7 +19,7 @@ function M.reset()
1819 state .messages = {}
1920end
2021
21- --- Set up all subscriptions
22+ --- Set up all subscriptions, for both local and server events
2223function M .setup_subscriptions (_ )
2324 M ._subscriptions .active_session = function (_ , _ , old )
2425 if not old then
@@ -58,18 +59,27 @@ function M._cleanup_subscriptions()
5859 M ._subscriptions = {}
5960end
6061
62+ --- Clean up and teardown streaming renderer. Unsubscribes from all
63+ --- events, local state and server
6164function M .teardown ()
6265 M ._cleanup_subscriptions ()
6366 M .reset ()
6467end
6568
69+ --- Get number of lines in output buffer
70+ --- @return integer
6671function M ._get_buffer_line_count ()
6772 if not state .windows or not state .windows .output_buf then
6873 return 0
6974 end
7075 return vim .api .nvim_buf_line_count (state .windows .output_buf )
7176end
7277
78+ --- Shift cached line positions by delta starting from from_line
79+ --- Uses state.messages rather than M._part_cache so it can
80+ --- stop early
81+ --- @param from_line integer Line number to start shifting from
82+ --- @param delta integer Number of lines to shift (positive or negative )
7383function M ._shift_lines (from_line , delta )
7484 if delta == 0 then
7585 return
@@ -105,6 +115,10 @@ function M._shift_lines(from_line, delta)
105115 -- vim.notify('Shifting lines from: ' .. from_line .. ' by delta: ' .. delta .. ' examined: ' .. examined .. ' shifted: ' .. shifted)
106116end
107117
118+ --- Apply extmarks to buffer at given line offset
119+ --- @param buf integer Buffer handle
120+ --- @param line_offset integer Line offset to apply extmarks at
121+ --- @param extmarks table<integer , table[]> ? Extmarks indexed by line
108122function M ._apply_extmarks (buf , line_offset , extmarks )
109123 if not extmarks or type (extmarks ) ~= ' table' then
110124 return
@@ -122,12 +136,21 @@ function M._apply_extmarks(buf, line_offset, extmarks)
122136 end
123137end
124138
139+ --- The output buffer isn't modifiable so this is a wrapper that
140+ --- temporarily makes the buffer modifiable while so we can add content
141+ --- @param buf integer Buffer handle
142+ --- @param start_line integer Start line (0-indexed )
143+ --- @param end_line integer End line (0-indexed , -1 for end of buffer )
144+ --- @param strict_indexing boolean Use strict indexing
145+ --- @param lines string[] Lines to set
125146function M ._set_lines (buf , start_line , end_line , strict_indexing , lines )
126147 vim .api .nvim_set_option_value (' modifiable' , true , { buf = buf })
127148 vim .api .nvim_buf_set_lines (buf , start_line , end_line , strict_indexing , lines )
128149 vim .api .nvim_set_option_value (' modifiable' , false , { buf = buf })
129150end
130151
152+ --- Auto-scroll to bottom if user was already at bottom
153+ --- Respects cursor position if user has scrolled up
131154function M ._scroll_to_bottom ()
132155 local ok , line_count = pcall (vim .api .nvim_buf_line_count , state .windows .output_buf )
133156 if not ok then
@@ -153,6 +176,9 @@ function M._scroll_to_bottom()
153176 end
154177end
155178
179+ --- Write data to output_buf, including normal text and extmarks
180+ --- @param formatted_data { lines : string[] , extmarks : table ?} Formatted data with lines and extmarks
181+ --- @return { line_start : integer , line_end : integer }? Range where data was written
156182function M ._write_formatted_data (formatted_data )
157183 local buf = state .windows .output_buf
158184 local buf_lines = M ._get_buffer_line_count ()
@@ -171,13 +197,21 @@ function M._write_formatted_data(formatted_data)
171197 }
172198end
173199
200+ --- Write message header to buffer
201+ --- @param message table Message object
202+ --- @param msg_idx integer Message index
203+ --- @return { line_start : integer , line_end : integer }? Range where header was written
174204function M ._write_message_header (message , msg_idx )
175205 local formatter = require (' opencode.ui.session_formatter' )
176206 local header_data = formatter .format_message_header_isolated (message , msg_idx )
177207 local line_range = M ._write_formatted_data (header_data )
178208 return line_range
179209end
180210
211+ --- Insert new part at end of buffer
212+ --- @param part_id string Part ID
213+ --- @param formatted_data { lines : string[] , extmarks : table ?} Formatted data
214+ --- @return boolean Success status
181215function M ._insert_part_to_buffer (part_id , formatted_data )
182216 local cached = M ._part_cache [part_id ]
183217 if not cached then
@@ -198,6 +232,11 @@ function M._insert_part_to_buffer(part_id, formatted_data)
198232 return true
199233end
200234
235+ --- Replace existing part in buffer
236+ --- Adjusts line positions of subsequent parts if line count changes
237+ --- @param part_id string Part ID
238+ --- @param formatted_data { lines : string[] , extmarks : table ?} Formatted data
239+ --- @return boolean Success status
201240function M ._replace_part_in_buffer (part_id , formatted_data )
202241 local cached = M ._part_cache [part_id ]
203242 if not cached or not cached .line_start or not cached .line_end then
@@ -227,6 +266,8 @@ function M._replace_part_in_buffer(part_id, formatted_data)
227266 return true
228267end
229268
269+ --- Remove part from buffer and adjust subsequent line positions
270+ --- @param part_id string Part ID
230271function M ._remove_part_from_buffer (part_id )
231272 local cached = M ._part_cache [part_id ]
232273 if not cached or not cached .line_start or not cached .line_end then
@@ -247,6 +288,9 @@ function M._remove_part_from_buffer(part_id)
247288 M ._part_cache [part_id ] = nil
248289end
249290
291+ --- Event handler for message.updated events
292+ --- Creates new message or updates existing message info
293+ --- @param event EventMessageUpdated Event object
250294function M .on_message_updated (event )
251295 if not event or not event .properties or not event .properties .info then
252296 return
@@ -283,6 +327,9 @@ function M.on_message_updated(event)
283327 M ._scroll_to_bottom ()
284328end
285329
330+ --- Event handler for message.part.updated events
331+ --- Inserts new parts or replaces existing parts in buffer
332+ --- @param event EventMessagePartUpdated Event object
286333function M .on_part_updated (event )
287334 if not event or not event .properties or not event .properties .part then
288335 return
@@ -373,6 +420,8 @@ function M.on_part_updated(event)
373420 M ._scroll_to_bottom ()
374421end
375422
423+ --- Event handler for message.part.removed events
424+ --- @param event EventMessagePartRemoved Event object
376425function M .on_part_removed (event )
377426 -- XXX: I don't have any sessions that remove parts so this code is
378427 -- currently untested
@@ -407,6 +456,9 @@ function M.on_part_removed(event)
407456 M ._remove_part_from_buffer (part_id )
408457end
409458
459+ --- Event handler for message.removed events
460+ --- Removes message and all its parts from buffer
461+ --- @param event EventMessageRemoved Event object
410462function M .on_message_removed (event )
411463 -- XXX: I don't have any sessions that remove messages so this code is
412464 -- currently untested
@@ -443,17 +495,23 @@ function M.on_message_removed(event)
443495 table.remove (state .messages , message_idx )
444496end
445497
446- function M .on_session_compacted ()
498+ --- Event handler for session.compacted events
499+ --- @param event EventSessionCompacted Event object
500+ function M .on_session_compacted (event )
447501 vim .notify (' on_session_compacted' )
448502 -- TODO: render a note that the session was compacted
449503end
450504
505+ --- Reset and re-render the whole session via output_renderer
506+ --- This means something went wrong with streaming rendering
451507function M .reset_and_render ()
452508 M .reset ()
453509 vim .notify (' reset and render:\n ' .. debug.traceback ())
454510 require (' opencode.ui.output_renderer' ).render (state .windows , true )
455511end
456512
513+ --- Event handler for session.error events
514+ --- @param event EventSessionError Event object
457515function M .on_session_error (event )
458516 if not event or not event .properties or not event .properties .error then
459517 return
@@ -469,6 +527,9 @@ function M.on_session_error(event)
469527 M ._scroll_to_bottom ()
470528end
471529
530+ --- Event handler for permission.updated events
531+ --- Re-renders part that requires permission
532+ --- @param event EventPermissionUpdated Event object
472533function M .on_permission_updated (event )
473534 if not event or not event .properties then
474535 return
@@ -488,6 +549,9 @@ function M.on_permission_updated(event)
488549 end
489550end
490551
552+ --- Event handler for permission.replied events
553+ --- Re-renders part after permission is resolved
554+ --- @param event EventPermissionReplied Event object
491555function M .on_permission_replied (event )
492556 if not event or not event .properties then
493557 return
@@ -505,6 +569,11 @@ function M.on_permission_replied(event)
505569 end
506570end
507571
572+ --- Find part ID by call ID
573+ --- Searches messages in reverse order for efficiency
574+ --- Useful for finding a part for a permission
575+ --- @param call_id string Call ID to search for
576+ --- @return string ? part_id Part ID if found , nil otherwise
508577function M ._find_part_by_call_id (call_id )
509578 if not state .messages then
510579 return nil
@@ -525,6 +594,9 @@ function M._find_part_by_call_id(call_id)
525594 return nil
526595end
527596
597+ --- Re-render existing part with current state
598+ --- Used for permission updates and other dynamic changes
599+ --- @param part_id string Part ID to re-render
528600function M ._rerender_part (part_id )
529601 local cached = M ._part_cache [part_id ]
530602 if not cached then
0 commit comments