#7173
(1) The command palette no longer has any selection by default.
If and when we introduce most recently used commands, defaulting to that
would make sense. A selection only appears when the arrow keys are used
or the user starts typing.
(2) The selection with arrow keys now wraps, so if you press "down" on
the last option, it will wrap to the first option, and if you press "up"
on the first option, it will wrap to the last option. This matches both
VSCode and Zed.
Removes the withAnimation closure which caused flashing when scrolling
up or down with arrow keys. Also removes the center anchor to behave
more like other command palletes (e.g., Zed, Raycast).
This introduces a command palette (inspired by @pluiedev's work in
#5681, but not using it as a base) for macOS.
The command palette is available in the `View` menu and also bindable
via `toggle_command_palette`, default binding is `cmd+shift+p` to match
VSCode.
The commands in the command palette must map to a _bindable_ action,
though they may not have an associated keybinding. This means that any
new binding actions we add in the future can be represented here and
also makes it easy in the future to add configuration to add new custom
entries to the command palette. For this initial PR, the available
commands are hardcoded (`src/input/commands.zig`).
I've noticed in other programs (VSCode, Zed), the command palette
contains pretty much _all available actions_ even if they're basically
useless in the context of a command palette. For example, Zed has the
"toggle command palette" action in the command palette and it... does
nothing (it probably should hide the palette). I followed @pluiedev's
lead and made this subjective in this PR but I wonder if we should
actually force all binding actions to be available.
There are various other improvements I'd like to make but omitted from
this PR for the sake of limiting scope:
* Instead of an entry with no matches doing nothing, we can allow users
to manually input _any_ configurable binding.
* Localization, since macOS doesn't have any yet. But for Linux when we
port this we probably have to change our strings extraction.
## Demo
https://github.com/user-attachments/assets/a2155cfb-d86b-4c1a-82b5-74ba927e4d69
Resolves#7108
This PR adds visual notification badges to the Ghostty dock icon when
bell events are triggered while the application is in the background.
This complements the existing dock bounce notification, making it easier
for users to notice when a terminal needs attention.
https://github.com/user-attachments/assets/b54c881f-fea8-4085-8614-432d9e5847b9
Fixes a regression where `C-S-c` stopped working properly in both legacy
and Kitty modes (although the Kitty mode side only affected alternates
and not the key itself so it probably worked fine in most programs).
The issue is that `charactersIgnoringModifiers` changes behavior if
`control` is pressed, so it doesn't really ignore all modifiers. We have
to use `characters(byApplyingModifiers:)` to get the proper unshifted
codepoint when `control` is pressed.
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.
This PR should have no user impact. This is just some cleanup for future
work.
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.
Fixes a regression where `C-S-c` stopped working properly in both legacy
and Kitty modes (although the Kitty mode side only affected alternates
and not the key itself so it probably worked fine in most programs).
The issue is that `charactersIgnoringModifiers` changes behavior if
`control` is pressed, so it doesn't really ignore all modifiers. We have
to use `characters(byApplyingModifiers:)` to get the proper unshifted codepoint
when `control` is pressed.
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.
Fixes#7131
Regression from #7121
Our consumed mods should not include "alt" if `macos-option-as-alt` is
set. To do this, we need to calculate our consumed mods based on the
actual translation event mods (if available, only available during
keyDown).
This is a large refactor of the keyboard input handling code in
libghostty and macOS. Previously, libghostty did a lot of things that
felt out of scope or was repeated work due to lacking context. For
example, libghostty would do full key translation from key event to
character (including unshifted translation) as well as managing dead key
states and setting the proper preedit text.
This is all information the apprt can and should have on its own.
NSEvent on macOS already provides us with all of this information,
there's no need to redo the work. The reason we did in the first place
is mostly historical: libghostty powered our initial macOS port years
ago when we didn't have an AppKit runtime yet.
This cruft has already practically been the source of numerous issues, e.g.
#5558, but many other hacks along the way, too.
This commit pushes all preedit (e.g. dead key) handling and key translation
including unshifted keys up into the caller of libghostty.
Besides code cleanup, a practical benefit of this is that key event
handling on macOS is now about 10x faster on average. That's because
we're avoiding repeated key translations as well as other unnecessary
work. This should have a meaningful impact on input latency but I didn't
measure the full end-to-end latency.
A scarier part of this commit is that key handling is not well tested
since its a GUI component. I suspect we'll have some fallout for certain
keyboard layouts or input methods, but I did my best to run through
everything I could think of.
Fixes#7099
This adds basic bell features to macOS to conceptually match the GTK
implementation. When a bell is triggered, macOS will do the following:
1. Bounce the dock icon once, if the app isn't already in focus.
2. Add a bell emoji (🔔) to the title of the surface that triggered
the bell. This emoji will be removed after the surface is focused
or a keyboard event if the surface is already focused. This
behavior matches iTerm2.
This doesn't add an icon badge because macOS's dockTitle.badgeLabel API
wasn't doing anything for me and I wasn't able to fully figure out
why...
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.
Fixes#7071
When the mouse is being actively dragged, AppKit continues to emit
mouseDragged events which will update our position appropriately. The
mouseExit event we were sending sends a synthetic (-1, -1) position
which was causing a scroll up.
Fixes#7000
Related to #6909, the same mechanism, but it turns out some control+keys
are also handled in this same way (namely control+esc leads to "cancel"
by default, which is not what we want).
Fixes#2595
This fixes an issue where a left mouse click on a terminal while not
focused would subsequently be encoded to the pty as a mouse event. This
is atypical for macOS applications in general and wasn't something we
wanted to do.
We do, however, want to ensure our terminal gains focus when clicked
without focus. Specifically, a split. This matches iTerm2 behavior and
is rather nice. We had this behavior before but our logic to make this
work before caused the issue this commit is fixing.
I also tested this with command+click which is a common macOS shortcut
to emit a mouse event without raising the focus of the target window. In
this case, we will properly focus the split but will not encode the
mouse event to the pty. I think we actually do a _better job_ here tha
iTerm2 (but, subjective) because we do encode the pty event properly if
the split is focused whereas iTerm2 never does.
Fixes a regression from #6909
See #6887
In certain scenarios, the last command key state would linger around (I
could only see this happen with global keybinds for unknown reasons
yet). This state is only meant to have an effect within the cycle of a
single keybind and only so we can ensure an event reaches keyDown so it should
be reset if keyDown is ever sent (since, by definition at that point,
keyDown has been reached).
I'm still not happy that this is necessary and I suspect there is a
better root cause to resolve, but I'd rather get this fix in now and
figure out the root cause later.
Fixes#5522
This commit re-dispatches command inputs that are unhandled by our macOS
app so they can be encoded to the pty and handled by the core libghostty
key callback system.
We've had a special case `cmd+period` handling in Ghostty for a very
long time (since well into the private beta). `cmd+period` by default
binds to "cancel" in macOS, so it doesn't encode to the pty. We don't
handle "cancel" in any meaningful way in Ghostty, so we special-cased it
to encode properly to the pty.
However, as shown in #5522, if the user rebinds `cmd+period` at the
system level to some other operation, then this is ignored and we encode
it still. This isn't desirable, we just want to work around not caring
about "cancel."
The callback path that AppKit takes for key events is a bit convoluted.
For command keys, it first calls `performKeyEquivalent`. If this returns
false (we want to continue standard processing), then it calls EITHER
`keyDown` or `doCommand(by:)`. It calls the latter if there is a
standard system command that matches the key event. For `cmd+period` by
default, this is "cancel." Unfortunately, from `doCommand` we can't say
"oops, we don't want to handle this, please continue processing." Its
too late.
So, this commit stores the last command key event from
`performKeyEquivalent` and if we reach `doCommand` for it without having
called `keyDown`, we re-dispatch the event and send it to keyDown.
I'm honestly pretty sus about this whole logic but it is scoped to only
command-keys and I couldn't trigger any adverse behavior in my testing.
It also definitely fixed#5522 as far as I could reproduce it before.