1198 Commits

Author SHA1 Message Date
Mitchell Hashimoto
636b1fff8a macos: initial window shouldn't support undo 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
d2d3852026 macos: remove debug log 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
49cc88f0d3 macos: configurable undo timeout 2025-06-07 12:46:14 -07:00
Mitchell Hashimoto
3e02c0cbd5 macos: fix an incorrect bindable write during view update 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
Mitchell Hashimoto
41ee578b7a macos: quick terminal restores previous size when exiting final surface
This fixes a regression from the new split work last week, but it was
also probably an issue before that in a slightly different way.

With the new split work, the quick terminal was becoming unusable when
the final surface explicitly `exit`-ed, because AppKit/SwiftUI would
resize the window to a very small size and you couldn't see the new
terminal on the next toggle.

Prior to this, I think the quick terminal would've reverted to its
original size but I'm not sure (even if the user resized it manually).

This commit saves the size of the quick terminal at the point all
surfaces are exited and restores it when the quick terminal is shown the
next time with a new initial surface.
2025-06-07 12:37:44 -07:00
Mitchell Hashimoto
269d29624b Add bell feature flags for audio, attention, and title actions on macOS (#7533)
Resolve #7526
2025-06-06 12:59:23 -07:00
Aaron Ruan
5f6a15abef Add bell feature flags for audio, attention, and title actions on macOS
Signed-off-by: Aaron Ruan <i@ar212.com>
2025-06-06 23:54:34 +08:00
Mitchell Hashimoto
70f030e3c2 macos: dismiss notifications on focus, application exit
I've only recently been using programs that use user notifications heavily
and this commit addresses a number of annoyances I've encountered.

  1. Notifications dispatched while the source terminal surface is
     focused are now only shown for a short time (3 seconds hardcoded)
     and then automatically dismiss.

  2. Notifications are dismissed when the target surface becomes focused
     from an unfocused state. This dismissal happens immediately (no
     delay).

  3. Notifications are dismissed when the application exits.

  4. This fixes a bug where notification callbacks were modifying view
     state, but the notification center doesn't guarantee that the
     callback is called on the main thread. We now ensure that
     the callback is always called on the main thread.
2025-06-06 07:28:32 -07:00
Mitchell Hashimoto
08101b0bc5 macos: fix hasWindowButtons logic (#7504 follow-up) (#7528)
Took another look through #7504 after the merge and realized that the
logic behind the `hasWindowButtons` property wasn't quite sound. It
would return `false` if either _at least one_ button were missing in the
`standardWindowButton(.theButton) == nil` sense, or if _all_ buttons
were hidden in the `isHidden` sense.

With this PR, the logic is rectified: `false` if _all_ buttons are
missing or hidden in any sense, otherwise `true`.

In practice, I suppose Ghostty won't ever instantiate a `TerminalWindow`
where `standardWindowButton(.theButton) == nil`, but might as well get
it right and sleep better at night.
2025-06-05 14:11:10 -07:00
Daniel Wennberg
c2c267439b macos: fix hasWindowButtons logic 2025-06-05 13:48:53 -07:00
Mitchell Hashimoto
045c84acb7 macos: split directional navigation should use distance to leaf
Fixes regression from #7523

I messed two things up around spatial navigation in the split tree
that this commit fixes:

  1. The distance in the spatial tree only used a single dimension
     that we were navigating. This commit now uses 2D euclidean
     distance from the top-left corners of nodes. This handles the case
     where the nodes are directly above or below each other better.

  2. The spatial slots include split containers because they are layout
     elements. But we should only navigate to leaf nodes. This was
     causing the wrong navigatin to happen in some scenarios.
2025-06-05 13:43:07 -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
6db455eee5 fix: exit non-native fullscreen on close (#7525)
Fixes https://github.com/ghostty-org/ghostty/discussions/7159.
2025-06-05 07:36:50 -07:00
Mitchell Hashimoto
d4249679e3 macos: simplify some ServiceProvider code (#7508)
First, remove the always-inlined openTerminalFromPasteboard code and
combine it with openTerminal. Now that we're doing a bit of work inside
openTerminal, there's little better to having an intermediate, inlined
function.

Second, combine some type-casting operations (saving a .map() call).

Lastly, adjust some variable names because a generic `objs` or `urls`
was a little ambiguous now that we're all in one function scope.
2025-06-05 07:30:03 -07:00
Francisco Giordano
9008e21637 fix: exit non-native fullscreen on close 2025-06-05 07:25:53 -07:00
Mitchell Hashimoto
c40ac6b785 input: add focus split directional commands to command palette 2025-06-05 07:11:18 -07:00
Mitchell Hashimoto
1966dfdef7 macos: moving some files around 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
f8e3539b7d macos: remove the unused resizeEvent code from SplitView 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
01fa87f2ab macos: fix iOS builds 2025-06-05 07:05:13 -07:00
Mitchell Hashimoto
9474092f77 macos: remove the old split implementation 2025-06-05 07:05:13 -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
Mitchell Hashimoto
aef61661a0 macos: fix up command palette, focusing 2025-06-05 07:05:12 -07:00
Mitchell Hashimoto
7dcfebcd5d macos: isSplit guarding on focus split directions works 2025-06-05 07:05:12 -07:00
Mitchell Hashimoto
0fb58298a7 macos: focus split previous/next 2025-06-05 07:05:11 -07:00
Mitchell Hashimoto
b84b715ddb macos: unify confirm close in our terminal controllers 2025-06-05 07:05:11 -07:00
Mitchell Hashimoto
d1dce1e372 macos: restoration for new split tree 2025-06-05 07:05:11 -07:00
Mitchell Hashimoto
33d94521ea macos: setup sequence for SplitTree 2025-06-05 07:05:11 -07:00
Mitchell Hashimoto
672d276276 macos: confirm close on split close 2025-06-05 07:05:11 -07:00