356 Commits

Author SHA1 Message Date
Mitchell Hashimoto
658ec2eb6f macos: add reset zoom to all window titles 2025-06-12 14:33:53 -07:00
Mitchell Hashimoto
70029bf82a macos: tahoe terminal tabs shows title 2025-06-12 13:39:19 -07:00
Mitchell Hashimoto
5877913ab8 macoS: Split out terminal tabs for ventura vs tahoe 2025-06-12 12:06:30 -07:00
Mitchell Hashimoto
fd785f98bb macos: titlebar tabs uses legacy window for now 2025-06-12 11:39:10 -07:00
Mitchell Hashimoto
ccfd33022f macos: only titlebar tabs uses legacy styling now 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
e5cb33e911 typos 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
63e56d0402 macos: titlebar fonts work with new terminal window 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
a804dab288 macos: native terminal style works with new subclasses 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
dfa7a114de macos: make transparent titlebars robust against show/hide tabs 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
3595b2a847 macos: transparent titlebar handles transparent background 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
6ce7f612a6 macos: transparent titlebar needs to be rehidden when tabs change 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
7d02977482 macos: add NSView hierarchy debugging code 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
4d33a73fc4 wip: redo terminal window styling 2025-06-11 15:18:02 -07:00
Mitchell Hashimoto
3db5b3da75 macos: hidden titlebar windows should cascade on new tab
Windows with `macos-titlebar-style = hidden` create new windows when the
new tab binding is pressed. This behavior has existed for a long time.

However, these windows did not cascade, meaning they'd appear overlapped
directly on top of the previous window, which is kind of nasty.

This commit changes it so that new windows created via new tab from a
hidden titlebar window will cascade.
2025-06-10 14:31:41 -07:00
Mitchell Hashimoto
1f340b4b2d macos: for windowShouldClose, only close the tab if we have multiple
Fixes a regression from our undo/redo rework. We were accidentally
closing the entire window when the "X" button in the tab bar was
clicked.
2025-06-10 12:39:09 -07:00
Mitchell Hashimoto
2b9a6a4820 macos: unsplit window shouldn't allow split zooming
This was always the case, and is a recent regression from the SplitTree
rework. This brings it back to the previous behavior.
2025-06-10 12:11:18 -07:00
Mitchell Hashimoto
26e1dd8f8e macos: clear out the surface trees to prevent repeat undo
see the comment
2025-06-08 12:41:45 -07:00
Mitchell Hashimoto
3de3f48faf macos: fix undo/redo for closing windows with multiple tabs
When closing a window that contains multiple tabs, the undo operation
now properly restores all tabs as a single tabbed window rather than
just restoring the active tab.

The implementation:
- Collects undo states from all windows in the tab group before closing
- Sorts them by their original tab index to preserve order
- Clears tab group references to avoid referencing garbage collected objects
- Restores all windows and re-adds them as tabs to the first window
- Tracks and restores which tab was focused (or focuses the last tab if none were)

AI prompts that generated this commit are below.

Each separate prompt is separated by a blank line, so this session was
made up with many prompts in a back-and-forth conversation.

> We need to update the undo/redo implementation in
> @macos/Sources/Features/Terminal/TerminalController.swift `closeWindowImmediately`
> to handle the case that multiple windows in a tab group are closed all at once,
> and to restore them as a tabbed window. To do this, I think we should collect
> all the `undoStates`, sort them by `tabIndex` (null at the end), and then on j
> restore, restore them one at a time but add them back to the same tabGroup. We
> can't use the tab group in the `undoState` because it will be garbage collected
> by then. To be sure, we should just set it to nil.

I should note at this point that the feature already worked, but the
code quality and organization wasn't up to my standards. If someone
using AI were just trying to make something work, they might be done at
this point.

I do think this is the biggest gap I worry about with AI-assisted
development: bridging between the "it works" stage at a junior quality
and the "it works and is maintainable" stage at a senior quality. I
suspect this will be a balance of LLMs getting better but also senior
code reviewers remaining highly involved in the process.

> Let's extract all the work you just did into a dedicated private method
> called `registerUndoForCloseWindow`

Manual: made some tweaks to comments, moved some lines around, didn’t change
any logic.

> I think we can pull the tabIndex directly from the undoState instead of
> storing it in a tuple.

> Instead of `var undoStates`, I think we can create a `let undoStates` and
> build and filter and sort them all in a chain of functional mappings.

> Okay, looking at your logic for restoration, the `var firstController` and
> conditionals are littly messy. Can you make your own pass at cleaning those
> up and I'll review and provide more specific guidance after.

> Excellent. Perfect. The last thing we're missing is restoring the proper
> focused window of the tab group. We should store that and make sure the
> proper window is made key. If no windows were key, then we should make the
> last one key.

