112 Commits

Author SHA1 Message Date
Mitchell Hashimoto
49cc88f0d3 macos: configurable undo timeout 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
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
Francisco Giordano
9008e21637 fix: exit non-native fullscreen on close 2025-06-05 07:25:53 -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
aef61661a0 macos: fix up command palette, focusing 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
e2a0f439c6 macOS: save/restore firstResponder on non-native fullscreen
Fixes #6999

It appears that at some point one of the operations causes focus to move
away for non-native fullscreen. We previously relied on the delegate
method to restore this but a better approach appears to handle this
directly in the fullscreen implementations. This fixes the linked issue.

I still think long term all the `Ghostty.moveFocus` stuff is a code
smell and we should be auditing all that code to see if we can
eliminate it. But this is a step in the right direction, and removes one
of those uses.
2025-05-06 12:59:31 -07:00
Mitchell Hashimoto
9b78917246 macOS: handle scenario cgWindowId is nil
Fixes #7114
Supercedes #7271

This fixes a crash that could occur with non-native fullscreen and
`fullscreen = true` set at once.

The "windowNumber" can be `<= 0` if the window "doesn't have a
window device." I don't fully know all the scenarios this is true but it
is true when the window is not visible, at least.
2025-05-06 10:33:56 -07:00
Aaron Ruan
3827ce9e4c feat: beautify command palette 2025-04-24 12:46:38 +08:00
Friedrich Stoltzfus
f58fba54a0 macOS: add descriptions for PgUp, PgDown, End, and Home keys
These keys were not being assigned a description in the KeyboardShortcut
extension. This caused problems for the macOS command pallete.
2025-04-23 16:05:51 -04:00
Mitchell Hashimoto
e33eed0216 macOS: command palette visual tweaks 2025-04-21 10:40:30 -07:00
Mitchell Hashimoto
5fab6faf04 macOS: hook up command palette C API to actual command palette 2025-04-21 08:32:42 -07:00
Mitchell Hashimoto
b05826ac9d macOS: use KeyboardShortcut rather than homegrown KeyEquivalent
This replaces the use of our custom `Ghostty.KeyEquivalent` with
the SwiftUI `KeyboardShortcut` type. This is a more standard way to
represent keyboard shortcuts and lets us more tightly integrate with
SwiftUI/AppKit when necessary over our custom type.

Note that not all Ghostty triggers can be represented as
KeyboardShortcut values because macOS itself does not support
binding keys such as function keys (e.g. F1-F12) to KeyboardShortcuts.

This isn't an issue since all input also passes through a lower level
libghostty API which can handle all key events, we just can't show these
keyboard shortcuts on things like the menu bar. This was already true
before this commit.
2025-04-19 14:39:48 -07:00
Mitchell Hashimoto
b77c5634f0 macos: quick terminal uses padded notch mode if notch is visible
Fixes #6612
2025-04-15 08:52:20 -07:00
Mitchell Hashimoto
d1c15dbf07 macOS: quick terminal should retain menu if not frontmost
This is a bug I noticed in the following scenario:

  1. Open Ghostty
  2. Fullscreen normal terminal window (native fullscreen)
  3. Open quick terminal
  4. Move spaces, QT follows
  5. Fullscreen the quick terminal

The result was that the menu bar would not disappear since our app is
not frontmost but we set the fullscreen frame such that we expected it.
2025-04-14 11:17:11 -07:00
Mitchell Hashimoto
453e6590e8 macOS: non-native fullscreen should not hide menu on fullscreen space
Fixes #7075

We have to use private APIs for this, I couldn't find a reliable way
otherwise.
2025-04-14 10:38:54 -07:00
Mitchell Hashimoto
ac7aa757bd macos: add padded-notch option for macos-non-native-fullscreen
Finishes #378
Supercedes #4159

This adds a new enum value for `macos-non-native-fullscreen`:
`padded-notch`. This value will add padding to the top of the window to
account for the notch on applicable devices while still hiding the
menu.

This value is preferred over "visible-menu" by some people because for
screens without a notch, the window will take up the full height.

The plan in the future is that we may color the padded area when a notch
is present. In this commit it appears as transparent.
2025-02-13 20:27:42 -08:00
Bryan Lee
31273aaabc Remember last focused window position for next startup 2025-02-11 12:33:29 -08:00
Mitchell Hashimoto
a58b1998a9 macos: hide dock globally if the dock conflicts
Related to #5361

