This changes the way Ghostty assigns itself and subprocesses to
cgroups and how resource controls are applied.
* Ghostty itself no longer modifies it's own cgroup or moves itself
to a transient scope. To modify the main Ghostty process' resource
controls ensure that you're launching Ghostty with a systemd unit and
use the standard systemd methods for overriding and applying changes
to systemd units.
* If configured (on by default), the process used to run your command
will be moved to a transient systemd scope after it is forked from
Ghostty but before the user's command is executed. Resource controls
will be applied to the transient scope at this time. Changes to
the `linux-cgroup*` configuration entries will not alter existing
commands. If changes are made to the `linux-cgroup*` configuration
entries commands will need to be relaunched. Resource limits can also
be modified after launch outside of Ghostty using systemd tooling. The
transient scope name can be shown by running `systemctl --user whoami`
in a shell running inside Ghostty.
Fixes#2084.
Related to #6669
The `cursor` shell feature always used a blinking bar (beam), often to
the surprise of users who set `cursor-style-blink = false`.
This change extends our GHOSTTY_SHELL_FEATURES format to include either
`cursor:blink` (default) or `cursor:steady` based on cursor-style-blink
when the `cursor` feature is enabled, and all shell integrations have
been updated to use that additional information to choose the DECSCUSR
cursor value (5=blinking bar, 6=steady bar).
I also considered passing a DECSCUSR value in GHOSTTY_SHELL_FEATURES
(e.g. `cursor:5`). This mostly worked well, but zsh also needs the blink
state on its own for its block cursor. We also don't support any other
shell feature cursor configurability (e.g. the shape), so this was an
over generalization.
This does change the behavior for users who like the blinking bar in the
shell but have `cursor-blink-style = false` for other reasons. We could
provide additional `cursor` shell feature configurability (e.g.
`cursor:blink` in `shell-integration-features`), but I'll propose that
as its own change.
See: #2812Closes: #8681
---
**AI Disclosure:** I did a lot of rubber ducking with Claude Code while
trying out various ideas. It was particularly useful for this kind of
feature because I could try out one thing and have it evaluate the
impact on all of the shell integration scripts at once.
This pull request addresses some of the remaining issues when matching
`~`, `$VAR`, `.directory/`, and embedded commas. It does not address
issues with embedded line breaks.
The PR is split in multiple commits carefully applying a set of changes
to
1. make the big regex more composable / readable
2. update some doc strings
3. add more test cases for the issues mentioned
4. two simple commits, each fixing the issues
Changes:
- **url: refactor regex into documented branches**
Break up the big monolithic URL and path regex into named sub-pattern
constants and compose the final expression from three commented
branches:
- URLs with a scheme
- absolute or dot-relative paths
- bare relative paths
This commit only breaks up the regex. It keeps the existing matching
behavior unchanged.
- **url: update top-level comment**
- **url: carefully extend test cases**
Extend existing test cases with `~`, `$VAR`, and bare .-prefixed paths
and embedded `,` comma handling.
See following issue comments:
-
https://github.com/ghostty-org/ghostty/pull/10570#issuecomment-3853842036
-
https://github.com/ghostty-org/ghostty/issues/1972#issuecomment-3859329233
-
https://github.com/ghostty-org/ghostty/issues/1972#issuecomment-3857881196
- **url: remove `,` from path_chars**
Related to #1972
Fixes an issue when paths have embedded comma, e.g.:
shared/src/foo/SomeItem.m:12, shared/src/
with path_chars greedily consuming the rest of the string.
Now file path matching stops at comma. Scheme URLs are unchanged and
still using the comma.
- **url: fix matching `~`, `$VAR`, `.directory/`**
Related to #1972
This commit adds three new alternatives for
`rooted_or_relative_path_prefix`:
- `~/`
- `$VAR` and
- `.local/`, `.config/` etc. for dot-prefixed directory names
Remaining commits fix edge cases one by one:
- **url: fix mid-string dot partial matches**
`"foo.local/share"` (was partial match) → now matches fully
- **url: fix $-numeric character matches**
`"$10/$20"` → no match
- **url: fix partial match of mid string $-variable**
`"foo/$BAR/baz"` (was partial match) → matches fully now
- **url: fix incomplete $-numeric behavior**
`"$10/bar.txt"` (was partial match) → but should not match at all
Paths like `./.config/ghostty:` and `./Downloads:` were incorrectly
including the trailing colon. Add a `no_trailing_colon` lookbehind to
all path branches and prevent space segments from starting after a colon
so that `": text"` is not consumed as part of the path.
This
$10/bar.txt
was partially matching but should not match at all.
This commit fixes this by simply `[\w]` to `[A-Za-z_]` as the first
character of a bare relative path, so digit-starting fragments can't
match.
Another option would be a lookbehind but I think the check above is much
simpler.
A string like this
foo/$BAR/baz
should match fully, not partially.
This commit fixes this by expanding `\$[A-Za-z_]\w*\/` to
`(?:[\w][\w\-.]*\/)*\$[A-Za-z_]\w*\/` in rooted_or_relative_path_prefix
so that we optionally eat everything before a variable `$VAR/`.
Strings like
$10/$20
Should not match.
This commit fixes this by narrowing `\w` after `$` to `[A-Za-z_]` which
is— according to Google Gemini— what environment variables can start
with.
A string like
foo.local/share
should match fully, not partially.
This commit fixes this by moving `dotted_path_lookahead` before
`bare_relative_path_prefix` so the dot-check scans the entire match
rather than only the text after it.
Related to #1972
This commit adds three new alternatives for
`rooted_or_relative_path_prefix`:
- `~/`
- `$VAR` and
- `.local/`, `.config/` etc. for dot-prefixed directory names
Related to #1972
Fixes an issue when paths have embedded comma, e.g.:
shared/src/foo/SomeItem.m:12, shared/src/
with path_chars greedily consuming the rest of the string.
Now file path matching stops at comma. Scheme URLs are unchanged and
still using the comma.
Break up the big monolithic URL and path regex into named sub-pattern
constants and compose the final expression from three commented
branches:
- URLs with a scheme
- absolute or dot-relative paths
- bare relative paths
This commit only breaks up the regex. It keeps the existing matching
behavior unchanged.
The `cursor` shell feature always used a blinking bar (beam), often to
the surprise of users who set `cursor-style-blink = false`.
This change extends our GHOSTTY_SHELL_FEATURES format to include either
`cursor:blink` (default) or `cursor:steady` based on cursor-style-blink
when the `cursor` feature is enabled, and all shell integrations have
been updated to use that additional information to choose the DECSCUSR
cursor value (5=blinking bar, 6=steady bar).
I also considered passing a DECSCUSR value in GHOSTTY_SHELL_FEATURES
(e.g. `cursor:5`). This mostly worked well, but zsh also needs the blink
state on its own for its block cursor. We also don't support any other
shell feature cursor configurability (e.g. the shape), so this was an
over generalization.
This does change the behavior for users who like the blinking bar in the
shell but have `cursor-blink-style = false` for other reasons. We could
provide additional `cursor` shell feature configurability, but I think
that's best left to a separate change.
Related to #1972
The URL regex for file path detection requires paths to start with
`../`, `./`, or `/`. For bare relative paths like
`"src/config/url.zig"`, the regex could only match starting at the first
`/`, producing `"/config/url.zig"` as a result — always dropping the
first part of the path.
Fix: added a third top-level alternative to the regex. This matches bare
relative paths where:
1. The first component is word characters (possibly with dots/dashes):
`[\w][\w\-.]*\/`
2. The remaining path must contain a dot (via positive lookahead) — this
requires a file extension to avoid false positives on text like
input/output
3. Add a `(?<!\w)\/` instead of `\/` in the existing prefix group — the
standalone `/` prefix now requires that `/` is not preceded by a word
character. This prevents `"input/output"` from falsely matching
`"/output"`
Test cases added:
- src/config/url.zig → matches fully
- app/folder/file.rb:1 → matches with line number
- modified: src/config/url.zig → matches only the path part
- lib/ghostty/terminal.zig:42:10 → matches with line:col
- some-pkg/src/file.txt more text → stops before trailing text
- input/output and foo/bar → correctly do not match (no file extension)
The issue was nailed down here:
https://github.com/ghostty-org/ghostty/issues/1972#issuecomment-3845717672
Adds palette and color scheme uniforms to custom shaders, allowing
custom shaders to access terminal color information:
- iPalette[256]: Full 256-color terminal palette (RGB)
- iBackgroundColor, iForegroundColor: Terminal colors (RGB)
- iCursorColor, iCursorText: Cursor colors (RGB)
- iSelectionBackgroundColor, iSelectionForegroundColor: Selection
colors (RGB)
Colors are normalized to [0.0, 1.0] range and update when the palette
changes via OSC sequences or configuration changes. The palette_dirty
flag tracks when colors need to be refreshed, initialized to true to
ensure correct colors on new surfaces.
Switches several default keybindings from physical key codes
`.physical = .equal // or .bracket_left or .bracket_right`
to unicode characters
`.unicode = '=' // or '[' or ']'`
to support alternative keyboard layouts like Dvorak and
keyboards with dedicated plus keys (like German layouts).
I found in testing that all of these must be fixed at once otherwise
the bracket physical keys overshadew the correct (for dvorak) plus key.
With this fix, tab and pane navigation (cmd+[], cmd+shift+[]), as well
as cmd+shift+equals and cmd+equals work as expected on dvoark layout on MacOS.
- Change all codepoint types from u32 to u21 to align with Zig stdlib
- Update ArrayList to use Zig 0.15 unmanaged pattern (.empty)
- Remove unnecessary @intCast when encoding UTF-8
- Fix formatEntry to use stack-allocated buffer
Refactor the selection-word-chars implementation to parse UTF-8 boundary
characters once during config initialization instead of on every selection
operation.
Changes:
- Add SelectionWordChars type that stores pre-parsed []const u32 codepoints
- Parse UTF-8 to codepoints in parseCLI() during config load
- Remove UTF-8 parsing logic from selectWord() hot path (27 lines removed)
- Remove arbitrary 64-character buffer limit
- Update selectWord() and selectWordBetween() to accept []const u32
- Update DerivedConfig to store codepoints directly
- Update all tests to use codepoint arrays
Benefits:
- No runtime UTF-8 parsing overhead on every selection
- No arbitrary character limit (uses allocator instead)
- Cleaner separation of concerns (config handles parsing, selection uses data)
- Better performance in selection hot path
Add new `selection-word-chars` config option to customize which characters
mark word boundaries during text selection operations (double-click, word
selection, etc.). Similar to zsh's WORDCHARS environment variable, but
specifies boundary characters rather than word characters.
Default boundaries: ` \t'"│`|:;,()[]{}<>$`
Users can now customize word selection behavior, such as treating
semicolons as part of words or excluding periods from boundaries:
selection-word-chars = " \t'\"│`|:,()[]{}<>$"
Changes:
- Add selection-word-chars config field with comprehensive documentation
- Modify selectWord() and selectWordBetween() to accept boundary_chars parameter
- Parse UTF-8 boundary string to u32 codepoints at runtime
- Update all call sites in Surface.zig and embedded.zig
- Update all test cases to pass boundary characters
### Background
~~I was trying to add a few UI test cases for
`macOS-titlebar-style`[Already in this PR]~~. In order to do this, I
need a way from `GhosttyKit` to load a temporary configuration without
messing around with users'.
### Changes
- Add `ghostty_config_load_file` using the existing
[`loadFile`](dafb9e89a3/src/config/Config.zig (L3399))
- Use `xcbeautify` to format test&build errors
**Couldn't find a way to do this in `GhosttyXcodebuild`, if you have a
better approach please let me know!**
- Add GhosttyUITests target and test cases for
`GhosttyTitlebarTabsUITests`(#2349) and `GhosttyThemeTests`(#9360)
### NOTE
Running UI tests on the runner could be **very** slow and I couldn't
find a way to guarantee success, so I made these only runnable by
manually testing in Xcode.
Better to squash this🤪
> > Some of the test cases could fail when testing all the cases
together; a rerun would succeed.
Nushell <https://www.nushell.sh/> is a modern interactive shell that
provides many shell features out-of-the-box, like `title` support. Our
shell integration therefore focuses on Ghostty-specific features like
`sudo`.
We use Nushell's module system to provide a `ghostty` module containing
our shell integration features. This module is automatically loaded from
$XDG_DATA_DIRS/nushell/vendor/autoload/ when `nushell` shell integration
is enabled.
Exported module functions need to be explicitly "used" before they're
available to the interactive shell environment. We do that automatically
by adding `--execute "use ghostty *"` to the `nu` command line.
This imports all available functions, and individual shell features are
runtime-guarded by the script code (using $GHOSTTY_SHELL_FEATURES). We
can consider further refining this later.
When automatic shell integration is disabled, users can still manually
source and enable the shell integration module:
source $GHOSTTY_RESOURCES_DIR/shell-integration/nushell/vendor/autoload/ghostty.nu
use ghostty *
This initial work implements our TERMINFO-aware `sudo` wrapper (via the
`sudo` shell feature). Support for additional features, like `ssh-env`
and `ssh-terminfo`, will follow.
This is recommended for ongoing performance:
https://github.com/ziglang/zig/issues/17851
Likely not an issue for this particular use case which is why it never
bit us; we don't actively modify this map much once it is created. But,
its still good hygiene and ArrayHashMap made some of the API usage
nicer.