> Excellent. Any more cleanups or comments you'd recommend in the places you
> changed?

Notes on the last one: it gave me a bunch of suggestions, I rejected most but
did accept some.

> Can you write me a commit message summarizing the changes?

It wrote me a part of the commit message you're reading now, but I
always manually tweak the commit message and add my own flair.
2025-06-08 08:08:01 -07:00
Mitchell Hashimoto
ec043e1386 macos: red traffic light should be undoable 2025-06-08 07:00:51 -07:00
Mitchell Hashimoto
20744f0482 macos: fix some CI build issues 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
973a2afdde macos: make sure we're not registering unnecessary undos 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
aeede903f5 macos: undo close all windows 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
d92db73f25 macos: undo new tab 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
797c10af37 macos: undo new window 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
33d128bcff macos: remove TerminalManager
All logic related to TerminalController is now in TerminalController.
2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
5507ec0fc0 macos: compile errors in CI 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
49cc88f0d3 macos: configurable undo timeout 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
b044f4864a add undo/redo keybindings, default them on macOS 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
e1847da139 macos: more robust undo tab that goes back to the same position 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
5f74445b14 macos: basic undo tab, not quite working 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
104cc2adfe macos: basic undo close window, not very robust yet 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
f571519157 macos: setup undo responders at the AppDelegate level 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
6d32b01c64 macos: implement a custom ExpiringUndoManager, setup undo for new/close 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
493b1f5350 wip: undo 2025-06-07 12:46:13 -07:00
Daniel Wennberg
c2c267439b macos: fix hasWindowButtons logic 2025-06-05 13:48:53 -07:00
Mitchell Hashimoto
efc1ceab5d macOS: New value-based split tree implementation, move split logic out of SwiftUI into AppKit (#7523)
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!
2025-06-05 12:59:43 -07:00
Mitchell Hashimoto
a2a3863ad2 macOS: Add option to hide window buttons (#7504)
Conversion of #7497 to a PR. This implements a feature requested in
#7331: an option to hide the default window buttons on macOS for a
cleaner aesthetic.

~~Builds on #7502 as it requires the same change to avoid the main
toolbar title showing on top of the tab bar.~~ EDIT: rebased on main now
that #7502 was merged.

I aligned the scope of the new option with `macos-titlebar-style`, since
they both customize titlebar elements. This means it has the same edge
case quirks: For example, if you change the setting, reload the config,
and then open a new tab, the appearance of the current window will
depend on which tab is in the foreground. I did it this way because
`macos-titlebar-style` provided an easy template for which derived
configs and functions to modify. Let me know if you want me to try
adjusting this so that a change in the setting also takes effect for
current windows/tabs, which I _think_ should be possible.

Screenshots:
* `macos-titlebar-style = transparent` (default)
![Screenshot 2025-06-01 at 18 04
56](https://github.com/user-attachments/assets/01fa3953-d2ef-4c39-a6e3-f236488dd841)
![Screenshot 2025-06-01 at 18 07
24](https://github.com/user-attachments/assets/cd463ded-a0b2-4f69-9abe-384e7eecaa27)
* `macos-titlebar-style = tabs`
![Screenshot 2025-06-01 at 17 56
35](https://github.com/user-attachments/assets/bf99d046-cdbb-4e5d-b1c5-d51bbba79007)
![Screenshot 2025-06-01 at 17 56
48](https://github.com/user-attachments/assets/098164b8-bf97-4df1-9dff-c1c17e12665d)
2025-06-05 07:46:57 -07:00
Mitchell Hashimoto
c40ac6b785 input: add focus split directional commands to command palette 2025-06-05 07:11:18 -07:00
Mitchell Hashimoto
69c3c359cb macos: resize split keybind handling 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
5299f10e13 macos: unzoom on new split and focus change 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
19a9156ae1 macos: address remaining todos 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
6c97e4a59a macos: fix focus after closing splits 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
77458ef308 macos: rename surfaceTree2 to surfaceTree 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
f1ed07caf4 macos: Remove the legacy SurfaceTree 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
22819f8a29 macos: transfer doesBorderTop 2025-06-05 07:05:12 -07:00
Mitchell Hashimoto
8b979d6dce macos: handle surfaceTreeDidChange 2025-06-05 07:05:12 -07:00
Mitchell Hashimoto
ea1ff438f8 macos: handle split zooming 2025-06-05 07:05:12 -07:00
Mitchell Hashimoto
b7c01b5b4a macos: spatial focus navigation 2025-06-05 07:05:12 -07:00
Mitchell Hashimoto
ec7fd94d0f macos: equalize splits works with new tree 2025-06-05 07:05:12 -07:00
Mitchell Hashimoto
a389926ca7 macos: use surfaceTree2 needsConfirmQuit 2025-06-05 07:05:12 -07:00