Files
galaxy/app/assets/bundled/bootstrap/bash_body.sh
T
rkw6086 75d4965385 Port recent Warp terminal and UI improvements
Improve tab-group search, rename focus, history navigation, shell prompt and
completion handling, emoji width promotion, and macOS clipboard support.
Guard unavailable settings/model-selector/undo-close views and forward the
MCP OAuth callback issuer through the existing authentication flow.

Adapt local changes from 14 upstream Warp commits through f571865ca3f2,
reviewed from the latest 200 commits. Preserve Galaxy provider lifecycle,
permissions, storage, and branding; exclude hosted/cloud infrastructure.

Fix existing boolean-assertion lints and use stronger optimization with four
code-generation units for the app test target to fit macOS unwind offsets.
Production and dependency compilation settings remain unchanged.

Validation: two clean builds and a final build; both prescribed Clippy
commands with -D warnings; formatting, shell syntax and completion cleanup;
167 focused regression tests. Galaxy was not launched.
2026-10-06 14:48:09 -05:00

1423 lines
69 KiB
Bash

# Note that WARP_SESSION_ID is expected to have been set when executing commands to
# emit the InitShell payload, which includes the session ID.
#
# Throughout, command -p is used to call external binaries. command -p resolves the
# given command using the system default $PATH, which ensures the shells can locate
# the corresponding binaries even if the user has a clobbered value of $PATH.
if [ -z "$WARP_BOOTSTRAPPED" ]; then
# Byte sequence used to signal the start of a DCS. ([0x1b, 0x50, 0x24] which
# maps to <ESC>, P, $ in ASCII.)
DCS_START="$(printf '\eP$')"
# Appended to $DCS_START to signal that the following message is JSON-encoded.
DCS_JSON_MARKER="d"
# Byte sequence used to signal the end of a DCS (7-bit ST: ESC \).
DCS_END="$(printf '\x1b\x5c')"
OSC_START="$(printf '\e]9278;')"
OSC_END="$(printf '\a')"
OSC_PARAM_SEPARATOR=";"
RESET_GRID_OSC="$(printf '\e]9279\a')"
# OSC used to mark the start of in-band command output.
#
# Printable characters received this OSC and OSC_END_GENERATOR_OUTPUT are parsed and handled as
# output for an in-band command.
OSC_START_GENERATOR_OUTPUT="$(printf '\e]9277;A\a')"
# OSC used to mark the end of in-band command output.
#
# Printable characters received between OSC_START_GENERATOR_OUTPUT and this are parsed and
# handled as output for an in-band command.
OSC_END_GENERATOR_OUTPUT="$(printf '\e]9277;B\a')"
# Attempt to cd to the desired initial working directory, swallowing any
# errors. If this fails, the user will end up in their home directory.
if [[ ! -z "$GALAXY_INITIAL_WORKING_DIR" ]]; then
cd "$GALAXY_INITIAL_WORKING_DIR" >/dev/null 2>&1
unset GALAXY_INITIAL_WORKING_DIR
fi
# We configure history to `ignorespace` to avoid leaking our bootstrap script
# into the user's history. At this point, we unset this option because we don't
# need it anymore and to avoid side effects.
# Note that bash-preexec will change this setting for its functionality and
# clobbers `ignorespace`.
unset HISTCONTROL
define_bashpreexec_functions
# The temporary files used to track generator PIDs. We'll fill these in later,
# if we execute any generator commands.
_WARP_GENERATOR_PIDS_STARTED_TMP_FILE=""
_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE=""
# Make sure we delete generator PID files when the shell exits, if they exist.
__warp_generator_pid_file_cleanup() {
if [[ -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then
command -p rm $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE
fi
if [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then
command -p rm $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE
fi
}
trap __warp_generator_pid_file_cleanup EXIT
# Writes a hex-encoded JSON message to the pty.
warp_send_json_message () {
# Sends a message to the controlling terminal as a DSC control sequence.
# Note that because the JSON string may contain characters that we don't control (including
# unicode), we encode it as hexadecimal string to avoid prematurely calling unhook if
# one of the bytes in JSON is 9c (ST) or other (CAN, SUB, ESC).
encoded_message=$(warp_hex_encode_string "$1")
# We send the InitShell hook via OSCs when on WSL or MSYS2 or SSH from Windows and via DCSs otherwise.
# Note that $WARP_USING_WINDOWS_CON_PTY is set in the init shell script.
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then
printf $OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR$encoded_message$OSC_END
else
printf $DCS_START$DCS_JSON_MARKER$encoded_message$DCS_END
fi
}
# Emit the ExitShell hook right before the remote shell exits so the Warp
# client can drop per-session resources (specifically the
# `ssh … remote-server-proxy` child that holds a multiplexed channel on
# the foreground ssh ControlMaster). This avoids a hang where the master
# waits on orphaned slave channels when the user ends their interactive
# session.
#
# Only relevant for remote SSH shells. WARP_IS_SSH is exported to "1"
# by `warp_ssh_helper` on the remote side of a Warp-managed SSH session
# and is unset everywhere else (local shells, subshells, docker
# sandboxes, etc.), so the hook only fires where a remote-server-proxy
# actually needs tearing down.
#
# Installed after warp_send_json_message is defined so the handler is
# callable the moment the trap is registered.
if [[ "$WARP_IS_SSH" == "1" ]]; then
__warp_emit_exit_shell() {
if [[ -n "$WARP_SESSION_ID" ]]; then
warp_send_json_message \
"{\"hook\": \"ExitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
fi
}
# Bash allows only one handler per signal, so compose with the
# already-installed generator cleanup. Cover both normal exit (exit,
# logout, Ctrl-D) and SIGHUP (connection drop).
__warp_on_exit() {
__warp_emit_exit_shell
__warp_generator_pid_file_cleanup
}
trap __warp_on_exit EXIT HUP
fi
warp_maybe_send_reset_grid_osc () {
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then
printf $RESET_GRID_OSC
fi
}
# Expects the first argument to be the shell hook.
warp_send_hook_via_kv_pairs_start () {
printf "${OSC_START}k;A;%s\a" $1
}
# Expects the first argument to be the key and the second argument to be the value.
warp_send_hook_kv_pair_escaped () {
# Note that we only escape the value.
if [[ -n "$2" ]]; then
printf "${OSC_START}k;B;%s;%q\a" "$1" "$2"
else
# Don't print anything for the empty value.
printf "${OSC_START}k;B;%s;\a" "$1"
fi
}
# Expects the first argument to be the key and the second argument to be the value.
warp_send_hook_kv_pair () {
# Note that we only escape the value.
if [[ -n "$2" ]]; then
printf "${OSC_START}k;B;%s;%s\a" "$1" "$2"
else
# Don't print anything for the empty value.
printf "${OSC_START}k;B;%s;\a" "$1"
fi
}
warp_send_hook_via_kv_pairs_end () {
printf "${OSC_START}k;C\a"
}
# Hex-encodes the given argument and writes it to the PTY, wrapped in the OSC
# sequences for generator output.
#
#
# Usage:
# warp_send_generator_output_osc $my_message
#
# The payload of the OSC is "<content_length>;<hex-encoded content>".
warp_send_generator_output_osc () {
local hex_encoded_message=$(warp_hex_encode_string "$1")
warp_send_generator_output_osc_pre_hex_encoded "$hex_encoded_message"
}
# Note: If we're on windows, we send a reset grid to erase any cursor mutations caused by
# the in-band command.
warp_send_generator_output_osc_pre_hex_encoded () {
local byte_count=$(LC_ALL="C"; printf "${#1}")
printf "%b%i;%s%b" $OSC_START_GENERATOR_OUTPUT $byte_count $1 $OSC_END_GENERATOR_OUTPUT
warp_maybe_send_reset_grid_osc
}
# Executes the given command and writes its output to the pty wrapped in a
# DCS. The written DCS conforms to a basic schema including other metadata:
# "<command_id>;<command_output>;<exit_code>"
# where command_id is the ID given as the first argument to this function,
# exit_code is the exit code of the executed command, and command_output is
# the output itself.
_warp_execute_command() {
local command_id=$1
# This is shorthand to slice the 2nd-nth arguments of this function (i.e.
# the command array) into its own array. The first argument is the
# command_id stored above.
#
# This must be double-quoted to prevent bash word-splitting, which would effectively replace
# newlines and tabs with spaces, potentially invalidating the syntactical correctness of the
# command.
local command="${@:2}"
# Bash cannot handle null characters in variables or command substitutions, so hex encode the
# output immediately before it's stored anywhere. This hex encoding must be done inline --
# bash doesn't like functions called with null bytes either.
local generator_output="$( {
echo -n "$command_id;";
# Command substitution only captures stdout, so redirect stderr to stdout.
eval "$command" 2>&1;
echo -n ";$?";
} | command -p od -An -v -tx1 | command -p tr -d ' \n')"
warp_send_generator_output_osc_pre_hex_encoded "$generator_output"
}
# Runs the given command in the background, records its PID in
# _WARP_GENERATOR_PIDS_STARTED_TMP_FILE, and adds its PID from the file when
# the job is completed.
_warp_run_generator_command_internal() {
# $@ must be double-quoted to prevent word-splitting, which would cause the given command to
# be split into a bash list on $IFS chars (spaces, tabs, newlines), which could invalidate
# the syntactical correctness of the command.
_warp_execute_command "$@" &
# $! contains the PID of the most recently backgrounded command.
local pid=$!
echo $pid >> $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE
wait $pid 2> /dev/null
# If the exit code of the backgrounded _warp_execute_command process is non-zero,
# the call to send the generator output failed (most likely because this is being
# executed in an old bash version that doesn't support some syntax in
# _warp_execute_command function itself). In this case, send empty output with
# exit code 1 to indicate generator execution failed.
if [[ $? -ne 0 ]]; then
warp_send_generator_output_osc "$1;;1"
fi
# Add the PID to the completed generators PID file.
#
# The completed generator PIDs file may not exist if this generator was (by
# error) left running/not cancelled properly in warp_preexec.
if [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then
echo $pid >> $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE
fi
}
# Executes a generator command in the background, where the first argument is
# the "command ID" assigned by the Rust app and the second-nth arguments
# specify the command to be run.
#
# Note that the command string should be single-quoted so any variables are
# not substituted until the command string is actually evaluated.
#
# Usage:
# warp_run_generator_command <command_id> '<command> <arg1> ... <argn>'
warp_run_generator_command() {
# Setting this environment variable prevents warp_precmd from emitting the
# 'Block started' hook to the Rust app.
_WARP_GENERATOR_COMMAND=1
# Ensure the started and completed generator PID files exist.
if [[ -z $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE || ! -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then
_WARP_GENERATOR_PIDS_STARTED_TMP_FILE="$(command -p mktemp)"
fi
if [[ -z $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE || ! -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then
_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE="$(command -p mktemp)"
fi
# To minimize latency and prevent the user from being blocked from entering a command,
# cache the user's precmd_functions and only register warp_precmd. In the warp_precmd
# execution following this generator command, the user's precmd_functions are restored.
_USER_PRECMD_FUNCTIONS=(${precmd_functions[@]})
precmd_functions=(warp_precmd)
# $@ must be double-quoted to prevent word-splitting, which would cause the given command to
# be split into a bash list on $IFS chars (spaces, tabs, newlines), which could invalidate
# the syntactical correctness of the command.
(_warp_run_generator_command_internal "$@" &)
}
# Note that this is very performance sensitive code, so try not to
# invoke any external commands in here.
warp_preexec () {
# Use the $BASH_COMMAND environment variable instead of $1, which is passed in by bash_preeexec.
#
# Bash_preexec intends to pass the command to preexec functions (as $1), but it utilizes session
# history to do so. This means that $1 is not the correct command if the executed command is ignored
# by history (e.g. via $HISTCONTROL or $HISTIGNORE); for example, all in-band generators are ignored
# by history.
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "Preexec"
warp_send_hook_kv_pair "command" "$BASH_COMMAND"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
local truncated_command=$(warp_escape_json "$BASH_COMMAND")
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$truncated_command\", \"session_id\": $WARP_SESSION_ID}}"
fi
warp_maybe_send_reset_grid_osc
# Since we did not early-return above, this hook is for a user-entered
# command. Kill ongoing generator jobs so their output does not interfere
# with the user command's output.
if [[ "$BASH_COMMAND" != warp_run_generator_command* ]] && [[ -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]] && [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]
then
# Read PIDs from the started generators tmp file that are not present in
# the completed generators tmp file into a bash array.
#
# The logic used to be the following:
#
# pids=($(command -p comm -23 $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE))
#
# However, that requires that the files are sorted, which we do not enforce (the OS can assign PIDs
# in any order). While we could sort the files and then compare them, the files are expected to be
# small, so we avoid the overhead of spawning multiple processes and instead do the comparison
# manually.
completed_pids=()
while IFS= read -r pid; do
completed_pids+=("$pid")
done < $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE
pids=()
while IFS= read -r pid; do
found=0
for completed_pid in "${completed_pids[@]}"; do
if [[ "$pid" == "$completed_pid" ]]; then
found=1
break
fi
done
if (( found == 0 )); then
pids+=("$pid")
fi
done < $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE
# If the array is not empty, kill the ongoing pids.
if [[ ! -z $pids ]]; then
# Suppress stderr output; kill writes to stderr if any of the given
# PIDS are not running (which might rarely be the case due to race
# conditions in checking which PIDS to cancel and this kill command.
kill -9 $pids >/dev/null 2>/dev/null
fi
fi
}
# Set terminal window and tab title to the same title value. Note that for values longer than 25
# characters, we truncate the title and prepend "..".
# Usage warp_title "title"
# Users can disable the auto title if they chose to by setting WARP_DISABLE_AUTO_TITLE.
warp_title () {
DISABLE_AUTO_TITLE="1"
# truncating the title's len to 25 characters and leading ".."
tmp_len=$((${#1}-25)) # starting character's position
len=$((tmp_len>0 ? tmp_len : 0)) # account for the shorter strings
if [[ $len -ne 0 ]]; then
title="..${1:$len}" # shorten the argument and prepend the leading ".."
else
title="$1"
fi
# Set the title. Be sure to make the title a %s argument to prevent title content from ending up
# in the block output, see:
# https://linear.app/warpdotdev/issue/WAR-6064/bash-commands-having-esc-write-the-command-to-the-block-output
printf "\033]0;%s\a" "$title"
}
# Runs before executing the command
warp_set_title_idle_on_precmd () {
# If the user wants to set the title themselves, they can set the WARP_DISABLE_AUTO_TITLE flag.
if [ ! -z "$WARP_DISABLE_AUTO_TITLE" ]; then
return
fi
# Note that in older versions of bash (the one builtin with MacOS) the `~` character is just that,
# however, when used within `""` (double quotes) on a newer (homebrew) bash version,
# it automatically EXPANDS to the actual value of $HOME and needs to be escaped to give a proper
# tilde character. So instead, we have it as a separate variable that uses `''` (single quote)
# to avoid expanding, and use it later within the new bash term title. This way both old and
# new bash see tilde as tilde, and tilde only.
new_home='~'
bash_term_tab_title="${PWD/#$HOME/$new_home}"
if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
warp_title "$bash_term_tab_title"
else
bash_term_tab_title_remote="${HOSTNAME%%.*}:$bash_term_tab_title"
warp_title "$bash_term_tab_title_remote"
fi
}
# Runs before executing the command
warp_set_title_active_on_preexec () {
# If the user wants to set the title themselves, they can set the WARP_DISABLE_AUTO_TITLE flag.
if [ ! -z "$WARP_DISABLE_AUTO_TITLE" ]; then
return
fi
cmd="$1"
# warp_set_title_active_on_preexec is a preexec_function, which accepts 1 argument
#(currently invoked command)
local this_command_spec
read -r -a this_command_spec <<< "$1"
# if running fg, figure out the command from its process id
if [[ "${this_command_spec[0]}" == "fg" ]]; then
# note that `fg` and `jobs` essentially take the same arguments (but to be extra safe,
# we swapped empty/no-arguments to current background process), so we can just use that
jobspec=${this_command_spec[1]}
if [[ "$jobspec" == "" ]]; then
jobspec="%%"
fi
# the output is of format:
# [1]+ Stopped <Command>
# the following modifications replace all whitespaces to a single space, and remote first
# two columns from the output (leaving the command name, which itself can include spaces).
fg_command_name=$(jobs "$jobspec" 2> /dev/null | command -p tr -s ' ' | command -p cut -d ' ' -f3-)
if [[ "$fg_command_name" == "" ]]; then
# in the weird case when this returns an empty string, lets just stick to whatever the
# tab title was originally set to.
return
fi
cmd="$fg_command_name"
fi
warp_title "$cmd"
}
# The git prompt's git commands are read-only and should not interfere with
# other processes. This environment variable is equivalent to running with `git
# --no-optional-locks`, but falls back gracefully for older versions of git.
# See git(1) for and git-status(1) for a description of that flag.
#
# We wrap in a local function instead of exporting the variable directly in
# order to avoid interfering with manually-run git commands by the user.
warp_git () {
GIT_OPTIONAL_LOCKS=0 command git "$@"
}
# Note that this is very performance sensitive code, so try not to
# invoke any external commands in here.
warp_precmd () {
# $? is relative to the process so we MUST check this first
# or else the exit code will correspond to the commands
# executed within this block instead of the actual last
# command that was run.
local exit_code=$?
local next_block_id="precmd-$WARP_SESSION_ID-$((block_id++))"
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "CommandFinished"
warp_send_hook_kv_pair "exit_code" "$exit_code"
warp_send_hook_kv_pair "next_block_id" "$next_block_id"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"$next_block_id\", \"session_id\": $WARP_SESSION_ID}}"
fi
warp_maybe_send_reset_grid_osc
if [[ $PS1 == "" ]]; then
# Use the saved PS1, if we've already unset it (due to active Warp prompt).
WARP_PS1="$SAVED_PS1"
else
# If we haven't unset it yet, then we can use the current PS1 value.
WARP_PS1="$PS1"
fi
# If this is being called for a generator command, short circuit and send an unpopulated
# precmd payload (except for pwd), since we don't re-render the prompt after generator commands
# are run.
if [ ! -z $_WARP_GENERATOR_COMMAND ]; then
# Restore the user's precmd_functions, since they were un-registered prior to executing
# the generator.
precmd_functions=(${_USER_PRECMD_FUNCTIONS[@]})
unset _WARP_GENERATOR_COMMAND
warp_send_json_message "{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"\",
\"ps1\": \"\",
\"git_head\": \"\",
\"git_branch\": \"\",
\"virtual_env\": \"\",
\"conda_env\": \"\",
\"node_version\": \"\",
\"session_id\": $WARP_SESSION_ID,
\"is_after_in_band_command\": true
}}"
return 0
fi
# If the files for tracking generator PIDs exist, clear them.
if [[ -n $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE && -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then
echo "" > $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE
fi
if [[ -n $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE && -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then
echo "" > $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE
fi
if [[ -z $WARP_INPUT_REPORTING_SUPPORTED ]]; then
WARP_INPUT_REPORTING_SUPPORTED=$(warp_input_reporting_supported)
fi
local honor_ps1
local deref_ps1=""
local escaped_ps1=""
if [[ "$WARP_HONOR_PS1" == "1" ]]; then
honor_ps1="true"
else
honor_ps1="false"
# If we haven't already, cache information about supported features.
if [[ -z $WARP_PS1_EXPANSION_SUPPORTED ]]; then
WARP_PS1_EXPANSION_SUPPORTED=$(warp_ps1_expanding_supported)
fi
if [[ $WARP_PS1_EXPANSION_SUPPORTED == "1" ]]; then
# When evaluating the PS1, we want to ensure that it's aware of the last exit code.
# Since we captured it already and executed multiple other commands, the actual
# last exit code has changed. So before the evaluation, we want to trick the shell
# into returning the correct value for the $? that may be in PS1
exit_code_hack() {
return $1
}
exit_code_hack $exit_code
deref_ps1=${WARP_PS1@P}
else
# Tricking the shell into rendering the prompt
# Note that in more modern versions of bash we could use ${PS1@P} to achieve the same,
# but MacOS comes by default with a much older version of bash, and we want to be compatible.
deref_ps1=$(echo -e "\n" | PS1="$WARP_PS1" BASH_SILENCE_DEPRECATION_WARNING=1 "$BASH" --norc -i 2>&1 | command -p head -2 | command -p tail -1)
fi
if [ "$WARP_IN_MSYS2" = false ]; then
escaped_ps1=$(warp_escape_ps1 "$(echo "$deref_ps1")")
fi
fi
# Flush history
history -a
# Reset the custom kill-whole-line binding as the user's bashrc (which is sourced after bashrc_warp)
# could have added another bind. This won't have any user-impact because these shortcuts are only run
# in the context of the bash editor, which isn't displayed in Warp.
bind -r '"\C-p"'
bind "\C-p":kill-whole-line
# Reset the report-input binding in case the user's bashrc modified it.
# This is arbitrarily bound to ESC-i in all supported shells ("i" for input).
if [[ $WARP_INPUT_REPORTING_SUPPORTED == "1" ]]; then
bind -r '"\ei"'
bind -x '"\ei":"warp_report_input"'
fi
# We need to register bindkeys to enable intra-session switching of the prompt
# (these bindkeys are used by Warp to communicate the prompt mode switch to bash).
# We remove any existing bindkey for ESC-P ("p" for prompt/PS1) and register the bindkey
# to our custom function. Note that this specific keybinding is arbitrary.
bind -r '"\ep"'
bind -x '"\ep":"warp_change_prompt_modes_to_ps1"'
# We remove any existing bindkey for ESC-P ("w" for Warp prompt) and register the bindkey
# to our custom function. Note that this specific keybinding is arbitrary.
bind -r '"\ew"'
bind -x '"\ew":"warp_change_prompt_modes_to_warp_prompt"'
local escaped_pwd
if [ "$WARP_IN_MSYS2" = false ]; then
if [ -n "$WSL_DISTRO_NAME" ]; then
# In WSL, avoid symlinks b/c on Windows `std::fs` is unable to resolve symlink inside WSL containers.
escaped_pwd=$(warp_escape_json "$(pwd -P)")
else
escaped_pwd=$(warp_escape_json "$PWD")
fi
fi
local escaped_virtual_env=""
local escaped_conda_env=""
local escaped_node_version=""
local escaped_git_head=""
local escaped_git_branch=""
local git_head=""
local git_branch=""
# Only fill these fields once we've finished bootstrapping, as the
# blocks created during the bootstrap process don't have visible
# prompts, and we don't want to invoke `git` before we've sourced the
# user's rcfiles and have a fully-populated PATH.
if [[ -n "$WARP_BOOTSTRAPPED" ]]; then
if [[ -n "$VIRTUAL_ENV" ]] && [ "$WARP_IN_MSYS2" = false ]; then
escaped_virtual_env=$(warp_escape_json "$VIRTUAL_ENV")
fi
if [[ -n "$CONDA_DEFAULT_ENV" ]] && [ "$WARP_IN_MSYS2" = false ]; then
escaped_conda_env=$(warp_escape_json "$CONDA_DEFAULT_ENV")
fi
# Get the Node.js version, but only when the Node.js Version chip is enabled.
# Warp sets WARP_PROMPT_NODE_VERSION_ENABLED to "0" when the chip is not in the
# prompt (defaulting to enabled when unset), so we avoid spawning `node` on
# every prompt when the chip is not shown.
if [[ "$WARP_PROMPT_NODE_VERSION_ENABLED" != "0" ]] && command -v node > /dev/null 2>&1 && [ "$WARP_IN_MSYS2" = false ]; then
# Check for package.json in current directory and parent directories
local current_dir="$PWD"
local found_package_json=false
local package_json_dir=""
while [[ -n "$current_dir" ]]; do
if [[ -f "$current_dir/package.json" ]]; then
found_package_json=true
package_json_dir="$current_dir"
break
fi
[[ "$current_dir" == "/" ]] && break
# Strip the last path segment without spawning `dirname`.
current_dir="${current_dir%/*}"
[[ -z "$current_dir" ]] && current_dir="/"
done
# Only show node version if package.json is within a git repository
if [[ "$found_package_json" = true ]]; then
local git_dir="$package_json_dir"
local in_git_repo=false
while [[ -n "$git_dir" ]]; do
if [[ -d "$git_dir/.git" ]]; then
in_git_repo=true
break
fi
[[ "$git_dir" == "/" ]] && break
git_dir="${git_dir%/*}"
[[ -z "$git_dir" ]] && git_dir="/"
done
if [[ "$in_git_repo" = true ]]; then
# Cache the resolved version keyed on PWD + PATH so we only spawn
# `node --version` when the directory or PATH changes (PATH changes
# on `nvm use`). The cache vars are global (no `local`) so they
# persist across precmd invocations.
local node_cache_key="$PWD:$PATH"
if [[ "$node_cache_key" == "$_WARP_NODE_VERSION_CACHE_KEY" ]]; then
escaped_node_version="$_WARP_NODE_VERSION_CACHE_VALUE"
else
local node_version=$(node --version 2>/dev/null)
if [[ -n "$node_version" ]]; then
escaped_node_version=$(warp_escape_json "$node_version")
fi
_WARP_NODE_VERSION_CACHE_KEY="$node_cache_key"
_WARP_NODE_VERSION_CACHE_VALUE="$escaped_node_version"
fi
fi
fi
fi
# Note: We explicitly do _not_ use command -p here, as `git` is a command that can be
# installed in non-standard locations and so is not always available on the shell's
# default PATH. Instead, we rely on the active PATH, as if the user doesn't have git
# available to their session, it is unlikely they will be looking for git branch
# information from the prompt.
if command -v git >/dev/null 2>&1; then
git_branch=$(warp_git symbolic-ref --short HEAD 2> /dev/null)
# The git branch the user is on, or the git commit hash if they're not on a branch.
git_head="${git_branch:-$(warp_git rev-parse --short HEAD 2> /dev/null)}"
fi
if [ "$WARP_IN_MSYS2" = false ]; then
escaped_git_head=$(warp_escape_json "$git_head")
escaped_git_branch=$(warp_escape_json "$git_branch")
fi
fi
# At this point, escaped prompt looks something like
# \\u{001B}\\u{005B}\\u{0030}\\u{0031}\\u{003B} ...
# We need to maintain the double quoting of \\u in the message that
# is sent otherwise the receiving side will interpret the value
# as JS string literals of the form \uHEX, and will include
# ctrl characters (like ESC) in the json, which will cause a JSON
# parse error.
# Note WARP_SESSION_ID doesn't need to be escaped since it's a number
# We also pass the shell's notion of `honor_ps1` to ensure it's synced correctly on the Warp-side for prompt handling.
# This is passed as a "real boolean" via the JSON payload (string interpolated into JSON string below).
# We send the escaped PS1, if we are in active Warp prompt mode, for prompt preview rendering (note the shell's PS1 is unset in this case).
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "Precmd"
warp_send_hook_kv_pair "exit_code" "$exit_code"
warp_send_hook_kv_pair "next_block_id" "$next_block_id"
warp_send_hook_kv_pair "pwd" "$PWD"
warp_send_hook_kv_pair_escaped "ps1" "$deref_ps1"
warp_send_hook_kv_pair "ps1_is_encoded" "false"
warp_send_hook_kv_pair "honor_ps1" "$honor_ps1"
warp_send_hook_kv_pair "git_head" "$git_head"
warp_send_hook_kv_pair "git_branch" "$git_branch"
warp_send_hook_kv_pair "virtual_env" "$VIRTUAL_ENV"
warp_send_hook_kv_pair "conda_env" "$CONDA_DEFAULT_ENV"
warp_send_hook_kv_pair "node_version" "$node_version"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
local escaped_json="{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"$escaped_pwd\",
\"ps1\": \"$escaped_ps1\",
\"honor_ps1\": $honor_ps1,
\"ps1_is_encoded\": true,
\"git_head\": \"$escaped_git_head\",
\"git_branch\": \"$escaped_git_branch\",
\"virtual_env\": \"$escaped_virtual_env\",
\"conda_env\": \"$escaped_conda_env\",
\"node_version\": \"$escaped_node_version\",
\"session_id\": $WARP_SESSION_ID
}}"
warp_send_json_message "$escaped_json"
fi
}
warp_clear_on_next_block () {
warp_send_json_message '{"hook": "ClearOnNextBlock"}'
}
# Format a string value according to JSON syntax.
warp_escape_json () {
# Explanation of the sed replacements (each command is separated by a `;`):
# s/(["\\])/\\\1/g - Replace all double-quote (") and backslash (\) characters with the escaped versions (\" and \\)
# s/\b/\\b/g - Replace all backspace characters with \b
# s/\t/\\t/g - Replace all tab characters with \t
# s/\f/\\f/g - Replace all form feed characters with \f
# s/\r/\\r/g - Replace all carriage return characters with \r
# $!s/$/\\n/ - On every line except the last, insert the \n escape at the end of the line
# Note: sed acts line-by-line, so it doesn't see the literal newline characters to replace
#
# tr -d '\n' - Remove the literal newlines from the final output
#
# Additional note: In a shell script between single quotes ('), no escape sequences are interpreted.
# To work around that and insert the literal values into the regular expressions, we stop the single-quote,
# then add the literal using ANSI-C syntax ($'\t'), then start a new single-quote. That is the meaning
# behind the various `'$'\b''` blocks in the command. All of these separate strings are then concatenated
# together to form the full argument to send to sed.
#
# We also use 'command sed' to ensure that we aren't accidentally executing an alias or function named 'sed'
command -p sed -E 's/(["\\])/\\\1/g; s/'$'\b''/\\b/g; s/'$'\t''/\\t/g; s/'$'\f''/\\f/g; s/'$'\r''/\\r/g; $!s/$/\\n/' <<<"$*" | command -p tr -d '\n'
}
# Turns out that the processed prompt is a complicated data structure that includes lots of
# information that's passed to the shell (including the actual shell, version, working directory
# and, well, the prompt itself). What is more, prompt can also include emojis - unicode characters
# that sometimes contain special bytes (ie. ST, CAN or SUB) that are otherwise used as unhook
# triggers for the precmd. Instead of escaping those and extracting the value of the prompt itself,
# we simply convert the entire data structure into a single line hex string, which Warp
# later decodes and sends to the grid to show the prompt.
# Note: before converting the prompt to a hex string, we remove the multi-line newlines and replace
# them with a single space (to avoid prompts that span multiple empty lines).
warp_escape_ps1 () {
command -p tr '\n\n' ' ' <<< "$*" | command -p od -An -v -tx1 | command -p tr -d ' \n'
}
# warp_hex_encode_string encodes the entire DCS string (JSON) with od making it essentially
# a very long hexadecimal string.
# Afterwards it's decoded in rust and parsed as usual.
# Accepts one argument: DCS JSON string
warp_hex_encode_string () {
echo "$1" | command -p od -An -v -tx1 | command -p tr -d ' \n'
}
# Returns encoded InitShell hook
# Accepts one argument: shell [bash, zsh, fish (future)]
init_shell_hook () {
init_shell="{\"hook\": \"InitShell\", \"value\": {\"shell\": \"$1\"}}"
echo $(warp_hex_encode_string "$init_shell")
}
# Checks whether the current version of bash is at least as high as the expected ($1) one.
# To match rest of our codebase, it returns "1" if the bash version is higher or equal, and
# 0 otherwise.
warp_at_least_bash_version () {
if [[ $(printf '%s\n%s\n' "$BASH_VERSION" "$1" | command -p sort -rVC ; echo $?) -eq 0 ]]; then
echo "1"
else
echo "0"
fi
}
# @P substitution was introduced in 4.4 bash version, so it returns "1" if the current bash
# version is 4.4 or higher.
warp_ps1_expanding_supported () {
warp_at_least_bash_version "4.4"
}
# The $READLINE_LINE variable in `bind -x` sequences was introduced in bash 4.0,
# so we can only report the input buffer if the bash version is 4.0 or higher.
warp_input_reporting_supported () {
warp_at_least_bash_version "4.0"
}
# Report the current input buffer contents to Warp. This only works correctly
# if `warp_input_reporting_supported` returns "1".
warp_report_input () {
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "InputBuffer"
warp_send_hook_kv_pair "buffer" "$READLINE_LINE"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
local escaped_input="$(warp_escape_json "$READLINE_LINE")"
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\", \"session_id\": $WARP_SESSION_ID } }"
fi
# This prevents bash from re-printing typeahead after we've removed it.
READLINE_LINE=""
}
# Check whether the prompt-related variables have OSC prompt marker sequences,
# and if not, wrap them with the appropriate markers so that we can direct the
# prompt bytes to the appropriate grids.
function warp_update_prompt_vars() {
# 133;A and 133;B are standard prompt marker OSCs.
# See https://learn.microsoft.com/en-us/windows/terminal/tutorials/shell-integration and
# https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/6/diffs for details.
local prompt_prefix=$'\e]133;A\a'
local prompt_suffix=$'\e]133;B\a'
if [[ "$WARP_HONOR_PS1" != "1" ]] && [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then
local suffix="$prompt_suffix$RESET_GRID_OSC"
else
local suffix="$prompt_suffix"
fi
# The "\[" and "\]" indicate to bash that the sequence between the markers are
# "non-printable", which effectively means they should not change the position
# of the cursor as they're "printed".
local prompt_prefix_with_cursor_marker_surrounded="\[$prompt_prefix\]"
local suffix_with_cursor_marker_surrounded="\[$suffix\]"
# Clear the user-defined prompt again, if using Warp's built-in prompt, before the command
# is rendered as it could have been reset by the user's bashrc or by setting
# the variable on the command line. This is used for same-line prompt and leads to the temporary
# product behavior of Warp prompt switches only taking effect in new sessions.
# Certain prompt plugins can reset the prompt to a non-empty value, after we've initially unset it.
# Confirm that it is unset, if using built-in Warp prompt (update prompt vars is forced to run as the last precmd fn).
if [[ "$WARP_HONOR_PS1" != "1" ]]; then
if [[ "$PS1" != "" ]]; then
# If the PS1 has its original value, then we save it in SAVED_PS1 so we can restore to this value, if we were to unset it for
# the Warp prompt case, but the user wants to switch back to PS1 later.
SAVED_PS1=$PS1
fi
# Note that we DO NOT unset the PS1 here, since we want to pass it along as a "hidden left prompt" for
# prompt preview purposes, if the Warp prompt is being used. Specifically, we want to show this prompt preview
# for the Edit Prompt modal and onboarding prompt block.
fi
if [[ -n "$PS1" ]]; then
# Remove any existing prompt/cursor markers from the prompt, before we re-modify it. Notably,
# we want to wrap the latest version of the prompt (which may have been changed e.g. virtualenv
# added).
if [[ "$PS1" == *"$prompt_prefix_with_cursor_marker_surrounded"* ]]; then
local preceding_prefix=${PS1%%"$prompt_prefix_with_cursor_marker_surrounded"*}
local following_prefix=${PS1#*"$prompt_prefix_with_cursor_marker_surrounded"}
PS1=$preceding_prefix$following_prefix
fi
if [[ "$PS1" == *"$suffix_with_cursor_marker_surrounded"* ]]; then
local preceding_suffix=${PS1%"$suffix_with_cursor_marker_surrounded"*}
local following_suffix=${PS1##*"$suffix_with_cursor_marker_surrounded"}
PS1=$preceding_suffix$following_suffix
fi
ORIGINAL_PS1=$PS1
PS1="$prompt_prefix$PS1$suffix"
fi
# Unset the PS1, if we are using the Warp prompt.
if [[ "$WARP_HONOR_PS1" != "1" ]]; then
PS1=""
# Otherwise, if we are using the PS1, we use the normal prompt markers.
else
if [[ "$PS1" != "\["*"\]" ]]; then
# We surround the non-printable OSCs with cursor markers to make sure the shell does NOT
# account for them when keeping track of its internal cursor position.
PS1="\[$prompt_prefix\]$ORIGINAL_PS1\[$suffix\]"
fi
fi
# Ensure that this is always the last precmd hook. This prevents any other precmd hook, which might
# modify $PS1, from interfering with our prompt-escaping logic.
#
# Remove warp_update_prompt_vars from the precmd_functions list and then re-append it to ensure it's
# ordered last.
# Initialize an empty array to hold the filtered functions.
filtered_precmd_functions=()
# Loop through each function in the original precmd_functions array.
for func in "${precmd_functions[@]}"; do
# Add the function to the filtered array if it's not warp_update_prompt_vars
if [[ "$func" != "warp_update_prompt_vars" ]]; then
filtered_precmd_functions+=("$func")
fi
done
# Assign the filtered array back to precmd_functions.
precmd_functions=("${filtered_precmd_functions[@]}")
# Append warp_update_prompt_vars to the end of the precmd_functions array.
precmd_functions+=("warp_update_prompt_vars")
}
# Changes the WARP_HONOR_PS1 variable to 1, to indicate we want to use the PS1. Restores
# the original PS1 value (which we unset for Warp prompt) and calls warp_update_prompt_vars
# to refresh the prompt. Note that we use an "empty block" workaround to achieve instant
# prompt switching in bash, since there is no built-in methods to repaint the prompt, unlike
# Zsh/fish.
function warp_change_prompt_modes_to_ps1() {
PS1="$SAVED_PS1"
WARP_HONOR_PS1="1"
warp_update_prompt_vars
}
# Changes the WARP_HONOR_PS1 variable to 0, to indicate we want to use the Warp prompt. Calls
# warp_update_prompt_vars to refresh the prompt (note the PS1 will be unset in this logic).
# Note that we use an "empty block" workaround to achieve instant prompt switching in bash,
# since there is no built-in methods to repaint the prompt, unlike Zsh/fish.
function warp_change_prompt_modes_to_warp_prompt() {
WARP_HONOR_PS1="0"
warp_update_prompt_vars
}
function clear() {
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "Clear"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
fi
}
function warp_finish_update {
local update_id="$1"
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "FinishUpdate"
warp_send_hook_kv_pair "update_id" "$update_id"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\", \"session_id\": $WARP_SESSION_ID} }"
fi
}
# Check if the warp apt source file has been renamed to `warpdotdev.list.distUpgrade` due to an ubuntu version update.
# If this occurred, we want to rename the source file back to `warpdotdev.list` to ensure updates can proceed.
# We purposefully skip this if either the `warpdotdev.list` file already exists (indicating that the user has already
# done this themselves) _or_ if a `warpdotdev.sources` file exists (which is the new Deb822 format for source files).
# The `.sources` file could only exist if a user manually created it; Ubuntu doesn't create one automatically for the
# warp source file due to a bug in its update flow where it considers our source file to be "invalid" because it
# contains a `signed-by` key.
function warp_handle_dist_upgrade {
local source_file_name="$1"
eval "$(command apt-config shell APT_SOURCESDIR 'Dir::Etc::sourceparts/d')"
if [[ ! -e $APT_SOURCESDIR$source_file_name.list && \
! -e $APT_SOURCESDIR$source_file_name.sources && \
-e $APT_SOURCESDIR$source_file_name.list.distUpgrade ]]; then
# DO NOT DO THIS. We should never run a command for user with `sudo`. The only reason this is safe here is because
# we insert this function into the input for the user to determine if they want to execute (we never run it on
# their behalf without their permission). To be transparent about what is being executed with sudo, we echo out the
# command we're about to run.
echo "Executing: sudo cp \"$APT_SOURCESDIR$source_file_name.list.distUpgrade\" \"$APT_SOURCESDIR$source_file_name.list\""
sudo cp "$APT_SOURCESDIR$source_file_name.list.distUpgrade" "$APT_SOURCESDIR$source_file_name.list"
fi
}
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
# recursive SSH sessions.
if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
# This helper function determines whether the user's ssh arguments imply
# creation of a non-interactive session or otherwise would conflict with
# our SSH wrapper. Returns 0 for an interactive session; >0 otherwise.
function is_interactive_ssh_session() {
ARGS=() # this array holds any positional arguments
while [ $# -gt 0 ]; do
# Initialize this to 1 before each call, as per getopts documentation.
OPTIND=1
# Parse through all ssh options, as defined in the ssh man pages.
while getopts :1246AaCfgKkMNnqsTtVvXxYyb:c:D:e:F:i:L:l:m:O:o:p:R:S:W:w: OPTION; do
case $OPTION in
# -T disables pty allocation (aka a non-interactive session)
T) return 1;;
# -W implies -T
W) return 1;;
# The user provided an invalid option; kick it to real ssh.
\?) return 1;;
# The user omitted a required argument; kick it to real ssh.
:) return 1;;
esac
done
[ $? -eq 0 ] || return 2 # getopts failed
[ $OPTIND -gt $# ] && break # we reached the end of the parameters
shift "$((OPTIND - 1))" # skip all options processed so far
ARGS[${#ARGS[@]}]=$1 # save first non-option argument (a.k.a. positional argument)
shift # remove saved arg
done
# If there is more than one positional argument, the user is attempting to
# run a command, not start an interactive session. If there is less than
# one positional argument, the user should be shown the usage text.
if [[ ${#ARGS[@]} -ne 1 ]]; then
return 1
fi
}
function warp_ssh_helper() {
init_shell_bash=$(init_shell_hook "bash")
init_shell_zsh=$(init_shell_hook "zsh")
local remote_session_id=$(command -p od -An -N8 -tu8 /dev/urandom 2>/dev/null | command -p tr -d ' \n')
if [[ -z "$remote_session_id" || "$remote_session_id" == "0" ]]; then
# If we cannot generate a non-zero random token, run plain SSH instead.
command ssh "${@:1}"
return
fi
# Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters
# We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte
# and use printf to convert back to plaintext
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID='$remote_session_id'; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
# Optionally attach to an existing ControlMaster the user already
# runs for this destination instead of creating our own. Resolve
# the user's configured ControlPath with `ssh -G` (which expands
# tokens like %h/%p/%r/%C into a literal path), then verify the
# master is alive with `ssh -O check`. Both probes are local-only
# commands. On any failure we fall back to creating a Warp-owned
# master, preserving the existing behavior.
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
local control_master_mode="yes"
local external_control_master="false"
if [[ "$GALAXY_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
case "$user_control_path" in
"" | none)
# No ControlPath configured for this destination.
;;
*[![:alnum:]._/~@:+,-]*)
# The resolved path contains characters we cannot safely
# embed in the SSH hook JSON below (e.g. an unexpanded %
# token, quotes, or whitespace); fall back to a
# Warp-owned master.
;;
*)
if command ssh -O check -o ControlPath="$user_control_path" "${@:1}" >/dev/null 2>&1; then
# A live master exists: multiplex through it and let
# the client know Warp does not own it.
control_path="$user_control_path"
control_master_mode="no"
external_control_master="true"
fi
;;
esac
fi
# Keep remote commands up-to-date with shell.rs & bash.sh.
# Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be
# escaped with "''" to avoid the local shell from expanding them before they're passed to the remote shell.
# We check the SHELL env var and use shell string manipulation to get the contents after the last slash to
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
# support.
command ssh -o ControlMaster=$control_master_mode -o ControlPath="$control_path" \
-t "${@:1}" \
"
export TERM_PROGRAM='WarpTerminal'
# Mark the remote side of a Warp-managed SSH session so the bootstrap
# body can distinguish it from local shells. Used to gate the ExitShell
# hook which tears down the remote-server-proxy subprocess.
export WARP_IS_SSH='1'
test -n '$GALAXY_CLIENT_VERSION' && export GALAXY_CLIENT_VERSION='$GALAXY_CLIENT_VERSION'
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
test -n '$GALAXY_CLI_AGENT_PROTOCOL_VERSION' && export GALAXY_CLI_AGENT_PROTOCOL_VERSION='$GALAXY_CLI_AGENT_PROTOCOL_VERSION'
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
# Emulate the SSHD logic to print the MotD. Because the Warp SSH wrapper passes
# a command to run, SSHD does a quiet login, updating utmp and other login
# state, but not printing the MotD. For bash and zsh, this is instead handled
# by our bootstrap script.
if test ! -e "'$HOME/.hushlogin'"; then
# This uses an if-else chain instead of a for-loop to avoid expansion issues on older shells.
if test -r /etc/motd; then
command -p cat /etc/motd
elif test -r /run/motd; then
command -p cat /run/motd
elif test -r /run/motd.dynamic; then
command -p cat /run/motd.dynamic
elif test -r /usr/lib/motd; then
command -p cat /usr/lib/motd
elif test -r /usr/lib/motd.dynamic; then
command -p cat /usr/lib/motd.dynamic
fi
fi
# Likewise, emulate a login shell by sourcing /etc/profile
if test -r /etc/profile; then
. /etc/profile
fi
exec "'$SHELL'"
fi
case "'${SHELL##*/}'" in
bash)
exec -a bash bash --rcfile <(echo '"'
command -p stty raw
HISTCONTROL=ignorespace
HISTIGNORE=" *"
WARP_SESSION_ID='$remote_session_id'
WARP_HONOR_PS1="'$WARP_HONOR_PS1'"
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
_user=$(command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
if [[ "'$OS'" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi
printf '\''"'\e]9278;d;%s\x07'"'\'' \""'$_msg'"\"')
unset _hostname _user _msg
;;
zsh) WARP_TMP_DIR="'$(command -p mktemp -d warptmp.XXXXXX)'"
local ZSH_ENV_SCRIPT='$zsh_env_script'
if [[ "'$?'" == 0 ]]; then
if command -pv xxd >/dev/null 2>&1; then
echo "'$ZSH_ENV_SCRIPT'" | command -p xxd -p -r > "'$WARP_TMP_DIR'"/.zshenv
else
for i in {0..\$((\${#ZSH_ENV_SCRIPT} - 1))..2}; do
builtin printf "'"\x${ZSH_ENV_SCRIPT:$i:2}"'"
done > "'$WARP_TMP_DIR'"/.zshenv
fi
else
echo \"Failed to bootstrap warp. Continuing with a non-bootstrapped shell.\"
fi
TMPPREFIX="'$HOME/.zshtmp-'" WARP_SSH_RCFILES="'${ZDOTDIR:-$HOME}'" ZDOTDIR="'$WARP_TMP_DIR'" exec -l zsh -g $TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE
;;
esac
"
}
function ssh() {
if is_interactive_ssh_session "$@"; then
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
# If the SSH wrapper is not enabled for this session, don't use it.
if [ "$GALAXY_USE_SSH_WRAPPER" = "1" ]; then
local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE=""
if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then
TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x"
fi
warp_ssh_helper "$@"
else
command ssh "$@"
fi
else
command ssh "$@"
fi
}
fi
# Send a precmd message to the terminal to differentiate between the warp
# bootstrap logic pasted into the PTY and the output of shell startup files.
warp_precmd
# Before calling rcfiles, print the MotD.
# In general, login(1) or pam_motd(8) is supposed to do this. However, we don't
# go through the normal login flow when bootstrapping a Warp session. In
# addition, Warp bash shells are _not_ login shells, because bash does not
# support custom rcfiles in login shells.
if [[ ! -e "$HOME/.hushlogin" ]]; then
for motd_file in /etc/motd /run/motd /run/motd.dynamic /usr/lib/motd /usr/lib/motd.dynamic; do
if [[ -r "$motd_file" ]]; then
command -p cat "$motd_file"
break
fi
done
fi
# This reflects the bootstrap sequence in a login shell. We want to
# Do other shell startup first so we can ensure Warp goes last.
#
# If this is a subshell, the user and system RC files have already been sourced.
if [[ -z $WARP_IS_SUBSHELL ]]; then
# Make sure we force the locale used for number formatting to "C", to avoid
# issues in locales that use a comma as the decimal separator.
rcfiles_start_time="$(LC_ALL="C"; echo $EPOCHREALTIME)"
if [[ -e /etc/profile ]]; then
source /etc/profile;
elif [[ -e /etc/bash.bashrc ]]; then
# /etc/bash.bashrc is only included in the startup sequence if bash is
# compiled with -DSYS_BASHRC. This is disabled in bash by default but
# included in Debian and adopted in other distributions. There's no efficient way
# to tell if it's enabled or not, so just check for the file if there is
# no /etc/profile and source it if it exists.
# Note that a lot of the time, /etc/profile sources /etc/bash.bashrc.
source /etc/bash.bashrc
fi
if [[ -e $HOME/.bash_profile ]]; then
source $HOME/.bash_profile
elif [[ -e $HOME/.bash_login ]]; then
source $HOME/.bash_login
elif [[ -e $HOME/.profile ]]; then
source $HOME/.profile
fi
rcfiles_end_time="$(LC_ALL="C"; echo $EPOCHREALTIME)"
fi
# Unset HISTFILESIZE and HISTSIZE if the user rcfiles didn't change them
# away from our very large sentinel values. We need to set the initial
# values to ensure that the user's history file and in-memory history list
# don't get truncated when we spawn the shell, but once bootstrap has
# completed, we want the values to be what they would have been if we hadn't
# set initial values.
#
# For more context, see: https://github.com/warpdotdev/Warp/issues/1262
if [[ $HISTFILESIZE == $WARP_INITIAL_HISTFILESIZE ]]; then
unset HISTFILESIZE
fi
unset WARP_INITIAL_HISTFILESIZE
if [[ $HISTSIZE == $WARP_INITIAL_HISTSIZE ]]; then
unset HISTSIZE
fi
unset WARP_INITIAL_HISTSIZE
# Save the value of HISTCONTROL as it existed just after reading the user's
# rcfiles.
USER_HISTCONTROL="$HISTCONTROL"
# Add a pattern to ignore in-band commands in shell history, while preserving the user's
# HISTIGNORE value which may been set in an RC file sourced above. It is important to
# ensure that this happens _after_ the user's RC files have been sourced.
if [[ ! -z $HISTIGNORE ]]; then
HISTIGNORE="*warp_run_generator_command*:$HISTIGNORE"
else
HISTIGNORE="*warp_run_generator_command*"
fi
# If the user has PROMPT_COMMAND set in their bootstrap scripts,
# save it into USER_PROMPT_COMMAND and install a precmd hook so we can
# respect it. It behaves exactly the same as precmd (i.e. executed
# before the prompt is shown).
# Since bash-preexec actually uses PROMPT_COMMAND, we shouldn't do this if bash-preexec
# is installed from /etc/bash.bashrc. If it's included in the user's rcfiles, it
# should've guarded on the presence of bash_preexec_imported and no-oped.
if [[ -n $PROMPT_COMMAND && -z $BASH_PREEXEC_IN_ETC_BASHRC ]]; then
# Do not honor the user's PROMPT_COMMAND if it re-assigns PROMPT_COMMAND, as that
# will break the precmd hook. This is the case in Kali Linux's default .bashrc
# https://gist.github.com/Searge/158f9061da831a53eeacff44eac71447#file-bashrc-L100
if [[ ! $PROMPT_COMMAND =~ "PROMPT_COMMAND=" ]]; then
# We treat the PROMPT_COMMAND as an array in case users are using bash 5.1
# array variant of PROMPT_COMMAND. If PROMPT_COMMAND is a string, this will
# still work, just create a length 1 array.
USER_PROMPT_COMMAND=("${PROMPT_COMMAND[@]}")
function user_prompt_command() {
for command in "${USER_PROMPT_COMMAND[@]}"; do
eval $command
done
}
fi
# We need to unset PROMPT_COMMAND here. Otherwise, it will be executed twice.
unset PROMPT_COMMAND
fi
# If the user's rc files turned PROMPT_COMMAND into an array, we must undo that.
# Since Bash 5.1, the PROMPT_COMMAND variable can be an array, see:
# https://tiswww.case.edu/php/chet/bash/NEWS
# Unfortunately, doing so will break Warp because of the way it interacts with bash-preexec.
# When PROMPT_COMMAND is an array, the DEBUG signal fires for each array element, and since
# bash-preexec uses a DEBUG trap to trigger the preexec functions, it will run our preexec
# functions before the command at PROMPT_COMMAND[1], PROMPT_COMMAND[2], etc. This means our
# Preexec hook gets called without the user submitting a command, putting the input block into
# a broken state, e.g. see https://github.com/warpdotdev/Warp/issues/2636
# If they end up fixing this, we may be able to remove this at some point, check this:
# https://github.com/rcaloras/bash-preexec/issues/130
#
# If PROMPT_COMMAND is set and it's an array, "join" the array into a newline-delimited string,
# then overwrite PROMPT_COMMAND
if [[ -n $PROMPT_COMMAND && "$(declare -p PROMPT_COMMAND)" =~ "declare -a" ]]; then
PROMPT_COMMAND_FLATTENED=$(IFS=$'\n'; echo "${PROMPT_COMMAND[*]}")
unset PROMPT_COMMAND
PROMPT_COMMAND=$PROMPT_COMMAND_FLATTENED
fi
## ----- Bash_preexec initialization -----
# If bash-preexec is already installed, we don't install it again.
if [[ "${PROMPT_COMMAND:-}" != *"__bp_precmd_invoke_cmd"* ]]; then
install_bashpreexec
else
# If we changed the HISTCONTROL value to work around the behavior in Debian bash,
# we want to re-run the logic so bash-preexec works properly (otherwise, it won't
# receive the command in preexec).
__bp_adjust_histcontrol
fi
## ----- Warp initialization -----
# Append additional PATH entries if provided via WARP_PATH_APPEND. This is after the user's RC
# files are sourced in case they reset PATH (/etc/profile on Debian does this, for example).
if [[ ! -z "$WARP_PATH_APPEND" ]]; then
export PATH="$PATH:$WARP_PATH_APPEND"
unset WARP_PATH_APPEND
fi
# Read through shell options to determine if the user has enabled vi mode.
vi_mode_enabled=0
IFS=':' read -ra SHELLOPT <<< "$SHELLOPTS"
for i in "${SHELLOPT[@]}"; do
if [[ "$i" == "vi" ]]; then
vi_mode_enabled=1
fi
done
if kernel_name="$(uname)"; then
if [[ "$kernel_name" == "Darwin" ]]; then
os_category="MacOS"
elif [[ "$kernel_name" == "Linux" ]]; then
os_category="Linux"
default_os_release_filepath="/etc/os-release"
fallback_os_release_filepath="/usr/lib/os-release"
# We first try /etc/os-release and then try /usr/lib/os-release as a fallback.
if test -f "$default_os_release_filepath"; then
os_release_file="$default_os_release_filepath"
elif test -f "$fallback_os_release_filepath"; then
os_release_file="$fallback_os_release_filepath"
fi
if test -f "$os_release_file"; then
linux_distribution="$(cat $os_release_file | sed -nE 's/^NAME="(.*)"$/\1/p')"
fi
fi
fi
precmd_functions+=(warp_precmd)
preexec_functions+=(warp_preexec)
precmd_functions+=(warp_set_title_idle_on_precmd)
preexec_functions+=(warp_set_title_active_on_preexec)
if declare -f user_prompt_command 2>&1 >/dev/null; then
precmd_functions+=(user_prompt_command)
fi
WARP_BOOTSTRAPPED=1
warp_update_prompt_vars
# Set the history file to append
shopt -s histappend
shell_plugins=()
function warp_bootstrapped () {
local aliases="`alias`"
local env_var_names="`compgen -e`"
local function_names="`compgen -A function`"
local builtins="`compgen -b`"
local keywords="`compgen -k`"
if [ "$WARP_IN_MSYS2" = false ]; then
# Note that for now we don't support dynamically changing HISTFILE within a session.
local escaped_histfile="$(warp_escape_json "$HISTFILE")"
local escaped_abbrs=""
local escaped_aliases="$(warp_escape_json "$aliases")"
local escaped_env_var_names="$(warp_escape_json "$env_var_names")"
local escaped_function_names="$(warp_escape_json "$function_names")"
local escaped_builtins="$(warp_escape_json "$builtins")"
local escaped_keywords="$(warp_escape_json "$keywords")"
fi
local shell_options="`shopt -s | command -p cut -f 1`"
# Provide terminal logic access to the value of HISTCONTROL via shell
# options. Prefix the fake option with "!" to avoid conflicts with
# real bash options.
if [[ -n $USER_HISTCONTROL ]]; then
shell_options="$shell_options \n !histcontrol_$USER_HISTCONTROL"
fi
# Check if Starship is active for Bash. Note that another prompt could still be overriding Starship, however,
# this is our best guess for the currently active prompt plugin.
if [ "$STARSHIP_SHELL" = "bash" ]; then
shell_plugins+=("starship")
fi
if [ "$WARP_IN_MSYS2" = false ]; then
local escaped_shell_plugins=$(warp_escape_json "$shell_plugins")
local escaped_path="$(warp_escape_json "$PATH")"
local escaped_shell_options=$(warp_escape_json "$shell_options")
fi
local _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
local _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "Bootstrapped"
warp_send_hook_kv_pair "histfile" "$HISTFILE"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_kv_pair "shell" "bash"
warp_send_hook_kv_pair "home_dir" "$HOME"
warp_send_hook_kv_pair "user" "$_user"
warp_send_hook_kv_pair "hostname" "$_hostname"
warp_send_hook_kv_pair "path" "$PATH"
warp_send_hook_kv_pair "cdpath" "$CDPATH"
warp_send_hook_kv_pair_escaped "env_var_names" "$env_var_names"
warp_send_hook_kv_pair "abbreviations" ""
warp_send_hook_kv_pair_escaped "aliases" "$aliases"
warp_send_hook_kv_pair_escaped "function_names" "$function_names"
warp_send_hook_kv_pair_escaped "builtins" "$builtins"
warp_send_hook_kv_pair_escaped "keywords" "$keywords"
warp_send_hook_kv_pair "shell_plugins" "$shell_plugins"
warp_send_hook_kv_pair "shell_version" "$BASH_VERSION"
warp_send_hook_kv_pair "shell_options" "$shell_options"
warp_send_hook_kv_pair "rcfiles_start_time" "$rcfiles_start_time"
warp_send_hook_kv_pair "rcfiles_end_time" "$rcfiles_end_time"
warp_send_hook_kv_pair "vi_mode_enabled" "$vi_mode_enabled"
warp_send_hook_kv_pair "os_category" "$os_category"
warp_send_hook_kv_pair "linux_distribution" "$linux_distribution"
warp_send_hook_kv_pair "wsl_name" "$WSL_DISTRO_NAME"
warp_send_hook_kv_pair "shell_path" "$BASH"
warp_send_hook_via_kv_pairs_end
else
local escaped_editor="$(warp_escape_json "$EDITOR")"
local escaped_shell_path="$(warp_escape_json "$BASH")"
local escaped_cdpath="$(warp_escape_json "$CDPATH")"
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"home_dir\": \"$HOME\", \"user\":\"$_user\", \"host\":\"$_hostname\", \"path\": \"$escaped_path\", \"cdpath\": \"$escaped_cdpath\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$escaped_env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$escaped_function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$BASH_VERSION\", \"shell_options\": \"$escaped_shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
warp_send_json_message "$escaped_json"
fi
}
warp_bootstrapped
fi