Skip to content

Group unchanged stretches whose folds are unpaired - #28

Open
ketan0 wants to merge 1 commit into
mainfrom
fix/group-unpaired-folds
Open

ketan0 wants to merge 1 commit into
mainfrom
fix/group-unpaired-folds

Conversation

@ketan0

@ketan0 ketan0 commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

What

When the structural diff exceeds diff.graph_limit, the engine keeps each side's parse folds but marks them all unpaired (folds::unmatched) and takes change positions from a line diff (too_complex fallback). The context plugin only grouped a stretch's siblings into one context fold when both sides matched fold for fold (same_shape). After a fallback they never do, so:

  • every member of an unchanged stretch collapsed on its own, giving dozens of back-to-back "N unchanged lines" rows, and
  • each fold collapsed on the lhs only. The rhs kept it open because nothing on the rhs shares its fold state.

This change, in plugins/context/src/lib.rs:

  • When the shapes differ, an lhs part is grouped together with the rhs part covering the same lines of the stretch, as long as that rhs part has two or more siblings and its folds hide only the stretch. Both runs go into one JoinFolds, so the two new group folds share a fold state. Consumers that pair folds by fold_state_id (Review) render them as one band that opens and closes together. Members keep their own visibility, so opening the group shows code directly (as in Reveal unchanged context groups without nested folds #23). Labels and MIN_GAP rules are unchanged.
  • A fold that shares no fold state with any lhs region and isn't in a group now collapses on the rhs by itself, so it no longer stays open while the lhs collapses.
  • Matched (same_shape) stretches work exactly as before. They now take the rhs group members straight from the matching rhs part instead of rebuilding them through leaf pairing, which gives the same set of members.

Remaining gap: when the two sides' parts cover different lines (a fold edge on one side only), or when a part is a single region, there is no rhs part to join with. Those still collapse member by member, as before. A single unpaired fold then collapses on each side under its own fold state (two bands, not one).

Before / after

packages/review/src/review-api/local-data.ts in the review repo, 48a9385a..82e40383 (fallback.code: too_complex):

before after
collapsed regions starting in lhs lines 131–1030 69 1 (892 lines)
…of those with no rhs partner 42 0
collapsed regions, whole file (lhs / rhs) 107 / 48 12 / 13

Every group in the file now has a matching rhs group with the same fold_state_id. The visible added/removed counts are unchanged (71 / 19).

Testing

  • New a_fallback_stretch_over_unpaired_folds_collapses_as_one_group_per_side: three unchanged functions between two changes with graph_limit: 1. Each side gets one "14 unchanged lines" group, both groups share a fold state, and opening a group reveals all the code.
  • New an_unpaired_fold_alone_in_a_stretch_collapses_on_each_side: hand-built trees in which an unpaired fold alone in a stretch collapses on both sides.
  • a_line_diff_fallback_has_unpaired_folds now expects the unchanged function to collapse on the rhs too. It used to document the one-sided behaviour this PR fixes.
  • a_stretch_over_whole_folds_collapses_as_one_group and the other matched-stretch tests pass unchanged.
  • cargo test --workspace, cargo xtask test-plugins, cargo fmt --all -- --check and cargo build --release all pass. Clippy (stable) reports nothing new.

This change was written with Claude Code.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WbJh11mqHtrFZS7zP2peyf

When the structural matcher exceeds diff.graph_limit, the parse's folds
stand but are all unpaired, so no stretch matches fold for fold. The
context plugin only grouped a stretch's siblings when both sides matched
one for one, so each member collapsed on its own: dozens of back-to-back
"N unchanged lines" rows, and every fold collapsed on the lhs alone
while the rhs kept it open.

When the shapes differ, a part now groups with the rhs part over the
same lines of the stretch, if that part has two or more siblings whose
folds hide only the stretch. Both runs are wrapped in one join, so the
two groups share a fold state and consumers that pair folds by fold
state render them as one band. Members keep their visibility, so
opening a group reveals code directly. A fold that no lhs region shares
a fold state with and that is not grouped now collapses on the rhs by
itself. Matched stretches take the same path as before.

On review's local-data.ts at 48a9385a..82e40383 (too_complex), lhs
lines 131-1030 go from 69 collapsed regions, 42 without an rhs partner,
to one group paired across sides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WbJh11mqHtrFZS7zP2peyf

This branch has not been deployed

No deployments
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.

1 participant