The fix in 5361 wasn't sufficient since it only applied if our app was
in the foreground. Our quick terminal is a non-activating NSPanel to
allow it to work on any space (fullscreen included). This means that
Ghostty doesn't become the active app when the quick terminal is shown
and another app is in the foreground.

To work around this, we now hide the dock globally when the quick
terminal is shown AND the dock is in a conflicting position. We restore
this state when the quick terminal is hidden, loses focus, or Ghostty is
quit.
2025-01-24 20:18:44 -08:00
Mitchell Hashimoto
a5a73f8352 macos: autohide dock if quick terminal would conflict with it
Fixes #5328

The dock sits above the level of the quick terminal, and the quick
terminal frame typical includes the dock. Hence, if the dock is visible
and the quick terminal would conflict with it, then part of the terminal
is obscured.

This commit makes the dock autohide if the quick terminal would conflict
with it. The autohide is disabled when the quick terminal is closed.

We can't set our window level above the dock, as this would prevent
things such as input methods from rendering properly in the quick
terminal window.

iTerm2 (the only other macOS terminal I know of that supports a dropdown
mode) frames the terminal around the dock. I think this looks less
aesthetically pleasing and I prefer autohiding the dock instead.

We can introduce a setting to change this behavior if desired later.

Additionally, this commit introduces a mechanism to safely set
app-global presentation options from multiple sources without stepping
on each other.
2025-01-24 14:51:17 -08:00
Alexandre Antonio Juca
39bb949973 fix: Ensure file paths derived from pasteboard operations are properly escaped 2025-01-14 00:18:28 +01:00
Bryan Lee
08314d414f Preserve full URL when pasting from clipboard 2025-01-14 00:48:56 +08:00
Mitchell Hashimoto
0811b1d5ac macos: paste multiple files separated by space
https://github.com/ghostty-org/ghostty/discussions/4892#discussioncomment-11808631
2025-01-11 13:58:09 -08:00
Mitchell Hashimoto
200aee9acf macos: rework toggle_visibility to better match iTerm2
Two major changes:

1. Hiding uses `NSApp.hide` which hides all windows, preserves tabs, and
   yields focus to the next app.

2. Unhiding manually tracks and brings forward only the windows we hid.
   Proper focus should be retained.
2025-01-10 14:40:02 -08:00
Wes Campaigne
e86b9a112e Implement "Paste Selection" on macOS like Terminal.app 2025-01-08 09:26:13 -08:00
Mitchell Hashimoto
4ffd281de3 macos: detect IME input source change as part of keyDown event
Fixes #4539

AquaSKK is a Japanese IME (Input Method Editor) for macOS. It uses
keyboard inputs to switch between input modes. I don't know any other
IMEs that do this, but it's possible that there are others. Prior to
this change, the keyboard inputs to switch between input modes were
being sent to the terminal, resulting in erroneous characters being
written.

This change adds a check during keyDown events to see if the input
source changed _during the event_. If it did, we assume an IME captured
it and we don't pass the event to the terminal.

This makes AquaSKK functional in Ghostty.
2025-01-04 21:45:25 -08:00
Mitchell Hashimoto
9d6b9d78d8 macos: set NSAppearance on windowDidLoad
Fixes #3072

Previously, when `window-theme = auto`, the appearance was delayed
enough on the DispatchQueue that the window was already visible. This
would result in the window appearing with the wrong appearance before
switching to the correct one.

For annoying reasons, we can't set the NSApplication.shared.appearance
in `applicationDidFinishLaunching` because it results in a deadlock with
AppKit.

This commit moves to set the `NSWindow.appearance` in `windowDidLoad`
(and any config sync) to ensure that the appearance is set before the
window is visible.

