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.
1423 lines
69 KiB
Bash
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
|