Commit Graph

79 Commits

Author SHA1 Message Date
Mitchell Hashimoto
7895bf1d02 shell-integration: respect cursor-style-blink (#10643)
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: #2812
Closes: #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.
2026-02-16 14:08:23 -08:00
Jon Parise
897b918f67 bash: remove redundant out-of-band OSC 133;A
The printf was part of the original script (9d6121245), and at the time,
this was the only place we'd emit the 133;A mark.

A PS1-based 133;P;k=i mark was introduced in 2bf1f80f7, and then it
become a full 133;A mark in aa47047a6, making the original printf line
redundant (because bash will also redraw PS1 on SIGWINCH).

The PS1-based 133;A was only missing the aid= option, and with that
added, it handles all of our cases (prompts, initial draw, and resizes).
2026-02-16 13:46:55 -05:00
Jon Parise
a271f85cd2 bash: preserve existing PS0 value
We were previously overwriting PS0 on every PROMPT_COMMAND. We now
append to PS0, but only if it doesn't already contain our hook.

This is also more consistent with the bash-preexec behavior we maintain
for older bash versions.
2026-02-12 12:02:23 -05:00
Jon Parise
3cfb9d64d1 shell-integration: respect cursor-style-blink
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.
2026-02-10 18:46:36 -05:00
Jon Parise
d0b403304d bash: use PROMPT_COMMAND array form in bash 5.1+
PROMPT_COMMAND array support for introduced in bash 5.1, and it's the
preferred format moving forward. Using the string form is also fine, but
it's easy to be a modern bash citizen here, so let's do so.
2026-02-08 09:40:05 -05:00
Jon Parise
5425569a19 bash: remove dependency on bash-preexec for bash 4.4+
bash-preexec implements support for its "precmd" and "preexec" hooks
using a combination of PROMPT_COMMAND and a DEBUG trap. The latter is
unfortunately quick slow (in a relative sense), and the overall system
is a bit more generalized than what we need for our shell integration
(e.g. supporting multiple function hooks, subshells, etc.).

Bash 4.4 introduced the PS0 variable, which is expanded and displayed by
interactive shells after reading a complete command but before executing
it. This is all we need to implement our own shell integration hooks.

In Bash 5.1, PROMPT_COMMAND can be an array variable, each element of
which can contain a command to be executed like a string PROMPT_COMMAND
variable. When adding our hook to PROMPT_COMMAND, we preserve its type
(string or array) to be minimally intrusive. This also matches direnv's
approach.

Bash 5.3 introduced support for function substitution, which is an even
more efficient way to run code from PS0, so we use that when available.
Otherwise, we use the more traditional command substitution approach.

Earlier versions of bash (such as 3.2, which still ships with macOS)
continue to use the bash-preexec path. This gives us two code paths to
maintain, but I think that's preferable to fully maintaining our own
DEBUG trap-based system for older bash versions given that bash-preexec
has proven to work reliably in those environments. We also wouldn't
unlock any other user benefits aside from removing the bash-preexec
script dependency.

See: #3724, #7734
2026-02-07 15:29:04 -05:00
Mitchell Hashimoto
2fa9eff0ef shell-integration/bash: advertise cl=line support 2026-02-02 13:36:29 -08:00
Mitchell Hashimoto
918c2934a3 terminal: add redraw=last for bash for OSC133 2026-01-31 15:26:14 -08:00
Mitchell Hashimoto
4bee8202a8 shell-integration/bash: mark each line in multiline prompts as secondary
Insert OSC 133 A k=s marks after each newline in PS1, so that all lines
following the first are marked as secondary prompts. This prevents ghostty
from erasing leading lines during terminal resize.
2026-01-31 14:46:18 -08:00
definfo
4a04efaff1 fix: explicitly allow preservation for TERMINFO in shell-integration
Due to security issues, `sudo` implementations may not preserve
environment variables unless appended with `--preserve-env=list`.

Signed-off-by: definfo <hjsdbb1@gmail.com>
2025-12-13 16:55:41 +08:00
Jon Parise
4989f92c71 shell-integration: remove redundant comments
I think the conditions are sufficiently self-descriptive.
2025-10-01 10:27:42 -04:00
Matthew Hrehirchuk
4cc663fc60 feat: add GHOSTTY_BIN_DIR to path via shell integration 2025-09-30 11:26:20 -06:00
rhodes-b
1397c76243 mark ssh shell-integration wrapper as a function this matches other features + fixes a case where users alias to some other command 2025-09-15 21:49:48 -05:00
Jon Parise
f4e434fffd bash: upgrade to bash-preexec 0.6.0
https://github.com/rcaloras/bash-preexec/releases/tag/0.6.0

This is a small update for us because we've been using a patched version
of this script in Ghostty for some time, and the 0.6.0 release includes
most of the local changes we made as part of maintaining and improving
our bash shell integration.

- https://github.com/rcaloras/bash-preexec/pull/167
- https://github.com/rcaloras/bash-preexec/pull/170

We continue to maintain one local HISTCONTROL-related modification
(#2478). There are a few upstream conversations related to HISTCONTROL
that might eliminate the need for this local patch, so we may revisit
that in the future.
2025-08-03 09:15:35 -04:00
Jon Parise
01233a48d1 bash: preserve an existing ENV value
Our bash shell integration code uses ENV (in POSIX mode) to bootstrap
our shell integration script. This had the side effect of overwriting an
existing ENV value.

This change preserves ENV by storing it temporarily in GHOSTTY_BASH_ENV.

Note that this doesn't enable --posix mode support for automatic shell
integration. (--posix does work; we just skip shell integration when
that flag is specified.) We can reconsider implementing full --posix
support separately.
2025-07-10 15:47:55 -04:00
Jon Parise
f5f2a4dd20 shell-integration: use $GHOSTTY_BIN_DIR/ghostty
Locate our ghostty binary using $GHOSTTY_BIN_DIR rather than searching
the PATH.
2025-07-09 17:25:34 -04:00
Jon Parise
e522d54d7b shell-integration: simplify "ssh target" checks
This value is always set to a non-empty string, and we only need this
value after we've determined that 'ssh_hostname' is non-empty.

In bash and zsh, we also don't need to check for the 'ghostty' command
before we attempt to add the target to the cache. That command will
safely fail silently if it's not available.
2025-07-09 15:59:59 -04:00
Jason Rayne
f95476b181 refactor: apply maintainer feedback to SSH integration scripts across all shells
- Update Bash script (baseline): Simplify cache checking logic, clarify
"xterm-ghostty terminfo" message, remove unnecessary ssh_opts from
terminfo installation, remove extra success message
- Align ZSH/Fish/Elvish with updated Bash: Remove extra success
messages, adopt simplified cache checking, standardize setup messages
- Apply Elvish improvements: Remove unnecessary try/catch blocks, use
idiomatic error handling patterns
- Apply Fish improvements: Replace string pattern matching with
efficient `contains` checks on split features list
2025-07-08 10:45:42 -07:00
Jason Rayne
f279937377 refactor: simplify terminfo handling and remove base64 dependency
- Default ssh_term to xterm-256color to eliminate fallback assignments
- Remove base64 and replace infocmp -Q2 with standard -0 -x options for
compatibility
- Use process substitution instead of intermediate ssh_config variable
- Always set TERM explicitly since ssh_term is always defined
2025-07-07 11:33:26 -07:00
Jason Rayne
c3b14dff71 refactor: simplify SSH terminfo and environment handling
- Simplify feature detection to use single wildcard check
- Replace ssh_env array with simple ssh_term string variable
- Use TERM environment prefix instead of save/restore pattern
- Remove unnecessary backgrounded subshell for cache operations
2025-07-07 10:00:56 -07:00
Jason Rayne
08db61e27e refactor: simplify SSH environment variable handling
- Remove complex ssh_exported_vars tracking and local environment
modification in favor of trusting Ghostty's local environment
- Replace regex patterns with glob-based feature detection for better
performance
- Fix local variable declaration consistency throughout
- Streamline logic while maintaining all functionality
2025-07-07 09:06:15 -07:00
Jason Rayne
2d2df4b99f Merge branch 'main' into ssh-integration 2025-07-05 13:34:47 -07:00
Jason Rayne
a22074a85c fix: optimize SSH integration and improve error handling
- Replace dual-loop SSH config parsing with efficient single-pass case
statement
- Remove overly cautious timeout logic from cache checks for simplicity
- Add base64 availability check with xterm-256color fallback when
missing
- Include hostname in terminfo setup messages for better UX
- Maintain SendEnv/SetEnv dual approach for maximum OpenSSH
compatibility (relying on SetEnv alone seems to drop some vars during my
tests, despite them being explicitly included in AcceptEnv on the remote
host)
2025-07-05 13:24:59 -07:00
Jon Parise
b8931dd1db bash: stop using PS0 for the 'cursor' feature
Our use of PS0 (which bash runs before command execution) was causing
raw command sequences to be printed between multiple commands in a
sequence.

    $ alias garbage='echo start
    > echo end'

    $ garbage
    start
    �\���dend

I wasn't able to definitely track down all of the reasons for why this
only happens in the command sequence case, but I suspect it's related to
the way that __ghostty_preexec runs from within the bash DEBUG trap (by
way of bash-preexec).

This problem occurs when PS0 is set to _any_ string (even "") inside of
__ghostty_preexec, which also rules out most/any Ghostty-specific code.
PS1 and PS2 appear to be safe to (re)set in this context.

Fortunately, we can avoid using PS0 entirely by instead printing the
cursor reset escape sequence directly from __ghostty_precmd because it
also runs just before command execution.
2025-07-04 20:06:06 -04:00
Jason Rayne
75c703071a feat(ssh): rewrite SSH cache system in native Zig
- Eliminates standalone bash dependency
- Consolidates `+list-ssh-cache` and `+clear-ssh-cache` actions into
single `+ssh-cache` action with args
- Structured cache format with timestamps and expiration support
- Memory-safe entry handling with proper file locking
- Comprehensive hostname validation (IPv4/IPv6/domains)
- Atomic updates via temp file + rename
- Updated shell integrations for improved cross-platform support and
reliability
- Cache operations are now unit-testable
2025-07-03 20:11:45 -07:00
Jason Rayne
076f742dd4 fix: replace non-existent GHOSTTY_VERSION with TERM_PROGRAM_VERSION in shell integration
GHOSTTY_VERSION was mistakenly referenced but is never set. Use
TERM_PROGRAM_VERSION which is actually provided by Exec.zig from
build_config.version_string.
2025-06-25 17:50:15 -07:00
Jason Rayne
0565ed3954 refactor: replace ghostty wrapper with proper CLI actions for terminfo cache management
- Add +list-ssh-cache and +clear-ssh-cache CLI actions
- Remove ghostty() wrapper functions from all shell integrations
- Improve variable naming in shell scripts for readability

Addresses @00-kat's feedback about CLI discoverability and naming
consistency. The new CLI actions follow established Ghostty patterns
and are discoverable via `ghostty --help`, while maintaining clean
separation of concerns between shell logic and cache management.
2025-06-25 15:46:18 -07:00
Jason Rayne
8a2fa6485e refactor: extract SSH cache functionality to shared script
Addresses feedback about separation of concerns in shell integration
scripts.

Extracts host caching logic to
`src/shell-integration/shared/ghostty-ssh-cache` and updates all four
shell integrations to use the shared script. The `shared/` subdirectory
preserves the existing organizational pattern where all shell-specific
code lives in subdirectories. This cleanly separates SSH transport logic
from cache management while reducing code duplication by ~25%.

All existing SSH integration behavior remains identical.
2025-06-25 15:46:18 -07:00
Jason Rayne
81641e56b1 ssh-integration: replace levels with flags, optimize implementation
Rewrote shell functions to support the two new flags for
shell-integration-features:
- ssh-env: TERM compatibility + best effort environment variable
propagation (anything beyond TERM will depend on what the remote host
allows)
- ssh-terminfo: automatic terminfo installation with control socket
orchestration
- Flags work independently or combined

Implementation optimizations:
- ~65% code reduction through unified execution path
- Eliminated GHOSTTY_SSH_INTEGRATION environment variable system
- Replaced complex function dispatch with direct flag detection
- Consolidated 4 cache helper functions into single _ghst_cache()
utility
- Simplified control socket management (removed multi-step
orchestration)
- Subsequent connections to cached hosts are now directly executed and
more reliable

New additions:
- If ssh-terminfo is enabled, ghostty will be wrapped to provide users
with convenient commands to invoke either of the two utility functions:
`ghostty ssh-cache-list` and `ghostty ssh-cache-clear`
2025-06-25 15:46:18 -07:00
Jason Rayne
ddd3da487e fix: update cache file location 2025-06-25 15:46:18 -07:00
Jason Rayne
30683979bc fix: catch up to current state 2025-06-25 15:46:18 -07:00
Jason Rayne
f206e76841 ssh-integration: improve host caching, new method for "full" integration
Need a sanity check on this new approach for "full" to help determine if
it's worth additional iteration/refinement.

It solves the double auth issue, successfully propagates env vars, and
avoids output noise for connections that happen after terminfo is
installed. The only issue I don't have time to fix tonight is the fact
that it drops the MOTD for cached (re)connections.
2025-06-25 15:46:18 -07:00
Jason Rayne
4cebee5c8e fix: add client-side caching to eliminate redundant terminfo installations
- Cache known hosts with terminfo in
$GHOSTTY_RESOURCES_DIR/terminfo_hosts
- Skip installation step for cached hosts (single connection instead of
two)
- Use secure file permissions (600) and atomic writes
- Extract SSH target safely from command arguments
- Maintains full functionality while improving user experience on
repeated connections
2025-06-25 15:46:18 -07:00
Jason Rayne
b6bb9abfbc fix: address comprehensive shell integration code review issues
- Fix elvish function name mismatch and use conj for list operations
- Simplify terminfo installation command per ghostty docs (tic -x -)
- Fix conditional structure to ensure error messages always print
- Remove redundant checks and optimize array initialization
- Use consistent patterns across bash, fish, elvish, and zsh
implementations
2025-06-25 15:46:18 -07:00
Jason Rayne
995fb09813 fix: add builtin prefix for safety and consistency 2025-06-25 15:46:18 -07:00
Jason Rayne
2ddcf2fffe fix: remove resources_dir var, add builtin prefix for consistency 2025-06-25 15:46:18 -07:00
Jason Rayne
2e9a0e92db fix: clean up SSH environment variable propagation 2025-06-25 15:46:18 -07:00
Jason Rayne
c70643404c bash: revert all formatting changes
Keeps only functional additions for SSH integration wrapper,
preserving original line breaks and indentation to minimize
diff noise per maintainer feedback.
2025-06-25 15:46:18 -07:00
Jason Rayne
842ced9212 bash: preserve mixed indentation in SSH integration changes
Preserves existing mixed indentation in ghostty.bash to minimize
diff noise per maintainer feedback.
2025-06-25 15:46:18 -07:00
Jason Rayne
2babdb458f refactor: simplify ssh integration environment variable checks 2025-06-25 15:46:18 -07:00
Jason Rayne
8f93d8fe03 fix: use kebab-case for ssh-integration enum values 2025-06-25 15:46:18 -07:00
Jason Rayne
142e07c502 feat: add SSH integration wrapper for shell integration
- Implements opt-in SSH wrapper following sudo pattern
- Supports term_only, basic, and full integration levels
- Fixes xterm-ghostty TERM compatibility on remote systems
- Propagates shell integration environment variables
- Allows for automatic installation of terminfo if desired
- Addresses GitHub discussions #5892 and #4156
2025-06-25 15:43:32 -07:00
Jon Parise
b629f3337a bash: remove dependency on $GHOSTTY_RESOURCES_DIR
We were depending on $GHOSTTY_RESOURCES_DIR for two reasons:

1. To locate our script-adjacent bash-preexec.sh script
2. To restrict our script's execution to environments in which
   $GHOSTTY_RESOURCES_DIR is available (i.e. Ghostty-only shells)

For (1), we can instead determine our directory using $BASH_SOURCE[0].
This is slightly differently than our previous behavior, where we'd
always load bash-preexec.sh from the $GHOSTTY_RESOURCES_DIR hierarchy,
even if ghostty.bash from source from somewhere else on the file system
... but we never relied on that behavior, even in development.

For (2), there's no harm in source'ing this script outside of Ghostty,
and if that does become a concern, we can restore this condition or use
something more targeted based on those specific cases.

Historically, I believe (2) was in place to enable (1), so addressing
(1) removes the need for (2).

And lastly, none of the other shell integration scripts depend on
$GHOSTTY_RESOURCES_DIR.
2025-06-16 19:54:27 -04:00
Jon Parise
314d52ac3a shell-integration: switch to $GHOSTTY_SHELL_FEATURES
This change consolidates all three opt-out shell integration environment
variables into a single opt-in $GHOSTTY_SHELL_FEATURES variable. Its
value is a comma-delimited list of the enabled shell feature names (e.g.
"cursor,title").

$GHOSTTY_SHELL_FEATURES is set at runtime and automatically added to the
shell environment. Its value is based on the shell-integration-features
configuration option.

$GHOSTTY_SHELL_FEATURES is only set when at least one shell feature is
enabled. It won't be set when 'shell-integration-features = false'.

$GHOSTTY_SHELL_FEATURES lists only the enabled shell feature names. We
could have alternatively gone in the opposite direction and listed the
disabled features, letting the scripts assume each feature is on by
default like we did before, but I think this explicit approach is a
little safer and easier to reason about / debug.

It also doesn't support the "no-" negation prefix used by the config
system (e.g. "cursor,no-title"). This simplifies the implementation
requirements of our (multiple) shell integration scripts, and because
$GHOSTTY_SHELL_FEATURES is derived from shell-integration-features,
the user-facing configuration interface retains that expressiveness.

$GHOSTTY_SHELL_FEATURES is intended to primarily be an internal concern:
an interface between the runtime and our shell integration scripts. It
could be used by people with particular use cases who want to manually
source those scripts, but that isn't the intended audience.

... and because the previous $GHOSTTY_SHELL_INTEGRATION_NO_* variables
were also meant to be an internal concern, this change does not include
backwards compatibility support for those names.

One last advantage of a using a single $GHOSTTY_SHELL_FEATURES variable
is that it can be easily forwarded to e.g. ssh sessions or other shell
environments.
2025-03-22 10:16:59 -04:00
Jon Parise
afa23532b6 bash: revert automatic shell integration changes
The intention of #5075 was to create a less intrusive, more hermetic
environment in which to source the bash startup files. This caused
problems for multiple people, and I believe that's because the general
expectation is that these files are sourced at global (not function)
scope.

For example, when a file is sourced from within a function scope, any
variables that weren't explicitly exported into the global environment
won't be available outside of the scope of the function. Most system and
personal startup files aren't written with that constraint because it's
not how bash itself loads these files.

As a small improvement over the original code, `rcfile` has been renamed
to `__ghostty_rcfile`. Avoiding leaking this variable while sourcing
these files was a goal of #5075, and prefixing it make it much less of a
potential issue.

This change also reverts the $HOME to ~/ change. While the ~/ notation
is more concise, using $HOME is more common and easier to implement
safely with regard to quoting.
2025-01-20 10:56:47 -05:00
Mitchell Hashimoto
b4a3ca999a bash: improve prior_trap processing (#5142)
We use `trap` to bootstrap our installation function (__bp_install). We
remove our code upon first execution but need to restore any preexisting
trap calls. We previously used `sed` to process the trap string, but
that had two downsides:

1. `sed` is an external command dependency. It needs to exist on the
system, and we need to invoke it in a subshell (which has some runtime
cost).
2. The regular expression pattern was imperfect and didn't handle
trickier cases like `'` characters in the trap string:

        $ (trap "echo 'hello'" DEBUG; trap -p DEBUG)
        hello
        trap -- 'echo '\''hello'\''' DEBUG

This change removes the dependency on `sed` by locally evaluating the
trap string and extracting any prior trap. This works reliably because
we control the format our trap string, which looks like this (with
newlines expanded):

    __bp_trap_string="$(trap -p DEBUG)"
    trap - DEBUG
    __bp_install

Upstream: https://github.com/rcaloras/bash-preexec/pull/170
2025-01-16 13:04:57 -08:00
Mitchell Hashimoto
d1eb8ccc52 bash: remove sed dependency for history processing (#5141)
We post-process history 1's output to extract the current command. This
processing needs to strip the leading history number, an optional *
character indicating whether the entry was modified (or a space), and
then a space separating character.

We were previously using sed(1) for this, but we can implement an
equivalent transformation using bash's native parameter expansion
syntax.

This also results in ~4x reduction in per-prompt command overhead.

Upstream: https://github.com/rcaloras/bash-preexec/pull/167
2025-01-16 13:04:24 -08:00
Jon Parise
df2d0b33cc bash: improve prior_trap processing
We use `trap` to bootstrap our installation function (__bp_install). We
remove our code upon first execution but need to restore any preexisting
trap calls. We previously used `sed` to process the trap string, but
that had two downsides:

1. `sed` is an external command dependency. It needs to exist on the
    system, and we need to invoke it in a subshell (which has some
    runtime cost).
2. The regular expression pattern was imperfect and didn't handle
    trickier cases like `'` characters in the trap string:

        $ (trap "echo 'hello'" DEBUG; trap -p DEBUG)
        hello
        trap -- 'echo '\''hello'\''' DEBUG

This change removes the dependency on `sed` by locally evaluating the
trap string and extracting any prior trap. This works reliably because
we control the format our trap string, which looks like this (with
newlines expanded):

    __bp_trap_string="$(trap -p DEBUG)"
    trap - DEBUG
    __bp_install
2025-01-16 08:30:27 -05:00
Jon Parise
07994d10e9 bash: remove sed dependency for history processing
We post-process history 1's output to extract the current command. This
processing needs to strip the leading history number, an optional *
character indicating whether the entry was modified (or a space), and
then a space separating character.

We were previously using sed(1) for this, but we can implement an
equivalent transformation using bash's native parameter expansion
syntax.

This also results in ~4x reduction in per-prompt command overhead.
2025-01-16 08:22:40 -05:00
Jon Parise
6af1850ab4 bash: less intrusive automatic shell integration
We now use a temporary function (__ghostty_bash_startup) to perform the
bash startup sequence. This gives us a local function scope in which to
store some temporary values (like rcfile). This way, they won't leak
into the sourced files' scopes.

Also, use `~/` instead of `$HOME` for home directory paths as a simpler
shorthand notation.
2025-01-16 08:11:26 -05:00