This is probably the right solution anyways because this allows windows
with different background colors to each have their own distinct
appearance.
2024-12-22 19:33:01 -08:00
Mitchell Hashimoto
abf713feec macos: support the macos-icon configurations 2024-12-21 19:31:30 -08:00
Mitchell Hashimoto
29929a473d config: macos-icon configurations 2024-12-21 19:31:30 -08:00
Mitchell Hashimoto
c4bec781b0 macos: colorized Ghostty icon begins! 2024-12-21 19:31:30 -08:00
Mitchell Hashimoto
63f77f0c9f macos: fix unwanted resize in non native fullscreen (#2901)
Fixes #2516 

Those changes mean that when we have one ghostty window in non-native
fullscreen and another ghostty window not in fullscreen switching to not
fullscreen window won't cause appearing menu bar and dock. I think it
looks good:
![Screenshot 2024-12-07 at 19 18
33](https://github.com/user-attachments/assets/125eb18f-3b2f-469a-a5ba-0469dd12462a)

If we implement detection and make menu bar and dock appear for not
fullscreen window in this case it will cause the fullscreen window to
change its size and will look bad.

Non-native fullscreen works bad with multiple screens in either way.
E.g. switching to a non-native fullscreen window would cause menubar
disappering on another screen or switching to not fullscreen window
would show menu bar over fullscreen window on another screen. I think
nobody would want non-native fullscreen with multiple screens.
2024-12-11 08:52:25 -08:00
Dmitry Zhlobo
49f105cd27 macos: make non-native fullscreen windows not resizeable 2024-12-08 16:34:44 -08:00
Dmitry Zhlobo
080afce649 found a better explanation for the reason to hide dock before menu 2024-12-08 13:09:37 +01:00
Dmitry Zhlobo
0d0aeccf0f fix unwanted resize of non-native fullscreen window
Removing autoHideDock and autoHideMenuBar options cause window to
resize.

Fix #2516
2024-12-08 13:09:37 +01:00
Mitchell Hashimoto
f384fd038b macos: trigger fullscreenDidChange on any fullscreen event
Fixes #2840
Related to #2842

This builds on #2842 by missing a key situation: when native fullscreen
is toggled using the menu bar items it doesn't go through our
`FullscreenStyle` machinery so we don't trigger fullscreen change
events.

This commit makes it so that our FullscreenStyle always listens for
native fullscreen change (even in non-native modes) to fire a fullscreen
did change event. This way we can always rely on the event to be fired
when fullscreen changes no matter what.
2024-12-01 11:37:04 -08:00
Mitchell Hashimoto
ddde68d1f4 macos: add NSAppearance.isDark 2024-11-22 10:31:31 -08:00
Mitchell Hashimoto
a733ea381b macos: OSColor.hexString must be able to compile with UIKit 2024-11-21 14:50:03 -08:00
Mitchell Hashimoto
1aa77837eb macos: use correct title background if top surface 2024-11-21 14:20:31 -08:00
Mitchell Hashimoto
70acb0d76e macos: clamp window size to screen size on screen parameter changes
Fixes #2462

This sets up a listener for screen parameter changes. This only triggers
when a screen is added, removed, or a parameter such as its resolution
changes. This doesn't trigger when a window is simply moved from one
screen to another.

On parameter change, we ensure that the window is within the bounds of
the screen. As an exception, if the window was previously already
outside the bounds of the screen, we don't move it back in.
2024-10-19 10:13:16 -07:00
Mitchell Hashimoto
eec77e271c macos: change our minimum version to macOS 13
macOS 12 is officially EOL by Apple and the project only supports
officially supported versions of macOS. Once publicly released, users on
older macOS versions will have to use older released builds.
2024-10-09 14:41:57 -07:00
Mitchell Hashimoto
1ae9322959 macos: non-native fs keeps track of screen number for change screen comp
Fixes #2370

Comparing NSScreens directly was fragile. It appears that AppKit/Cocoa
can return different instances of NSScreen for the same screen for
unknown reasons between calls to windowDidChangeScreen. I don't fully
understand why this happens.

In any case, our comparison was not safe. Instad, we now keep track of
of the CGDirectDisplayID for each screen and compare those instead.
2024-10-05 06:21:30 -10:00
Mitchell Hashimoto
22b9f24978 macos: clarify comments, clean up notifications for fullscreen 2024-09-30 20:10:23 -07:00
Mitchell Hashimoto
639434df33 macos: exit native fullscreen if we try to enter non-native 2024-09-30 15:01:44 -07:00
Mitchell Hashimoto
93f0b9dce9 macos: only exit on screen change if the screen really changed 2024-09-30 14:48:05 -07:00
Mitchell Hashimoto
ef5b835dc4 macos: only hide dock on the screen with the dock 2024-09-30 14:25:33 -07:00
Mitchell Hashimoto
408c33e174 macos: handle non-native fullscreen changing screens 2024-09-30 14:14:01 -07:00
Mitchell Hashimoto
35462331ae macos: non-native fs sets frame async to account for style changes
Fixes #1996
2024-09-30 10:44:26 -07:00