summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJason Rayne <yo@arcayne.dev>2025-06-25 12:47:38 -0700
committerJason Rayne <yo@arcayne.dev>2025-06-25 15:46:18 -0700
commit0565ed39546a982b7f47d0dd0ba341bc41cc092f (patch)
treeb2cc6fa4f0609c4befe649b30afcbb864aff15a6 /src
parent6789b7fb6e459b8ca143b946fd0f4f7e6d8fda34 (diff)
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.
Diffstat (limited to 'src')
-rw-r--r--src/cli/action.zig12
-rw-r--r--src/cli/clear_ssh_cache.zig40
-rw-r--r--src/cli/list_ssh_cache.zig38
-rw-r--r--src/cli/ssh_cache.zig71
-rw-r--r--src/shell-integration/bash/ghostty.bash46
-rw-r--r--src/shell-integration/elvish/lib/ghostty-integration.elv104
-rw-r--r--src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish95
-rw-r--r--src/shell-integration/zsh/ghostty-integration70
8 files changed, 301 insertions, 175 deletions
diff --git a/src/cli/action.zig b/src/cli/action.zig
index 009afb4c9..1d1c3bfa0 100644
--- a/src/cli/action.zig
+++ b/src/cli/action.zig
@@ -9,6 +9,8 @@ const list_keybinds = @import("list_keybinds.zig");
const list_themes = @import("list_themes.zig");
const list_colors = @import("list_colors.zig");
const list_actions = @import("list_actions.zig");
+const list_ssh_cache = @import("list_ssh_cache.zig");
+const clear_ssh_cache = @import("clear_ssh_cache.zig");
const edit_config = @import("edit_config.zig");
const show_config = @import("show_config.zig");
const validate_config = @import("validate_config.zig");
@@ -41,6 +43,12 @@ pub const Action = enum {
/// List keybind actions
@"list-actions",
+ /// List hosts with Ghostty SSH terminfo installed
+ @"list-ssh-cache",
+
+ /// Clear Ghostty SSH terminfo cache
+ @"clear-ssh-cache",
+
/// Edit the config file in the configured terminal editor.
@"edit-config",
@@ -155,6 +163,8 @@ pub const Action = enum {
.@"list-themes" => try list_themes.run(alloc),
.@"list-colors" => try list_colors.run(alloc),
.@"list-actions" => try list_actions.run(alloc),
+ .@"list-ssh-cache" => @import("list_ssh_cache.zig").run(alloc),
+ .@"clear-ssh-cache" => @import("clear_ssh_cache.zig").run(alloc),
.@"edit-config" => try edit_config.run(alloc),
.@"show-config" => try show_config.run(alloc),
.@"validate-config" => try validate_config.run(alloc),
@@ -192,6 +202,8 @@ pub const Action = enum {
.@"list-themes" => list_themes.Options,
.@"list-colors" => list_colors.Options,
.@"list-actions" => list_actions.Options,
+ .@"list-ssh-cache" => list_ssh_cache.Options,
+ .@"clear-ssh-cache" => clear_ssh_cache.Options,
.@"edit-config" => edit_config.Options,
.@"show-config" => show_config.Options,
.@"validate-config" => validate_config.Options,
diff --git a/src/cli/clear_ssh_cache.zig b/src/cli/clear_ssh_cache.zig
new file mode 100644
index 000000000..062af4221
--- /dev/null
+++ b/src/cli/clear_ssh_cache.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const args = @import("args.zig");
+const Action = @import("action.zig").Action;
+const ssh_cache = @import("ssh_cache.zig");
+
+pub const Options = struct {
+ pub fn deinit(self: Options) void {
+ _ = self;
+ }
+
+ /// Enables `-h` and `--help` to work.
+ pub fn help(self: Options) !void {
+ _ = self;
+ return Action.help_error;
+ }
+};
+
+/// Clear the Ghostty SSH terminfo cache.
+///
+/// This command removes the cache of hosts where Ghostty's terminfo has been installed
+/// via the ssh-terminfo shell integration feature. After clearing, terminfo will be
+/// reinstalled on the next SSH connection to previously cached hosts.
+///
+/// Use this if you need to force reinstallation of terminfo or clean up old entries.
+pub fn run(alloc: Allocator) !u8 {
+ var opts: Options = .{};
+ defer opts.deinit();
+
+ {
+ var iter = try args.argsIterator(alloc);
+ defer iter.deinit();
+ try args.parse(Options, alloc, &opts, &iter);
+ }
+
+ const stdout = std.io.getStdOut().writer();
+ try ssh_cache.clearCache(alloc, stdout);
+
+ return 0;
+}
diff --git a/src/cli/list_ssh_cache.zig b/src/cli/list_ssh_cache.zig
new file mode 100644
index 000000000..a799d95bd
--- /dev/null
+++ b/src/cli/list_ssh_cache.zig
@@ -0,0 +1,38 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const args = @import("args.zig");
+const Action = @import("action.zig").Action;
+const ssh_cache = @import("ssh_cache.zig");
+
+pub const Options = struct {
+ pub fn deinit(self: Options) void {
+ _ = self;
+ }
+
+ /// Enables `-h` and `--help` to work.
+ pub fn help(self: Options) !void {
+ _ = self;
+ return Action.help_error;
+ }
+};
+
+/// List hosts with Ghostty SSH terminfo installed via the ssh-terminfo shell integration feature.
+///
+/// This command shows all remote hosts where Ghostty's terminfo has been successfully
+/// installed through the SSH integration. The cache is automatically maintained when
+/// connecting to remote hosts with `shell-integration-features = ssh-terminfo` enabled.
+pub fn run(alloc: Allocator) !u8 {
+ var opts: Options = .{};
+ defer opts.deinit();
+
+ {
+ var iter = try args.argsIterator(alloc);
+ defer iter.deinit();
+ try args.parse(Options, alloc, &opts, &iter);
+ }
+
+ const stdout = std.io.getStdOut().writer();
+ try ssh_cache.listCachedHosts(alloc, stdout);
+
+ return 0;
+}
diff --git a/src/cli/ssh_cache.zig b/src/cli/ssh_cache.zig
new file mode 100644
index 000000000..c3484735a
--- /dev/null
+++ b/src/cli/ssh_cache.zig
@@ -0,0 +1,71 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Child = std.process.Child;
+
+/// Get the path to the shared cache script
+fn getCacheScriptPath(alloc: Allocator) ![]u8 {
+ // Use GHOSTTY_RESOURCES_DIR if available, otherwise assume relative path
+ const resources_dir = std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR") catch {
+ // Fallback: assume we're running from build directory
+ return try alloc.dupe(u8, "src/shell-integration/shared/ghostty-ssh-cache");
+ };
+ defer alloc.free(resources_dir);
+
+ return try std.fs.path.join(alloc, &[_][]const u8{ resources_dir, "shell-integration", "shared", "ghostty-ssh-cache" });
+}
+
+/// List cached hosts by calling the external script
+pub fn listCachedHosts(alloc: Allocator, writer: anytype) !void {
+ const script_path = try getCacheScriptPath(alloc);
+ defer alloc.free(script_path);
+
+ var child = Child.init(&[_][]const u8{ script_path, "list" }, alloc);
+ child.stdout_behavior = .Pipe;
+ child.stderr_behavior = .Pipe;
+
+ try child.spawn();
+
+ const stdout = try child.stdout.?.readToEndAlloc(alloc, std.math.maxInt(usize));
+ defer alloc.free(stdout);
+
+ const stderr = try child.stderr.?.readToEndAlloc(alloc, std.math.maxInt(usize));
+ defer alloc.free(stderr);
+
+ _ = try child.wait();
+
+ // Output the results regardless of exit code
+ try writer.writeAll(stdout);
+ if (stderr.len > 0) {
+ try writer.writeAll(stderr);
+ }
+
+ // Script handles its own success/error messaging, so we don't need to check exit code
+}
+
+/// Clear cache by calling the external script
+pub fn clearCache(alloc: Allocator, writer: anytype) !void {
+ const script_path = try getCacheScriptPath(alloc);
+ defer alloc.free(script_path);
+
+ var child = Child.init(&[_][]const u8{ script_path, "clear" }, alloc);
+ child.stdout_behavior = .Pipe;
+ child.stderr_behavior = .Pipe;
+
+ try child.spawn();
+
+ const stdout = try child.stdout.?.readToEndAlloc(alloc, std.math.maxInt(usize));
+ defer alloc.free(stdout);
+
+ const stderr = try child.stderr.?.readToEndAlloc(alloc, std.math.maxInt(usize));
+ defer alloc.free(stderr);
+
+ _ = try child.wait();
+
+ // Output the results regardless of exit code
+ try writer.writeAll(stdout);
+ if (stderr.len > 0) {
+ try writer.writeAll(stderr);
+ }
+
+ // Script handles its own success/error messaging, so we don't need to check exit code
+}
diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash
index 9ba11c845..f07df125e 100644
--- a/src/shell-integration/bash/ghostty.bash
+++ b/src/shell-integration/bash/ghostty.bash
@@ -100,19 +100,11 @@ if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-(env|terminfo) ]]; then
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-terminfo ]]; then
readonly _CACHE="${GHOSTTY_RESOURCES_DIR}/shell-integration/shared/ghostty-ssh-cache"
- # If 'ssh-terminfo' flag is enabled, wrap ghostty to provide cache management commands
- ghostty() {
- case "$1" in
- ssh-cache-list) "$_CACHE" list ;;
- ssh-cache-clear) "$_CACHE" clear ;;
- *) builtin command ghostty "$@" ;;
- esac
- }
fi
# SSH wrapper
ssh() {
- local e=() o=() c=() # Removed 't' from here
+ local env=() opts=() ctrl=()
# Set up env vars first so terminfo installation inherits them
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
@@ -123,35 +115,35 @@ if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-(env|terminfo) ]]; then
)
for v in "${vars[@]}"; do
builtin export "${v?}"
- o+=(-o "SendEnv ${v%=*}" -o "SetEnv $v")
+ opts+=(-o "SendEnv ${v%=*}" -o "SetEnv $v")
done
fi
# Install terminfo if needed, reuse control connection for main session
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-terminfo ]]; then
# Get target (only when needed for terminfo)
- builtin local t
- t=$(builtin command ssh -G "$@" 2>/dev/null | awk '/^(user|hostname) /{print $2}' | paste -sd'@')
+ builtin local target
+ target=$(builtin command ssh -G "$@" 2>/dev/null | awk '/^(user|hostname) /{print $2}' | paste -sd'@')
- if [[ -n "$t" ]] && "$_CACHE" chk "$t"; then
- e+=(TERM=xterm-ghostty)
+ if [[ -n "$target" ]] && "$_CACHE" chk "$target"; then
+ env+=(TERM=xterm-ghostty)
elif builtin command -v infocmp >/dev/null 2>&1; then
- builtin local ti
- ti=$(infocmp -x xterm-ghostty 2>/dev/null) || builtin echo "Warning: xterm-ghostty terminfo not found locally." >&2
- if [[ -n "$ti" ]]; then
+ builtin local tinfo
+ tinfo=$(infocmp -x xterm-ghostty 2>/dev/null) || builtin echo "Warning: xterm-ghostty terminfo not found locally." >&2
+ if [[ -n "$tinfo" ]]; then
builtin echo "Setting up Ghostty terminfo on remote host..." >&2
- builtin local cp
- cp="/tmp/ghostty-ssh-$USER-$RANDOM-$(date +%s)"
- case $(builtin echo "$ti" | builtin command ssh "${o[@]}" -o ControlMaster=yes -o ControlPath="$cp" -o ControlPersist=60s "$@" '
+ builtin local cpath
+ cpath="/tmp/ghostty-ssh-$USER-$RANDOM-$(date +%s)"
+ case $(builtin echo "$tinfo" | builtin command ssh "${opts[@]}" -o ControlMaster=yes -o ControlPath="$cpath" -o ControlPersist=60s "$@" '
infocmp xterm-ghostty >/dev/null 2>&1 && echo OK && exit
command -v tic >/dev/null 2>&1 || { echo NO_TIC; exit 1; }
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && echo OK || echo FAIL
') in
OK)
builtin echo "Terminfo setup complete." >&2
- [[ -n "$t" ]] && "$_CACHE" add "$t"
- e+=(TERM=xterm-ghostty)
- c+=(-o "ControlPath=$cp")
+ [[ -n "$target" ]] && "$_CACHE" add "$target"
+ env+=(TERM=xterm-ghostty)
+ ctrl+=(-o "ControlPath=$cpath")
;;
*) builtin echo "Warning: Failed to install terminfo." >&2 ;;
esac
@@ -163,14 +155,14 @@ if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-(env|terminfo) ]]; then
# Fallback TERM only if terminfo didn't set it
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
- [[ $TERM == xterm-ghostty && ! " ${e[*]} " =~ " TERM=" ]] && e+=(TERM=xterm-256color)
+ [[ $TERM == xterm-ghostty && ! " ${env[*]} " =~ " TERM=" ]] && env+=(TERM=xterm-256color)
fi
# Execute
- if [[ ${#e[@]} -gt 0 ]]; then
- env "${e[@]}" ssh "${o[@]}" "${c[@]}" "$@"
+ if [[ ${#env[@]} -gt 0 ]]; then
+ env "${env[@]}" ssh "${opts[@]}" "${ctrl[@]}" "$@"
else
- builtin command ssh "${o[@]}" "${c[@]}" "$@"
+ builtin command ssh "${opts[@]}" "${ctrl[@]}" "$@"
fi
}
fi
diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv
index 09aa09f31..7048d56b3 100644
--- a/src/shell-integration/elvish/lib/ghostty-integration.elv
+++ b/src/shell-integration/elvish/lib/ghostty-integration.elv
@@ -100,85 +100,68 @@
# SSH Integration
use str
- use path
- use re
-
- if (re:match 'ssh-(env|terminfo)' $E:GHOSTTY_SHELL_FEATURES) {
- if (re:match 'ssh-terminfo' $E:GHOSTTY_SHELL_FEATURES) {
- var _cache_script = (path:join $E:GHOSTTY_RESOURCES_DIR shell-integration shared ghostty-ssh-cache)
-
- # Wrap ghostty command to provide cache management commands
- fn ghostty {|@args|
- if (eq $args[0] ssh-cache-list) {
- (external $_cache_script) list
- } elif (eq $args[0] ssh-cache-clear) {
- (external $_cache_script) clear
- } else {
- (external ghostty) $@args
- }
- }
- edit:add-var ghostty~ $ghostty~
+ if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) or (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-terminfo) {
+
+ if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-terminfo) {
+ var _CACHE = $E:GHOSTTY_RESOURCES_DIR/shell-integration/shared/ghostty-ssh-cache
}
# SSH wrapper
fn ssh {|@args|
- var e = []
- var o = []
- var c = []
+ var env = []
+ var opts = []
+ var ctrl = []
# Set up env vars first so terminfo installation inherits them
- if (re:match 'ssh-env' $E:GHOSTTY_SHELL_FEATURES) {
- set-env COLORTERM (or $E:COLORTERM truecolor)
- set-env TERM_PROGRAM (or $E:TERM_PROGRAM ghostty)
- if (has-env GHOSTTY_VERSION) {
- set-env TERM_PROGRAM_VERSION $E:GHOSTTY_VERSION
- }
-
- var vars = [COLORTERM=truecolor TERM_PROGRAM=ghostty]
- if (has-env GHOSTTY_VERSION) {
+ if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) {
+ var vars = [
+ COLORTERM=truecolor
+ TERM_PROGRAM=ghostty
+ ]
+ if (not-eq $E:GHOSTTY_VERSION '') {
set vars = [$@vars TERM_PROGRAM_VERSION=$E:GHOSTTY_VERSION]
}
+
for v $vars {
- var varname = (str:split &max=2 '=' $v | take 1)
- set o = [$@o -o "SendEnv "$varname -o "SetEnv "$v]
+ set-env (str:split = $v | take 1) (str:split = $v | drop 1 | str:join =)
+ var varname = (str:split = $v | take 1)
+ set opts = [$@opts -o 'SendEnv '$varname -o 'SetEnv '$v]
}
}
# Install terminfo if needed, reuse control connection for main session
- if (re:match 'ssh-terminfo' $E:GHOSTTY_SHELL_FEATURES) {
- # Get target (only when needed for terminfo)
- var t = ""
+ if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-terminfo) {
+ # Get target
+ var target = ''
try {
- set t = (e:ssh -G $@args 2>/dev/null | awk '/^(user|hostname) /{print $2}' | paste -sd'@' | str:trim-space)
- } catch e {
- # Ignore errors
- }
+ set target = (e:ssh -G $@args 2>/dev/null | e:awk '/^(user|hostname) /{print $2}' | e:paste -sd'@')
+ } catch { }
- if (and (not-eq $t "") (try { (external $_cache_script) chk $t } catch e { put $false })) {
- set e = [$@e TERM=xterm-ghostty]
+ if (and (not-eq $target '') ($_CACHE chk $target)) {
+ set env = [$@env TERM=xterm-ghostty]
} elif (has-external infocmp) {
- var ti = ""
+ var tinfo = ''
try {
- set ti = (infocmp -x xterm-ghostty 2>/dev/null | slurp)
- } catch e {
+ set tinfo = (e:infocmp -x xterm-ghostty 2>/dev/null)
+ } catch {
echo "Warning: xterm-ghostty terminfo not found locally." >&2
}
- if (not-eq $ti "") {
+
+ if (not-eq $tinfo '') {
echo "Setting up Ghostty terminfo on remote host..." >&2
- var cp = "/tmp/ghostty-ssh-"$E:USER"-"(randint 10000)"-"(date +%s | str:trim-space)
- var result = (echo $ti | e:ssh $@o -o ControlMaster=yes -o ControlPath=$cp -o ControlPersist=60s $@args '
+ var cpath = '/tmp/ghostty-ssh-'$E:USER'-'(randint 0 32767)'-'(date +%s)
+ var result = (echo $tinfo | e:ssh $@opts -o ControlMaster=yes -o ControlPath=$cpath -o ControlPersist=60s $@args '
infocmp xterm-ghostty >/dev/null 2>&1 && echo OK && exit
command -v tic >/dev/null 2>&1 || { echo NO_TIC; exit 1; }
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && echo OK || echo FAIL
- ' | str:trim-space)
+ ')
+
if (eq $result OK) {
echo "Terminfo setup complete." >&2
- if (not-eq $t "") {
- (external $_cache_script) add $t
- }
- set e = [$@e TERM=xterm-ghostty]
- set c = [$@c -o ControlPath=$cp]
+ if (not-eq $target '') { $_CACHE add $target }
+ set env = [$@env TERM=xterm-ghostty]
+ set ctrl = [$@ctrl -o ControlPath=$cpath]
} else {
echo "Warning: Failed to install terminfo." >&2
}
@@ -189,22 +172,19 @@
}
# Fallback TERM only if terminfo didn't set it
- if (re:match 'ssh-env' $E:GHOSTTY_SHELL_FEATURES) {
- if (and (eq $E:TERM xterm-ghostty) (not (re:match 'TERM=' (str:join ' ' $e)))) {
- set e = [$@e TERM=xterm-256color]
+ if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) {
+ if (and (eq $E:TERM xterm-ghostty) (not (str:contains (str:join ' ' $env) 'TERM='))) {
+ set env = [$@env TERM=xterm-256color]
}
}
# Execute
- if (> (count $e) 0) {
- e:env $@e e:ssh $@o $@c $@args
+ if (> (count $env) 0) {
+ e:env $@env e:ssh $@opts $@ctrl $@args
} else {
- e:ssh $@o $@c $@args
+ e:ssh $@opts $@ctrl $@args
}
}
-
- # Export ssh function for global use
- set edit:add-var[ssh] = $ssh~
}
defer {
diff --git a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish
index 55ce34985..9b82f5fa2 100644
--- a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish
+++ b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish
@@ -87,89 +87,86 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
end
# SSH Integration
- if string match -qr 'ssh-(env|terminfo)' "$GHOSTTY_SHELL_FEATURES"
- if string match -qr ssh-terminfo "$GHOSTTY_SHELL_FEATURES"
- set -g _cache_script "$GHOSTTY_RESOURCES_DIR/shell-integration/shared/ghostty-ssh-cache"
-
- # Wrap ghostty command to provide cache management commands
- function ghostty -d "Wrap ghostty to provide cache management commands"
- switch "$argv[1]"
- case ssh-cache-list
- command "$_cache_script" list
- case ssh-cache-clear
- command "$_cache_script" clear
- case "*"
- command ghostty $argv
- end
- end
+ if string match -qr 'ssh-(env|terminfo)' $GHOSTTY_SHELL_FEATURES
+
+ if string match -q '*ssh-terminfo*' $GHOSTTY_SHELL_FEATURES
+ set -g _CACHE "$GHOSTTY_RESOURCES_DIR/shell-integration/shared/ghostty-ssh-cache"
end
# SSH wrapper
function ssh
- set -l e
- set -l o
- set -l c
+ set -l env
+ set -l opts
+ set -l ctrl
# Set up env vars first so terminfo installation inherits them
- if string match -qr ssh-env "$GHOSTTY_SHELL_FEATURES"
- set -gx COLORTERM (test -n "$COLORTERM" && echo "$COLORTERM" || echo "truecolor")
- set -gx TERM_PROGRAM (test -n "$TERM_PROGRAM" && echo "$TERM_PROGRAM" || echo "ghostty")
- test -n "$GHOSTTY_VERSION" && set -gx TERM_PROGRAM_VERSION "$GHOSTTY_VERSION"
+ if string match -q '*ssh-env*' $GHOSTTY_SHELL_FEATURES
+ set -l vars \
+ COLORTERM=truecolor \
+ TERM_PROGRAM=ghostty
- set -l vars COLORTERM=truecolor TERM_PROGRAM=ghostty
- test -n "$GHOSTTY_VERSION" && set vars $vars "TERM_PROGRAM_VERSION=$GHOSTTY_VERSION"
+ if test -n "$GHOSTTY_VERSION"
+ set -a vars "TERM_PROGRAM_VERSION=$GHOSTTY_VERSION"
+ end
for v in $vars
- set -l varname (string split -m1 '=' "$v")[1]
- set o $o -o "SendEnv $varname" -o "SetEnv $v"
+ set -l parts (string split = $v)
+ set -gx $parts[1] $parts[2]
+ set -a opts -o "SendEnv $parts[1]" -o "SetEnv $v"
end
end
# Install terminfo if needed, reuse control connection for main session
- if string match -qr ssh-terminfo "$GHOSTTY_SHELL_FEATURES"
- # Get target (only when needed for terminfo)
- set -l t (builtin command ssh -G $argv 2>/dev/null | awk '/^(user|hostname) /{print $2}' | paste -sd'@')
+ if string match -q '*ssh-terminfo*' $GHOSTTY_SHELL_FEATURES
+ # Get target
+ set -l target (command ssh -G $argv 2>/dev/null | awk '/^(user|hostname) /{print $2}' | paste -sd'@')
- if test -n "$t" && command "$_cache_script" chk "$t"
- set e $e TERM=xterm-ghostty
+ if test -n "$target" -a ("$_CACHE" chk "$target")
+ set -a env TERM=xterm-ghostty
else if command -v infocmp >/dev/null 2>&1
- set -l ti
- set ti (infocmp -x xterm-ghostty 2>/dev/null) || builtin echo "Warning: xterm-ghostty terminfo not found locally." >&2
- if test -n "$ti"
- builtin echo "Setting up Ghostty terminfo on remote host..." >&2
- set -l cp "/tmp/ghostty-ssh-$USER-"(random)"-"(date +%s)
- set -l result (builtin echo "$ti" | builtin command ssh $o -o ControlMaster=yes -o ControlPath="$cp" -o ControlPersist=60s $argv '
+ set -l tinfo (infocmp -x xterm-ghostty 2>/dev/null)
+ set -l status_code $status
+
+ if test $status_code -ne 0
+ echo "Warning: xterm-ghostty terminfo not found locally." >&2
+ end
+
+ if test -n "$tinfo"
+ echo "Setting up Ghostty terminfo on remote host..." >&2
+ set -l cpath "/tmp/ghostty-ssh-$USER-"(random)"-"(date +%s)
+ set -l result (echo "$tinfo" | command ssh $opts -o ControlMaster=yes -o ControlPath="$cpath" -o ControlPersist=60s $argv '
infocmp xterm-ghostty >/dev/null 2>&1 && echo OK && exit
command -v tic >/dev/null 2>&1 || { echo NO_TIC; exit 1; }
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && echo OK || echo FAIL
')
+
switch $result
case OK
- builtin echo "Terminfo setup complete." >&2
- test -n "$t" && command "$_cache_script" add "$t"
- set e $e TERM=xterm-ghostty
- set c $c -o "ControlPath=$cp"
+ echo "Terminfo setup complete." >&2
+ test -n "$target" && "$_CACHE" add "$target"
+ set -a env TERM=xterm-ghostty
+ set -a ctrl -o "ControlPath=$cpath"
case '*'
- builtin echo "Warning: Failed to install terminfo." >&2
+ echo "Warning: Failed to install terminfo." >&2
end
end
else
- builtin echo "Warning: infocmp not found locally. Terminfo installation unavailable." >&2
+ echo "Warning: infocmp not found locally. Terminfo installation unavailable." >&2
end
end
# Fallback TERM only if terminfo didn't set it
- if string match -qr ssh-env "$GHOSTTY_SHELL_FEATURES"
- if test "$TERM" = xterm-ghostty && not string match -q '*TERM=*' "$e"
- set e $e TERM=xterm-256color
+ if string match -q '*ssh-env*' $GHOSTTY_SHELL_FEATURES
+ if test "$TERM" = xterm-ghostty -a ! (string join ' ' $env | string match -q '*TERM=*')
+ set -a env TERM=xterm-256color
end
end
# Execute
- if test (count $e) -gt 0
- env $e ssh $o $c $argv
+ if test (count $env) -gt 0
+ env $env command ssh $opts $ctrl $argv
else
- builtin command ssh $o $c $argv
+ command ssh $opts $ctrl $argv
end
end
end
diff --git a/src/shell-integration/zsh/ghostty-integration b/src/shell-integration/zsh/ghostty-integration
index 4305ab61f..4d4461775 100644
--- a/src/shell-integration/zsh/ghostty-integration
+++ b/src/shell-integration/zsh/ghostty-integration
@@ -246,80 +246,76 @@ _ghostty_deferred_init() {
# SSH Integration
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-(env|terminfo) ]]; then
+
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-terminfo ]]; then
- readonly _cache_script="${GHOSTTY_RESOURCES_DIR}/shell-integration/shared/ghostty-ssh-cache"
-
- # Wrap ghostty command to provide cache management commands
- ghostty() {
- case "$1" in
- ssh-cache-list) "$_cache_script" list ;;
- ssh-cache-clear) "$_cache_script" clear ;;
- *) builtin command ghostty "$@" ;;
- esac
- }
+ readonly _CACHE="${GHOSTTY_RESOURCES_DIR}/shell-integration/shared/ghostty-ssh-cache"
fi
# SSH wrapper
ssh() {
- local -a e o c
-
+ local -a env opts ctrl
+ env=()
+ opts=()
+ ctrl=()
+
# Set up env vars first so terminfo installation inherits them
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
- local vars=(
+ local -a vars
+ vars=(
COLORTERM=truecolor
TERM_PROGRAM=ghostty
${GHOSTTY_VERSION:+TERM_PROGRAM_VERSION=$GHOSTTY_VERSION}
)
for v in "${vars[@]}"; do
- builtin export "${v?}"
- o+=(-o "SendEnv ${v%=*}" -o "SetEnv $v")
+ export "${v?}"
+ opts+=(-o "SendEnv ${v%=*}" -o "SetEnv $v")
done
fi
# Install terminfo if needed, reuse control connection for main session
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-terminfo ]]; then
# Get target (only when needed for terminfo)
- builtin local t
- t=$(builtin command ssh -G "$@" 2>/dev/null | awk '/^(user|hostname) /{print $2}' | paste -sd'@')
+ local target
+ target=$(command ssh -G "$@" 2>/dev/null | awk '/^(user|hostname) /{print $2}' | paste -sd'@')
- if [[ -n $t ]] && "$_cache_script" chk "$t"; then
- e+=(TERM=xterm-ghostty)
- elif builtin command -v infocmp >/dev/null 2>&1; then
- local ti
- ti=$(infocmp -x xterm-ghostty 2>/dev/null) || builtin echo "Warning: xterm-ghostty terminfo not found locally." >&2
- if [[ -n $ti ]]; then
- builtin echo "Setting up Ghostty terminfo on remote host..." >&2
- local cp
- cp="/tmp/ghostty-ssh-$USER-$RANDOM-$(date +%s)"
- case $(builtin echo "$ti" | builtin command ssh "${o[@]}" -o ControlMaster=yes -o ControlPath="$cp" -o ControlPersist=60s "$@" '
+ if [[ -n "$target" ]] && "$_CACHE" chk "$target"; then
+ env+=(TERM=xterm-ghostty)
+ elif command -v infocmp >/dev/null 2>&1; then
+ local tinfo
+ tinfo=$(infocmp -x xterm-ghostty 2>/dev/null) || echo "Warning: xterm-ghostty terminfo not found locally." >&2
+ if [[ -n "$tinfo" ]]; then
+ echo "Setting up Ghostty terminfo on remote host..." >&2
+ local cpath
+ cpath="/tmp/ghostty-ssh-$USER-$RANDOM-$(date +%s)"
+ case $(echo "$tinfo" | command ssh "${opts[@]}" -o ControlMaster=yes -o ControlPath="$cpath" -o ControlPersist=60s "$@" '
infocmp xterm-ghostty >/dev/null 2>&1 && echo OK && exit
command -v tic >/dev/null 2>&1 || { echo NO_TIC; exit 1; }
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && echo OK || echo FAIL
') in
OK)
- builtin echo "Terminfo setup complete." >&2
- [[ -n $t ]] && "$_cache_script" add "$t"
- e+=(TERM=xterm-ghostty)
- c+=(-o "ControlPath=$cp")
+ echo "Terminfo setup complete." >&2
+ [[ -n "$target" ]] && "$_CACHE" add "$target"
+ env+=(TERM=xterm-ghostty)
+ ctrl+=(-o "ControlPath=$cpath")
;;
- *) builtin echo "Warning: Failed to install terminfo." >&2 ;;
+ *) echo "Warning: Failed to install terminfo." >&2 ;;
esac
fi
else
- builtin echo "Warning: infocmp not found locally. Terminfo installation unavailable." >&2
+ echo "Warning: infocmp not found locally. Terminfo installation unavailable." >&2
fi
fi
# Fallback TERM only if terminfo didn't set it
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
- [[ $TERM == xterm-ghostty && ! " ${(j: :)e} " =~ " TERM=" ]] && e+=(TERM=xterm-256color)
+ [[ $TERM == xterm-ghostty && ! " ${env[*]} " =~ " TERM=" ]] && env+=(TERM=xterm-256color)
fi
# Execute
- if (( ${#e} > 0 )); then
- env "${e[@]}" ssh "${o[@]}" "${c[@]}" "$@"
+ if [[ ${#env[@]} -gt 0 ]]; then
+ env "${env[@]}" command ssh "${opts[@]}" "${ctrl[@]}" "$@"
else
- builtin command ssh "${o[@]}" "${c[@]}" "$@"
+ command ssh "${opts[@]}" "${ctrl[@]}" "$@"
fi
}
fi