From 984ff2b402f06dcd32e59b3ce8241d2fe081d5a4 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Thu, 29 Jan 2026 13:06:14 -0500 Subject: [PATCH] nushell: refactor ssh wrapper for clarity The ssh wrapper previously used a separate set_ssh_terminfo function that returned a record to be merged, which result in some redundant control flow and TERM assignments. This inlines the terminfo logic and builds env/opts incrementally based on feature flags. TERM is set to a fallback early and only overridden on success, which simplifies our error handling and avoids mutable variable capture issues in closures. Lastly, warnings are now consistently written to stderr, and I made various other control flow and syntax improvements. --- .../nushell/vendor/autoload/ghostty.nu | 159 ++++++++---------- 1 file changed, 68 insertions(+), 91 deletions(-) diff --git a/src/shell-integration/nushell/vendor/autoload/ghostty.nu b/src/shell-integration/nushell/vendor/autoload/ghostty.nu index 93e5fd909..78c981ebe 100644 --- a/src/shell-integration/nushell/vendor/autoload/ghostty.nu +++ b/src/shell-integration/nushell/vendor/autoload/ghostty.nu @@ -4,106 +4,83 @@ export module ghostty { $feature in ($env.GHOSTTY_SHELL_FEATURES | default "" | split row ',') } - # Enables automatic terminfo installation on remote hosts. - # Attempts to install Ghostty's terminfo entry using infocmp and tic when - # connecting to hosts that lack it. - # Requires infocmp to be available locally and tic to be available on remote hosts. - # Caches installations to avoid repeat installations. - def set_ssh_terminfo [ - ssh_opts: list - ssh_args: list - ]: [nothing -> record>] { - let ssh_cfg = ^ssh -G ...($ssh_args) - | lines - | parse "{key} {value}" - | where key in ["user" "hostname"] - | select key value - | transpose -rd - | default {user: $env.USER hostname: "localhost"} - - let ssh_id = $"($ssh_cfg.user)@($ssh_cfg.hostname)" - let ghostty_bin = $env.GHOSTTY_BIN_DIR | path join "ghostty" - - let is_cached = ( - ^$ghostty_bin ...(["+ssh-cache" $"--host=($ssh_id)"]) - | complete - | $in.exit_code == 0 - ) - - if not $is_cached { - let terminfo_data = try { ^infocmp -0 -x xterm-ghostty } catch { - print "Warning: Could not generate terminfo data." - return {ssh_term: "xterm-256color" ssh_opts: $ssh_opts} - } - - print $"Setting up xterm-ghostty terminfo on ($ssh_cfg.hostname)..." - - let ctrl_path = ( - mktemp -td $"ghostty-ssh-($ssh_cfg.user).XXXXXX" - | path join "socket" - ) - - let master_parts = $ssh_opts ++ ["-o" "ControlMaster=yes" "-o" $"ControlPath=($ctrl_path)" "-o" "ControlPersist=60s"] ++ $ssh_args - - ($terminfo_data) | ^ssh ...( - $master_parts ++ - [ - ' - 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' - ] - ) - | complete - | if $in.exit_code != 0 { - print "Warning: Failed to install terminfo." - return {ssh_term: "xterm-256color" ssh_opts: $ssh_opts} - } - - ^$ghostty_bin ...(["+ssh-cache" $"--add=($ssh_id)"]) o+e>| ignore - - return {ssh_term: "xterm-ghostty" ssh_opts: ($ssh_opts ++ ["-o" $"ControlPath=($ctrl_path)"])} - } - - return {ssh_term: "xterm-ghostty" ssh_opts: $ssh_opts} - } - # Wrap `ssh` with Ghostty TERMINFO support - export def --wrapped ssh [...ssh_args: string]: any -> any { - if ($ssh_args | is-empty) { - return (^ssh) - } - # `ssh-env` enables SSH environment variable compatibility. - # Converts TERM from xterm-ghostty to xterm-256color - # and propagates COLORTERM, TERM_PROGRAM, and TERM_PROGRAM_VERSION - # Check your sshd_config on remote host to see if these variables are accepted - let base_ssh_opts = if (has_feature "ssh-env") { - ["-o" "SetEnv COLORTERM=truecolor" "-o" "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION"] - } else { - [] - } - let base_ssh_term = if (has_feature "ssh-env") { - "xterm-256color" - } else { - ($env.TERM? | default "") + export def --wrapped ssh [...args] { + mut ssh_env = {} + mut ssh_opts = [] + + # `ssh-env`: use xterm-256color and propagate COLORTERM/TERM_PROGRAM vars + if (has_feature "ssh-env") { + $ssh_env.TERM = "xterm-256color" + $ssh_opts = [ + "-o" "SetEnv COLORTERM=truecolor" + "-o" "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION" + ] } - let session = if (has_feature "ssh-terminfo") { - set_ssh_terminfo $base_ssh_opts $ssh_args - } else { - {ssh_term: $base_ssh_term ssh_opts: $base_ssh_opts} + # `ssh-terminfo`: auto-install xterm-ghostty terminfo on remote hosts + if (has_feature "ssh-terminfo") { + let ghostty = ($env.GHOSTTY_BIN_DIR? | default "") | path join "ghostty" + + let ssh_cfg = ^ssh -G ...$args + | lines + | parse "{key} {value}" + | where key in ["user" "hostname"] + | select key value + | transpose -rd + | default {user: $env.USER hostname: "localhost"} + let ssh_id = $"($ssh_cfg.user)@($ssh_cfg.hostname)" + + if (^$ghostty "+ssh-cache" $"--host=($ssh_id)" | complete | $in.exit_code == 0) { + $ssh_env.TERM = "xterm-ghostty" + } else { + $ssh_env.TERM = "xterm-256color" + + let terminfo = try { + ^infocmp -0 -x xterm-ghostty + } catch { + print -e "infocmp failed, using xterm-256color" + } + + if ($terminfo | is-not-empty) { + print $"Setting up xterm-ghostty terminfo on ($ssh_cfg.hostname)..." + + let ctrl_path = ( + mktemp -td $"ghostty-ssh-($ssh_cfg.user).XXXXXX" + | path join "socket" + ) + + let remote_args = $ssh_opts ++ [ + "-o" "ControlMaster=yes" + "-o" $"ControlPath=($ctrl_path)" + "-o" "ControlPersist=60s" + ] ++ $args + + $terminfo | ^ssh ...$remote_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' + | complete + | if $in.exit_code == 0 { + ^$ghostty "+ssh-cache" $"--add=($ssh_id)" e>| print -e + $ssh_env.TERM = "xterm-ghostty" + $ssh_opts = ($ssh_opts ++ ["-o" $"ControlPath=($ctrl_path)"]) + } else { + print -e "terminfo install failed, using xterm-256color" + } + } + } } - with-env {TERM: $session.ssh_term} { - ^ssh ...($session.ssh_opts ++ $ssh_args) + let ssh_args = $ssh_opts ++ $args + with-env $ssh_env { + ^ssh ...$ssh_args } } # Wrap `sudo` to preserve Ghostty's TERMINFO environment variable - export def --wrapped sudo [ - ...args # Arguments to pass to `sudo` - ] { + export def --wrapped sudo [...args] { mut sudo_args = $args if (has_feature "sudo") {