@@ -9,6 +9,44 @@ local append = require('opencode.ui.renderer.append')
99
1010local M = {}
1111
12+ local function with_suppressed_output_autocmds (fn )
13+ local output_win = state .windows and state .windows .output_win
14+ local has_output_win = output_win and vim .api .nvim_win_is_valid (output_win )
15+ local saved_eventignorewin = has_output_win and vim .api .nvim_get_option_value (' eventignorewin' , { win = output_win })
16+ or nil
17+
18+ if has_output_win then
19+ vim .api .nvim_set_option_value (' eventignorewin' , ' all' , { win = output_win , scope = ' local' })
20+ end
21+
22+ local begin_ok , began_update = xpcall (output_window .begin_update , debug.traceback )
23+ if not begin_ok then
24+ if has_output_win then
25+ vim .api .nvim_set_option_value (' eventignorewin' , saved_eventignorewin , { win = output_win , scope = ' local' })
26+ end
27+ error (began_update )
28+ end
29+
30+ local ok , result = xpcall (fn , debug.traceback )
31+ local end_ok , end_err = true , nil
32+
33+ if began_update then
34+ end_ok , end_err = xpcall (output_window .end_update , debug.traceback )
35+ end
36+ if has_output_win then
37+ vim .api .nvim_set_option_value (' eventignorewin' , saved_eventignorewin , { win = output_win , scope = ' local' })
38+ end
39+
40+ if not ok then
41+ error (result )
42+ end
43+ if not end_ok then
44+ error (end_err )
45+ end
46+
47+ return result
48+ end
49+
1250local function lines_equal (a , b )
1351 a = a or {}
1452 b = b or {}
@@ -282,50 +320,46 @@ local function apply_pending(pending)
282320 end
283321
284322 local scroll_snapshot = scroll .pre_flush (buf )
285- local saved_eventignore = vim .o .eventignore
286- vim .o .eventignore = ' all'
287- output_window .begin_update ()
288-
289- for _ , part_id in ipairs (pending .removed_part_order ) do
290- if pending .removed_parts [part_id ] then
291- buffer .remove_part_now (part_id )
323+ with_suppressed_output_autocmds (function ()
324+ for _ , part_id in ipairs (pending .removed_part_order ) do
325+ if pending .removed_parts [part_id ] then
326+ buffer .remove_part_now (part_id )
327+ end
292328 end
293- end
294329
295- for _ , message_id in ipairs (pending .removed_message_order ) do
296- if pending .removed_messages [message_id ] then
297- buffer .remove_message_now (message_id )
330+ for _ , message_id in ipairs (pending .removed_message_order ) do
331+ if pending .removed_messages [message_id ] then
332+ buffer .remove_message_now (message_id )
333+ end
298334 end
299- end
300335
301- for _ , message_id in ipairs (pending .dirty_message_order ) do
302- if pending .dirty_messages [message_id ] then
303- apply_message (message_id )
304- end
336+ for _ , message_id in ipairs (pending .dirty_message_order ) do
337+ if pending .dirty_messages [message_id ] then
338+ apply_message (message_id )
339+ end
305340
306- local dirty_parts = pending .dirty_part_by_message [message_id ]
307- if dirty_parts then
308- local message = ctx .render_state :get_message (message_id )
309- local parts = message and message .message and message .message .parts or {}
310- for _ , part in ipairs (parts or {}) do
311- if part .id and dirty_parts [part .id ] then
312- apply_part (part .id , message_id )
313- dirty_parts [part .id ] = nil
314- pending .dirty_parts [part .id ] = nil
341+ local dirty_parts = pending .dirty_part_by_message [message_id ]
342+ if dirty_parts then
343+ local message = ctx .render_state :get_message (message_id )
344+ local parts = message and message .message and message .message .parts or {}
345+ for _ , part in ipairs (parts or {}) do
346+ if part .id and dirty_parts [part .id ] then
347+ apply_part (part .id , message_id )
348+ dirty_parts [part .id ] = nil
349+ pending .dirty_parts [part .id ] = nil
350+ end
315351 end
316352 end
317353 end
318- end
319354
320- for _ , part_id in ipairs (pending .dirty_part_order ) do
321- local message_id = pending .dirty_parts [part_id ]
322- if message_id then
323- apply_part (part_id , message_id )
355+ for _ , part_id in ipairs (pending .dirty_part_order ) do
356+ local message_id = pending .dirty_parts [part_id ]
357+ if message_id then
358+ apply_part (part_id , message_id )
359+ end
324360 end
325- end
361+ end )
326362
327- output_window .end_update ()
328- vim .o .eventignore = saved_eventignore
329363 scroll .post_flush (scroll_snapshot , buf )
330364 return true
331365end
@@ -396,21 +430,23 @@ function M.end_bulk_mode()
396430 end
397431
398432 -- Write all lines at once. Suppress autocmds so render-markdown and similar
399- -- plugins don't fire mid-write; we trigger them explicitly via vim.schedule
400- -- below. begin_update/end_update handles the modifiable toggle.
401- local saved_eventignore = vim .o .eventignore
402- vim .o .eventignore = ' all'
403- output_window .begin_update ()
404- output_window .set_lines (lines , 0 , - 1 )
405- output_window .end_update ()
406- vim .o .eventignore = saved_eventignore
407-
408- if next (ctx .bulk_extmarks_by_line ) then
409- output_window .set_extmarks (ctx .bulk_extmarks_by_line , 0 )
410- end
433+ -- plugins don't fire mid-write; restore state even if the write fails.
434+ local ok , err = xpcall (function ()
435+ with_suppressed_output_autocmds (function ()
436+ output_window .set_lines (lines , 0 , - 1 )
437+ end )
438+
439+ if next (ctx .bulk_extmarks_by_line ) then
440+ output_window .set_extmarks (ctx .bulk_extmarks_by_line , 0 )
441+ end
442+ end , debug.traceback )
411443
412444 ctx :bulk_reset ()
413445
446+ if not ok then
447+ error (err )
448+ end
449+
414450 vim .schedule (function ()
415451 M .request_on_data_rendered (true )
416452 end )
0 commit comments