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)
This commit is contained in:
Jason Rayne
2025-07-05 13:02:35 -07:00
parent 5ec18f426c
commit a22074a85c
4 changed files with 381 additions and 422 deletions

View File

@ -97,121 +97,105 @@ fi
# SSH Integration
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-(env|terminfo) ]]; then
: "${GHOSTTY_SSH_CACHE_TIMEOUT:=5}"
: "${GHOSTTY_SSH_CHECK_TIMEOUT:=3}"
# SSH wrapper that preserves Ghostty features across remote connections
ssh() {
local ssh_env=() ssh_opts=()
builtin local ssh_env ssh_opts ssh_exported_vars
ssh_env=()
ssh_opts=()
ssh_exported_vars=()
# Configure environment variables for remote session
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
local -a ssh_env_vars=(
ssh_opts+=(-o "SetEnv COLORTERM=truecolor")
if [[ -n "${TERM_PROGRAM+x}" ]]; then
ssh_exported_vars+=("TERM_PROGRAM=${TERM_PROGRAM}")
else
ssh_exported_vars+=("TERM_PROGRAM")
fi
builtin export "TERM_PROGRAM=ghostty"
ssh_opts+=(-o "SendEnv TERM_PROGRAM")
if [[ -n "$TERM_PROGRAM_VERSION" ]]; then
if [[ -n "${TERM_PROGRAM_VERSION+x}" ]]; then
ssh_exported_vars+=("TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
else
ssh_exported_vars+=("TERM_PROGRAM_VERSION")
fi
ssh_opts+=(-o "SendEnv TERM_PROGRAM_VERSION")
fi
ssh_env+=(
"COLORTERM=truecolor"
"TERM_PROGRAM=ghostty"
)
if [[ -n "$TERM_PROGRAM_VERSION" ]]; then
ssh_env_vars+=("TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION")
ssh_env+=("TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION")
fi
# Temporarily export variables for SSH transmission
local -a ssh_exported_vars=()
for ssh_v in "${ssh_env_vars[@]}"; do
local ssh_var_name="${ssh_v%%=*}"
if [[ -n "${!ssh_var_name+x}" ]]; then
ssh_exported_vars+=("$ssh_var_name=${!ssh_var_name}")
else
ssh_exported_vars+=("$ssh_var_name")
fi
builtin export "${ssh_v?}"
# Use both SendEnv and SetEnv for maximum compatibility
ssh_opts+=(-o "SendEnv $ssh_var_name")
ssh_opts+=(-o "SetEnv $ssh_v")
done
ssh_env+=("${ssh_env_vars[@]}")
fi
# Install terminfo on remote host if needed
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-terminfo ]]; then
builtin local ssh_config ssh_user ssh_hostname
ssh_config=$(builtin command ssh -G "$@" 2>/dev/null)
ssh_user=$(echo "$ssh_config" | while IFS=' ' read -r ssh_key ssh_value; do
[[ "$ssh_key" == "ssh_user" ]] && echo "$ssh_value" && break
done)
ssh_hostname=$(echo "$ssh_config" | while IFS=' ' read -r ssh_key ssh_value; do
[[ "$ssh_key" == "hostname" ]] && echo "$ssh_value" && break
done)
while IFS=' ' read -r ssh_key ssh_value; do
case "$ssh_key" in
user) ssh_user="$ssh_value" ;;
hostname) ssh_hostname="$ssh_value" ;;
esac
[[ -n "$ssh_user" && -n "$ssh_hostname" ]] && break
done <<< "$ssh_config"
ssh_target="${ssh_user}@${ssh_hostname}"
if [[ -n "$ssh_hostname" ]]; then
# Detect timeout command (BSD compatibility)
local ssh_timeout_cmd=""
if command -v timeout >/dev/null 2>&1; then
ssh_timeout_cmd="timeout"
elif command -v gtimeout >/dev/null 2>&1; then
ssh_timeout_cmd="gtimeout"
fi
# Check if terminfo is already cached
local ssh_cache_check_success=false
if command -v ghostty >/dev/null 2>&1; then
if [[ -n "$ssh_timeout_cmd" ]]; then
$ssh_timeout_cmd "${GHOSTTY_SSH_CHECK_TIMEOUT}s" ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1 && ssh_cache_check_success=true
else
builtin local ssh_cache_check_success=false
if builtin command -v ghostty >/dev/null 2>&1; then
ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1 && ssh_cache_check_success=true
fi
fi
if [[ "$ssh_cache_check_success" == "true" ]]; then
ssh_env+=(TERM=xterm-ghostty)
elif builtin command -v infocmp >/dev/null 2>&1; then
builtin local ssh_terminfo
if ! builtin command -v base64 >/dev/null 2>&1; then
builtin echo "Warning: base64 command not available for terminfo installation." >&2
ssh_env+=(TERM=xterm-256color)
else
builtin local ssh_terminfo ssh_base64_decode_cmd
# Generate terminfo data (BSD base64 compatibility)
# BSD vs GNU base64 compatibility
if base64 --help 2>&1 | grep -q GNU; then
ssh_base64_decode_cmd="base64 -d"
ssh_terminfo=$(infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 -w0 2>/dev/null)
else
ssh_base64_decode_cmd="base64 -D"
ssh_terminfo=$(infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 2>/dev/null | tr -d '\n')
fi
if [[ -n "$ssh_terminfo" ]]; then
builtin echo "Setting up Ghostty terminfo on remote host..." >&2
builtin echo "Setting up Ghostty terminfo on $ssh_hostname..." >&2
builtin local ssh_cpath_dir ssh_cpath
ssh_cpath_dir=$(mktemp -d "/tmp/ghostty-ssh-$ssh_user.XXXXXX" 2>/dev/null) || ssh_cpath_dir="/tmp/ghostty-ssh-$ssh_user.$$"
ssh_cpath="$ssh_cpath_dir/socket"
local ssh_base64_decode_cmd
if base64 --help 2>&1 | grep -q GNU; then
ssh_base64_decode_cmd="base64 -d"
else
ssh_base64_decode_cmd="base64 -D"
fi
if builtin echo "$ssh_terminfo" | $ssh_base64_decode_cmd | builtin command ssh "${ssh_opts[@]}" -o ControlMaster=yes -o ControlPath="$ssh_cpath" -o ControlPersist=60s "$@" '
infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
command -v tic >/dev/null 2>&1 || exit 1
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
exit 1
' 2>/dev/null; then
builtin echo "Terminfo setup complete." >&2
builtin echo "Terminfo setup complete on $ssh_hostname." >&2
ssh_env+=(TERM=xterm-ghostty)
ssh_opts+=(-o "ControlPath=$ssh_cpath")
# Cache successful installation
if [[ -n "$ssh_target" ]] && command -v ghostty >/dev/null 2>&1; then
if [[ -n "$ssh_target" ]] && builtin command -v ghostty >/dev/null 2>&1; then
(
set +m
{
if [[ -n "$ssh_timeout_cmd" ]]; then
$ssh_timeout_cmd "${GHOSTTY_SSH_CACHE_TIMEOUT}s" ghostty +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true
else
ghostty +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true
fi
} &
)
fi
@ -223,6 +207,7 @@ if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-(env|terminfo) ]]; then
builtin echo "Warning: Could not generate terminfo data." >&2
ssh_env+=(TERM=xterm-256color)
fi
fi
else
builtin echo "Warning: ghostty command not available for cache management." >&2
ssh_env+=(TERM=xterm-256color)
@ -234,22 +219,30 @@ if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-(env|terminfo) ]]; then
fi
fi
# Ensure TERM is set when using ssh-env feature
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
local ssh_term_set=false
# Execute SSH with environment handling
builtin local ssh_term_override=""
for ssh_v in "${ssh_env[@]}"; do
if [[ "$ssh_v" =~ ^TERM= ]]; then
ssh_term_set=true
if [[ "$ssh_v" =~ ^TERM=(.*)$ ]]; then
ssh_term_override="${BASH_REMATCH[1]}"
break
fi
done
if [[ "$ssh_term_set" == "false" && "$TERM" == "xterm-ghostty" ]]; then
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env && -z "$ssh_term_override" ]]; then
ssh_env+=(TERM=xterm-256color)
fi
ssh_term_override="xterm-256color"
fi
if [[ -n "$ssh_term_override" ]]; then
builtin local ssh_original_term="$TERM"
builtin export TERM="$ssh_term_override"
builtin command ssh "${ssh_opts[@]}" "$@"
local ssh_ret=$?
builtin export TERM="$ssh_original_term"
else
builtin command ssh "${ssh_opts[@]}" "$@"
local ssh_ret=$?
fi
# Restore original environment variables
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then

