feat: flexible split panes with session persistence (#543)#704
Open
feat: flexible split panes with session persistence (#543)#704
Conversation
Implement Phase 2 of flexible split panes: - PaneManager: manages pane layout tree with per-pane TabManagers - PaneTreeView: recursive SwiftUI view rendering PaneNode splits - Drag tab to right edge → split right, bottom edge → split down - Drag tab between panes → move tab - PaneDividerView: draggable divider for resize with cursor feedback - Drop zone overlays with semi-transparent indicators - TabDragInfo: encoded drag data for cross-pane tab transfer - 25 unit tests covering PaneManager, TabDragInfo, PaneDropZone
- Replace magic numbers (300, 200) in drop zone detection with percentage-based thresholds using actual pane size from GeometryReader - Switch TabDragInfo from pipe-separated encoding to JSON (Codable) - Use custom UTType (.paneTabDrag) instead of .text for drag operations, registered as exported type in Info.plist - Add guard against repeated NSCursor.push() in onContinuousHover to prevent cursor stack leak - Remove dead paneTabDragUTType global variable, replaced by UTType extension - Add TODO for persisting split pane layout in SessionState - Update TabDragInfoTests for JSON encoding format
…nd exclude PaneTreeView from coverage - PaneManager: focus cycle, updateSplitRatio, move tab edge cases (invalid source, non-existent tab, content preservation), remove pane edge cases, rapid splits, nil tabURL split - TabDragInfo: unicode paths, deep paths, partial/null/array JSON, extra fields, multiple instances - PaneDropZone: all zones identity, sendable conformance, switch exhaustiveness - Exclude PaneTreeView.swift from coverage (pure SwiftUI view)
Resolve merge conflicts in AccessibilityIdentifiers, ContentView, PineApp, and ProjectManager to incorporate toast notifications, inline diff hunks, tab context menu actions, and git fetcher changes from main while preserving split panes functionality.
- Use custom UTType .paneTabDrag instead of .text in SinglePaneSplitDropDelegate to prevent external text drops from breaking the app - Add onDisappear cleanup for NSCursor push/pop in PaneDividerView to prevent cursor leaks when SwiftUI deallocates the view without hover .ended phase - Replace magic pixel numbers (300/200) with percentage-based thresholds (0.7) in SinglePaneSplitDropDelegate, unified with PaneSplitDropDelegate via shared PaneDropZone.zone(for:in:) static method - Fix moveTab to preserve all EditorTab state (cursorPosition, scrollOffset, foldState, isPinned, encoding, etc.) via new EditorTab.reidentified(from:) - Remove duplicated PaneEditorTabBar, reuse EditorTabBar with overridePaneID - Replace trivial PaneDropZone.Equatable tests with meaningful tests for drop zone calculation, EditorTab.reidentified, and tab state preservation
- PaneLeafView now shows dirty tab confirmation dialog on close - Connected onCloseOtherTabs/onCloseTabsToTheRight/onCloseAllTabs context menu handlers - Wired lineDiffs from GitStatusProvider for gutter diff markers - Wired blameLines from git blame for inline blame annotations - Added diffHunks/onAcceptHunk/onRevertHunk for inline diff - Added StatusBarView to each pane leaf - Removed dead PaneContent.terminal case from enum - Updated PaneNodeTests for single PaneContent case - Added PaneLeafCloseTests with 12 tests covering close logic
…tap conflicts 1. pm.tabManager → pm.activeTabManager in all menu commands (Cmd+S/W, Save As, Duplicate, Ctrl+Tab, Cmd+1..9), CloseDelegate, and applicationShouldTerminate/WillTerminate. Multi-pane mode now correctly targets the focused pane. 2. Merged two .onDrop handlers in EditorAreaView into a single EditorAreaUnifiedDropDelegate — the second .onDrop was overriding the first, breaking file drops from Finder in single-pane mode. 3. Replaced .onTapGesture in PaneLeafView with PaneFocusDetector (NSView local event monitor) — the tap gesture was blocking clicks on the code editor text and tab bar buttons. 4. Reordered moveTab in PaneManager: add to destination first, then remove from source. Prevents tab loss if append fails. 5. Session persistence now collects tabs from ALL panes via pm.allTabs, so split-pane tabs survive save/restore cycles. 6. DocumentEditedTracker uses pm.hasUnsavedChanges (all panes) instead of single tabManager.
…cycle CloseDelegate.closeActiveTab() now removes the active pane when closing its last tab, matching PaneLeafView behavior. PaneFocusNSView.paneManager is now a weak reference to prevent retain cycles.
- Resolve merge conflict in ContentView.swift (keep pane layout logic) - Remove stale onAcceptHunk/onRevertHunk params from PaneTreeView and ContentView (CodeEditorView no longer accepts them after main changes) - Add nonisolated(unsafe) on PaneFocusNSView.monitor for deinit access - Move @mainactor from individual test methods to struct level on PaneManagerTests, MultiPaneIntegrationTests, PaneLeafCloseTests, PaneFocusNSViewTests (matches convention from #690) - Replace force unwrapping with guard-let in MultiPaneIntegrationTests (SwiftLint fix)
… files Break up the 716-line god-file into 5 focused files: - PaneTreeView.swift — PaneTreeView + PaneSplitView (recursive tree rendering) - PaneDividerView.swift — draggable divider between panes - PaneLeafView.swift — single leaf pane with editor, git, and tab management - PaneDropZone.swift — drop zone enum, overlay, preference key, drop delegate - PaneFocusDetector.swift — NSViewRepresentable focus detection via event monitor Private types promoted to internal where needed for cross-file access.
Move duplicated tab close confirmation logic from PaneLeafView and ContentView+Helpers into a shared TabCloseHelper enum. Both call sites now delegate to the helper, eliminating ~80 lines of copy-paste code.
Save and restore the PaneNode tree, per-pane tab assignments, and active pane ID in SessionState. On restore, multi-pane layouts are recreated with tabs distributed to their original panes. Falls back gracefully to single-pane restore for sessions saved before this change.
Contributor
✅ Code Coverage: 72.9%Threshold: 70% Coverage is above the minimum threshold. Generated by CI — see job summary for detailed file-level breakdown. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Split panes driven by drag & drop — builds on #645 with all review blockers resolved:
TabCloseHelpereliminates ~80 lines of copy-paste between PaneLeafView and ContentViewCloses #543
Test plan