@@ -2,18 +2,69 @@ local state = require('opencode.state')
22
33local M = {}
44
5+ M ._subscriptions = {}
56M ._part_cache = {}
67M ._message_cache = {}
7- M ._session_id = nil
88M ._namespace = vim .api .nvim_create_namespace (' opencode_stream' )
9+ M ._prev_line_count = 0
910
1011function M .reset ()
1112 M ._part_cache = {}
1213 M ._message_cache = {}
13- M ._session_id = nil
14+ M ._prev_line_count = 0
15+
16+ -- FIXME: this prolly isn't the right place for state.messages to be
17+ -- cleared. It would probably be better to have streaming_renderer
18+ -- subscribe to state changes and if state.messages is cleared, it
19+ -- should clear it's state too
1420 state .messages = {}
1521end
1622
23+ --- Set up all subscriptions
24+ function M .setup_subscriptions (_ )
25+ M ._subscriptions .active_session = function (_ , _ , old )
26+ if not old then
27+ return
28+ end
29+ M .reset ()
30+ end
31+ state .subscribe (' active_session' , M ._subscriptions .active_session )
32+ M ._setup_event_subscriptions ()
33+ end
34+
35+ --- Set up server event subscriptions
36+ --- @param subscribe ? boolean false to unsubscribe
37+ function M ._setup_event_subscriptions (subscribe )
38+ if not state .event_manager then
39+ return
40+ end
41+
42+ local method = (subscribe == false ) and ' unsubscribe' or ' subscribe'
43+
44+ state .event_manager [method ](state .event_manager , ' message.updated' , M .on_message_updated )
45+ state .event_manager [method ](state .event_manager , ' message.part.updated' , M .on_part_updated )
46+ state .event_manager [method ](state .event_manager , ' message.removed' , M .on_message_removed )
47+ state .event_manager [method ](state .event_manager , ' message.part.removed' , M .on_part_removed )
48+ state .event_manager [method ](state .event_manager , ' session.compacted' , M .on_session_compacted )
49+ state .event_manager [method ](state .event_manager , ' session.error' , M .on_session_error )
50+ state .event_manager [method ](state .event_manager , ' permission.updated' , M .on_permission_updated )
51+ state .event_manager [method ](state .event_manager , ' permission.replied' , M .on_permission_replied )
52+ end
53+
54+ --- Unsubscribe from local state and server subscriptions
55+ function M ._cleanup_subscriptions ()
56+ M ._setup_event_subscriptions (false )
57+ for key , cb in pairs (M ._subscriptions ) do
58+ state .unsubscribe (key , cb )
59+ end
60+ M ._subscriptions = {}
61+ end
62+
63+ function M .teardown ()
64+ M ._cleanup_subscriptions ()
65+ M .reset ()
66+ end
67+
1768function M ._get_buffer_line_count ()
1869 if not state .windows or not state .windows .output_buf then
1970 return 0
@@ -87,11 +138,29 @@ function M._text_to_lines(text)
87138end
88139
89140function M ._scroll_to_bottom ()
90- local debounced_scroll = require (' opencode.util' ).debounce (function ()
91- require (' opencode.ui.ui' ).scroll_to_bottom ()
92- end , 50 )
141+ local ok , line_count = pcall (vim .api .nvim_buf_line_count , state .windows .output_buf )
142+ if not ok then
143+ return
144+ end
93145
94- debounced_scroll ()
146+ local botline = vim .fn .line (' w$' , state .windows .output_win )
147+ local cursor = vim .api .nvim_win_get_cursor (state .windows .output_win )
148+ local cursor_row = cursor [1 ] or 0
149+ local is_focused = vim .api .nvim_get_current_win () == state .windows .output_win
150+
151+ local prev_line_count = M ._prev_line_count or 0
152+ M ._prev_line_count = line_count
153+
154+ local was_at_bottom = (botline >= prev_line_count ) or prev_line_count == 0
155+
156+ if is_focused and cursor_row < prev_line_count - 1 then
157+ return
158+ end
159+
160+ if was_at_bottom or not is_focused then
161+ -- vim.notify('was_at_bottom: ' .. tostring(was_at_bottom) .. ' is_focused: ' .. tostring(is_focused))
162+ require (' opencode.ui.ui' ).scroll_to_bottom ()
163+ end
95164end
96165
97166function M ._write_formatted_data (formatted_data )
@@ -206,7 +275,7 @@ function M._remove_part_from_buffer(part_id)
206275 M ._part_cache [part_id ] = nil
207276end
208277
209- function M .handle_message_updated (event )
278+ function M .on_message_updated (event )
210279 if not event or not event .properties or not event .properties .info then
211280 return
212281 end
@@ -216,13 +285,10 @@ function M.handle_message_updated(event)
216285 return
217286 end
218287
219- if M ._session_id and M ._session_id ~= message .sessionID then
220- -- TODO: there's probably more we need to do here
221- M .reset ()
288+ if state .active_session .id ~= message .sessionID then
289+ vim .notify (' Session id does not match, discarding part: ' .. vim .inspect (message ), vim .log .levels .WARN )
222290 end
223291
224- M ._session_id = message .sessionID
225-
226292 local found_idx = nil
227293 for i = # state .messages , math.max (1 , # state .messages - 2 ), - 1 do
228294 if state .messages [i ].info .id == message .id then
@@ -252,7 +318,7 @@ function M.handle_message_updated(event)
252318 M ._scroll_to_bottom ()
253319end
254320
255- function M .handle_part_updated (event )
321+ function M .on_part_updated (event )
256322 if not event or not event .properties or not event .properties .part then
257323 return
258324 end
@@ -262,7 +328,7 @@ function M.handle_part_updated(event)
262328 return
263329 end
264330
265- if M . _session_id and M . _session_id ~= part .sessionID then
331+ if state . active_session . id ~= part .sessionID then
266332 vim .notify (' Session id does not match, discarding part: ' .. vim .inspect (part ), vim .log .levels .WARN )
267333 return
268334 end
@@ -342,7 +408,7 @@ function M.handle_part_updated(event)
342408 M ._scroll_to_bottom ()
343409end
344410
345- function M .handle_part_removed (event )
411+ function M .on_part_removed (event )
346412 -- XXX: I don't have any sessions that remove parts so this code is
347413 -- currently untested
348414 if not event or not event .properties then
@@ -376,7 +442,7 @@ function M.handle_part_removed(event)
376442 M ._remove_part_from_buffer (part_id )
377443end
378444
379- function M .handle_message_removed (event )
445+ function M .on_message_removed (event )
380446 -- XXX: I don't have any sessions that remove messages so this code is
381447 -- currently untested
382448 if not event or not event .properties then
@@ -416,10 +482,9 @@ function M.handle_message_removed(event)
416482 end
417483end
418484
419- function M .handle_session_compacted ()
420- M .reset ()
421- vim .notify (' handle_session_compacted' )
422- require (' opencode.ui.output_renderer' ).render (state .windows , true )
485+ function M .on_session_compacted ()
486+ vim .notify (' on_session_compacted' )
487+ -- TODO: render a note that the session was compacted
423488end
424489
425490function M .reset_and_render ()
@@ -428,7 +493,7 @@ function M.reset_and_render()
428493 require (' opencode.ui.output_renderer' ).render (state .windows , true )
429494end
430495
431- function M .handle_session_error (event )
496+ function M .on_session_error (event )
432497 if not event or not event .properties or not event .properties .error then
433498 return
434499 end
@@ -443,7 +508,7 @@ function M.handle_session_error(event)
443508 M ._scroll_to_bottom ()
444509end
445510
446- function M .handle_permission_updated (event )
511+ function M .on_permission_updated (event )
447512 if not event or not event .properties then
448513 return
449514 end
@@ -461,7 +526,7 @@ function M.handle_permission_updated(event)
461526 end
462527end
463528
464- function M .handle_permission_replied (event )
529+ function M .on_permission_replied (event )
465530 if not event or not event .properties then
466531 return
467532 end
0 commit comments