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.
This commit is contained in:
Jon Parise
2026-01-29 13:06:14 -05:00
parent 685daee01b
commit 984ff2b402

View File

@@ -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<string>
ssh_args: list<string>
]: [nothing -> record<ssh_term: string, ssh_opts: list<string>>] {
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") {