diff options
| author | Jason Rayne <yo@arcayne.dev> | 2025-07-05 13:02:35 -0700 |
|---|---|---|
| committer | Jason Rayne <yo@arcayne.dev> | 2025-07-05 13:24:59 -0700 |
| commit | a22074a85ca21ccae413e161766d6bf7f063c596 (patch) | |
| tree | 59e9b1fe6325c423f90ca242dbf9c97464d23379 /src/shell-integration | |
| parent | 5ec18f426c409273599d72a4b6f98055aff8e53b (diff) | |
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)
Diffstat (limited to 'src/shell-integration')
| -rw-r--r-- | src/shell-integration/bash/ghostty.bash | 195 | ||||
| -rw-r--r-- | src/shell-integration/elvish/lib/ghostty-integration.elv | 228 | ||||
| -rw-r--r-- | src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish | 190 | ||||
| -rw-r--r-- | src/shell-integration/zsh/ghostty-integration | 188 |
4 files changed, 380 insertions, 421 deletions
diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 8c4cd9e12..6016e9096 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -97,131 +97,116 @@ 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=( - "COLORTERM=truecolor" - "TERM_PROGRAM=ghostty" - ) - if [[ -n "$TERM_PROGRAM_VERSION" ]]; then - ssh_env_vars+=("TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION") - fi + ssh_opts+=(-o "SetEnv COLORTERM=truecolor") - # 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 "${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 "${!ssh_var_name+x}" ]]; then - ssh_exported_vars+=("$ssh_var_name=${!ssh_var_name}") + 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+=("$ssh_var_name") + ssh_exported_vars+=("TERM_PROGRAM_VERSION") fi + ssh_opts+=(-o "SendEnv TERM_PROGRAM_VERSION") + 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[@]}") + ssh_env+=( + "COLORTERM=truecolor" + "TERM_PROGRAM=ghostty" + ) + if [[ -n "$TERM_PROGRAM_VERSION" ]]; then + ssh_env+=("TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION") + fi 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 - ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1 && ssh_cache_check_success=true - fi + 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 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 - - # Generate terminfo data (BSD base64 compatibility) - if base64 --help 2>&1 | grep -q GNU; then - ssh_terminfo=$(infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 -w0 2>/dev/null) + 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 - 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 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" + builtin local ssh_terminfo ssh_base64_decode_cmd - local ssh_base64_decode_cmd + # 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 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 - 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 - ( - 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 + if [[ -n "$ssh_terminfo" ]]; then + 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" + + 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 on $ssh_hostname." >&2 + ssh_env+=(TERM=xterm-ghostty) + ssh_opts+=(-o "ControlPath=$ssh_cpath") + + # Cache successful installation + if [[ -n "$ssh_target" ]] && builtin command -v ghostty >/dev/null 2>&1; then + ( + set +m + { ghostty +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true - fi - } & - ) + } & + ) + fi + else + builtin echo "Warning: Failed to install terminfo." >&2 + ssh_env+=(TERM=xterm-256color) fi else - builtin echo "Warning: Failed to install terminfo." >&2 + builtin echo "Warning: Could not generate terminfo data." >&2 ssh_env+=(TERM=xterm-256color) fi - else - builtin echo "Warning: Could not generate terminfo data." >&2 - ssh_env+=(TERM=xterm-256color) fi else builtin echo "Warning: ghostty command not available for cache management." >&2 @@ -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 - for ssh_v in "${ssh_env[@]}"; do - if [[ "$ssh_v" =~ ^TERM= ]]; then - ssh_term_set=true - break - fi - done - if [[ "$ssh_term_set" == "false" && "$TERM" == "xterm-ghostty" ]]; then - ssh_env+=(TERM=xterm-256color) + # Execute SSH with environment handling + builtin local ssh_term_override="" + for ssh_v in "${ssh_env[@]}"; do + if [[ "$ssh_v" =~ ^TERM=(.*)$ ]]; then + ssh_term_override="${BASH_REMATCH[1]}" + break fi + done + + if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env && -z "$ssh_term_override" ]]; then + ssh_env+=(TERM=xterm-256color) + ssh_term_override="xterm-256color" fi - builtin command ssh "${ssh_opts[@]}" "$@" - local ssh_ret=$? + 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 diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index 76fa7bafa..52d01e4fb 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -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_VERSION) { - set ssh-env-vars = [$@ssh-env-vars TERM_PROGRAM_VERSION=$E:TERM_PROGRAM_VERSION] + 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"] - # 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) { + 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"] } - - # 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-opts = [$@ssh-opts -o "SendEnv TERM_PROGRAM_VERSION"] } - set ssh-env = [$@ssh-env $@ssh-env-vars] + 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] + } } # Install terminfo on remote host if needed @@ -160,52 +151,28 @@ for line (str:split "\n" $ssh-config) { var parts = (str:split " " $line) - if (and (> (count $parts) 1) (eq $parts[0] user)) { - set ssh-user = $parts[1] - } - if (and (> (count $parts) 1) (eq $parts[0] hostname)) { - set ssh-hostname = $parts[1] + if (> (count $parts) 1) { + if (eq $parts[0] user) { + set ssh-user = $parts[1] + } 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 - } - } + external ghostty +ssh-cache --host=$ssh-target >/dev/null 2>&1 + set ssh-cache-check-success = $true } catch { - # ghostty not available + # cache check failed } if $ssh-cache-check-success { @@ -213,74 +180,68 @@ } else { try { external infocmp --help >/dev/null 2>&1 - - # Generate terminfo data (BSD base64 compatibility) - var ssh-terminfo = "" - try { - var base64-help = (external base64 --help 2>&1 | slurp) - if (str:contains $base64-help GNU) { - set ssh-terminfo = (external infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | external base64 -w0 2>/dev/null | slurp) - } else { - set ssh-terminfo = (external infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | external base64 2>/dev/null | external tr -d '\n' | slurp) - } - } catch { - set ssh-terminfo = "" - } - if (not-eq $ssh-terminfo "") { - echo "Setting up Ghostty terminfo on remote host..." >&2 - var ssh-cpath-dir = "" - try { - set ssh-cpath-dir = (external mktemp -d "/tmp/ghostty-ssh-"$ssh-user".XXXXXX" 2>/dev/null | slurp) - } catch { - set ssh-cpath-dir = "/tmp/ghostty-ssh-"$ssh-user"."(randint 10000 99999) - } - var ssh-cpath = $ssh-cpath-dir"/socket" + 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 { - set ssh-base64-decode-cmd = "base64 -d" + set ssh-terminfo = "" } - 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 ' - 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 - ' >/dev/null 2>&1 - set terminfo-install-success = $true - } catch { - set terminfo-install-success = $false - } + if (not-eq $ssh-terminfo "") { + 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) + } catch { + set ssh-cpath-dir = "/tmp/ghostty-ssh-"$ssh-user"."(randint 10000 99999) + } + var ssh-cpath = $ssh-cpath-dir"/socket" + + 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 ' + 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 + ' >/dev/null 2>&1 + set terminfo-install-success = $true + } catch { + set terminfo-install-success = $false + } - if $terminfo-install-success { - echo "Terminfo setup complete." >&2 - set ssh-env = [$@ssh-env TERM=xterm-ghostty] - set ssh-opts = [$@ssh-opts -o ControlPath=$ssh-cpath] + if $terminfo-install-success { + 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 { + # Cache successful installation + if (and (not-eq $ssh-target "") (has-external ghostty)) { 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] } } else { - echo "Warning: Failed to install terminfo." >&2 + echo "Warning: Could not generate terminfo data." >&2 set ssh-env = [$@ssh-env TERM=xterm-256color] } - } else { - echo "Warning: Could not generate terminfo data." >&2 + } catch { + echo "Warning: base64 command not available for terminfo installation." >&2 set ssh-env = [$@ssh-env TERM=xterm-256color] } } catch { @@ -295,25 +256,36 @@ } } - # Ensure TERM is set when using ssh-env feature - if (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-env) { - var ssh-term-set = $false - for ssh-v $ssh-env { - if (str:has-prefix $ssh-v TERM=) { - set ssh-term-set = $true - break - } - } - if (and (not $ssh-term-set) (eq $E:TERM xterm-ghostty)) { - set ssh-env = [$@ssh-env TERM=xterm-256color] + # Execute SSH with environment handling + var ssh-term-override = "" + for ssh-v $ssh-env { + if (str:has-prefix $ssh-v TERM=) { + set ssh-term-override = (str:trim-prefix $ssh-v TERM=) + break } } + 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 - try { - external ssh $@ssh-opts $@args - } catch e { - set ssh-ret = $e[reason][exit-status] + 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 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 7dc121919..332675264 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 @@ -86,132 +86,119 @@ 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" - - if test -n "$TERM_PROGRAM_VERSION" - set -a ssh_env_vars "TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION" + 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" - # 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 test -n "$TERM_PROGRAM_VERSION" + 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 - - # 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_opts -o "SendEnv TERM_PROGRAM_VERSION" end - set -a ssh_env $ssh_env_vars + 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 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 + if ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1 + set ssh_cache_check_success true 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) - set -l ssh_terminfo - if base64 --help 2>&1 | grep -q GNU - set ssh_terminfo (infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 -w0 2>/dev/null) + 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 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 - 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_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 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 - 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 - " & + if test -n "$ssh_terminfo" + 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" + + 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 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 "ghostty +ssh-cache --add='$ssh_target' >/dev/null 2>&1; or true" & + end + else + echo "Warning: Failed to install terminfo." >&2 + set -a ssh_env TERM=xterm-256color end else - echo "Warning: Failed to install terminfo." >&2 + echo "Warning: Could not generate terminfo data." >&2 set -a ssh_env TERM=xterm-256color end - else - echo "Warning: Could not generate terminfo data." >&2 - set -a ssh_env TERM=xterm-256color end else echo "Warning: ghostty command not available for cache management." >&2 @@ -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 - for ssh_v in $ssh_env - if string match -q 'TERM=*' -- $ssh_v - set ssh_term_set true - break - end - end - if test "$ssh_term_set" = "false"; and test "$TERM" = "xterm-ghostty" - set -a ssh_env TERM=xterm-256color + # 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_override (string replace 'TERM=' '' -- $ssh_v) + break end end - command ssh $ssh_opts $argv - set -l ssh_ret $status + if string match -q '*ssh-env*' -- "$GHOSTTY_SHELL_FEATURES"; and test -z "$ssh_term_override" + set -a ssh_env TERM=xterm-256color + 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 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" diff --git a/src/shell-integration/zsh/ghostty-integration b/src/shell-integration/zsh/ghostty-integration index 9f78e9a89..40ee58b49 100644 --- a/src/shell-integration/zsh/ghostty-integration +++ b/src/shell-integration/zsh/ghostty-integration @@ -244,132 +244,116 @@ _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=( - "COLORTERM=truecolor" - "TERM_PROGRAM=ghostty" - ) - [[ -n "$TERM_PROGRAM_VERSION" ]] && ssh_env_vars+=("TERM_PROGRAM_VERSION=$TERM_PROGRAM_VERSION") + ssh_opts+=(-o "SetEnv COLORTERM=truecolor") - # 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 "${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 "${(P)ssh_var_name+x}" ]]; then - ssh_exported_vars+=("$ssh_var_name=${(P)ssh_var_name}") + 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+=("$ssh_var_name") + ssh_exported_vars+=("TERM_PROGRAM_VERSION") fi + ssh_opts+=(-o "SendEnv TERM_PROGRAM_VERSION") + 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[@]}") + ssh_env+=( + "COLORTERM=truecolor" + "TERM_PROGRAM=ghostty" + ) + [[ -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 + ghostty +ssh-cache --host="$ssh_target" >/dev/null 2>&1 && ssh_cache_check_success=true fi if [[ "$ssh_cache_check_success" == "true" ]]; then ssh_env+=(TERM=xterm-ghostty) elif (( $+commands[infocmp] )); then - local ssh_terminfo - - # Generate terminfo data (BSD base64 compatibility) - if base64 --help 2>&1 | grep -q GNU; then - ssh_terminfo=$(infocmp -0 -Q2 -q xterm-ghostty 2>/dev/null | base64 -w0 2>/dev/null) + if ! (( $+commands[base64] )); then + print "Warning: base64 command not available for terminfo installation." >&2 + ssh_env+=(TERM=xterm-256color) else - 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 - local ssh_cpath_dir ssh_cpath + local ssh_terminfo ssh_base64_decode_cmd - 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 + # 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 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 - 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 + if [[ -n "$ssh_terminfo" ]]; then + 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" + + 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 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 + { ghostty +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true - fi - } &! + } &! + fi + else + print "Warning: Failed to install terminfo." >&2 + ssh_env+=(TERM=xterm-256color) fi else - print "Warning: Failed to install terminfo." >&2 + print "Warning: Could not generate terminfo data." >&2 ssh_env+=(TERM=xterm-256color) fi - else - print "Warning: Could not generate terminfo data." >&2 - ssh_env+=(TERM=xterm-256color) fi else print "Warning: ghostty command not available for cache management." >&2 @@ -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 - for ssh_v in "${ssh_env[@]}"; do - [[ "$ssh_v" =~ ^TERM= ]] && ssh_term_set=true && break - done - [[ "$ssh_term_set" == "false" && "$TERM" == "xterm-ghostty" ]] && ssh_env+=(TERM=xterm-256color) + # Execute SSH with environment handling + local ssh_term_override="" + local ssh_v + for ssh_v in "${ssh_env[@]}"; do + if [[ "$ssh_v" =~ ^TERM=(.*)$ ]]; then + ssh_term_override="${match[1]}" + break + fi + done + + if [[ "$GHOSTTY_SHELL_FEATURES" =~ ssh-env && -z "$ssh_term_override" ]]; then + ssh_env+=(TERM=xterm-256color) + ssh_term_override="xterm-256color" fi - command ssh "${ssh_opts[@]}" "$@" - local ssh_ret=$? + local ssh_ret + if [[ -n "$ssh_term_override" ]]; then + local ssh_original_term="$TERM" + export TERM="$ssh_term_override" + command ssh "${ssh_opts[@]}" "$@" + 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}" |
