Skip to content

fix: fold error when using max_messages#469

Draft
phanen wants to merge 14 commits into
sudo-tee:mainfrom
phanen:fix/fold
Draft

fix: fold error when using max_messages#469
phanen wants to merge 14 commits into
sudo-tee:mainfrom
phanen:fix/fold

Conversation

@phanen

@phanen phanen commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

often get error like this when use max_messages

lua/opencode/ui/renderer/flush.lua:91: ui/output_window.lua:420: Lua: runtime/lua/vim/_core/editor.lua:490: nvim_exec2(), line 1: Vim(foldopen):E16: Invalid range: 1328,1338foldopen!
stack traceback:
	[C]: in function 'nvim_exec2'
	runtime/lua/vim/_core/editor.lua:490: in function 'cmd'
	ui/output_window.lua:432: in function <ui/output_window.lua:420>
	[C]: in function 'nvim_win_call'
	ui/output_window.lua:420: in function 'set_folds'
	ui/renderer/buffer.lua:726: in function 'update_part_folds'
	ui/renderer/buffer.lua:753: in function 'append_part_now'
	opencode/ui/renderer/flush.lua:394: in function 'apply_part'
	opencode/ui/renderer/flush.lua:489: in function <opencode/ui/renderer/flush.lua:454>
	[C]: in function 'xpcall'
	opencode/ui/renderer/flush.lua:80: in function 'with_suppressed_output_autocmds'
	opencode/ui/renderer/flush.lua:454: in function 'apply_pending'
	opencode/ui/renderer/flush.lua:600: in function 'flush'
	opencode/ui/renderer/flush.lua:280: in function <opencode/ui/renderer/flush.lua:278>

@sudo-tee

Copy link
Copy Markdown
Owner

@phanen

Is the MR ready for review or you still need to adjust some things ?

@phanen

phanen commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

not yet, I will take a look again asap

phanen added 14 commits July 25, 2026 01:08
Problem: when buffer shrinks faster than the cached fold ranges get
updated, vim.fn.foldclosed() reports ranges that no longer exist in
the buffer. The fold creation loop already had a line_count guard but
the foldopen! loop did not, surfacing as 'E16: Invalid range'.

Solution: mirror the same line_count guard on the foldopen! loop so
out-of-range entries are silently skipped instead of raising.
… buffer var

The opencode_folds b:var had to round-trip through vim's lua bridge
on every set_folds call (one nvim_buf_set_var write plus one
nvim_buf_get_var read). Microbench on a freshly created buffer:

  bridge set+get round-trip:  1.04 us
  bridge set only:            0.70 us
  bridge get only:            0.45 us
  pure Lua ref:               0.003 us

Move fold state to state.windows.output_folds, sitting alongside
output_buf and output_was_at_bottom. set_windows replaces the whole
windows object on every mount, so output_folds resets with it
automatically - no separate clear path needed. Fold state is now
fetched with state.ui.get_output_folds(), a pure Lua table read.

Also drops the fold_state.starts field that shift_folds was
maintaining but nothing was reading.
Problem: diff computations were duplicated across flush + buffer.
flush.format_message did a full lines_equal + extmarks_equal walk,
and buffer.upsert_*_now each computed unchanged_prefix_len that
extmark_clear_range then recomputed internally. append.lua held a
hand-rolled tail walk duplicating the same primitive.

Solution: introduce output_diff.lua with shared prefix walks, slice
helpers, mark equality, and the high-level is_unchanged / is_append_only
predicates. Make extmark_clear_range accept a precomputed prefix_len
and extract write_in_place so both upsert paths walk once. Collapse
append.lua into output_diff.lua.
Problem: update_part_folds aggregated new_global from ctx.part_folds,
which stores absolute coordinates computed at the time each part was
last updated. When a part's line_start shifts (upsert/append path did
not call shift_all before c451c90+x, remove paths still do not), the
stored entry becomes stale and the next update_part_folds re-emits
fold ranges referencing lines that no longer host the part's content.

Solution: rebuild new_global from ctx.formatted_parts (source of truth
for what's currently rendered) on every update, recomputing each part's
absolute folds from its current cached_part.line_start. This mirrors
set_all_folds and stops stale entries from leaking into set_folds.
Problem: remove_part_now and remove_message_now only called
output_window.shift_folds to update the canonical fold state, leaving
ctx.render_state entries for parts/messages below the removed range
with stale line_start/line_end. Any subsequent update_part_folds then
fed those stale coordinates back into set_folds.

Solution: mirror the upsert path. After shift_folds, call
ctx.render_state:shift_all(...) so siblings shift in lockstep with the
canonical fold state, and drop ctx.part_folds[part_id] so the removed
part does not leak its absolute coords into the next aggregate.
Problem: when an in-place upsert grew or shrunk the rewritten range
(other_line_end = write_in_place returns != cached.line_end), only the
current part's line_end was updated via update_part_lines. Sibling
parts/messages below kept their old line_start, and the canonical fold
state was not shifted, so ctx.part_folds for siblings pointed to lines
whose content had moved out from under them.

Solution: mirror the upsert_message_now path. On non-zero delta, call
output_window.shift_folds and ctx.render_state:shift_all starting at
old_line_end + 1 so siblings move in lockstep with the buffer.
Problem: append_part_now inserted lines past cached.line_end but only
called update_part_lines on the current part. ctx.render_state and the
canonical fold state both kept their pre-append coordinates for parts
below the insertion, so the next update_part_folds produced fold ranges
that targeted lines whose content had been pushed down by the append.

Solution: shift both downstream of the insert site after update_part_lines,
matching the upsert paths. set_lines moves the buffer; shift_folds and
shift_all keep render_state and the canonical fold list aligned.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants