Skip to content

feat(ui): sync fold open/close across diff panes in compact mode (#171)#409

Merged
esmuellert merged 2 commits into
mainfrom
feat/issue-171-synced-folds
Jun 7, 2026
Merged

feat(ui): sync fold open/close across diff panes in compact mode (#171)#409
esmuellert merged 2 commits into
mainfrom
feat/issue-171-synced-folds

Conversation

@esmuellert

Copy link
Copy Markdown
Owner

Summary

Closes #171. In compact mode, folds in the two panes were applied independently — the user had to manually zo/zc on each side to keep them aligned. This PR adds fold synchronization that mirrors Vim's native diff-mode behavior: opening or closing a fold in one pane applies the same action to the corresponding region on the partner pane.

Implementation

Mirrors Neovim's setManualFold algorithm, which propagates fold actions to other diff windows:

  1. Apply the fold action in the current window.
  2. For each other diff pane, map the cursor line to that pane's coordinate space via the diff hunk data.
  3. Apply the same fold action there.

Because codediff's diff buffers use foldmethod=expr (not diff), Neovim's C-level sync is bypassed. We replicate it in Lua via buffer-local keymap wraps on zo/zO/zc/zC/za/zA/zv/zx/zX/zM/zR.

Key design choices:

  • Compact-mode scoped: enable/disable lifecycle hooks install/remove the wraps. Outside compact mode, default zo/zc behavior is preserved.
  • Inline mode no-op: single pane = nothing to sync.
  • Count prefix supported: 3za propagates as 3za.
  • Cursor save/restore: partner pane's cursor returns to its original position so it doesn't visibly jump.
  • Recursion guard: _syncing flag prevents the propagated action from triggering another sync cycle.

New config

diff = {
  compact_sync_folds = true,  -- default on, mirrors Vim diff mode
}

Test plan

  • All 325 UI tests pass (was 311 + 14 new).
  • 9 new unit tests for compute_corresponding_lnum (identity, no changes, before/after change, inside change, multi-change delta accumulation, negative delta, pure insertion, reverse direction).
  • 5 integration tests covering keymap install/teardown, opt-out config, inline-mode skip, and end-to-end zc propagation across panes.
  • End-to-end verification headlessly: zc on pane A at line 20 closes the fold at lines 14-23 on both panes (matching Vim diff-mode behavior).

Known gaps (out of scope for v1)

These bypass keymaps and would need a CursorMoved snapshot+delta fallback to catch. Native Vim diff sync catches them because it hooks at the C-level fold operation:

  • :foldopen / :foldclose ex-commands
  • nvim_set_option_value("foldlevel", ...) from a plugin
  • vim.fn.foldopen() calls bypassing the keymap

The keymap-wrap covers the user-facing common case (the keys); the fallback can be added later if anyone reports a gap.

Compatibility

Non-breaking. Feature is opt-in via compact mode (gc), which is itself opt-in. Default behavior unchanged. Config flag compact_sync_folds = true defaults to on — matches what the issue reporter expects ("just like Vim's diff mode").

🤖 Generated with Claude Code

esmuellert and others added 2 commits June 6, 2026 23:15
Replicates Vim's native diff-mode fold sync (src/nvim/fold.c
setManualFold): when the user opens or closes a fold in one pane, the
same action is applied to the corresponding region on the partner
pane. Closes #171.

Approach mirrors Neovim's C-level algorithm:
  1. Apply the fold action in the current window.
  2. For each other diff pane, map the cursor line to that pane's
     coordinate space via the diff hunk data.
  3. Apply the same fold action there.

Because our diff buffers use foldmethod=expr (not diff), Neovim's
C-level sync is bypassed. We replicate it in Lua via buffer-local
keymap wraps on zo/zO/zc/zC/za/zA/zv/zx/zX/zM/zR.

New config: compact_sync_folds = true (default on). Inline mode is a
no-op (single pane). Cursor is saved/restored so the partner pane
doesn't visibly jump. _syncing recursion guard breaks the bounce-back
loop.

Tests: 9 unit tests for compute_corresponding_lnum + 5 integration
tests including end-to-end verification that pressing zc on one pane
closes the matching fold on the other.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@esmuellert esmuellert enabled auto-merge June 7, 2026 03:16
@esmuellert esmuellert merged commit c772f23 into main Jun 7, 2026
13 checks passed
@esmuellert esmuellert deleted the feat/issue-171-synced-folds branch June 8, 2026 05:05
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.

Feature: Synced folds

1 participant