View File

@ -100,50 +100,41 @@
# SSH Integration
use str
use re
if (or (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-terminfo)) {
var GHOSTTY_SSH_CACHE_TIMEOUT = (if (has-env GHOSTTY_SSH_CACHE_TIMEOUT) { echo $E:GHOSTTY_SSH_CACHE_TIMEOUT } else { echo 5 })
var GHOSTTY_SSH_CHECK_TIMEOUT = (if (has-env GHOSTTY_SSH_CHECK_TIMEOUT) { echo $E:GHOSTTY_SSH_CHECK_TIMEOUT } else { echo 3 })
# SSH wrapper that preserves Ghostty features across remote connections
fn ssh {|@args|
var ssh-env = []
var ssh-opts = []
var ssh-exported-vars = []
# Configure environment variables for remote session
if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) {
var ssh-env-vars = [
COLORTERM=truecolor
TERM_PROGRAM=ghostty
]
set ssh-opts = [$@ssh-opts -o "SetEnv COLORTERM=truecolor"]
if (has-env TERM_PROGRAM) {
set ssh-exported-vars = [$@ssh-exported-vars "TERM_PROGRAM="$E:TERM_PROGRAM]
} else {
set ssh-exported-vars = [$@ssh-exported-vars "TERM_PROGRAM"]
}
set-env TERM_PROGRAM ghostty
set ssh-opts = [$@ssh-opts -o "SendEnv TERM_PROGRAM"]
if (has-env TERM_PROGRAM_VERSION) {
set ssh-env-vars = [$@ssh-env-vars TERM_PROGRAM_VERSION=$E:TERM_PROGRAM_VERSION]
}
# Store original values for restoration
var ssh-exported-vars = []
for ssh-v $ssh-env-vars {
var ssh-var-name = (str:split &max=2 = $ssh-v)[0]
if (has-env $ssh-var-name) {
var original-value = (get-env $ssh-var-name)
set ssh-exported-vars = [$@ssh-exported-vars $ssh-var-name=$original-value]
if (has-env TERM_PROGRAM_VERSION) {
set ssh-exported-vars = [$@ssh-exported-vars "TERM_PROGRAM_VERSION="$E:TERM_PROGRAM_VERSION]
} else {
set ssh-exported-vars = [$@ssh-exported-vars $ssh-var-name]
set ssh-exported-vars = [$@ssh-exported-vars "TERM_PROGRAM_VERSION"]
}
set ssh-opts = [$@ssh-opts -o "SendEnv TERM_PROGRAM_VERSION"]
}
# Export the variable
var ssh-var-parts = (str:split &max=2 = $ssh-v)
set-env $ssh-var-parts[0] $ssh-var-parts[1]
# Use both SendEnv and SetEnv for maximum compatibility
set ssh-opts = [$@ssh-opts -o "SendEnv "$ssh-var-name]
set ssh-opts = [$@ssh-opts -o "SetEnv "$ssh-v]
set ssh-env = [
"COLORTERM=truecolor"
"TERM_PROGRAM=ghostty"
]
if (has-env TERM_PROGRAM_VERSION) {
set ssh-env = [$@ssh-env "TERM_PROGRAM_VERSION="$E:TERM_PROGRAM_VERSION]
}
set ssh-env = [$@ssh-env $@ssh-env-vars]
}
# Install terminfo on remote host if needed
@ -160,53 +151,29 @@
for line (str:split "\n" $ssh-config) {
var parts = (str:split " " $line)
if (and (> (count $parts) 1) (eq $parts[0] user)) {
if (> (count $parts) 1) {
if (eq $parts[0] user) {
set ssh-user = $parts[1]
}
if (and (> (count $parts) 1) (eq $parts[0] hostname)) {
} elif (eq $parts[0] hostname) {
set ssh-hostname = $parts[1]
}
if (and (not-eq $ssh-user "") (not-eq $ssh-hostname "")) {
break
}
}
}
var ssh-target = $ssh-user"@"$ssh-hostname
if (not-eq $ssh-hostname "") {
# Detect timeout command (BSD compatibility)
var ssh-timeout-cmd = ""
try {
external timeout --help >/dev/null 2>&1
set ssh-timeout-cmd = timeout
} catch {
try {
external gtimeout --help >/dev/null 2>&1
set ssh-timeout-cmd = gtimeout
} catch {
# no timeout command available
}
}
# Check if terminfo is already cached
var ssh-cache-check-success = $false
try {
external ghostty --help >/dev/null 2>&1
if (not-eq $ssh-timeout-cmd "") {
try {
external $ssh-timeout-cmd $GHOSTTY_SSH_CHECK_TIMEOUT"s" ghostty +ssh-cache --host=$ssh-target >/dev/null 2>&1
set ssh-cache-check-success = $true
} catch {
# cache check failed
}
} else {
try {
external ghostty +ssh-cache --host=$ssh-target >/dev/null 2>&1
set ssh-cache-check-success = $true
} catch {
# cache check failed
}
}
} catch {
# ghostty not available
}
if $ssh-cache-check-success {
set ssh-env = [$@ssh-env TERM=xterm-ghostty]
@ -214,13 +181,19 @@
try {
external infocmp --help >/dev/null 2>&1
try {
external base64 --help >/dev/null 2>&1
# Generate terminfo data (BSD base64 compatibility)
var ssh-terminfo = ""
var ssh-base64-decode-cmd = ""
try {
var base64-help = (external base64 --help 2>&1 | slurp)
if (str:contains $base64-help GNU) {
set ssh-base64-decode-cmd = "base64 -d"
set ssh-terminfo = (external infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | external base64 -w0 2>/dev/null | slurp)
} else {
set ssh-base64-decode-cmd = "base64 -D"
set ssh-terminfo = (external infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | external base64 2>/dev/null | external tr -d '\n' | slurp)
}
} catch {
@ -228,7 +201,7 @@
}
if (not-eq $ssh-terminfo "") {
echo "Setting up Ghostty terminfo on remote host..." >&2
echo "Setting up Ghostty terminfo on "$ssh-hostname"..." >&2
var ssh-cpath-dir = ""
try {
set ssh-cpath-dir = (external mktemp -d "/tmp/ghostty-ssh-"$ssh-user".XXXXXX" 2>/dev/null | slurp)
@ -237,18 +210,6 @@
}
var ssh-cpath = $ssh-cpath-dir"/socket"
var ssh-base64-decode-cmd = ""
try {
var base64-help = (external base64 --help 2>&1 | slurp)
if (str:contains $base64-help GNU) {
set ssh-base64-decode-cmd = "base64 -d"
} else {
set ssh-base64-decode-cmd = "base64 -D"
}
} catch {
set ssh-base64-decode-cmd = "base64 -d"
}
var terminfo-install-success = $false
try {
echo $ssh-terminfo | external sh -c $ssh-base64-decode-cmd | external ssh $@ssh-opts -o ControlMaster=yes -o ControlPath=$ssh-cpath -o ControlPersist=60s $@args '
@ -263,18 +224,14 @@
}
if $terminfo-install-success {
echo "Terminfo setup complete." >&2
echo "Terminfo setup complete on "$ssh-hostname"." >&2
set ssh-env = [$@ssh-env TERM=xterm-ghostty]
set ssh-opts = [$@ssh-opts -o ControlPath=$ssh-cpath]
# Cache successful installation
if (and (not-eq $ssh-target "") (has-external ghostty)) {
if (not-eq $ssh-timeout-cmd "") {
external $ssh-timeout-cmd $GHOSTTY_SSH_CACHE_TIMEOUT"s" ghostty +ssh-cache --add=$ssh-target >/dev/null 2>&1 &
} else {
external ghostty +ssh-cache --add=$ssh-target >/dev/null 2>&1 &
}
}
} else {
echo "Warning: Failed to install terminfo." >&2
set ssh-env = [$@ssh-env TERM=xterm-256color]
@ -283,6 +240,10 @@
echo "Warning: Could not generate terminfo data." >&2
set ssh-env = [$@ssh-env TERM=xterm-256color]
}
} catch {
echo "Warning: base64 command not available for terminfo installation." >&2
set ssh-env = [$@ssh-env TERM=xterm-256color]
}
} catch {
echo "Warning: ghostty command not available for cache management." >&2
set ssh-env = [$@ssh-env TERM=xterm-256color]
@ -295,26 +256,37 @@
}
}
# Ensure TERM is set when using ssh-env feature
if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) {
var ssh-term-set = $false
# Execute SSH with environment handling
var ssh-term-override = ""
for ssh-v $ssh-env {
if (str:has-prefix $ssh-v TERM=) {
set ssh-term-set = $true
set ssh-term-override = (str:trim-prefix $ssh-v TERM=)
break
}
}
if (and (not $ssh-term-set) (eq $E:TERM xterm-ghostty)) {
if (and (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) (eq $ssh-term-override "")) {
set ssh-env = [$@ssh-env TERM=xterm-256color]
}
set ssh-term-override = xterm-256color
}
var ssh-ret = 0
if (not-eq $ssh-term-override "") {
var ssh-original-term = $E:TERM
set-env TERM $ssh-term-override
try {
external ssh $@ssh-opts $@args
} catch e {
set ssh-ret = $e[reason][exit-status]
}
set-env TERM $ssh-original-term
} else {
try {
external ssh $@ssh-opts $@args
} catch e {
set ssh-ret = $e[reason][exit-status]
}
}
# Restore original environment variables
if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) {

View File

@ -86,124 +86,110 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
end
end
# SSH Integration for Fish Shell
# SSH Integration
if string match -q '*ssh-env*' -- "$GHOSTTY_SHELL_FEATURES"; or string match -q '*ssh-terminfo*' -- "$GHOSTTY_SHELL_FEATURES"
set -g GHOSTTY_SSH_CACHE_TIMEOUT (test -n "$GHOSTTY_SSH_CACHE_TIMEOUT"; and echo $GHOSTTY_SSH_CACHE_TIMEOUT; or echo 5)
set -g GHOSTTY_SSH_CHECK_TIMEOUT (test -n "$GHOSTTY_SSH_CHECK_TIMEOUT"; and echo $GHOSTTY_SSH_CHECK_TIMEOUT; or echo 3)
# SSH wrapper that preserves Ghostty features across remote connections
function ssh --wraps=ssh --description "SSH wrapper with Ghostty integration"
set -l ssh_env
set -l ssh_opts
set -l ssh_exported_vars
# Configure environment variables for remote session
if string match -q '*ssh-env*' -- "$GHOSTTY_SHELL_FEATURES"
set -l ssh_env_vars \
"COLORTERM=truecolor" \
"TERM_PROGRAM=ghostty"
set -a ssh_opts -o "SetEnv COLORTERM=truecolor"
if set -q TERM_PROGRAM
set -a ssh_exported_vars "TERM_PROGRAM=$TERM_PROGRAM"
else
set -a ssh_exported_vars "TERM_PROGRAM"
end
set -gx TERM_PROGRAM ghostty
set -a ssh_opts -o "SendEnv TERM_PROGRAM"
if test -n "$TERM_PROGRAM_VERSION"
set -a ssh_env_vars "TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION"
end
# Store original values for restoration
set -l ssh_exported_vars
for ssh_v in $ssh_env_vars
set -l ssh_var_name (string split -m1 '=' -- $ssh_v)[1]
if set -q $ssh_var_name
set -a ssh_exported_vars "$ssh_var_name="(eval echo \$$ssh_var_name)
if set -q TERM_PROGRAM_VERSION
set -a ssh_exported_vars "TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION"
else
set -a ssh_exported_vars $ssh_var_name
set -a ssh_exported_vars "TERM_PROGRAM_VERSION"
end
set -a ssh_opts -o "SendEnv TERM_PROGRAM_VERSION"
end
# Export the variable
set -gx (string split -m1 '=' -- $ssh_v)
# Use both SendEnv and SetEnv for maximum compatibility
set -a ssh_opts -o "SendEnv $ssh_var_name"
set -a ssh_opts -o "SetEnv $ssh_v"
set -a ssh_env "COLORTERM=truecolor"
set -a ssh_env "TERM_PROGRAM=ghostty"
if test -n "$TERM_PROGRAM_VERSION"
set -a ssh_env "TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION"
end
set -a ssh_env $ssh_env_vars
end
# Install terminfo on remote host if needed
if string match -q '*ssh-terminfo*' -- "$GHOSTTY_SHELL_FEATURES"
set -l ssh_config (command ssh -G $argv 2>/dev/null)
set -l ssh_user (echo $ssh_config | while read -l ssh_key ssh_value
test "$ssh_key" = "user"; and echo $ssh_value; and break
end)
set -l ssh_hostname (echo $ssh_config | while read -l ssh_key ssh_value
test "$ssh_key" = "hostname"; and echo $ssh_value; and break
end)
set -l ssh_user
set -l ssh_hostname
for line in $ssh_config
set -l parts (string split ' ' -- $line)
if test (count $parts) -ge 2
switch $parts[1]
case user
set ssh_user $parts[2]
case hostname
set ssh_hostname $parts[2]
end
if test -n "$ssh_user"; and test -n "$ssh_hostname"
break
end
end
end
set -l ssh_target "$ssh_user@$ssh_hostname"
if test -n "$ssh_hostname"
# Detect timeout command (BSD compatibility)
set -l ssh_timeout_cmd
if command -v timeout >/dev/null 2>&1
set ssh_timeout_cmd timeout
else if command -v gtimeout >/dev/null 2>&1
set ssh_timeout_cmd gtimeout
end
# Check if terminfo is already cached
set -l ssh_cache_check_success false
if command -v ghostty >/dev/null 2>&1
if test -n "$ssh_timeout_cmd"
if $ssh_timeout_cmd "$GHOSTTY_SSH_CHECK_TIMEOUT"s ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1
set ssh_cache_check_success true
end
else
if ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1
set ssh_cache_check_success true
end
end
end
if test "$ssh_cache_check_success" = "true"
set -a ssh_env TERM=xterm-ghostty
else if command -v infocmp >/dev/null 2>&1
# Generate terminfo data (BSD base64 compatibility)
if not command -v base64 >/dev/null 2>&1
echo "Warning: base64 command not available for terminfo installation." >&2
set -a ssh_env TERM=xterm-256color
else
set -l ssh_terminfo
set -l ssh_base64_decode_cmd
# BSD vs GNU base64 compatibility
if base64 --help 2>&1 | grep -q GNU
set ssh_base64_decode_cmd "base64 -d"
set ssh_terminfo (infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 -w0 2>/dev/null)
else
set ssh_base64_decode_cmd "base64 -D"
set ssh_terminfo (infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 2>/dev/null | tr -d '\n')
end
if test -n "$ssh_terminfo"
echo "Setting up Ghostty terminfo on remote host..." >&2
echo "Setting up Ghostty terminfo on $ssh_hostname..." >&2
set -l ssh_cpath_dir (mktemp -d "/tmp/ghostty-ssh-$ssh_user.XXXXXX" 2>/dev/null; or echo "/tmp/ghostty-ssh-$ssh_user."(random))
set -l ssh_cpath "$ssh_cpath_dir/socket"
set -l ssh_base64_decode_cmd
if base64 --help 2>&1 | grep -q GNU
set ssh_base64_decode_cmd "base64 -d"
else
set ssh_base64_decode_cmd "base64 -D"
end
if echo "$ssh_terminfo" | eval $ssh_base64_decode_cmd | command ssh $ssh_opts -o ControlMaster=yes -o ControlPath="$ssh_cpath" -o ControlPersist=60s $argv '
infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
command -v tic >/dev/null 2>&1 || exit 1
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
exit 1
' 2>/dev/null
echo "Terminfo setup complete." >&2
echo "Terminfo setup complete on $ssh_hostname." >&2
set -a ssh_env TERM=xterm-ghostty
set -a ssh_opts -o "ControlPath=$ssh_cpath"
# Cache successful installation
if test -n "$ssh_target"; and command -v ghostty >/dev/null 2>&1
fish -c "
if test -n '$ssh_timeout_cmd'
$ssh_timeout_cmd '$GHOSTTY_SSH_CACHE_TIMEOUT's ghostty +ssh-cache --add='$ssh_target' >/dev/null 2>&1; or true
else
ghostty +ssh-cache --add='$ssh_target' >/dev/null 2>&1; or true
end
" &
fish -c "ghostty +ssh-cache --add='$ssh_target' >/dev/null 2>&1; or true" &
end
else
echo "Warning: Failed to install terminfo." >&2
@ -213,6 +199,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
echo "Warning: Could not generate terminfo data." >&2
set -a ssh_env TERM=xterm-256color
end
end
else
echo "Warning: ghostty command not available for cache management." >&2
set -a ssh_env TERM=xterm-256color
@ -224,22 +211,31 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
end
end
# Ensure TERM is set when using ssh-env feature
if string match -q '*ssh-env*' -- "$GHOSTTY_SHELL_FEATURES"
set -l ssh_term_set false
# Execute SSH with environment handling
set -l ssh_term_override
for ssh_v in $ssh_env
if string match -q 'TERM=*' -- $ssh_v
set ssh_term_set true
set ssh_term_override (string replace 'TERM=' '' -- $ssh_v)
break
end
end
if test "$ssh_term_set" = "false"; and test "$TERM" = "xterm-ghostty"
if string match -q '*ssh-env*' -- "$GHOSTTY_SHELL_FEATURES"; and test -z "$ssh_term_override"
set -a ssh_env TERM=xterm-256color
end
set ssh_term_override xterm-256color
end
set -l ssh_ret
if test -n "$ssh_term_override"
set -l ssh_original_term "$TERM"
set -gx TERM "$ssh_term_override"
command ssh $ssh_opts $argv
set -l ssh_ret $status
set ssh_ret $status
set -gx TERM "$ssh_original_term"
else
command ssh $ssh_opts $argv
set ssh_ret $status
end
# Restore original environment variables
if string match -q '*ssh-env*' -- "$GHOSTTY_SHELL_FEATURES"

View File

@ -244,123 +244,106 @@ _ghostty_deferred_init() {
}
fi
# SSH Integration
# SSH Integration
if [[ "$GHOSTTY_SHELL_FEATURES" =~ (ssh-env|ssh-terminfo) ]]; then
: "${GHOSTTY_SSH_CACHE_TIMEOUT:=5}"
: "${GHOSTTY_SSH_CHECK_TIMEOUT:=3}"
# SSH wrapper that preserves Ghostty features across remote connections
ssh() {
emulate -L zsh
setopt local_options no_glob_subst
local -a ssh_env ssh_opts
local ssh_env ssh_opts ssh_exported_vars
ssh_env=()
ssh_opts=()
ssh_exported_vars=()
# Configure environment variables for remote session
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
local -a ssh_env_vars=(
ssh_opts+=(-o "SetEnv COLORTERM=truecolor")
if [[ -n "${TERM_PROGRAM+x}" ]]; then
ssh_exported_vars+=("TERM_PROGRAM=${TERM_PROGRAM}")
else
ssh_exported_vars+=("TERM_PROGRAM")
fi
export "TERM_PROGRAM=ghostty"
ssh_opts+=(-o "SendEnv TERM_PROGRAM")
if [[ -n "$TERM_PROGRAM_VERSION" ]]; then
if [[ -n "${TERM_PROGRAM_VERSION+x}" ]]; then
ssh_exported_vars+=("TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
else
ssh_exported_vars+=("TERM_PROGRAM_VERSION")
fi
ssh_opts+=(-o "SendEnv TERM_PROGRAM_VERSION")
fi
ssh_env+=(
"COLORTERM=truecolor"
"TERM_PROGRAM=ghostty"
)
[[ -n "$TERM_PROGRAM_VERSION" ]] && ssh_env_vars+=("TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION")
# Temporarily export variables for SSH transmission
local -a ssh_exported_vars=()
local ssh_v ssh_var_name
for ssh_v in "${ssh_env_vars[@]}"; do
ssh_var_name="${ssh_v%%=*}"
if [[ -n "${(P)ssh_var_name+x}" ]]; then
ssh_exported_vars+=("$ssh_var_name=${(P)ssh_var_name}")
else
ssh_exported_vars+=("$ssh_var_name")
fi
export "${ssh_v}"
# Use both SendEnv and SetEnv for maximum compatibility
ssh_opts+=(-o "SendEnv $ssh_var_name")
ssh_opts+=(-o "SetEnv $ssh_v")
done
ssh_env+=("${ssh_env_vars[@]}")
[[ -n "$TERM_PROGRAM_VERSION" ]] && ssh_env+=("TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION")
fi
# Install terminfo on remote host if needed
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-terminfo ]]; then
local ssh_config ssh_user ssh_hostname ssh_target
local ssh_config ssh_user ssh_hostname
ssh_config=$(command ssh -G "$@" 2>/dev/null)
ssh_user=$(printf '%s\n' "${(@f)ssh_config}" | while IFS=' ' read -r ssh_key ssh_value; do
[[ "$ssh_key" == "user" ]] && printf '%s\n' "$ssh_value" && break
done)
ssh_hostname=$(printf '%s\n' "${(@f)ssh_config}" | while IFS=' ' read -r ssh_key ssh_value; do
[[ "$ssh_key" == "hostname" ]] && printf '%s\n' "$ssh_value" && break
done)
while IFS=' ' read -r ssh_key ssh_value; do
case "$ssh_key" in
user) ssh_user="$ssh_value" ;;
hostname) ssh_hostname="$ssh_value" ;;
esac
[[ -n "$ssh_user" && -n "$ssh_hostname" ]] && break
done <<< "$ssh_config"
ssh_target="${ssh_user}@${ssh_hostname}"
if [[ -n "$ssh_hostname" ]]; then
# Detect timeout command (BSD compatibility)
local ssh_timeout_cmd=""
if (( $+commands[timeout] )); then
ssh_timeout_cmd="timeout"
elif (( $+commands[gtimeout] )); then
ssh_timeout_cmd="gtimeout"
fi
# Check if terminfo is already cached
local ssh_cache_check_success=false
if (( $+commands[ghostty] )); then
if [[ -n "$ssh_timeout_cmd" ]]; then
$ssh_timeout_cmd "${GHOSTTY_SSH_CHECK_TIMEOUT}s" ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1 && ssh_cache_check_success=true
else
ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1 && ssh_cache_check_success=true
fi
fi
if [[ "$ssh_cache_check_success" == "true" ]]; then
ssh_env+=(TERM=xterm-ghostty)
elif (( $+commands[infocmp] )); then
local ssh_terminfo
if ! (( $+commands[base64] )); then
print "Warning: base64 command not available for terminfo installation." >&2
ssh_env+=(TERM=xterm-256color)
else
local ssh_terminfo ssh_base64_decode_cmd
# Generate terminfo data (BSD base64 compatibility)
# BSD vs GNU base64 compatibility
if base64 --help 2>&1 | grep -q GNU; then
ssh_base64_decode_cmd="base64 -d"
ssh_terminfo=$(infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 -w0 2>/dev/null)
else
ssh_base64_decode_cmd="base64 -D"
ssh_terminfo=$(infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 2>/dev/null | tr -d '\n')
fi
if [[ -n "$ssh_terminfo" ]]; then
print "Setting up Ghostty terminfo on remote host..." >&2
print "Setting up Ghostty terminfo on $ssh_hostname..." >&2
local ssh_cpath_dir ssh_cpath
ssh_cpath_dir=$(mktemp -d "/tmp/ghostty-ssh-$ssh_user.XXXXXX" 2>/dev/null) || ssh_cpath_dir="/tmp/ghostty-ssh-$ssh_user.$$"
ssh_cpath="$ssh_cpath_dir/socket"
local ssh_base64_decode_cmd
if base64 --help 2>&1 | grep -q GNU; then
ssh_base64_decode_cmd="base64 -d"
else
ssh_base64_decode_cmd="base64 -D"
fi
if print "$ssh_terminfo" | $ssh_base64_decode_cmd | command ssh "${ssh_opts[@]}" -o ControlMaster=yes -o ControlPath="$ssh_cpath" -o ControlPersist=60s "$@" '
infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
command -v tic >/dev/null 2>&1 || exit 1
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
exit 1
' 2>/dev/null; then
print "Terminfo setup complete." >&2
print "Terminfo setup complete on $ssh_hostname." >&2
ssh_env+=(TERM=xterm-ghostty)
ssh_opts+=(-o "ControlPath=$ssh_cpath")
# Cache successful installation
if [[ -n "$ssh_target" ]] && (( $+commands[ghostty] )); then
{
if [[ -n "$ssh_timeout_cmd" ]]; then
$ssh_timeout_cmd "${GHOSTTY_SSH_CACHE_TIMEOUT}s" ghostty +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true
else
ghostty +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true
fi
} &!
fi
else
@ -371,6 +354,7 @@ _ghostty_deferred_init() {
print "Warning: Could not generate terminfo data." >&2
ssh_env+=(TERM=xterm-256color)
fi
fi
else
print "Warning: ghostty command not available for cache management." >&2
ssh_env+=(TERM=xterm-256color)
@ -380,21 +364,35 @@ _ghostty_deferred_init() {
fi
fi
# Ensure TERM is set when using ssh-env feature
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
local ssh_term_set=false ssh_v
# Execute SSH with environment handling
local ssh_term_override=""
local ssh_v
for ssh_v in "${ssh_env[@]}"; do
[[ "$ssh_v" =~ ^TERM= ]] && ssh_term_set=true && break
if [[ "$ssh_v" =~ ^TERM=(.*)$ ]]; then
ssh_term_override="${match[1]}"
break
fi
done
[[ "$ssh_term_set" == "false" && "$TERM" == "xterm-ghostty" ]] && ssh_env+=(TERM=xterm-256color)
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env && -z "$ssh_term_override" ]]; then
ssh_env+=(TERM=xterm-256color)
ssh_term_override="xterm-256color"
fi
local ssh_ret
if [[ -n "$ssh_term_override" ]]; then
local ssh_original_term="$TERM"
export TERM="$ssh_term_override"
command ssh "${ssh_opts[@]}" "$@"
local ssh_ret=$?
ssh_ret=$?
export TERM="$ssh_original_term"
else
command ssh "${ssh_opts[@]}" "$@"
ssh_ret=$?
fi
# Restore original environment variables
if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env ]]; then
local ssh_v
for ssh_v in "${ssh_exported_vars[@]}"; do
if [[ "$ssh_v" == *=* ]]; then
export "${ssh_v}"