diff --git a/src/shell-integration/README.md b/src/shell-integration/README.md index 50c01344b..bd1702dde 100644 --- a/src/shell-integration/README.md +++ b/src/shell-integration/README.md @@ -76,6 +76,18 @@ allowing us to automatically integrate with the shell. For details on the Fish startup process, see the [Fish documentation](https://fishshell.com/docs/current/language.html). +### Nushell + +For `nushell` Ghostty prepends to the `XDG_DATA_DIRS` directory. Nushell automatically +loads configuration files in `/nushell/vendor/autoload/*.nu` on startup. These +directories are represented in `Nu` by `$nu.vendor-autoload-dirs`. For more details see + +[Nushell documentation](https://www.nushell.sh/book/configuration.html#configuration-overview) + +> [!NOTE] +> +> Ghostty implements concretely the `ssh-*` features. The rest of the features are supported mostly out of the box by Nushell. + ### Zsh For `zsh`, Ghostty sets `ZDOTDIR` so that it loads our configuration @@ -90,17 +102,3 @@ fi ``` Shell integration requires Zsh 5.1+. - -### Nushell - -For `nushell` Ghostty prepends to the `XDG_DATA_DIRS` directory. Nushell automatically -loads configuration files in `/nushell/vendor/autoload/*.nu` on startup. These -directories are represented in `Nu` by `$nu.vendor-autoload-dirs`. For more details see - -[Nushell documentation](https://www.nushell.sh/book/configuration.html#configuration-overview) - -> [!NOTE] -> -> Ghostty only prepends to `XDG_DATA_DIRS` in the case where the `ssh-*` features are enabled. -> Nushell supports most features out of the box, so other shell integration features are not -> necessary. diff --git a/src/shell-integration/nushell/vendor/autoload/ghostty-integration.nu b/src/shell-integration/nushell/ghostty-ssh-integration.nu similarity index 73% rename from src/shell-integration/nushell/vendor/autoload/ghostty-integration.nu rename to src/shell-integration/nushell/ghostty-ssh-integration.nu index f8e2c3e16..495b96c78 100644 --- a/src/shell-integration/nushell/vendor/autoload/ghostty-integration.nu +++ b/src/shell-integration/nushell/ghostty-ssh-integration.nu @@ -3,7 +3,10 @@ # and propagates COLORTERM, TERM_PROGRAM, and TERM_PROGRAM_VERSION # check your sshd_config on remote host to see if these variables are accepted def set_ssh_env []: nothing -> record> { - return {ssh_term: "xterm-256color", ssh_opts: ["-o", "SetEnv COLORTERM=truecolor", "-o", "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION"]} + return { + ssh_term: "xterm-256color", + ssh_opts: ["-o", "SetEnv COLORTERM=truecolor", "-o", "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION"] + } } # Enables automatic terminfo installation on remote hosts. @@ -11,7 +14,10 @@ def set_ssh_env []: nothing -> record> # 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] { +def set_ssh_terminfo [ + ssh_opts: list, + ssh_args: list +]: [nothing -> record>] { mut ssh_opts = $ssh_opts let ssh_cfg = ^ssh -G ...($ssh_args) | lines @@ -47,25 +53,24 @@ def set_ssh_terminfo [ssh_opts: list, ssh_args: list] { } | path join "socket" ) - let master_parts = $ssh_opts ++ ["-o", "ControlMaster=yes", "-o", $"ControlPath=($ctrl_path)", "-o", "ControlPersist=60s"] ++ $ssh_args + let master_parts = $ssh_opts ++ ["-o" "ControlMaster=yes" "-o" $"ControlPath=($ctrl_path)" "-o" "ControlPersist=60s"] ++ $ssh_args - let terminfo_present = ( - ^ssh ...($master_parts ++ ["infocmp", "xterm-ghostty"]) - | complete - | $in.exit_code == 0 + ($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' + ] ) - - if (not $terminfo_present) { - ( - $terminfo_data - | ^ssh ...($master_parts ++ ["mkdir", "-p", "~/.terminfo", "&&", "tic", "-x", "-"]) - ) - | complete - | if $in.exit_code != 0 { - print "Warning: Failed to install terminfo." - return {ssh_term: "xterm-256color", ssh_opts: $ssh_opts} - } + | 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 $ssh_opts ++= ["-o", $"ControlPath=($ctrl_path)"] } @@ -74,7 +79,7 @@ def set_ssh_terminfo [ssh_opts: list, ssh_args: list] { } # SSH Integration -export def --wrapped ssh [...ssh_args: string] { +export def --wrapped ssh [...ssh_args: string]: any -> any { if ($ssh_args | is-empty) { return (^ssh) } diff --git a/src/shell-integration/nushell/vendor/autoload/bootstrap-integration.nu b/src/shell-integration/nushell/vendor/autoload/bootstrap-integration.nu new file mode 100644 index 000000000..317ba62d3 --- /dev/null +++ b/src/shell-integration/nushell/vendor/autoload/bootstrap-integration.nu @@ -0,0 +1,29 @@ +let enable_integration = $env.GHOSTTY_SHELL_FEATURES | split row ',' + | where ($it in ["ssh-env" "ssh-terminfo"]) + | is-not-empty + +let ghostty_ssh_file = $env.GHOSTTY_RESOURCES_DIR + | path join "shell-integration" "nushell" "ghostty-ssh-integration.nu" + +let ssh_integration_file = $nu.data-dir | path join "ghostty-ssh-integration.nu" +let ssh_file_exists = $ssh_integration_file | path exists + +# TOD0: In case of an update to the `ghostty-ssh-integration.nu` file +# the file wont be updated here, so we need to support +# saving the new file once there is an update + +match [$enable_integration $ssh_file_exists] { + [true false] => { + # $nu.data-dir is not created by default + # https://www.nushell.sh/book/configuration.html#startup-variables + $nu.data-dir | path exists | if (not $in) { mkdir $nu.data-dir } + open $ghostty_ssh_file | save $ssh_integration_file + } + [false true] => { + # We need to check if the user disabled `ssh-integration` and thus + # the integration file needs to be removed so it doesnt get sourced by + # the `source-integration.nu` file + rm $ssh_integration_file + } + _ => { } +} diff --git a/src/shell-integration/nushell/vendor/autoload/source-integration.nu b/src/shell-integration/nushell/vendor/autoload/source-integration.nu new file mode 100644 index 000000000..1c21833a4 --- /dev/null +++ b/src/shell-integration/nushell/vendor/autoload/source-integration.nu @@ -0,0 +1,11 @@ +# Sourcing the `ghostty-integration.nu` cant be on the +# `bootstrap-integration.nu` file because it tries to resolve the `sourced` +# file at parsing time, which would make it source nothing. + +# But here we rely on the fact that `boostrap-integration.nu` gets parsed +# and executed first, and then we can count on `ssh_integration_file` being available + +#https://www.nushell.sh/book/thinking_in_nu.html#example-dynamically-generating-source + +const ssh_integration_file = $nu.data-dir | path join "ghostty-ssh-integration.nu" +source (if ($ssh_integration_file | path exists) { $ssh_integration_file } else { null }) diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index fcbdaef6a..8a03945c9 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -68,7 +68,6 @@ pub fn setup( command, env, exe, - features, ); // Setup our feature env vars @@ -83,7 +82,6 @@ fn setupShell( command: config.Command, env: *EnvMap, exe: []const u8, - features: config.ShellIntegrationFeatures, ) !?ShellIntegration { if (std.mem.eql(u8, "bash", exe)) { // Apple distributes their own patched version of Bash 3.2 @@ -132,7 +130,9 @@ fn setupShell( } if (std.mem.eql(u8, "nu", exe)) { - try setupNu(alloc_arena, resource_dir, env, features); + // Sets up XDG_DATA_DIRS so that it can be picked automatically by + // nushell on startup. + try setupXdgDataDirs(alloc_arena, resource_dir, env); return null; } @@ -659,19 +659,6 @@ test "xdg: existing XDG_DATA_DIRS" { try testing.expectEqualStrings("./shell-integration:/opt/share", env.get("XDG_DATA_DIRS").?); } -/// Setup the nushell shell integration. This works by setting -/// XDG_DATA_DIRS so that it can be picked automatically by -/// nushell on startup. -/// Only implements `ssh-*` shell features. Rest are not supported. -fn setupNu(alloc_arena: Allocator, resource_dir: []const u8, env: *EnvMap, features: config.ShellIntegrationFeatures) !void { - // This makes sure that `Nu` loads our integration file - // and wraps the `ssh` function only if the `ssh` features - // are enabled. - // Otherwise, it does not do anything. - if (features.@"ssh-env" or features.@"ssh-terminfo") { - try setupXdgDataDirs(alloc_arena, resource_dir, env); - } -} /// Setup the zsh automatic shell integration. This works by setting /// ZDOTDIR to our resources dir so that zsh will load our config. This /// config then loads the true user config.