6661 Commits

Author SHA1 Message Date
Mitchell Hashimoto
804d270ba1 macOS: Undo/Redo for changes to windows, tabs, and splits (#7535)
This PR implements the ability to undo/redo new and closed windows,
tabs, and splits.

## Demo


https://github.com/user-attachments/assets/98601810-71b8-4adb-bfa4-bdfaa2526dc6

## Details

### Undo Timeout

Running terminal sessions _remain running_ for a configurable period of
time after close, during which time they're undoable. This is similar to
"email unsend" (since email in the traditional sense can't be unsent,
clients simply hold onto it for a period of time before sending).

This behavior is not unique to Ghostty. The first and only place I've
seen it is in iTerm2. And iTerm2 behaves similarly, although details of
our behavior and our implementation vary greatly.

The configurable period of time is done via the `undo-timeout`
configuration. The default value is 5 seconds. This feels reasonable to
be and is grounded in being the default for iTerm2 as well, so it's
probably a safe choice.

Undo can be disabled by setting `undo-timeout = 0`. 

### Future

The actions that can be potentially undone/redone can be easily expanded
in the future. Some thoughts on things that make sense to me:

- Any sort of split resizing, including equalization
- Moving tabs or splits

#### What about Linux?

I'd love to support this on Linux. I don't think any other terminal on
Linux has this kind of feature (definitely might be wrong, but I've
never seen it and I've looked at a lot of terminal emulators 😄 ). But
there's some work to be done to get there.

## TODO for the Draft PR

This is still a draft. There are some items remaining (list will update
as I go):

- [x] Undoing a closed window is sometimes buggy still and I'm not sure
why, I have to dig into this.
- [x] New window should be undoable
- [x] New tab should be undoable
- [x] Close All Windows should be undoable
- [x] I think I have to get rid of TerminalManager. Undone windows won't
be in TerminalManager's list of controllers and I think that's going to
break a lot of things.
- [x] I haven't tested this with QuickTerminal at all. I expect bugs
there but I want undo to work with splits there.
- [x] Close window with the red traffic light button doesn't trigger
undo
- [x] Closing window with multiple tabs undoes them as separate windows
2025-06-08 12:54:55 -07:00
Kristófer R
6ed94b0034 move mac address length constant to file-level scope 2025-06-07 22:17:01 -04:00
Kristófer R
73e5f7e5d6 merge std.Uri.ParseError and os/hostname error sets 2025-06-07 22:12:26 -04:00
Kristófer R
e4a175d24a use explicit error set 2025-06-07 22:07:18 -04:00
Kristófer R
7760389ab8 add comptime check for platform
we only need the mac-address-as-hostname workaround on macos, so we
now have a comptime check to see if we're on macos.
2025-06-07 22:07:18 -04:00
Kristófer R
68f48b9911 name the 17 magic constant mac_address_length 2025-06-07 22:07:18 -04:00
Kristófer R
dfdb588f58 add tests for hostnames without a path component 2025-06-07 22:07:18 -04:00
Kristófer R
a24d0c9faf re-order end-of-hostname validity check 2025-06-07 22:07:18 -04:00
Kristófer R
bb07e9c026 don't rely on hard-coded schemes 2025-06-07 22:07:18 -04:00
Kristófer R
7a639a7119 use iterator syntax in for loop 2025-06-07 22:07:18 -04:00
Kristófer R
e0655a7f75 Move url parsing helper to os/hostname
Also adds a test to verify that the function is working as intended.
2025-06-07 22:07:18 -04:00
Kristófer R
ffe7f0d8bf extract url parsing into its own function 2025-06-07 22:07:18 -04:00
Kristófer R
64bfaf23f9 take kitty-shell-cwd scheme into account 2025-06-07 22:07:17 -04:00
Kristófer R
b66368b4d6 extract mac address validity check to function 2025-06-07 22:07:17 -04:00
Kristófer R
19ca1bfb1c Fix modulo operation and custom Uri struct init 2025-06-07 22:07:17 -04:00
Kristófer R
0e74b8027a pwd: fix hostname resolution on macos
When macOS's "Private WiFi address" feature is enabled it'll change the
hostname to a mac address. Mac addresses look like URIs with a hostname
and port component, e.g. 12:34:56:78:90:12 where `:12` looks like port
12. However, mac addresses can also contain letters a through f, so a
valid mac address like ab💿ef🆎cd:ef is valid, but will not be parsed
as a URI, because `:ef` is not a valid port.

This commit attempts to fix that by checking if the hostname is a valid
mac address when `std.Uri.parse()` fails and constructing a new std.Uri
struct using that information.

It's not perfect, but is equally compliant with the URI spec as std.Uri
currently is.
2025-06-07 22:07:17 -04:00
Mitchell Hashimoto
e986beb6a7 input: parse binds containing equal signs correctly (#7544)
Since the W3C rewrite we're able to specify codepoints like `+` directly
in the config format who otherwise have special meanings. Turns out we
forgot to do the same for `=`.
2025-06-07 16:30:01 -07:00
Leah Amelia Chen
ba15da4722 input: parse binds containing equal signs correctly
Since the W3C rewrite we're able to specify codepoints like `+` directly
in the config format who otherwise have special meanings. Turns out we
forgot to do the same for `=`.
2025-06-08 01:12:17 +02:00
Leah Amelia Chen
990d1cdf37 gtk/CommandPalette: prevent leaks on initialization (#7541)
* Deallocate the builder after use
* Don't hold a reference to `Command` after appending to `GListStore`
2025-06-07 23:15:16 +02:00
Mitchell Hashimoto
6e77a5a6ca macos: address quick terminal basic functionality with new API 2025-06-07 13:07:31 -07:00
Mitchell Hashimoto
396e53244d config: add super+shift+t as a default undo too to mimic browsers 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
3b77a16b63 Make undo/redo app-targeted so it works with no windows 2025-06-07 12:46:15 -07:00
Mitchell Hashimoto
966c4f98c7 apprt/glfw,gtk: noop undo/redo actions 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
b044f4864a add undo/redo keybindings, default them on macOS 2025-06-07 12:46:14 -07:00
Leorize
53c2874667 flatpak: free GError after use 2025-06-07 14:34:55 -05:00
Leorize
42bafe9d59 flatpak: detach process tracking thread after spawn
This makes sure the underlying thread implementation know to free
resources the moment the thread is no longer necessary, preventing leaks
from not manually collecting the thread.
2025-06-07 14:34:39 -05:00
Leorize
ea0766e62b gtk/CommandPalette: prevent leaks on initialization
* Deallocate the builder after use
* Don't hold a reference to `Command` after appending to `GListStore`
2025-06-07 12:00:51 -05:00
Mitchell Hashimoto
aab00da242 terminal: fix crash when reflowing grapheme with a spacer head
Fixes #7536

When we're reflowing a row and we need to insert a spacer head, we must
move to the next row to insert it. Previously, we were setting a spacer
head and then copying data into that spacer head, which could lead to
corrupt data and an eventual crash.

In debug builds this triggers assertion failures but in release builds
this would lead to silent corruption and a crash later on.

The unit test shows the issue clearly but effectively you need a
multi-codepoint grapheme such as `👨‍👨‍👦‍👦` to wrap across a row by changing
the columns.
2025-06-06 20:37:12 -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
5edf0dffda gtk/TabView: do not closeTab within close-page signal handler (#7515)
`TabView` assumes to be the sole owner of all `Tab`s within a Window. As
such, it could close the managed `Window` once all tabs are removed from
its widget.

However, during `AdwTabView::close-page` signal triggered by libadwaita,
the `Tab` to be closed will gain an another reference for the duration
of the signal, breaking `TabView.closeTab` (called via
`Tab.closeWithConfirmation`) assumptions that having no tabs meant they
are all destroyed.

This commit solves the issue by scheduling `Tab.closeWithConfirmation`
to be run after `AdwTabView::close-page` signal has finished processing.

Fixes #7426
2025-06-05 07:36:10 -07:00
Mitchell Hashimoto
2e0a23aa77 gtk: make requesting attention configurable (#7521)
Partially fixes #7520
2025-06-05 07:29:17 -07:00
Mitchell Hashimoto
c40ac6b785 input: add focus split directional commands to command palette 2025-06-05 07:11:18 -07:00
Leah Amelia Chen
77479feee6 gtk: make requesting attention configurable 2025-06-05 00:29:49 +02:00
Mitchell Hashimoto
f383d7b550 core: document keybind actions better (#7522)
The current documentation for actions are very sparse and would leave
someone (even including contributors) as to what exactly they do. On top
of that there are many stylistic and grammatical problems that are
simply no longer in line with our current standards, and certainly not
on par with our configuration options reference.

Hence, I've taken it upon myself to add, clarify, supplement, edit and
even rewrite the documentation for most of these actions, in a wider
effort of trying to offer better, clearer documentation for our users.
2025-06-04 09:04:16 -07:00
Leah Amelia Chen
2c8d6ba944 core: document keybind actions better
The current documentation for actions are very sparse and would leave
someone (even including contributors) as to what exactly they do.
On top of that there are many stylistic and grammatical problems that are
simply no longer in line with our current standards, and certainly not
on par with our configuration options reference.

Hence, I've taken it upon myself to add, clarify, supplement, edit and
even rewrite the documentation for most of these actions, in a wider
effort of trying to offer better, clearer documentation for our users.
2025-06-04 17:04:52 +02:00
Leorize
4e39144d39 gtk/TabView: do not closeTab within close-page signal handler
`TabView` assumes to be the sole owner of all `Tab`s within a Window.
As such, it could close the managed `Window` once all tabs are removed
from its widget.

However, during `AdwTabView::close-page` signal triggered by libadwaita,
the `Tab` to be closed will gain an another reference for the duration
of the signal, breaking `TabView.closeTab` (called via
`Tab.closeWithConfirmation`) assumptions that having no tabs meant they
are all destroyed.

This commit solves the issue by scheduling `Tab.closeWithConfirmation`
to be run after `AdwTabView::close-page` signal has finished processing.
2025-06-03 03:19:13 -05:00
Leorize
58cece07f0 gtk/GlobalShortcuts: don't request session with no shortcuts
There aren't any reason to pay the D-Bus tax if you don't use global
shortcuts.
2025-06-02 20:55:04 -05:00
Daniel Wennberg
232a46d2dc Add option to hide macOS traffic lights 2025-06-02 09:22:01 -07:00
Mitchell Hashimoto
5306e7cf56 config: add launched-from to specify launch source
Related to #7433

This extracts our "launched from desktop" logic into a config option.
The default value is detection using the same logic as before, but now
this can be overridden by the user.

This also adds the systemd and dbus activation sources from #7433.

There are a number of reasons why we decided to do this:

  1. It automatically gets us caching since the configuration is only
     loaded once (per reload, a rare occurrence).

  2. It allows us to override the logic when testing. Previously, we
     had to do more complex environment faking to get the same
     behavior.

  3. It forces exhaustive switches in any desktop handling code, which
     will make it easier to ensure valid behaviors if we introduce new
     launch sources (as we are in #7433).

  4. It lowers code complexity since callsites don't need to have N
     `launchedFromX()` checks and can use a single value.
2025-06-02 08:45:02 -07:00
Leah Amelia Chen
1ff9162598 gtk(wayland,x11): refactors (#7485) 2025-05-31 08:17:11 +02:00
Mitchell Hashimoto
7e85ca3a16 gtk: clean up per-surface cgroup on close (#7487)
Fixes #6766

This ensures that during surface deinit the cgroup is removed. By the
time the surface is deinitialized, the subprocess should already be dead
so the cgroup can be safely removed. If the cgroup cannot be removed for
any reason we log a warning.

I'm still investigating whether we also need to remove the transient
cgroup we create per app but that's a lot less noisy since app startup
and shutdown is a lot more rare.
2025-05-30 19:32:32 -07:00
Mitchell Hashimoto
2b9e781933 gtk: clean up per-surface cgroup on close
Fixes #6766

This ensures that during surface deinit the cgroup is removed. By the
time the surface is deinitialized, the subprocess should already be
dead so the cgroup can be safely removed. If the cgroup cannot be
removed for any reason we log a warning.
2025-05-30 19:31:04 -07:00
Mitchell Hashimoto
84aa359984 font: rework coretext discovery sorting (#7483)
This should make the sorting more robust to fonts with questionable
metadata or atypical style names.

I was originally just going to change the scoring slightly to account
for fonts whose regular italic style is named "Regular Italic" - which
previously resulted in the Bold Italic or Thin Italic style being chosen
instead because they're shorter names, but I decided to do some better
inspection of the metadata and looser style name matching while I was
changing code here anyway.

Also adds a unit test to verify the sorting works correctly, though a
more comprehensive set of tests may be desirable in the future.

(Feel free to make any changes to the PR you feel necessary before
merging while I'm gone over the weekend.)
2025-05-30 19:30:19 -07:00
Mitchell Hashimoto
6e69893f29 font/sprite: rework yQuads and friends for better alignment with draw_block (#7488)
This improves "outer edge" alignment of octants and other elements drawn
using `yQuads` and friends with blocks drawn with `draw_block` -- this
should guarantee alignment along a continuous edge, but may result in a
1px overlap of opposing edges (such as a top half block followed by a
bottom half block with an odd cell height, they will both have the
center row filled).

This is very necessary since several block elements are needed to
complete the set of octants, since dedicated octant characters aren't
included when they would be redundant.

Fixes #7479 

<details>
<summary><b><code>Box.ppm</code> diff</b></summary>


![image](https://github.com/user-attachments/assets/aea667fd-446f-4b60-a220-cdd636093d05)

</details>

> [!NOTE]
> In the future I think we should have a unified single source of truth
for grid positions (divisions of the cell) to ensure this type of thing
can't happen with other characters, and also it would make a lot of the
code cleaner. For now this works though.
2025-05-30 19:29:45 -07:00
Qwerasd
dd670f5107 font/sprite: rework yQuads and friends for better alignment with draw_block
This improves "outer edge" alignment of octants and other elements drawn
using `yQuads` and friends with blocks drawn with `draw_block` -- this
should guarantee alignment along a continuous edge, but may result in a
1px overlap of opposing edges (such as a top half block followed by a
bottom half block with an odd cell height, they will both have the
center row filled).

This is very necessary since several block elements are needed to
complete the set of octants, since dedicated octant characters aren't
included when they would be redundant.
2025-05-30 17:53:52 -06:00
Leah Amelia Chen
f99c988b27 gtk(wayland): automatically bind globals 2025-05-30 23:42:48 +02:00
Leah Amelia Chen
9ded668819 gtk(wayland,x11): remove even more redundant checks 2025-05-30 23:42:42 +02:00
Leah Amelia Chen
157f50e2de gtk: request user attention on bell (#7482)
I'm not sure if this should be enabled by default like the tab
animation, but on KDE at least this is unintrusive enough for me to
always enable by default. Alacritty appears to agree with me as well.

Fixes #7124
2025-05-30 23:33:12 +02:00