From 4cebee5c8e34a26861fea40034cd78abf679d98f Mon Sep 17 00:00:00 2001 From: Jason Rayne Date: Tue, 17 Jun 2025 17:43:21 -0700 Subject: [PATCH] fix: add client-side caching to eliminate redundant terminfo installations - Cache known hosts with terminfo in $GHOSTTY_RESOURCES_DIR/terminfo_hosts - Skip installation step for cached hosts (single connection instead of two) - Use secure file permissions (600) and atomic writes - Extract SSH target safely from command arguments - Maintains full functionality while improving user experience on repeated connections --- src/shell-integration/bash/ghostty.bash | 94 ++++++++++++-- .../elvish/lib/ghostty-integration.elv | 120 +++++++++++++++--- .../ghostty-shell-integration.fish | 101 +++++++++++++-- src/shell-integration/zsh/ghostty-integration | 102 +++++++++++++-- 4 files changed, 366 insertions(+), 51 deletions(-) diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 2ce1f9503..51ab6c3e5 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -99,6 +99,66 @@ fi # SSH if [[ -n "$GHOSTTY_SSH_INTEGRATION" ]]; then + # Cache file for tracking hosts with terminfo installed + _ghostty_cache_file="${GHOSTTY_RESOURCES_DIR:-$HOME/.config/ghostty}/terminfo_hosts" + + # Extract target host from SSH arguments + _ghostty_get_ssh_target() { + local target="" + local skip_next=false + + for arg in "$@"; do + if [[ "$skip_next" == "true" ]]; then + skip_next=false + continue + fi + + # Skip flags that take arguments + if [[ "$arg" =~ ^-[bcDEeFIiJLlmOopQRSWw]$ ]]; then + skip_next=true + continue + fi + + # Skip other flags + if [[ "$arg" =~ ^- ]]; then + continue + fi + + # This should be the target + target="$arg" + break + done + + echo "$target" + } + + # Check if host has terminfo installed + _ghostty_host_has_terminfo() { + local target="$1" + [[ -f "$_ghostty_cache_file" ]] && grep -qFx "$target" "$_ghostty_cache_file" 2>/dev/null + } + + # Add host to terminfo cache + _ghostty_cache_host() { + local target="$1" + local cache_dir + cache_dir="$(dirname "$_ghostty_cache_file")" + + # Create cache directory if needed + [[ ! -d "$cache_dir" ]] && mkdir -p "$cache_dir" + + # Atomic write to cache file + { + if [[ -f "$_ghostty_cache_file" ]]; then + cat "$_ghostty_cache_file" + fi + echo "$target" + } | sort -u > "$_ghostty_cache_file.tmp" && mv "$_ghostty_cache_file.tmp" "$_ghostty_cache_file" + + # Secure permissions + chmod 600 "$_ghostty_cache_file" 2>/dev/null + } + # Wrap `ssh` command to provide Ghostty SSH integration. # # This approach supports wrapping an `ssh` alias, but the alias definition @@ -153,21 +213,35 @@ if [[ -n "$GHOSTTY_SSH_INTEGRATION" ]]; then # Level: full - All features _ghostty_ssh_full() { - # Full integration: Two-step terminfo installation + local target + target="$(_ghostty_get_ssh_target "$@")" + + # Check if we already know this host has terminfo + if [[ -n "$target" ]] && _ghostty_host_has_terminfo "$target"; then + # Direct connection with xterm-ghostty + local env_vars=("TERM=xterm-ghostty") + [[ -n "$GHOSTTY_SHELL_FEATURES" ]] && env_vars+=("GHOSTTY_SHELL_FEATURES=$GHOSTTY_SHELL_FEATURES") + env "${env_vars[@]}" ssh "$@" + return 0 + fi + + # Full integration: Install terminfo if needed if builtin command -v infocmp >/dev/null 2>&1; then - echo "Installing Ghostty terminfo on remote host..." >&2 + # Install terminfo only if needed + if infocmp -x xterm-ghostty 2>/dev/null | builtin command ssh "$@" ' + if ! infocmp xterm-ghostty >/dev/null 2>&1; then + echo "Installing Ghostty terminfo..." >&2 + tic -x - 2>/dev/null + fi + '; then + echo "Connecting with full Ghostty support..." >&2 - # Step 1: Install terminfo - if infocmp -x xterm-ghostty 2>/dev/null | builtin command ssh "$@" 'tic -x - 2>/dev/null'; then - echo "Terminfo installed successfully. Connecting with full Ghostty support..." >&2 + # Cache this host for future connections + [[ -n "$target" ]] && _ghostty_cache_host "$target" - # Step 2: Connect with xterm-ghostty since we know terminfo is now available + # Connect with xterm-ghostty since terminfo is available local env_vars=("TERM=xterm-ghostty") - - # Propagate Ghostty shell integration environment variables [[ -n "$GHOSTTY_SHELL_FEATURES" ]] && env_vars+=("GHOSTTY_SHELL_FEATURES=$GHOSTTY_SHELL_FEATURES") - - # Normal SSH connection with Ghostty terminfo available env "${env_vars[@]}" ssh "$@" builtin return 0 fi diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index 2f938df97..deb258ae7 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -98,6 +98,70 @@ (external sudo) $@args } + # SSH Integration + # Cache file for tracking hosts with terminfo installed + var ghostty-cache-file = (if (has-env GHOSTTY_RESOURCES_DIR) { put $E:GHOSTTY_RESOURCES_DIR"/terminfo_hosts" } else { put $E:HOME"/.config/ghostty/terminfo_hosts" }) + + # Extract target host from SSH arguments + fn ghostty-get-ssh-target {|@args| + var target = "" + var skip-next = $false + + for arg $args { + if (eq $skip-next $true) { + set skip-next = $false + continue + } + + # Skip flags that take arguments + if (re:match '^-[bcDEeFIiJLlmOopQRSWw]$' $arg) { + set skip-next = $true + continue + } + + # Skip other flags + if (re:match '^-' $arg) { + continue + } + + # This should be the target + set target = $arg + break + } + + put $target + } + + # Check if host has terminfo installed + fn ghostty-host-has-terminfo {|target| + and (path:is-regular $ghostty-cache-file) ?(grep -qFx $target $ghostty-cache-file 2>/dev/null) + } + + # Add host to terminfo cache + fn ghostty-cache-host {|target| + var cache-dir = (path:dir $ghostty-cache-file) + + # Create cache directory if needed + if (not (path:is-dir $cache-dir)) { + mkdir -p $cache-dir + } + + # Atomic write to cache file + var temp-file = $ghostty-cache-file".tmp" + + { + if (path:is-regular $ghostty-cache-file) { + cat $ghostty-cache-file + } + echo $target + } | sort -u > $temp-file + + mv $temp-file $ghostty-cache-file + + # Secure permissions + ?chmod 600 $ghostty-cache-file 2>/dev/null + } + fn ssh-with-ghostty-integration {|@args| if (has-env GHOSTTY_SSH_INTEGRATION) { if (eq "term-only" $E:GHOSTTY_SSH_INTEGRATION) { @@ -127,17 +191,17 @@ fn ssh-basic {|@args| # Level: basic - TERM fix + environment variable propagation var env-vars = [] - + # Fix TERM compatibility if (eq "xterm-ghostty" $E:TERM) { set env-vars = (conj $env-vars TERM=xterm-256color) } - + # Propagate Ghostty shell integration environment variables if (not-eq "" $E:GHOSTTY_SHELL_FEATURES) { set env-vars = (conj $env-vars GHOSTTY_SHELL_FEATURES=$E:GHOSTTY_SHELL_FEATURES) } - + # Execute with environment variables if any were set if (> (count $env-vars) 0) { (external env) $@env-vars ssh $@args @@ -147,22 +211,47 @@ } fn ssh-full {|@args| - # Full integration: Two-step terminfo installation + var target = (ghostty-get-ssh-target $@args) + + # Check if we already know this host has terminfo + if (and (not-eq "" $target) (ghostty-host-has-terminfo $target)) { + # Direct connection with xterm-ghostty + var env-vars = [TERM=xterm-ghostty] + + # Propagate Ghostty shell integration environment variables + if (not-eq "" $E:GHOSTTY_SHELL_FEATURES) { + set env-vars = (conj $env-vars GHOSTTY_SHELL_FEATURES=$E:GHOSTTY_SHELL_FEATURES) + } + + (external env) $@env-vars ssh $@args + return + } + + # Full integration: Install terminfo if needed if (has-external infocmp) { - echo "Installing Ghostty terminfo on remote host..." >&2 - try { - infocmp -x xterm-ghostty 2>/dev/null | (external ssh) $@args 'tic -x - 2>/dev/null' - echo "Terminfo installed successfully. Connecting with full Ghostty support..." >&2 - - # Step 2: Connect with xterm-ghostty since we know terminfo is now available + # Install terminfo only if needed + infocmp -x xterm-ghostty 2>/dev/null | (external ssh) $@args ' + if ! infocmp xterm-ghostty >/dev/null 2>&1; then + echo "Installing Ghostty terminfo..." >&2 + tic -x - 2>/dev/null + fi + ' + echo "Connecting with full Ghostty support..." >&2 + + # Cache this host for future connections + if (not-eq "" $target) { + ghostty-cache-host $target + } + + # Connect with xterm-ghostty since terminfo is available var env-vars = [TERM=xterm-ghostty] - + # Propagate Ghostty shell integration environment variables if (not-eq "" $E:GHOSTTY_SHELL_FEATURES) { set env-vars = (conj $env-vars GHOSTTY_SHELL_FEATURES=$E:GHOSTTY_SHELL_FEATURES) } - + # Normal SSH connection with Ghostty terminfo available (external env) $@env-vars ssh $@args return @@ -170,15 +259,16 @@ echo "Terminfo installation failed. Using basic integration." >&2 } } - + # Fallback to basic integration ssh-basic $@args } -# Register SSH integration if enabled + # Register SSH integration if enabled if (and (has-env GHOSTTY_SSH_INTEGRATION) (has-external ssh)) { edit:add-var ssh~ $ssh-with-ghostty-integration~ - } + } + defer { mark-prompt-start report-pwd 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 0fe7155a1..bcf97cb82 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,8 +86,68 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" end end - # SSH integration wrapper + # SSH integration if test -n "$GHOSTTY_SSH_INTEGRATION" + # Cache file for tracking hosts with terminfo installed + set -l _ghostty_cache_file (string join / (test -n "$GHOSTTY_RESOURCES_DIR"; and echo "$GHOSTTY_RESOURCES_DIR"; or echo "$HOME/.config/ghostty") "terminfo_hosts") + + # Extract target host from SSH arguments + function _ghostty_get_ssh_target + set -l target "" + set -l skip_next false + + for arg in $argv + if test "$skip_next" = "true" + set skip_next false + continue + end + + # Skip flags that take arguments + if string match -qr '^-[bcDEeFIiJLlmOopQRSWw]$' -- "$arg" + set skip_next true + continue + end + + # Skip other flags + if string match -q -- '-*' "$arg" + continue + end + + # This should be the target + set target "$arg" + break + end + + echo "$target" + end + + # Check if host has terminfo installed + function _ghostty_host_has_terminfo + set -l target $argv[1] + test -f "$_ghostty_cache_file"; and grep -qFx "$target" "$_ghostty_cache_file" 2>/dev/null + end + + # Add host to terminfo cache + function _ghostty_cache_host + set -l target $argv[1] + set -l cache_dir (dirname "$_ghostty_cache_file") + + # Create cache directory if needed + test -d "$cache_dir"; or mkdir -p "$cache_dir" + + # Atomic write to cache file + begin + if test -f "$_ghostty_cache_file" + cat "$_ghostty_cache_file" + end + echo "$target" + end | sort -u > "$_ghostty_cache_file.tmp"; and mv "$_ghostty_cache_file.tmp" "$_ghostty_cache_file" + + # Secure permissions + chmod 600 "$_ghostty_cache_file" 2>/dev/null + end + + # Wrap `ssh` command to provide Ghostty SSH integration. function ssh -d "Wrap ssh to provide Ghostty SSH integration" switch "$GHOSTTY_SSH_INTEGRATION" case term-only @@ -136,22 +196,38 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" # Level: full - All features function _ghostty_ssh_full - # Full integration: Two-step terminfo installation - if type -q infocmp - echo "Installing Ghostty terminfo on remote host..." >&2 + set -l target (_ghostty_get_ssh_target $argv) - # Step 1: Install terminfo - if infocmp -x xterm-ghostty 2>/dev/null | ssh $argv 'tic -x - 2>/dev/null' - echo "Terminfo installed successfully. Connecting with full Ghostty support..." >&2 - - # Step 2: Connect with xterm-ghostty since we know terminfo is now available + # Check if we already know this host has terminfo + if test -n "$target"; and _ghostty_host_has_terminfo "$target" + # Direct connection with xterm-ghostty + set --local env_vars TERM=xterm-ghostty + if test -n "$GHOSTTY_SHELL_FEATURES" + set --append env_vars GHOSTTY_SHELL_FEATURES="$GHOSTTY_SHELL_FEATURES" + end + env $env_vars ssh $argv + return 0 + end + + # Full integration: Install terminfo if needed + if type -q infocmp + # Install terminfo only if needed + if infocmp -x xterm-ghostty 2>/dev/null | ssh $argv ' + if ! infocmp xterm-ghostty >/dev/null 2>&1 + echo "Installing Ghostty terminfo..." >&2 + tic -x - 2>/dev/null + end + ' + echo "Connecting with full Ghostty support..." >&2 + + # Cache this host for future connections + test -n "$target"; and _ghostty_cache_host "$target" + + # Connect with xterm-ghostty since terminfo is available set --local env_vars TERM=xterm-ghostty - - # Propagate Ghostty shell integration environment variables if test -n "$GHOSTTY_SHELL_FEATURES" set --append env_vars GHOSTTY_SHELL_FEATURES="$GHOSTTY_SHELL_FEATURES" end - env $env_vars ssh $argv builtin return 0 end @@ -162,6 +238,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" _ghostty_ssh_basic $argv end end + # Setup prompt marking function __ghostty_mark_prompt_start --on-event fish_prompt --on-event fish_cancel --on-event fish_posterror # If we never got the output end event, then we need to send it now. diff --git a/src/shell-integration/zsh/ghostty-integration b/src/shell-integration/zsh/ghostty-integration index 109c9fd60..c172fc02d 100644 --- a/src/shell-integration/zsh/ghostty-integration +++ b/src/shell-integration/zsh/ghostty-integration @@ -243,9 +243,69 @@ _ghostty_deferred_init() { fi } fi - + # SSH if [[ -n "$GHOSTTY_SSH_INTEGRATION" ]]; then + # Cache file for tracking hosts with terminfo installed + _ghostty_cache_file="${GHOSTTY_RESOURCES_DIR:-$HOME/.config/ghostty}/terminfo_hosts" + + # Extract target host from SSH arguments + _ghostty_get_ssh_target() { + local target="" + local skip_next=false + + for arg in "$@"; do + if [[ "$skip_next" == "true" ]]; then + skip_next=false + continue + fi + + # Skip flags that take arguments + if [[ "$arg" =~ ^-[bcDEeFIiJLlmOopQRSWw]$ ]]; then + skip_next=true + continue + fi + + # Skip other flags + if [[ "$arg" =~ ^- ]]; then + continue + fi + + # This should be the target + target="$arg" + break + done + + echo "$target" + } + + # Check if host has terminfo installed + _ghostty_host_has_terminfo() { + local target="$1" + [[ -f "$_ghostty_cache_file" ]] && grep -qFx "$target" "$_ghostty_cache_file" 2>/dev/null + } + + # Add host to terminfo cache + _ghostty_cache_host() { + local target="$1" + local cache_dir + cache_dir="$(dirname "$_ghostty_cache_file")" + + # Create cache directory if needed + [[ ! -d "$cache_dir" ]] && mkdir -p "$cache_dir" + + # Atomic write to cache file + { + if [[ -f "$_ghostty_cache_file" ]]; then + cat "$_ghostty_cache_file" + fi + echo "$target" + } | sort -u > "$_ghostty_cache_file.tmp" && mv "$_ghostty_cache_file.tmp" "$_ghostty_cache_file" + + # Secure permissions + chmod 600 "$_ghostty_cache_file" 2>/dev/null + } + # Wrap `ssh` command to provide Ghostty SSH integration ssh() { case "$GHOSTTY_SSH_INTEGRATION" in @@ -296,27 +356,41 @@ _ghostty_deferred_init() { # Level: full - All features _ghostty_ssh_full() { - # Full integration: Two-step terminfo installation + local target + target="$(_ghostty_get_ssh_target "$@")" + + # Check if we already know this host has terminfo + if [[ -n "$target" ]] && _ghostty_host_has_terminfo "$target"; then + # Direct connection with xterm-ghostty + local env_vars=("TERM=xterm-ghostty") + [[ -n "$GHOSTTY_SHELL_FEATURES" ]] && env_vars+=("GHOSTTY_SHELL_FEATURES=$GHOSTTY_SHELL_FEATURES") + env "${env_vars[@]}" ssh "$@" + return 0 + fi + + # Full integration: Install terminfo if needed if builtin command -v infocmp >/dev/null 2>&1; then - echo "Installing Ghostty terminfo on remote host..." >&2 - - # Step 1: Install terminfo - if infocmp -x xterm-ghostty 2>/dev/null | builtin command ssh "$@" 'tic -x - 2>/dev/null'; then - echo "Terminfo installed successfully. Connecting with full Ghostty support..." >&2 - - # Step 2: Connect with xterm-ghostty since we know terminfo is now available + # Install terminfo only if needed + if infocmp -x xterm-ghostty 2>/dev/null | builtin command ssh "$@" ' + if ! infocmp xterm-ghostty >/dev/null 2>&1; then + echo "Installing Ghostty terminfo..." >&2 + tic -x - 2>/dev/null + fi + '; then + echo "Connecting with full Ghostty support..." >&2 + + # Cache this host for future connections + [[ -n "$target" ]] && _ghostty_cache_host "$target" + + # Connect with xterm-ghostty since terminfo is available local env_vars=("TERM=xterm-ghostty") - - # Propagate Ghostty shell integration environment variables [[ -n "$GHOSTTY_SHELL_FEATURES" ]] && env_vars+=("GHOSTTY_SHELL_FEATURES=$GHOSTTY_SHELL_FEATURES") - - # Normal SSH connection with Ghostty terminfo available env "${env_vars[@]}" ssh "$@" builtin return 0 fi echo "Terminfo installation failed. Using basic integration." >&2 fi - + # Fallback to basic integration _ghostty_ssh_basic "$@" }