mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00

This is a major rework of how we represent, handle, and render splits in the macOS app. This new PR moves the split structure into a dedicated, generic (non-Ghostty-specific) value-type called `SplitTree<V>`. All logic associated with splits (new split, close split, move split, etc.) is now handled by notifications on `BaseTerminalController`. The view hierarchy is still SwiftUI but it has no logic associated with it anymore and purely renders a static tree of splits. Previously, the split hierarchy was owned by AppKit in a type called `SplitNode` (a recursive class that contained the tree structure). All logic around creating, zooming, etc. splits was handled by notification listeners directly within the SwiftUI hierarchy. SwiftUI managed a significant amount of state and we heavily used bindings, publishers, and more. The reasoning for this is mostly historical: splits date back to when Ghostty tried to go all-in on SwiftUI. Since then, we've taken a more balanced approach of SwiftUI for views and AppKit for data and business logic, and this has proven a lot more maintainable. ## Spatial Navigation Previously, focus moving was handled by traversing the tree structure. This led to some awkward behaviors. See: https://github.com/ghostty-org/ghostty/issues/524#issuecomment-2668396095 In this PR, we now handle focus moving spatially. This means that move "left" means moving to the visually left split (from the top-left corner, a future improvement would be to do it from the cursor position). Concretely, given the following split structure: ``` +----------+-----+ | | b | | | | | a +-----+ | | | | | | | | | | | | |----------| d | | c | | | | | +----------+-----+ ``` Moving "right" from `c` now moves to `d`. Previously, it would go to `b`. On Linux, it still goes to `b`. ## Value Types One of the major architectural shifts is moving **purely to immutable value types.** Whenever a split property changes such as a new split, the ratio between splits, zoomed state, etc. we _create an entirely new `SplitTree` value_ and replace it along the entire view hierarchy. This is in some ways wasteful, but split hierarchies are relatively small (even the largest I've seen in practical use are dozens of splits, which is small for a computer). And using value types lets us get rid of a ton of change notification soup around the SwiftUI hierarchy. We can rely on reference counting to properly clean up our closed views. > [!NOTE] > > As an aside, I think value types are going to make it a lot easier in the future to implement features like "undo close." We can just keep a trailing list of surface tree states and just restore them. This PR doesn't do anything like that, but it's now possible. ## SwiftUI Simplicity Our SwiftUI view hierarchy is dramatically simplified. See the difference in `TerminalSplitTreeView` (new) vs `TerminalSplit` (old). There's so much less logic in our new views (almost none!). All of it is in the AppKit layer which is just way nicer. ## AI Notes This PR was heavily written by AI. I reviewed every line of code that was rewritten, and I did manually rewrite at every step of the way in minor ways. But it was very much written in concert. Each commit usually started as an AI agent writing the whole commit, then nudging to get cleaned up in the right way. One thing I found in this task was that until the last commit, I kept the entire previous implementation around and compiling. The agent having access to a previous working version of code during a refactor made the code it produced as follow up in the new architecture significantly better, despite the new architecture having major fundamental differences in how it works!