Skip to content

fix: fold error when using max_messages - #469

Merged
sudo-tee merged 12 commits into
sudo-tee:mainfrom
phanen:fix/fold
Jul 27, 2026
Merged

fix: fold error when using max_messages#469
sudo-tee merged 12 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 10 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.
@phanen
phanen force-pushed the fix/fold branch 2 times, most recently from 8c8ba24 to ebef0ec Compare July 25, 2026 02:19
@phanen
phanen marked this pull request as ready for review July 25, 2026 02:24
@phanen
phanen marked this pull request as draft July 25, 2026 02:50
phanen added 2 commits July 25, 2026 11:01
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.

test(renderer): align 'merges existing folds' with formatted_parts source
fix(renderer): shift siblings on in-place upsert_part_now size change

fix(renderer): shift siblings on append_part_now

fix(renderer): drop redundant shift_all around render_state mutators
@phanen
phanen marked this pull request as ready for review July 25, 2026 03:41
@sudo-tee
sudo-tee merged commit e1e336a into sudo-tee:main Jul 27, 2026
5 checks passed
jugarpeupv pushed a commit to jugarpeupv/opencode.nvim that referenced this pull request Jul 29, 2026
* fix(output_window): guard foldopen! against out-of-range fold ranges

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.

* perf(output_window): batch fold commands into single nvim_exec2 call

* refactor(output_window): store fold state in state.windows instead of 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.

* chore: unused

* chore: fix types

* fix: skip check when debug not enable

* chore: dedup code

* refactor(renderer): extract output_diff and dedupe upsert path

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.

* chore: unused function

* style: fmt

* fix(renderer): derive global folds from formatted_parts

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.

test(renderer): align 'merges existing folds' with formatted_parts source

* fix(renderer): shift render_state and drop part_folds on remove

fix(renderer): shift siblings on in-place upsert_part_now size change

fix(renderer): shift siblings on append_part_now

fix(renderer): drop redundant shift_all around render_state mutators
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