{ use platform use str # Clean up XDG_DATA_DIRS by removing GHOSTTY_SHELL_INTEGRATION_XDG_DIR if (and (has-env GHOSTTY_SHELL_INTEGRATION_XDG_DIR) (has-env XDG_DATA_DIRS)) { set-env XDG_DATA_DIRS (str:replace $E:GHOSTTY_SHELL_INTEGRATION_XDG_DIR":" "" $E:XDG_DATA_DIRS) unset-env GHOSTTY_SHELL_INTEGRATION_XDG_DIR } # List of enabled shell integration features var features = [(str:split ',' $E:GHOSTTY_SHELL_FEATURES)] # State tracking for semantic prompt sequences # Values: 'prompt-start', 'pre-exec', 'post-exec' fn set-prompt-state {|new| set-env __ghostty_prompt_state $new } fn mark-prompt-start { if (not-eq $E:__ghostty_prompt_state 'prompt-start') { printf "\e]133;D;aid="$pid"\a" } set-prompt-state 'prompt-start' printf "\e]133;A;aid="$pid"\a" } fn mark-output-start {|_| set-prompt-state 'pre-exec' printf "\e]133;C\a" } fn mark-output-end {|cmd-info| set-prompt-state 'post-exec' var exit-status = 0 # in case of error: retrieve exit status, # unless does not exist (= builtin function failure), then default to 1 if (not-eq $nil $cmd-info[error]) { set exit-status = 1 if (has-key $cmd-info[error] reason) { if (has-key $cmd-info[error][reason] exit-status) { set exit-status = $cmd-info[error][reason][exit-status] } } } printf "\e]133;D;"$exit-status";aid="$pid"\a" } # NOTE: OSC 133;B (end of prompt, start of input) cannot be reliably # implemented at the script level in Elvish. The prompt function's output is # escaped, and writing to /dev/tty has timing issues because Elvish renders # its prompts on a background thread. Full semantic prompt support requires a # native implementation: https://github.com/elves/elvish/pull/1917 fn sudo-with-terminfo {|@args| var sudoedit = $false for arg $args { if (str:has-prefix $arg --) { if (eq $arg --edit) { set sudoedit = $true break } } elif (str:has-prefix $arg -) { if (str:contains (str:trim-prefix $arg -) e) { set sudoedit = $true break } } elif (not (str:contains $arg =)) { break } } if (not $sudoedit) { set args = [ --preserve-env=TERMINFO $@args ] } (external sudo) $@args } fn ssh-integration {|@args| var ssh-term = "xterm-256color" var ssh-opts = [] # Configure environment variables for remote session if (has-value $features ssh-env) { set ssh-opts = (conj $ssh-opts ^ -o "SetEnv COLORTERM=truecolor" ^ -o "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION") } if (has-value $features ssh-terminfo) { var ssh-user = "" var ssh-hostname = "" # Parse ssh config for line [((external ssh) -G $@args)] { var parts = [(str:fields $line)] if (> (count $parts) 1) { var ssh-key = $parts[0] var ssh-value = $parts[1] if (eq $ssh-key user) { set ssh-user = $ssh-value } elif (eq $ssh-key hostname) { set ssh-hostname = $ssh-value } if (and (not-eq $ssh-user "") (not-eq $ssh-hostname "")) { break } } } if (not-eq $ssh-hostname "") { var ghostty = $E:GHOSTTY_BIN_DIR/"ghostty" var ssh-target = $ssh-user"@"$ssh-hostname # Check if terminfo is already cached if (bool ?($ghostty +ssh-cache --host=$ssh-target)) { set ssh-term = "xterm-ghostty" } elif (has-external infocmp) { var ssh-terminfo = ((external infocmp) -0 -x xterm-ghostty 2>/dev/null | slurp) if (not-eq $ssh-terminfo "") { echo "Setting up xterm-ghostty terminfo on "$ssh-hostname"..." >&2 use os var ssh-cpath-dir = (os:temp-dir "ghostty-ssh-"$ssh-user".*") var ssh-cpath = $ssh-cpath-dir"/socket" if (bool ?(echo $ssh-terminfo | (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 ' 2>/dev/null)) { set ssh-term = "xterm-ghostty" set ssh-opts = (conj $ssh-opts -o ControlPath=$ssh-cpath) # Cache successful installation $ghostty +ssh-cache --add=$ssh-target >/dev/null } else { echo "Warning: Failed to install terminfo." >&2 } } else { echo "Warning: Could not generate terminfo data." >&2 } } else { echo "Warning: ghostty command not available for cache management." >&2 } } } with [E:TERM = $ssh-term] { (external ssh) $@ssh-opts $@args } } defer { mark-prompt-start } set edit:before-readline = (conj $edit:before-readline $mark-prompt-start~) set edit:after-readline = (conj $edit:after-readline $mark-output-start~) set edit:after-command = (conj $edit:after-command $mark-output-end~) if (str:contains $E:GHOSTTY_SHELL_FEATURES "cursor") { var cursor = "5" # blinking bar if (has-value $features cursor:steady) { set cursor = "6" # steady bar } fn beam { printf "\e["$cursor" q" } fn reset { printf "\e[0 q" } set edit:before-readline = (conj $edit:before-readline $beam~) set edit:after-readline = (conj $edit:after-readline {|_| reset }) } if (and (has-value $features path) (has-env GHOSTTY_BIN_DIR)) { if (not (has-value $paths $E:GHOSTTY_BIN_DIR)) { set paths = [$@paths $E:GHOSTTY_BIN_DIR] } } if (and (has-value $features sudo) (not-eq "" $E:TERMINFO) (has-external sudo)) { edit:add-var sudo~ $sudo-with-terminfo~ } if (and (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-) (has-external ssh)) { edit:add-var ssh~ $ssh-integration~ } # Report changes to the current directory. fn report-pwd { printf "\e]7;kitty-shell-cwd://%s%s\a" (platform:hostname) $pwd } set after-chdir = (conj $after-chdir {|_| report-pwd }) report-pwd }