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=( + 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 - 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 + builtin local ssh_terminfo ssh_base64_decode_cmd - 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" - - 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") + if [[ -n "$ssh_terminfo" ]]; then + builtin echo "Setting up Ghostty terminfo on $ssh_hostname..." >&2 + builtin local ssh_cpath_dir 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 + 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) { + 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"] } - - # 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" + 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" - 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] + 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 + } - # 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 { + 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)) { 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" - + 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 - - # 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" + 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" - # 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 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=( + 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 + 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 + local ssh_terminfo ssh_base64_decode_cmd - if [[ -n "$ssh_terminfo" ]]; then - print "Setting up Ghostty terminfo on remote host..." >&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 + # 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") + if [[ -n "$ssh_terminfo" ]]; then + print "Setting up Ghostty terminfo on $ssh_hostname..." >&2 + local ssh_cpath_dir 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 + 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}"