feat: expand Galaxy agent and remote tooling

Add Wormhole remote helpers, provider and agent improvements, filesystem diagnostics, model metadata support, and schema-aware settings IntelliSense.
This commit is contained in:
2026-08-23 13:55:47 -05:00
parent f17642fc62
commit 7c106eecd5
147 changed files with 2208 additions and 1514 deletions
+1 -1
View File
@@ -338,7 +338,7 @@ The code editor has full LSP-powered autocompletion with documentation resolutio
**Behavior:** **Behavior:**
- Auto-completes as you type (triggered by alphanumeric/underscore with 50ms debounce) - Auto-completes as you type (triggered by alphanumeric/underscore with 50ms debounce)
- Trigger characters: `.` and `::` fire immediately - Trigger characters: `.` and `::` fire immediately
- Manual trigger: `Ctrl+Alt+Space` - Manual trigger: `Ctrl+Space` while the code editor is focused
- Keyboard navigation: Up/Down to select, Tab/Enter to confirm - Keyboard navigation: Up/Down to select, Tab/Enter to confirm
- Mouse: hover an item to select it and show docs, click to confirm - Mouse: hover an item to select it and show docs, click to confirm
- Documentation panel appears beside the menu when the LSP returns docs for the selected item (via `completionItem/resolve`) - Documentation panel appears beside the menu when the LSP returns docs for the selected item (via `completionItem/resolve`)
+2 -2
View File
@@ -591,7 +591,7 @@ default = [
"revert_to_checkpoints", "revert_to_checkpoints",
"rewind_slash_command", "rewind_slash_command",
"hoa_code_review", "hoa_code_review",
"warpify_footer", "wormhole_footer",
"hoa_notifications", "hoa_notifications",
"hoa_onboarding_flow", "hoa_onboarding_flow",
"agent_toolbar_editor", "agent_toolbar_editor",
@@ -936,7 +936,7 @@ solo_user_byok = []
billing_and_usage_page_v2 = [] billing_and_usage_page_v2 = []
gpt_configurable_context_window = [] gpt_configurable_context_window = []
configurable_toolbar = [] configurable_toolbar = []
warpify_footer = [] wormhole_footer = []
hoa_onboarding_flow = [] hoa_onboarding_flow = []
git_operations_in_code_review = [] git_operations_in_code_review = []
hoa_remote_control = [] hoa_remote_control = []
+6 -6
View File
@@ -371,7 +371,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
new_home='~' new_home='~'
bash_term_tab_title="${PWD/#$HOME/$new_home}" bash_term_tab_title="${PWD/#$HOME/$new_home}"
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
warp_title "$bash_term_tab_title" warp_title "$bash_term_tab_title"
else else
bash_term_tab_title_remote="${HOSTNAME%%.*}:$bash_term_tab_title" bash_term_tab_title_remote="${HOSTNAME%%.*}:$bash_term_tab_title"
@@ -962,7 +962,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping # The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
# recursive SSH sessions. # recursive SSH sessions.
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
# This helper function determines whether the user's ssh arguments imply # This helper function determines whether the user's ssh arguments imply
# creation of a non-interactive session or otherwise would conflict with # creation of a non-interactive session or otherwise would conflict with
# our SSH wrapper. Returns 0 for an interactive session; >0 otherwise. # our SSH wrapper. Returns 0 for an interactive session; >0 otherwise.
@@ -1025,7 +1025,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID" local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
local control_master_mode="yes" local control_master_mode="yes"
local external_control_master="false" local external_control_master="false"
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then 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') local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
case "$user_control_path" in case "$user_control_path" in
"" | none) "" | none)
@@ -1064,9 +1064,9 @@ export TERM_PROGRAM='WarpTerminal'
# body can distinguish it from local shells. Used to gate the ExitShell # body can distinguish it from local shells. Used to gate the ExitShell
# hook which tears down the remote-server-proxy subprocess. # hook which tears down the remote-server-proxy subprocess.
export WARP_IS_SSH='1' export WARP_IS_SSH='1'
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION' 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). # Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION' 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")'" 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'" printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
@@ -1137,7 +1137,7 @@ esac
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}" 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 the SSH wrapper is not enabled for this session, don't use it.
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then if [ "$GALAXY_USE_SSH_WRAPPER" = "1" ]; then
local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="" local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE=""
if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then
TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x" TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x"
@@ -1 +1 @@
echo -e '\n# Auto-Warpify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> % echo -e '\n# Auto-Wormhole\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
+5 -5
View File
@@ -603,7 +603,7 @@ end
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping # The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
# recursive SSH sessions. # recursive SSH sessions.
if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1" if test "$GALAXY_IS_LOCAL_SHELL_SESSION" = "1"
function is_interactive_ssh_session function is_interactive_ssh_session
# Parse through all ssh options, as defined in the ssh man pages. Send # Parse through all ssh options, as defined in the ssh man pages. Send
# stderr to /dev/null to silence argparse output when an option is invalid. # stderr to /dev/null to silence argparse output when an option is invalid.
@@ -652,7 +652,7 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
set -l control_path "$SSH_SOCKET_DIR/$WARP_SESSION_ID" set -l control_path "$SSH_SOCKET_DIR/$WARP_SESSION_ID"
set -l control_master_mode "yes" set -l control_master_mode "yes"
set -l external_control_master "false" set -l external_control_master "false"
if test "$WARP_SSH_REUSE_CONTROL_MASTER" = "1" if test "$GALAXY_SSH_REUSE_CONTROL_MASTER" = "1"
set -l user_control_path (command ssh -G $argv 2>/dev/null | command sed -n 's/^controlpath //p') set -l user_control_path (command ssh -G $argv 2>/dev/null | command sed -n 's/^controlpath //p')
# Skip when no ControlPath is configured, and reject resolved # Skip when no ControlPath is configured, and reject resolved
# paths containing characters we cannot safely embed in the SSH # paths containing characters we cannot safely embed in the SSH
@@ -681,9 +681,9 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
-t $argv \ -t $argv \
" "
export TERM_PROGRAM='WarpTerminal' export TERM_PROGRAM='WarpTerminal'
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION' 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). # Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION' 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 od -An -v -tx1 | command tr -d " \n")'" 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 od -An -v -tx1 | command tr -d " \n")'"
printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'" printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'"
@@ -753,7 +753,7 @@ esac
if is_interactive_ssh_session $argv if is_interactive_ssh_session $argv
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}" warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
if [ "$WARP_USE_SSH_WRAPPER" = "1" ] if [ "$GALAXY_USE_SSH_WRAPPER" = "1" ]
if test $WARP_SHELL_DEBUG_MODE if test $WARP_SHELL_DEBUG_MODE
set -g TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE "-x" set -g TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE "-x"
else else
@@ -1 +1 @@
echo -e '\n# Auto-Warpify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> % echo -e '\n# Auto-Wormhole\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
@@ -7,7 +7,7 @@ if ($PSEdition -eq 'Desktop' -or $IsWindows) {
$EP = [Microsoft.PowerShell.ExecutionPolicy] $EP = [Microsoft.PowerShell.ExecutionPolicy]
# MachinePolicy and UserPolicy scopes cannot be overridden. If either is Restricted, there's nothing we can do. # MachinePolicy and UserPolicy scopes cannot be overridden. If either is Restricted, there's nothing we can do.
if ((Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Restricted -or (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Restricted) { if ((Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Restricted -or (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Restricted) {
Write-Error 'ExecutionPolicy is Restricted. Unable to Warpify this PowerShell session.' Write-Error 'ExecutionPolicy is Restricted. Unable to Wormhole this PowerShell session.'
} elseif ((Get-ExecutionPolicy) -eq $EP::Restricted -and (Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Undefined -and (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Undefined) { } elseif ((Get-ExecutionPolicy) -eq $EP::Restricted -and (Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Undefined -and (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Undefined) {
$global:_warp_PSProcessExecPolicy = $(Get-ExecutionPolicy -Scope Process) $global:_warp_PSProcessExecPolicy = $(Get-ExecutionPolicy -Scope Process)
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force
@@ -1 +1 @@
Success! This subshell spawned by % has been Warpified. Success! This subshell spawned by % has been Wormholed.
+6 -6
View File
@@ -583,7 +583,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# set the WARP_DISABLE_AUTO_TITLE flag. # set the WARP_DISABLE_AUTO_TITLE flag.
[[ "${WARP_DISABLE_AUTO_TITLE:-}" != true ]] || return [[ "${WARP_DISABLE_AUTO_TITLE:-}" != true ]] || return
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
warp_title "$ZSH_THEME_TERM_TITLE_IDLE" warp_title "$ZSH_THEME_TERM_TITLE_IDLE"
else else
warp_title "$ZSH_THEME_TERM_TAB_TITLE_IDLE_REMOTE" warp_title "$ZSH_THEME_TERM_TAB_TITLE_IDLE_REMOTE"
@@ -892,7 +892,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping # The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
# recursive SSH sessions. # recursive SSH sessions.
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
# This helper function determines whether the user's ssh arguments imply # This helper function determines whether the user's ssh arguments imply
# creation of a non-interactive session or otherwise would conflict with # creation of a non-interactive session or otherwise would conflict with
# our SSH wrapper. Returns 0 for an interactive session; >0 otherwise. # our SSH wrapper. Returns 0 for an interactive session; >0 otherwise.
@@ -952,7 +952,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID" local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
local control_master_mode="yes" local control_master_mode="yes"
local external_control_master="false" local external_control_master="false"
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then 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') local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
case "$user_control_path" in case "$user_control_path" in
"" | none) "" | none)
@@ -991,9 +991,9 @@ export TERM_PROGRAM='WarpTerminal'
# body can distinguish it from local shells. Used to gate the ExitShell # body can distinguish it from local shells. Used to gate the ExitShell
# hook which tears down the remote-server-proxy subprocess. # hook which tears down the remote-server-proxy subprocess.
export WARP_IS_SSH='1' export WARP_IS_SSH='1'
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION' 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). # Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION' 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")'" 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'" printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
@@ -1065,7 +1065,7 @@ esac
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}" 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 the SSH wrapper is not enabled for this session, don't use it.
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then if [ "$GALAXY_USE_SSH_WRAPPER" = "1" ]; then
local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="" local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE=""
if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then
TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x" TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x"
+4 -4
View File
@@ -71,7 +71,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
api::ToolType::SearchCodebase, api::ToolType::SearchCodebase,
]); ]);
} }
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => { Some(SessionType::WormholedRemote { host_id: Some(_) }) => {
// Remote session with a known host — enable tools that route // Remote session with a known host — enable tools that route
// through RemoteServerClient. The host_id is only populated // through RemoteServerClient. The host_id is only populated
// after a successful connection handshake, so its presence is a // after a successful connection handshake, so its presence is a
@@ -81,7 +81,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
supported_tools.push(api::ToolType::SearchCodebase); supported_tools.push(api::ToolType::SearchCodebase);
} }
} }
Some(SessionType::WarpifiedRemote { host_id: None }) => {} Some(SessionType::WormholedRemote { host_id: None }) => {}
} }
if FeatureFlag::ListSkills.is_enabled() { if FeatureFlag::ListSkills.is_enabled() {
@@ -113,13 +113,13 @@ fn get_supported_cli_agent_tools(params: &RequestParams) -> Vec<api::ToolType> {
supported_cli_agent_tools supported_cli_agent_tools
.extend(&[api::ToolType::ReadFiles, api::ToolType::SearchCodebase]); .extend(&[api::ToolType::ReadFiles, api::ToolType::SearchCodebase]);
} }
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => { Some(SessionType::WormholedRemote { host_id: Some(_) }) => {
supported_cli_agent_tools.push(api::ToolType::ReadFiles); supported_cli_agent_tools.push(api::ToolType::ReadFiles);
if FeatureFlag::RemoteCodebaseIndexing.is_enabled() { if FeatureFlag::RemoteCodebaseIndexing.is_enabled() {
supported_cli_agent_tools.push(api::ToolType::SearchCodebase); supported_cli_agent_tools.push(api::ToolType::SearchCodebase);
} }
} }
Some(SessionType::WarpifiedRemote { host_id: None }) => {} Some(SessionType::WormholedRemote { host_id: None }) => {}
} }
supported_cli_agent_tools supported_cli_agent_tools
+1 -1
View File
@@ -59,7 +59,7 @@ fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool
fn request_params_for_remote(host_id: Option<HostId>) -> RequestParams { fn request_params_for_remote(host_id: Option<HostId>) -> RequestParams {
let mut params = request_params_with_ask_user_question_enabled(false); let mut params = request_params_with_ask_user_question_enabled(false);
params.session_context = params.session_context =
SessionContext::new_with_session_type_for_test(Some(SessionType::WarpifiedRemote { SessionContext::new_with_session_type_for_test(Some(SessionType::WormholedRemote {
host_id, host_id,
})); }));
params params
+1 -1
View File
@@ -289,7 +289,7 @@ static DEFAULT_TIPS: LazyLock<Vec<AgentTip>> = LazyLock::new(|| {
}, },
AgentTip { AgentTip {
description: "Wormhole a remote SSH session to enable the agent inside that environment.".to_string(), description: "Wormhole a remote SSH session to enable the agent inside that environment.".to_string(),
link: Some("https://docs.warp.dev/terminal/warpify".to_string()), link: None,
binding_name: None, binding_name: None,
action: None, action: None,
kind: AgentTipKind::General, kind: AgentTipKind::General,
@@ -115,7 +115,7 @@ impl ReadFilesExecutor {
// Check if this is a remote session with a connected host. // Check if this is a remote session with a connected host.
let session_type = self.active_session.as_ref(ctx).session_type(ctx); let session_type = self.active_session.as_ref(ctx).session_type(ctx);
let host_request_handle = match &session_type { let host_request_handle = match &session_type {
Some(SessionType::WarpifiedRemote { Some(SessionType::WormholedRemote {
host_id: Some(host_id), host_id: Some(host_id),
}) => Some( }) => Some(
remote_server::manager::RemoteServerManager::as_ref(ctx) remote_server::manager::RemoteServerManager::as_ref(ctx)
@@ -127,7 +127,7 @@ impl ReadFilesExecutor {
// Remote session without a usable remote server connection. File reading // Remote session without a usable remote server connection. File reading
// requires either local access or a connected remote server, neither // requires either local access or a connected remote server, neither
// of which is available. // of which is available.
if matches!(session_type, Some(SessionType::WarpifiedRemote { .. })) if matches!(session_type, Some(SessionType::WormholedRemote { .. }))
&& host_request_handle.is_none() && host_request_handle.is_none()
{ {
return ActionExecution::Sync(AIAgentActionResultType::ReadFiles( return ActionExecution::Sync(AIAgentActionResultType::ReadFiles(
@@ -154,7 +154,7 @@ fn disconnected_remote_session_does_not_fall_back_to_client_global_bundled_skill
sessions.register_session_for_test( sessions.register_session_for_test(
SessionInfo::new_for_test() SessionInfo::new_for_test()
.with_id(session_id) .with_id(session_id)
.with_session_type(BootstrapSessionType::WarpifiedRemote), .with_session_type(BootstrapSessionType::WormholedRemote),
); );
}); });
let (_model_events_tx, model_events_rx) = unbounded(); let (_model_events_tx, model_events_rx) = unbounded();
@@ -235,7 +235,7 @@ fn remote_session_reads_remote_bundled_skill_catalog() {
sessions.register_session_for_test( sessions.register_session_for_test(
SessionInfo::new_for_test() SessionInfo::new_for_test()
.with_id(session_id) .with_id(session_id)
.with_session_type(BootstrapSessionType::WarpifiedRemote), .with_session_type(BootstrapSessionType::WormholedRemote),
); );
}); });
let session = sessions let session = sessions
@@ -370,7 +370,7 @@ impl RequestFileEditsExecutor {
}) })
.collect(); .collect();
let diff_session_type = match self.active_session.as_ref(ctx).session_type(ctx) { let diff_session_type = match self.active_session.as_ref(ctx).session_type(ctx) {
Some(SessionType::WarpifiedRemote { Some(SessionType::WormholedRemote {
host_id: Some(host_id), host_id: Some(host_id),
}) => DiffSessionType::Remote(host_id.clone()), }) => DiffSessionType::Remote(host_id.clone()),
_ => DiffSessionType::Local, _ => DiffSessionType::Local,
@@ -360,7 +360,7 @@ fn format_session_location(session: &Session, working_directory: Option<&str>) -
let hostname = session.hostname(); let hostname = session.hostname();
match session_type { match session_type {
SessionType::Local => Some(display_path), SessionType::Local => Some(display_path),
SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")), SessionType::WormholedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")),
} }
} }
@@ -510,7 +510,7 @@ fn current_working_directory_for_zero_state(terminal_model: &TerminalModel) -> O
.is_some_and(|pending_session_info| { .is_some_and(|pending_session_info| {
matches!( matches!(
pending_session_info.session_type, pending_session_info.session_type,
BootstrapSessionType::WarpifiedRemote BootstrapSessionType::WormholedRemote
) )
}); });
(!terminal_model.block_list().is_bootstrapped() && !is_bootstrapping_remote_shell) (!terminal_model.block_list().is_bootstrapped() && !is_bootstrapping_remote_shell)
+1 -1
View File
@@ -65,7 +65,7 @@ use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher};
use crate::terminal::view::ambient_agent::{ use crate::terminal::view::ambient_agent::{
is_cloud_agent_pre_first_exchange, AmbientAgentViewModel, AmbientAgentViewModelEvent, is_cloud_agent_pre_first_exchange, AmbientAgentViewModel, AmbientAgentViewModelEvent,
}; };
use crate::terminal::warpify::render::LEFT_STRIPE_WIDTH; use crate::terminal::wormhole::render::LEFT_STRIPE_WIDTH;
use crate::terminal::{ use crate::terminal::{
TerminalModel, CANCEL_COMMAND_KEYBINDING, TOGGLE_AUTOEXECUTE_MODE_KEYBINDING, TerminalModel, CANCEL_COMMAND_KEYBINDING, TOGGLE_AUTOEXECUTE_MODE_KEYBINDING,
TOGGLE_HIDE_CLI_RESPONSES_KEYBINDING, TOGGLE_QUEUE_NEXT_PROMPT_KEYBINDING, TOGGLE_HIDE_CLI_RESPONSES_KEYBINDING, TOGGLE_QUEUE_NEXT_PROMPT_KEYBINDING,
+5 -5
View File
@@ -314,11 +314,11 @@ impl SessionContext {
&self.current_working_directory &self.current_working_directory
} }
/// Returns the remote host ID if this is a `WarpifiedRemote` session with /// Returns the remote host ID if this is a `WormholedRemote` session with
/// a connected `RemoteServerClient`. /// a connected `RemoteServerClient`.
pub fn host_id(&self) -> Option<&galaxy_core::HostId> { pub fn host_id(&self) -> Option<&galaxy_core::HostId> {
match &self.session_type { match &self.session_type {
Some(SessionType::WarpifiedRemote { host_id }) => host_id.as_ref(), Some(SessionType::WormholedRemote { host_id }) => host_id.as_ref(),
Some(SessionType::Local) | None => None, Some(SessionType::Local) | None => None,
} }
} }
@@ -326,17 +326,17 @@ impl SessionContext {
/// Returns `true` if this is a remote session (regardless of whether /// Returns `true` if this is a remote session (regardless of whether
/// the remote server client is connected). /// the remote server client is connected).
pub fn is_remote(&self) -> bool { pub fn is_remote(&self) -> bool {
matches!(self.session_type, Some(SessionType::WarpifiedRemote { .. })) matches!(self.session_type, Some(SessionType::WormholedRemote { .. }))
} }
pub fn skill_path_origin(&self) -> SkillPathOrigin { pub fn skill_path_origin(&self) -> SkillPathOrigin {
match &self.session_type { match &self.session_type {
Some(SessionType::WarpifiedRemote { Some(SessionType::WormholedRemote {
host_id: Some(host_id), host_id: Some(host_id),
}) => SkillPathOrigin::Remote { }) => SkillPathOrigin::Remote {
host_id: host_id.clone(), host_id: host_id.clone(),
}, },
Some(SessionType::WarpifiedRemote { host_id: None }) => SkillPathOrigin::Unavailable, Some(SessionType::WormholedRemote { host_id: None }) => SkillPathOrigin::Unavailable,
Some(SessionType::Local) | None => SkillPathOrigin::Local, Some(SessionType::Local) | None => SkillPathOrigin::Local,
} }
} }
@@ -1,6 +1,6 @@
//! This module contains rendering functions for various requested inline actions that have not yet //! This module contains rendering functions for various requested inline actions that have not yet
//! been transformed into a [`View`] component. This currently encompasses UI for file retrieval, //! been transformed into a [`View`] component. This currently encompasses UI for file retrieval,
//! environmental variable collection, and SSH Warpification, to name a few. //! environmental variable collection, and SSH Wormholing, to name a few.
//! //!
//! There's quite a bit of duplication between function-based inline actions and view-based inline //! There's quite a bit of duplication between function-based inline actions and view-based inline
//! actions. Moreover, the header rendering functions here don't make use of the HeaderConfig. //! actions. Moreover, the header rendering functions here don't make use of the HeaderConfig.
@@ -408,7 +408,7 @@ impl PassiveSuggestionsModel {
.active_session .active_session
.as_ref(ctx) .as_ref(ctx)
.session_type(ctx) .session_type(ctx)
.map(|session_type| matches!(session_type, SessionType::WarpifiedRemote { .. })) .map(|session_type| matches!(session_type, SessionType::WormholedRemote { .. }))
.unwrap_or(true); .unwrap_or(true);
if !can_read_file || should_skip_for_remote { if !can_read_file || should_skip_for_remote {
let reason = if !can_read_file { let reason = if !can_read_file {
+151
View File
@@ -1037,6 +1037,157 @@ impl PersistedWorkspace {
); );
} }
/// Ensures Galaxy's own settings file has schema-backed TOML language support.
/// This managed server is intentionally not persisted as a code workspace.
#[cfg(feature = "local_fs")]
pub fn ensure_settings_toml_lsp(&mut self, file_path: PathBuf, ctx: &mut ModelContext<Self>) {
if file_path != crate::settings::user_preferences_toml_file_path() {
return;
}
let server_type = LSPServerType::Tombi;
let Some(workspace_root) = file_path.parent().map(Path::to_path_buf) else {
return;
};
if LspManagerModel::as_ref(ctx).server_registered(&workspace_root, server_type, ctx) {
LspManagerModel::handle(ctx).update(ctx, |manager, ctx| {
manager.start_all(workspace_root, ctx);
});
return;
}
if self.lsp_installation_status.get(&server_type)
== Some(&LSPInstallationStatus::Installing)
{
return;
}
self.lsp_installation_status
.insert(server_type, LSPInstallationStatus::Installing);
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
server_type,
status: LSPInstallationStatus::Installing,
});
let path_future = LocalShellState::handle(ctx).update(ctx, |shell_state, ctx| {
shell_state.get_interactive_path_env_var(ctx)
});
let http_client = ServerApiProvider::as_ref(ctx).get_http_client();
let file_path_for_install = file_path.clone();
ctx.spawn(
async move {
let path_env_var = path_future.await;
let executor = lsp::CommandBuilder::new(path_env_var.clone());
let candidate = server_type.candidate(http_client.clone());
if !candidate.is_installed(&executor).await {
let metadata = candidate.fetch_latest_server_metadata().await?;
candidate.install(metadata, &executor).await?;
}
let schema_path = crate::settings::schema_export::ensure_runtime_settings_schema()?;
let schema_uri = url::Url::from_file_path(&schema_path)
.map_err(|()| anyhow::anyhow!("Invalid settings schema path: {}", schema_path.display()))?;
let file_match = file_path_for_install.to_string_lossy().into_owned();
Ok::<_, anyhow::Error>((
path_env_var,
schema_uri.to_string(),
file_match,
))
},
move |me, result, ctx| match result {
Ok((path_env_var, schema_uri, file_match)) => {
me.lsp_installation_status
.insert(server_type, LSPInstallationStatus::Installed);
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
server_type,
status: LSPInstallationStatus::Installed,
});
let log_relative_path =
crate::code::lsp_logs::relative_log_path(server_type, &workspace_root);
let http_client = ServerApiProvider::as_ref(ctx).get_http_client();
let config = LspServerConfig::new(
server_type,
workspace_root.clone(),
path_env_var,
ChannelState::app_id().application_name().to_string(),
http_client,
)
.with_log_relative_path(log_relative_path)
.with_post_initialize_notification(
"tombi/associateSchema",
serde_json::json!({
"title": "Galaxy Settings",
"description": "Galaxy's generated settings schema",
"uri": schema_uri,
"fileMatch": [file_match],
"tomlVersion": "v1.1.0",
"force": true,
}),
);
let manager = LspManagerModel::handle(ctx);
manager.update(ctx, |manager, ctx| {
manager.register(workspace_root.clone(), config, ctx);
});
let workspace_root_display = workspace_root.display().to_string();
if let Some(server) = manager
.as_ref(ctx)
.servers_for_workspace(&workspace_root)
.and_then(|servers| servers.last())
.cloned()
{
ctx.subscribe_to_model(&server, move |_, _, event, ctx| {
if let LspEvent::Failed(error) = event {
if let Some(window_id) = WindowManager::as_ref(ctx).active_window() {
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
toast_stack.add_ephemeral_toast(
DismissibleToast::error(format!(
"Failed to start TOML language support for {workspace_root_display}: {error}"
)),
window_id,
ctx,
);
});
}
}
});
}
manager.update(ctx, |manager, ctx| {
manager.start_all(workspace_root, ctx);
});
}
Err(error) => {
log::warn!("Failed to prepare TOML language support: {error:#}");
me.lsp_installation_status
.insert(server_type, LSPInstallationStatus::NotInstalled);
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
server_type,
status: LSPInstallationStatus::NotInstalled,
});
if let Some(window_id) = WindowManager::as_ref(ctx).active_window() {
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
toast_stack.add_ephemeral_toast(
DismissibleToast::error(format!(
"Failed to prepare TOML language support: {error}"
)),
window_id,
ctx,
);
});
}
}
},
);
}
/// Starts all enabled LSP servers for the given file path. /// Starts all enabled LSP servers for the given file path.
/// This looks up the workspace root and starts any servers that are enabled but not yet running. /// This looks up the workspace root and starts any servers that are enabled but not yet running.
#[cfg(feature = "local_fs")] #[cfg(feature = "local_fs")]
+2
View File
@@ -159,12 +159,14 @@ mod appimage {
} }
mod package_manager { mod package_manager {
use anyhow::{bail, Result};
use galaxyui::elements::{Container, FormattedTextElement, HighlightedHyperlink}; use galaxyui::elements::{Container, FormattedTextElement, HighlightedHyperlink};
use galaxyui::{Element, SingletonEntity as _}; use galaxyui::{Element, SingletonEntity as _};
use markdown_parser::{ use markdown_parser::{
FormattedText, FormattedTextFragment, FormattedTextHeader, FormattedTextLine, FormattedText, FormattedTextFragment, FormattedTextHeader, FormattedTextLine,
}; };
use super::{PackageManager, CURRENT_EXE};
use crate::appearance::Appearance; use crate::appearance::Appearance;
pub struct AutoupdateContextBlock { pub struct AutoupdateContextBlock {
+13 -224
View File
@@ -1,160 +1,34 @@
//! Generates a JSON Schema file describing Warp's user-facing settings. //! Generates a JSON Schema file describing Galaxy's user-facing settings.
//! //!
//! Usage: //! Usage:
//! ``` //! ```
//! cargo run --bin generate_settings_schema -- [--channel dev|preview|stable] [output_path] //! cargo run --bin generate_settings_schema -- [--channel dev|preview|stable] [output_path]
//! ``` //! ```
use std::collections::HashSet;
use std::io::Write; use std::io::Write;
use galaxy_core::features::{ use galaxy::settings::schema_export::generate_settings_schema;
FeatureFlag, DEBUG_FLAGS, DOGFOOD_FLAGS, PREVIEW_FLAGS, RELEASE_FLAGS,
};
use schemars::SchemaGenerator;
use serde_json::{Map, Value};
use settings::schema::SettingSchemaEntry;
/// Ensures all `inventory::submit!` registrations from the app crate's /// Ensures all `inventory::submit!` registrations from the app crate's
/// dependency tree are linked into the binary. /// dependency tree are linked into the binary.
///
/// Binary targets only link crate code that is transitively referenced.
/// Without an explicit reference to the `warp` library, the linker will
/// not include most of the app's object files and the `inventory`
/// submissions they contain.
fn ensure_settings_linked() { fn ensure_settings_linked() {
let _ = std::hint::black_box(galaxy::settings::RESTORE_SESSION); let _ = std::hint::black_box(galaxy::settings::RESTORE_SESSION);
} }
/// Recursively strips `minimum`, `maximum`, and `format` from integer and
/// number schemas. schemars derives these from Rust type bounds (e.g. `u8`
/// → `minimum: 0, maximum: 255, format: "uint8"`), which are misleading
/// for settings whose valid domain is narrower than the type allows.
fn strip_numeric_metadata(value: &mut Value) {
match value {
Value::Object(map) => {
let is_numeric = map
.get("type")
.and_then(Value::as_str)
.is_some_and(|t| t == "integer" || t == "number");
if is_numeric {
map.remove("minimum");
map.remove("maximum");
map.remove("format");
}
for val in map.values_mut() {
strip_numeric_metadata(val);
}
}
Value::Array(arr) => {
for val in arr {
strip_numeric_metadata(val);
}
}
_ => {}
}
}
/// Removes `{"enum": [], "type": "string"}` entries from `oneOf` arrays.
/// schemars emits an empty enum bucket for externally-tagged enums when all
/// unit variants have individual descriptions (and are therefore promoted to
/// separate `oneOf` branches with `const`). The empty bucket is unreachable
/// and confuses schema consumers.
fn strip_empty_enum_entries(value: &mut Value) {
match value {
Value::Object(map) => {
if let Some(Value::Array(one_of)) = map.get_mut("oneOf") {
one_of.retain(|entry| {
!matches!(entry, Value::Object(obj)
if obj.get("enum").is_some_and(|e| e.as_array().is_some_and(|a| a.is_empty()))
)
});
}
for val in map.values_mut() {
strip_empty_enum_entries(val);
}
}
Value::Array(arr) => {
for val in arr {
strip_empty_enum_entries(val);
}
}
_ => {}
}
}
fn active_flags_for_channel(channel: &str) -> HashSet<FeatureFlag> {
let mut flags = HashSet::new();
let flag_lists: &[&[FeatureFlag]] = match channel {
"stable" => &[RELEASE_FLAGS],
"preview" => &[RELEASE_FLAGS, PREVIEW_FLAGS],
"dev" => &[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS],
other => {
eprintln!("Unknown channel '{other}', defaulting to dev");
&[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS]
}
};
for list in flag_lists {
for flag in *list {
flags.insert(*flag);
}
}
flags
}
/// Creates intermediate hierarchy objects so that a setting at e.g.
/// `appearance.text` is nested under `properties.appearance.properties.text.properties`.
fn ensure_hierarchy<'a>(
root_properties: &'a mut Map<String, Value>,
hierarchy: &str,
) -> &'a mut Map<String, Value> {
let segments: Vec<&str> = hierarchy.split('.').collect();
let mut current = root_properties;
for segment in segments {
// Ensure the segment object exists
let entry = current.entry(segment.to_string()).or_insert_with(|| {
Value::Object({
let mut m = Map::new();
m.insert("type".to_string(), Value::String("object".to_string()));
m.insert("properties".to_string(), Value::Object(Map::new()));
m
})
});
// Navigate into its properties
current = entry
.as_object_mut()
.expect("hierarchy node should be an object")
.entry("properties")
.or_insert_with(|| Value::Object(Map::new()))
.as_object_mut()
.expect("properties should be an object");
}
current
}
fn main() { fn main() {
ensure_settings_linked(); ensure_settings_linked();
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let mut channel = "dev"; let mut channel = "dev";
let mut output_path: Option<&str> = None; let mut output_path: Option<&str> = None;
let mut i = 1; let mut index = 1;
while i < args.len() {
match args[i].as_str() { while index < args.len() {
match args[index].as_str() {
"--channel" => { "--channel" => {
i += 1; index += 1;
if i < args.len() { if index < args.len() {
channel = &args[i]; channel = &args[index];
} }
} }
arg if !arg.starts_with('-') => { arg if !arg.starts_with('-') => {
@@ -165,101 +39,16 @@ fn main() {
std::process::exit(1); std::process::exit(1);
} }
} }
i += 1; index += 1;
} }
let active_flags = active_flags_for_channel(channel); let (output, entry_count) = generate_settings_schema(channel);
let mut generator = SchemaGenerator::default();
let mut root_properties = Map::new();
let mut entry_count = 0;
for entry in inventory::iter::<SettingSchemaEntry> {
// Skip private settings
if entry.is_private {
continue;
}
// Skip settings whose feature flag is not active
if let Some(flag) = entry.feature_flag {
if !active_flags.contains(&flag) {
continue;
}
}
let type_schema = (entry.schema_fn)(&mut generator);
let mut schema_value: Value = type_schema.to_value();
// Compute default value — prefer file default over serde default
let default_json = (entry.file_default_value_fn)();
if let Ok(default_value) = serde_json::from_str::<Value>(&default_json) {
if let Some(obj) = schema_value.as_object_mut() {
obj.insert("default".to_string(), default_value);
}
}
// Always overwrite description with the macro-provided one
if !entry.description.is_empty() {
if let Some(obj) = schema_value.as_object_mut() {
obj.insert(
"description".to_string(),
Value::String(entry.description.to_string()),
);
}
}
// Place the setting in the hierarchy
let target = if let Some(hierarchy) = entry.hierarchy {
ensure_hierarchy(&mut root_properties, hierarchy)
} else {
&mut root_properties
};
target.insert(entry.storage_key.to_string(), schema_value);
entry_count += 1;
}
// Collect $defs from the generator
let defs_map = generator.take_definitions(true);
// Assemble the root document
let mut root = Map::new();
root.insert(
"$schema".to_string(),
Value::String("https://json-schema.org/draft/2020-12/schema".to_string()),
);
root.insert(
"title".to_string(),
Value::String("Galaxy Settings".to_string()),
);
root.insert(
"description".to_string(),
Value::String(format!(
"JSON Schema for Galaxy settings ({channel} channel, {entry_count} settings)"
)),
);
root.insert("type".to_string(), Value::String("object".to_string()));
root.insert("properties".to_string(), Value::Object(root_properties));
if !defs_map.is_empty() {
root.insert("$defs".to_string(), Value::Object(defs_map));
}
// Strip type-derived numeric metadata (minimum, maximum, format) that
// schemars emits from Rust primitive bounds (e.g. u8 → max 255).
// These leak implementation details rather than semantic constraints.
let mut root_value = Value::Object(root);
strip_numeric_metadata(&mut root_value);
strip_empty_enum_entries(&mut root_value);
let output = serde_json::to_string_pretty(&root_value).expect("schema should serialize");
if let Some(path) = output_path { if let Some(path) = output_path {
let mut file = std::fs::File::create(path) let mut file = std::fs::File::create(path)
.unwrap_or_else(|e| panic!("Failed to create output file '{path}': {e}")); .unwrap_or_else(|error| panic!("Failed to create output file '{path}': {error}"));
file.write_all(output.as_bytes()) file.write_all(output.as_bytes())
.unwrap_or_else(|e| panic!("Failed to write to '{path}': {e}")); .unwrap_or_else(|error| panic!("Failed to write to '{path}': {error}"));
eprintln!("Wrote {entry_count} settings to {path}"); eprintln!("Wrote {entry_count} settings to {path}");
} else { } else {
println!("{output}"); println!("{output}");
+42 -18
View File
@@ -187,6 +187,13 @@ fn fuzzy_match(target: &str, query: &str) -> bool {
true true
} }
fn completion_documentation(item: &CompletionItem) -> Option<String> {
match item.documentation.as_ref()? {
lsp_types::Documentation::String(documentation) => Some(documentation.clone()),
lsp_types::Documentation::MarkupContent(documentation) => Some(documentation.value.clone()),
}
}
impl LocalCodeEditorView { impl LocalCodeEditorView {
pub(super) fn is_completion_enabled() -> bool { pub(super) fn is_completion_enabled() -> bool {
FeatureFlag::LspCompletion.is_enabled() FeatureFlag::LspCompletion.is_enabled()
@@ -254,12 +261,13 @@ impl LocalCodeEditorView {
}; };
if let Some(trigger) = trigger { if let Some(trigger) = trigger {
self.request_completion(cursor_offset, trigger, ctx); self.request_completion(cursor_offset, cursor_offset, trigger, ctx);
} }
} }
pub(super) fn request_completion( pub(super) fn request_completion(
&mut self, &mut self,
request_offset: CharOffset,
trigger_offset: CharOffset, trigger_offset: CharOffset,
trigger: CompletionTrigger, trigger: CompletionTrigger,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
@@ -279,7 +287,7 @@ impl LocalCodeEditorView {
let lsp_position = self let lsp_position = self
.editor() .editor()
.as_ref(ctx) .as_ref(ctx)
.offset_to_lsp_position(trigger_offset, ctx); .offset_to_lsp_position(request_offset, ctx);
let future = let future =
match lsp_server match lsp_server
@@ -318,7 +326,7 @@ impl LocalCodeEditorView {
} }
let word_start = self.find_word_start(offset, ctx); let word_start = self.find_word_start(offset, ctx);
self.request_completion(word_start, CompletionTrigger::Invoked, ctx); self.request_completion(offset, word_start, CompletionTrigger::Invoked, ctx);
} }
/// Find the start of the current identifier word by walking backwards from `offset`. /// Find the start of the current identifier word by walking backwards from `offset`.
@@ -440,7 +448,7 @@ impl LocalCodeEditorView {
/// Resolve documentation for the currently selected completion item. /// Resolve documentation for the currently selected completion item.
pub(super) fn resolve_selected_completion_docs(&mut self, ctx: &mut ViewContext<Self>) { pub(super) fn resolve_selected_completion_docs(&mut self, ctx: &mut ViewContext<Self>) {
let raw_item = match &self.completion_state { let (item_index, raw_item) = match &self.completion_state {
CompletionState::Showing { CompletionState::Showing {
items, items,
filtered_indices, filtered_indices,
@@ -458,14 +466,22 @@ impl LocalCodeEditorView {
{ {
return; return;
} }
items[item_idx].raw_item.clone() (item_idx, items[item_idx].raw_item.clone())
} }
_ => return, _ => return,
}; };
if let Some(documentation) = completion_documentation(&raw_item) {
self.set_resolved_completion_docs(item_index, documentation, ctx);
return;
}
let Some(lsp_server) = &self.lsp_server else { let Some(lsp_server) = &self.lsp_server else {
return; return;
}; };
if !lsp_server.as_ref(ctx).supports_completion_resolve() {
return;
}
let future = match lsp_server.as_ref(ctx).completion_resolve(raw_item) { let future = match lsp_server.as_ref(ctx).completion_resolve(raw_item) {
Ok(future) => future, Ok(future) => future,
@@ -473,8 +489,8 @@ impl LocalCodeEditorView {
}; };
let abort_handle = ctx let abort_handle = ctx
.spawn(future, |me, result, ctx| { .spawn(future, move |me, result, ctx| {
me.handle_completion_resolve_response(result, ctx); me.handle_completion_resolve_response(item_index, result, ctx);
}) })
.abort_handle(); .abort_handle();
@@ -492,6 +508,7 @@ impl LocalCodeEditorView {
fn handle_completion_resolve_response( fn handle_completion_resolve_response(
&mut self, &mut self,
item_index: usize,
result: anyhow::Result<CompletionItem>, result: anyhow::Result<CompletionItem>,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
@@ -500,22 +517,29 @@ impl LocalCodeEditorView {
Err(_) => return, Err(_) => return,
}; };
let doc_string = match resolved_item.documentation { let Some(documentation) = completion_documentation(&resolved_item) else {
Some(lsp_types::Documentation::String(s)) => s, return;
Some(lsp_types::Documentation::MarkupContent(m)) => m.value,
None => return,
}; };
if doc_string.trim().is_empty() { self.set_resolved_completion_docs(item_index, documentation, ctx);
}
fn set_resolved_completion_docs(
&mut self,
item_index: usize,
documentation: String,
ctx: &mut ViewContext<Self>,
) {
if documentation.trim().is_empty() {
return; return;
} }
let formatted = match markdown_parser::parse_markdown(&doc_string) { let formatted = match markdown_parser::parse_markdown(&documentation) {
Ok(text) => text, Ok(text) => text,
Err(_) => { Err(_) => {
use markdown_parser::{FormattedTextFragment, FormattedTextLine}; use markdown_parser::{FormattedTextFragment, FormattedTextLine};
FormattedText::new([FormattedTextLine::Line(vec![ FormattedText::new([FormattedTextLine::Line(vec![
FormattedTextFragment::plain_text(doc_string), FormattedTextFragment::plain_text(documentation),
])]) ])])
} }
}; };
@@ -529,9 +553,9 @@ impl LocalCodeEditorView {
} = &mut self.completion_state } = &mut self.completion_state
{ {
*resolve_abort_handle = None; *resolve_abort_handle = None;
if let Some(&item_idx) = filtered_indices.get(*selected_index) { if filtered_indices.get(*selected_index) == Some(&item_index) {
*resolved_docs = Some(ResolvedDocumentation { *resolved_docs = Some(ResolvedDocumentation {
item_index: item_idx, item_index,
text: formatted, text: formatted,
scroll_state: ClippedScrollStateHandle::default(), scroll_state: ClippedScrollStateHandle::default(),
}); });
@@ -552,7 +576,7 @@ impl LocalCodeEditorView {
} }
} }
/// Manually trigger completion (Ctrl+Alt+Space). /// Manually trigger completion (Ctrl+Space in the code editor).
pub(super) fn trigger_completion_manually(&mut self, ctx: &mut ViewContext<Self>) { pub(super) fn trigger_completion_manually(&mut self, ctx: &mut ViewContext<Self>) {
if !Self::is_completion_enabled() { if !Self::is_completion_enabled() {
return; return;
@@ -562,7 +586,7 @@ impl LocalCodeEditorView {
} }
let cursor_offset = self.editor().as_ref(ctx).cursor_head_offset(ctx); let cursor_offset = self.editor().as_ref(ctx).cursor_head_offset(ctx);
let word_start = self.find_word_start(cursor_offset, ctx); let word_start = self.find_word_start(cursor_offset, ctx);
self.request_completion(word_start, CompletionTrigger::Invoked, ctx); self.request_completion(cursor_offset, word_start, CompletionTrigger::Invoked, ctx);
} }
pub(super) fn render_completion_menu(&self, app: &AppContext) -> Option<Box<dyn Element>> { pub(super) fn render_completion_menu(&self, app: &AppContext) -> Option<Box<dyn Element>> {
+2
View File
@@ -158,6 +158,8 @@ pub enum CodeEditorEvent {
CompletionNavigateDown, CompletionNavigateDown,
/// Emitted when Tab/Enter is pressed and completion_intercept_keys is active. /// Emitted when Tab/Enter is pressed and completion_intercept_keys is active.
CompletionConfirm, CompletionConfirm,
/// Emitted when the manual completion keybinding is pressed.
CompletionTrigger,
} }
/// Store all states related to displaying the editor content. /// Store all states related to displaying the editor content.
+8
View File
@@ -78,6 +78,11 @@ pub fn init(app: &mut AppContext) {
CodeEditorViewAction::VimShiftEnter, CodeEditorViewAction::VimShiftEnter,
text_entry.clone() & id!("Vim"), text_entry.clone() & id!("Vim"),
), ),
FixedBinding::new(
"ctrl-space",
CodeEditorViewAction::TriggerCompletion,
editable_state.clone(),
),
FixedBinding::new( FixedBinding::new(
"backspace", "backspace",
CodeEditorViewAction::Backspace, CodeEditorViewAction::Backspace,
@@ -711,6 +716,7 @@ pub enum CodeEditorViewAction {
ShiftTab, ShiftTab,
ShowFindBar, ShowFindBar,
ShowGoToLine, ShowGoToLine,
TriggerCompletion,
Escape, Escape,
VimEnter, VimEnter,
VimTab, VimTab,
@@ -795,6 +801,7 @@ impl CodeEditorViewAction {
| Self::Copy | Self::Copy
| Self::ShowFindBar | Self::ShowFindBar
| Self::ShowGoToLine | Self::ShowGoToLine
| Self::TriggerCompletion
| Self::Escape | Self::Escape
| Self::HiddenSectionExpansion { .. } | Self::HiddenSectionExpansion { .. }
| Self::AddDiffHunkContext { .. } | Self::AddDiffHunkContext { .. }
@@ -1064,6 +1071,7 @@ impl TypedActionView for CodeEditorView {
ShowFindBar => self.show_find_bar(ctx), ShowFindBar => self.show_find_bar(ctx),
ShowGoToLine => self.show_goto_line(ctx), ShowGoToLine => self.show_goto_line(ctx),
TriggerCompletion => ctx.emit(CodeEditorEvent::CompletionTrigger),
Escape => self.escape(ctx), Escape => self.escape(ctx),
HiddenSectionExpansion { HiddenSectionExpansion {
line_range, line_range,
+1 -1
View File
@@ -2965,7 +2965,7 @@ impl View for FileTreeView {
if let CodingPanelEnablementState::RemoteSession { has_remote_server } = self.enablement if let CodingPanelEnablementState::RemoteSession { has_remote_server } = self.enablement
{ {
// When the session has a remote server connection (Auto SSH // When the session has a remote server connection (Auto SSH
// Warpification / mode 1), show a loading state — the server // Wormholing / mode 1), show a loading state — the server
// may push repo metadata momentarily. For other SSH modes // may push repo metadata momentarily. For other SSH modes
// (tmux, subshell) no data will arrive, so show the disabled // (tmux, subshell) no data will arrive, so show the disabled
// error instead. // error instead.
+12 -2
View File
@@ -1562,13 +1562,23 @@ impl GlobalBufferModel {
.flatten(); .flatten();
// If we have a previous version that wasn't synced, we need to do a full sync. // If we have a previous version that wasn't synced, we need to do a full sync.
let needs_full_sync = previous_version.is_some_and(|prev| { let server_requires_full_sync = lsp_server.as_ref(ctx).requires_full_document_sync();
let needs_full_sync = server_requires_full_sync
|| previous_version.is_some_and(|prev| {
last_synced.is_none() || last_synced.is_some_and(|synced| synced < prev) last_synced.is_none() || last_synced.is_some_and(|synced| synced < prev)
}); });
let deltas_len = deltas.len(); let deltas_len = deltas.len();
if needs_full_sync { if server_requires_full_sync {
lsp_server.as_ref(ctx).log_to_server_log(
LspServerLogLevel::Debug,
format!(
"didChange -> server: REQUIRED full-sync file={} send_version={current_version} deltas={deltas_len}",
path.display()
),
);
} else if needs_full_sync {
lsp_server.as_ref(ctx).log_to_server_log( lsp_server.as_ref(ctx).log_to_server_log(
LspServerLogLevel::Info, LspServerLogLevel::Info,
format!( format!(
+7 -11
View File
@@ -114,11 +114,6 @@ pub fn init(app: &mut AppContext) {
LocalCodeEditorAction::StartRename, LocalCodeEditorAction::StartRename,
id!("LocalCodeEditorView"), id!("LocalCodeEditorView"),
), ),
FixedBinding::new(
"ctrl-alt-space",
LocalCodeEditorAction::TriggerCompletion,
id!("LocalCodeEditorView"),
),
]); ]);
} }
@@ -216,8 +211,6 @@ pub enum LocalCodeEditorAction {
OpenCodeActions, OpenCodeActions,
/// Start LSP rename at cursor (F2). /// Start LSP rename at cursor (F2).
StartRename, StartRename,
/// Manually trigger completion (Ctrl+Alt+Space).
TriggerCompletion,
/// Hover over a completion item by display index. /// Hover over a completion item by display index.
CompletionHoverItem(usize), CompletionHoverItem(usize),
/// Confirm completion via mouse click. /// Confirm completion via mouse click.
@@ -510,6 +503,9 @@ impl LocalCodeEditorView {
CodeEditorEvent::CompletionConfirm => { CodeEditorEvent::CompletionConfirm => {
me.confirm_completion(ctx); me.confirm_completion(ctx);
} }
CodeEditorEvent::CompletionTrigger => {
me.trigger_completion_manually(ctx);
}
CodeEditorEvent::VimGotoDefinition CodeEditorEvent::VimGotoDefinition
| CodeEditorEvent::VimFindReferences | CodeEditorEvent::VimFindReferences
| CodeEditorEvent::VimShowHover => { | CodeEditorEvent::VimShowHover => {
@@ -995,9 +991,12 @@ impl LocalCodeEditorView {
// If the LSP is not registered, try to start it via PersistedWorkspace. // If the LSP is not registered, try to start it via PersistedWorkspace.
#[cfg(feature = "local_fs")] #[cfg(feature = "local_fs")]
{ {
use crate::ai::persisted_workspace::LspTask;
PersistedWorkspace::handle(ctx).update(ctx, |workspace, ctx| { PersistedWorkspace::handle(ctx).update(ctx, |workspace, ctx| {
if path == crate::settings::user_preferences_toml_file_path() {
workspace.ensure_settings_toml_lsp(path, ctx);
} else {
workspace.execute_lsp_task(LspTask::Spawn { file_path: path }, ctx); workspace.execute_lsp_task(LspTask::Spawn { file_path: path }, ctx);
}
}); });
} }
return; return;
@@ -2487,9 +2486,6 @@ impl TypedActionView for LocalCodeEditorView {
LocalCodeEditorAction::StartRename => { LocalCodeEditorAction::StartRename => {
self.start_rename(ctx); self.start_rename(ctx);
} }
LocalCodeEditorAction::TriggerCompletion => {
self.trigger_completion_manually(ctx);
}
LocalCodeEditorAction::CompletionHoverItem(display_index) => { LocalCodeEditorAction::CompletionHoverItem(display_index) => {
self.handle_completion_hover_item(*display_index, ctx); self.handle_completion_hover_item(*display_index, ctx);
} }
+1 -1
View File
@@ -9,7 +9,7 @@ pub(crate) enum CodingPanelEnablementState {
/// The active session is on a remote host. /// The active session is on a remote host.
/// ///
/// `has_remote_server` is `true` when the session is registered with /// `has_remote_server` is `true` when the session is registered with
/// `RemoteServerManager` (i.e. Auto SSH Warpification / mode 1). When /// `RemoteServerManager` (i.e. Auto SSH Wormholing / mode 1). When
/// `true`, remote repo metadata may arrive and the file tree should show /// `true`, remote repo metadata may arrive and the file tree should show
/// a loading state. When `false` (tmux or subshell SSH), no data will /// a loading state. When `false` (tmux or subshell SSH), no data will
/// arrive and the file tree should show a disabled message. /// arrive and the file tree should show a disabled message.
+1 -1
View File
@@ -101,7 +101,7 @@ impl SessionContext {
.filter_map(|res| res.and_then(EngineDirEntry::try_from).ok()) .filter_map(|res| res.and_then(EngineDirEntry::try_from).ok())
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
SessionType::WarpifiedRemote { .. } => { SessionType::WormholedRemote { .. } => {
let env_vars = self let env_vars = self
.session .session
.path() .path()
+1 -1
View File
@@ -91,7 +91,7 @@ pub fn ssh_session(ctx: &GeneratorContext) -> Option<ChipValue> {
if session.is_ssh_wrapper_session() if session.is_ssh_wrapper_session()
|| matches!( || matches!(
session.session_type(), session.session_type(),
crate::terminal::model::session::SessionType::WarpifiedRemote { .. } crate::terminal::model::session::SessionType::WormholedRemote { .. }
) )
{ {
let user = session.user(); let user = session.user();
+1 -1
View File
@@ -43,7 +43,7 @@ fn test_remote_sessions() {
let local_session = Session::test(); let local_session = Session::test();
let remote_session = Session::new( let remote_session = Session::new(
SessionInfo::new_for_test() SessionInfo::new_for_test()
.with_session_type(BootstrapSessionType::WarpifiedRemote) .with_session_type(BootstrapSessionType::WormholedRemote)
.with_hostname("remote-host".to_string()) .with_hostname("remote-host".to_string())
.with_user("remote-user".to_string()), .with_user("remote-user".to_string()),
Arc::new(TestCommandExecutor {}), Arc::new(TestCommandExecutor {}),
+2 -2
View File
@@ -1638,10 +1638,10 @@ impl DisplayChip {
.as_ref() .as_ref()
.map(|ctx| match ctx.session.session_type() { .map(|ctx| match ctx.session.session_type() {
SessionType::Local => true, SessionType::Local => true,
SessionType::WarpifiedRemote { host_id: Some(_) } => { SessionType::WormholedRemote { host_id: Some(_) } => {
FeatureFlag::RemoteCodeReview.is_enabled() FeatureFlag::RemoteCodeReview.is_enabled()
} }
SessionType::WarpifiedRemote { host_id: None } => false, SessionType::WormholedRemote { host_id: None } => false,
}) })
.unwrap_or(false); .unwrap_or(false);
+2 -2
View File
@@ -465,8 +465,8 @@ fn enabled_features() -> HashSet<FeatureFlag> {
FeatureFlag::CLIAgentRichInput, FeatureFlag::CLIAgentRichInput,
#[cfg(feature = "transfer_control_tool")] #[cfg(feature = "transfer_control_tool")]
FeatureFlag::TransferControlTool, FeatureFlag::TransferControlTool,
#[cfg(feature = "warpify_footer")] #[cfg(feature = "wormhole_footer")]
FeatureFlag::WarpifyFooter, FeatureFlag::WormholeFooter,
#[cfg(feature = "solo_user_byok")] #[cfg(feature = "solo_user_byok")]
FeatureFlag::SoloUserByok, FeatureFlag::SoloUserByok,
#[cfg(feature = "billing_and_usage_page_v2")] #[cfg(feature = "billing_and_usage_page_v2")]
+4 -4
View File
@@ -92,7 +92,7 @@ pub fn enter_local_subshell_command(shell: &str) -> TestStep {
} }
pub fn assert_subshell_banner_is_showing() -> TestStep { pub fn assert_subshell_banner_is_showing() -> TestStep {
TestStep::new("Assert the Warpify banner is visible") TestStep::new("Assert the Wormhole banner is visible")
.add_assertion(move |app, window_id| { .add_assertion(move |app, window_id| {
let terminal_view = single_terminal_view(app, window_id); let terminal_view = single_terminal_view(app, window_id);
terminal_view.read(app, |view, _ctx| { terminal_view.read(app, |view, _ctx| {
@@ -102,7 +102,7 @@ pub fn assert_subshell_banner_is_showing() -> TestStep {
.block_list_mut() .block_list_mut()
.active_block() .active_block()
.block_banner(), .block_banner(),
Some(WithinBlockBanner::WarpifyBanner(..)) Some(WithinBlockBanner::WormholeBanner(..))
)) ))
}) })
}) })
@@ -132,10 +132,10 @@ pub fn assert_subshell_is_bootstrapped(tab_index: usize, pane_index: usize) -> T
}; };
match rich_content_type { match rich_content_type {
Some(RichContentType::WarpifySuccessBlock) => {} Some(RichContentType::WormholeSuccessBlock) => {}
_ => { _ => {
return AssertionOutcome::failure( return AssertionOutcome::failure(
"Warpify success block wasn't added to the blocklist".to_owned(), "Wormhole success block wasn't added to the blocklist".to_owned(),
); );
} }
} }
+20 -6
View File
@@ -152,7 +152,19 @@ impl RemoteTransport for SshTransport {
fn check_binary(&self) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send>> { fn check_binary(&self) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send>> {
let socket_path = self.socket_path.clone(); let socket_path = self.socket_path.clone();
Box::pin(async move { Box::pin(async move {
let cmd = remote_server::setup::binary_check_command(); let binary = remote_server::setup::remote_server_binary();
let expected_helper_version = if remote_server::setup::uses_static_linux_helper() {
let platform = detect_remote_platform(&socket_path).await?;
installation::local_helper_version(&platform).await
} else {
None
};
let cmd = match expected_helper_version {
Some(version) => format!(
"{binary} --version >/dev/null && test \"$(cat {binary}.wormhole-version 2>/dev/null)\" = \"{version}\""
),
None => remote_server::setup::binary_check_command(),
};
log::info!("Running binary check: {cmd}"); log::info!("Running binary check: {cmd}");
let output = remote_server::ssh::run_ssh_command( let output = remote_server::ssh::run_ssh_command(
&socket_path, &socket_path,
@@ -161,16 +173,18 @@ impl RemoteTransport for SshTransport {
) )
.await?; .await?;
// `<binary> --version` exits 0 when present, executable, and // `<binary> --version` exits 0 when present, executable, and
// functional. Exit 127 means the binary was not found, and 126 // functional. Static helpers additionally compare their bundled
// means it exists but is not executable. Any other non-zero // build marker, where exit 1 means the remote copy is stale.
// exit (e.g. SSH exit 255 for a dead connection, or signal // Exit 127 means the binary was not found, and 126 means it exists
// termination) is treated as a transport-level failure. // but is not executable. Any other non-zero exit (e.g. SSH exit
// 255 for a dead connection, or signal termination) is treated as
// a transport-level failure.
let code = output.status.code(); let code = output.status.code();
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
log::info!("Binary check result: exit={code:?} stdout={stdout}"); log::info!("Binary check result: exit={code:?} stdout={stdout}");
match code { match code {
Some(0) => Ok(true), Some(0) => Ok(true),
Some(126) | Some(127) => Ok(false), Some(1) | Some(126) | Some(127) => Ok(false),
Some(code) => { Some(code) => {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
Err(Error::Other(anyhow::anyhow!( Err(Error::Other(anyhow::anyhow!(
@@ -4,6 +4,8 @@ mod scp_fallback;
use std::path::Path; use std::path::Path;
use anyhow::Result; use anyhow::Result;
use galaxy_core::channel::{Channel, ChannelState};
use remote_server::setup::RemotePlatform;
use remote_server::ssh::SshCommandError; use remote_server::ssh::SshCommandError;
use remote_server::transport::{Error, InstallOutcome, InstallSource}; use remote_server::transport::{Error, InstallOutcome, InstallSource};
@@ -13,7 +15,16 @@ use remote_server::transport::{Error, InstallOutcome, InstallSource};
pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome { pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome {
let binary_path = remote_server::setup::remote_server_binary(); let binary_path = remote_server::setup::remote_server_binary();
log::info!("Installing remote server binary to {binary_path}"); log::info!("Installing remote server binary to {binary_path}");
let mut outcome = match install_on_server(socket_path).await { let mut outcome = if matches!(ChannelState::channel(), Channel::Local | Channel::Oss) {
// Local-first builds never contact Warp's release service. Their
// statically linked Linux helpers are bundled with Galaxy and copied
// through the SSH connection instead.
InstallOutcome {
source: Some(InstallSource::Client),
result: scp_fallback::install_local_helper(socket_path).await,
}
} else {
match install_on_server(socket_path).await {
Ok(()) => InstallOutcome { Ok(()) => InstallOutcome {
source: Some(InstallSource::Server), source: Some(InstallSource::Server),
result: Ok(()), result: Ok(()),
@@ -38,6 +49,7 @@ pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome {
} }
} }
} }
}
}; };
// Post-install verification: confirm the binary actually landed at the // Post-install verification: confirm the binary actually landed at the
@@ -73,6 +85,13 @@ pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome {
outcome outcome
} }
/// Returns the build marker for the static helper bundled for `platform`, if
/// this client has one. The SSH transport uses this to avoid copying the helper
/// when the matching build is already installed remotely.
pub(super) async fn local_helper_version(platform: &RemotePlatform) -> Option<String> {
scp_fallback::local_helper_version(platform).await
}
/// Runs the install script on the remote host to download and install the /// Runs the install script on the remote host to download and install the
/// binary directly from the CDN. /// binary directly from the CDN.
async fn install_on_server(socket_path: &Path) -> Result<(), Error> { async fn install_on_server(socket_path: &Path) -> Result<(), Error> {
@@ -8,6 +8,9 @@ use remote_server::setup::RemotePlatform;
use remote_server::transport::Error; use remote_server::transport::Error;
const REMOTE_SERVER_TARBALL_CACHE_FILE_NAME: &str = "oz.tar.gz"; const REMOTE_SERVER_TARBALL_CACHE_FILE_NAME: &str = "oz.tar.gz";
const WORMHOLE_HELPER_TARBALL_FILE_NAME: &str = "galaxy-wormhole.tar.gz";
const WORMHOLE_HELPER_VERSION_FILE_NAME: &str = "galaxy-wormhole.version";
const WORMHOLE_HELPERS_DIR_ENV: &str = "GALAXY_WORMHOLE_HELPERS_DIR";
const REMOTE_SERVER_TARBALL_DOWNLOAD_ATTEMPTS: usize = 3; const REMOTE_SERVER_TARBALL_DOWNLOAD_ATTEMPTS: usize = 3;
// The local SCP fallback download can run over slow or captive networks. Match // The local SCP fallback download can run over slow or captive networks. Match
@@ -25,6 +28,29 @@ pub(super) fn should_try_install(error: &Error) -> bool {
!matches!(error, Error::ScriptFailed { exit_code, .. } if *exit_code == 2) !matches!(error, Error::ScriptFailed { exit_code, .. } if *exit_code == 2)
} }
/// Installs the static Linux helper shipped with local-first Galaxy builds.
/// No network download is attempted: the selected artifact is copied directly
/// through the existing SSH control connection.
pub(super) async fn install_local_helper(socket_path: &Path) -> Result<(), Error> {
let platform = super::super::detect_remote_platform(socket_path).await?;
let client_tarball_path = local_helper_tarball(&platform).ok_or_else(|| {
Error::Other(anyhow::anyhow!(
"Galaxy does not contain a Wormhole helper for Linux {}. Expected {} under the bundled resources or {}.",
platform.arch.as_str(),
helper_relative_path(&platform)
.map(|path| path.display().to_string())
.unwrap_or_else(|| "a supported Linux platform directory".to_string()),
WORMHOLE_HELPERS_DIR_ENV,
))
})?;
log::info!(
"Using bundled Wormhole helper at {}",
client_tarball_path.display()
);
install_tarball(socket_path, &client_tarball_path).await
}
/// Installs the remote server via SCP fallback. /// Installs the remote server via SCP fallback.
/// ///
/// The tarball is downloaded or reused from the local cache first, then uploaded /// The tarball is downloaded or reused from the local cache first, then uploaded
@@ -36,6 +62,10 @@ pub(super) async fn install(socket_path: &Path) -> Result<(), Error> {
let client_tarball_path = cached_remote_server_tarball(&platform) let client_tarball_path = cached_remote_server_tarball(&platform)
.await .await
.map_err(Error::Other)?; .map_err(Error::Other)?;
install_tarball(socket_path, &client_tarball_path).await
}
async fn install_tarball(socket_path: &Path, client_tarball_path: &Path) -> Result<(), Error> {
let timeout = remote_server::setup::SCP_INSTALL_TIMEOUT; let timeout = remote_server::setup::SCP_INSTALL_TIMEOUT;
let install_dir = remote_server::setup::remote_server_dir(); let install_dir = remote_server::setup::remote_server_dir();
let remote_tarball_name = format!("oz-upload-{}.tar.gz", uuid::Uuid::new_v4()); let remote_tarball_name = format!("oz-upload-{}.tar.gz", uuid::Uuid::new_v4());
@@ -63,7 +93,7 @@ pub(super) async fn install(socket_path: &Path) -> Result<(), Error> {
log::info!("Uploading tarball to remote at {remote_tarball_path}"); log::info!("Uploading tarball to remote at {remote_tarball_path}");
remote_server::ssh::scp_upload( remote_server::ssh::scp_upload(
socket_path, socket_path,
&client_tarball_path, client_tarball_path,
&remote_tarball_path, &remote_tarball_path,
timeout, timeout,
) )
@@ -88,6 +118,58 @@ pub(super) async fn install(socket_path: &Path) -> Result<(), Error> {
} }
} }
fn helper_relative_path(platform: &RemotePlatform) -> Option<PathBuf> {
if !matches!(&platform.os, remote_server::setup::RemoteOs::Linux) {
return None;
}
Some(
PathBuf::from(format!("linux-{}", platform.arch.as_str()))
.join(WORMHOLE_HELPER_TARBALL_FILE_NAME),
)
}
fn helper_roots() -> Vec<PathBuf> {
let mut roots = Vec::new();
if let Some(path) = std::env::var_os(WORMHOLE_HELPERS_DIR_ENV) {
roots.push(path.into());
}
if let Some(resources_dir) = galaxy_core::paths::bundled_resources_dir() {
roots.push(resources_dir.join("wormhole-helpers"));
}
if cfg!(debug_assertions) {
roots.push(
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("app manifest directory should have a workspace parent")
.join("resources")
.join("wormhole-helpers"),
);
}
roots
}
fn local_helper_tarball(platform: &RemotePlatform) -> Option<PathBuf> {
let relative_path = helper_relative_path(platform)?;
helper_roots()
.into_iter()
.map(|root| root.join(&relative_path))
.find(|path| {
std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file() && metadata.len() > 0)
})
}
pub(super) async fn local_helper_version(platform: &RemotePlatform) -> Option<String> {
let tarball = local_helper_tarball(platform)?;
let version_path = tarball.parent()?.join(WORMHOLE_HELPER_VERSION_FILE_NAME);
let version = async_fs::read_to_string(version_path).await.ok()?;
let version = version.trim();
if version.is_empty() || !version.chars().all(|c| c.is_ascii_hexdigit()) {
return None;
}
Some(version.to_owned())
}
fn remote_server_tarball_cache_root() -> PathBuf { fn remote_server_tarball_cache_root() -> PathBuf {
galaxy_core::paths::cache_dir() galaxy_core::paths::cache_dir()
.join("remote-server") .join("remote-server")
@@ -124,6 +206,11 @@ async fn is_valid_cached_tarball(path: &Path) -> bool {
/// Reuses an existing cached tarball when available; otherwise downloads the /// Reuses an existing cached tarball when available; otherwise downloads the
/// tarball into the cache and returns the newly cached path. /// tarball into the cache and returns the newly cached path.
async fn cached_remote_server_tarball(platform: &RemotePlatform) -> anyhow::Result<PathBuf> { async fn cached_remote_server_tarball(platform: &RemotePlatform) -> anyhow::Result<PathBuf> {
if let Some(path) = local_helper_tarball(platform) {
log::info!("Using bundled Wormhole helper at {}", path.display());
return Ok(path);
}
let cache_path = remote_server_tarball_cache_path(platform); let cache_path = remote_server_tarball_cache_path(platform);
if is_valid_cached_tarball(&cache_path).await { if is_valid_cached_tarball(&cache_path).await {
log::info!( log::info!(
+1 -1
View File
@@ -2749,7 +2749,7 @@ impl RootView {
} }
/// Insert a command that should create a subshell. If we support bootstrapping AKA /// Insert a command that should create a subshell. If we support bootstrapping AKA
/// "warpifying" its [`ShellType`], set a flag to automatically bootstrap it when the command's /// "wormholing" its [`ShellType`], set a flag to automatically bootstrap it when the command's
/// block receives the [`AfterBlockStarted`] event. /// block receives the [`AfterBlockStarted`] event.
pub fn insert_subshell_command_and_bootstrap_if_supported( pub fn insert_subshell_command_and_bootstrap_if_supported(
&mut self, &mut self,
+34 -34
View File
@@ -1739,7 +1739,7 @@ pub enum TelemetryEvent {
AddAddedSubshellCommand, AddAddedSubshellCommand,
RemoveAddedSubshellCommand, RemoveAddedSubshellCommand,
ReceivedSubshellRcFileDcs, ReceivedSubshellRcFileDcs,
ToggleSshWarpification { ToggleSshWormholing {
enabled: bool, enabled: bool,
}, },
/// User changed the SSH extension install mode. /// User changed the SSH extension install mode.
@@ -1751,11 +1751,11 @@ pub enum TelemetryEvent {
SshRemoteServerChoiceDoNotAskAgainToggled { SshRemoteServerChoiceDoNotAskAgainToggled {
checked: bool, checked: bool,
}, },
WarpifyFooterShown { WormholeFooterShown {
is_ssh: bool, is_ssh: bool,
}, },
AgentToolbarDismissed, AgentToolbarDismissed,
WarpifyFooterAcceptedWarpify { WormholeFooterAcceptedWormhole {
is_ssh: bool, is_ssh: bool,
}, },
ShowAliasExpansionBanner, ShowAliasExpansionBanner,
@@ -1861,7 +1861,7 @@ pub enum TelemetryEvent {
team_uid: ServerId, team_uid: ServerId,
}, },
CopyObjectToClipboard(TelemetryCloudObjectType), CopyObjectToClipboard(TelemetryCloudObjectType),
OpenAndWarpifyDockerSubshell { OpenAndWormholeDockerSubshell {
/// Some variant if we support this shell type, and None otherwise. /// Some variant if we support this shell type, and None otherwise.
shell_type: Option<ShellType>, shell_type: Option<ShellType>,
}, },
@@ -3438,8 +3438,8 @@ impl TelemetryEvent {
Some(json!({ "remember": remember })) Some(json!({ "remember": remember }))
} }
TelemetryEvent::AgentToolbarDismissed => None, TelemetryEvent::AgentToolbarDismissed => None,
TelemetryEvent::WarpifyFooterShown { is_ssh } TelemetryEvent::WormholeFooterShown { is_ssh }
| TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh } => { | TelemetryEvent::WormholeFooterAcceptedWormhole { is_ssh } => {
Some(json!({ "is_ssh": is_ssh })) Some(json!({ "is_ssh": is_ssh }))
} }
TelemetryEvent::ToggleSameLinePrompt { enabled } => Some(json!({ "enabled": enabled })), TelemetryEvent::ToggleSameLinePrompt { enabled } => Some(json!({ "enabled": enabled })),
@@ -3512,7 +3512,7 @@ impl TelemetryEvent {
TelemetryEvent::CopyObjectToClipboard(object_type) => { TelemetryEvent::CopyObjectToClipboard(object_type) => {
Some(json!({ "object_type": object_type })) Some(json!({ "object_type": object_type }))
} }
TelemetryEvent::OpenAndWarpifyDockerSubshell { shell_type } => { TelemetryEvent::OpenAndWormholeDockerSubshell { shell_type } => {
Some(json!({ "shell_type": shell_type })) Some(json!({ "shell_type": shell_type }))
} }
TelemetryEvent::ToggleBlockFilterQuery { enabled, source } => { TelemetryEvent::ToggleBlockFilterQuery { enabled, source } => {
@@ -3536,7 +3536,7 @@ impl TelemetryEvent {
TelemetryEvent::ToggleNewWindowsAtCustomSize { enabled } => { TelemetryEvent::ToggleNewWindowsAtCustomSize { enabled } => {
Some(json!({"enabled": enabled})) Some(json!({"enabled": enabled}))
} }
TelemetryEvent::ToggleSshWarpification { enabled } => Some(json!({"enabled": enabled})), TelemetryEvent::ToggleSshWormholing { enabled } => Some(json!({"enabled": enabled})),
TelemetryEvent::SetSshExtensionInstallMode { mode } => Some(json!({"mode": mode})), TelemetryEvent::SetSshExtensionInstallMode { mode } => Some(json!({"mode": mode})),
TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { checked } => { TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { checked } => {
Some(json!({"checked": checked})) Some(json!({"checked": checked}))
@@ -5053,9 +5053,9 @@ impl TelemetryEvent {
| TelemetryEvent::AddAddedSubshellCommand | TelemetryEvent::AddAddedSubshellCommand
| TelemetryEvent::RemoveAddedSubshellCommand | TelemetryEvent::RemoveAddedSubshellCommand
| TelemetryEvent::ReceivedSubshellRcFileDcs | TelemetryEvent::ReceivedSubshellRcFileDcs
| TelemetryEvent::WarpifyFooterShown { .. } | TelemetryEvent::WormholeFooterShown { .. }
| TelemetryEvent::AgentToolbarDismissed | TelemetryEvent::AgentToolbarDismissed
| TelemetryEvent::WarpifyFooterAcceptedWarpify { .. } | TelemetryEvent::WormholeFooterAcceptedWormhole { .. }
| TelemetryEvent::ShowAliasExpansionBanner | TelemetryEvent::ShowAliasExpansionBanner
| TelemetryEvent::EnableAliasExpansionFromBanner | TelemetryEvent::EnableAliasExpansionFromBanner
| TelemetryEvent::DismissAliasExpansionBanner | TelemetryEvent::DismissAliasExpansionBanner
@@ -5099,7 +5099,7 @@ impl TelemetryEvent {
| TelemetryEvent::LogOut | TelemetryEvent::LogOut
| TelemetryEvent::InviteTeammates { .. } | TelemetryEvent::InviteTeammates { .. }
| TelemetryEvent::CopyObjectToClipboard(_) | TelemetryEvent::CopyObjectToClipboard(_)
| TelemetryEvent::OpenAndWarpifyDockerSubshell { .. } | TelemetryEvent::OpenAndWormholeDockerSubshell { .. }
| TelemetryEvent::UpdateBlockFilterQuery | TelemetryEvent::UpdateBlockFilterQuery
| TelemetryEvent::UpdateBlockFilterQueryContextLines { .. } | TelemetryEvent::UpdateBlockFilterQueryContextLines { .. }
| TelemetryEvent::ToggleBlockFilterQuery { .. } | TelemetryEvent::ToggleBlockFilterQuery { .. }
@@ -5174,7 +5174,7 @@ impl TelemetryEvent {
| TelemetryEvent::MCPServerSpawned { .. } | TelemetryEvent::MCPServerSpawned { .. }
| TelemetryEvent::MCPToolCallAccepted { .. } | TelemetryEvent::MCPToolCallAccepted { .. }
| TelemetryEvent::ExecutedWarpDrivePrompt { .. } | TelemetryEvent::ExecutedWarpDrivePrompt { .. }
| TelemetryEvent::ToggleSshWarpification { .. } | TelemetryEvent::ToggleSshWormholing { .. }
| TelemetryEvent::SetSshExtensionInstallMode { .. } | TelemetryEvent::SetSshExtensionInstallMode { .. }
| TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { .. } | TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { .. }
| TelemetryEvent::SettingsImportInitiated | TelemetryEvent::SettingsImportInitiated
@@ -5604,12 +5604,12 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
Self::TriggerSubshellBootstrap => EnablementState::Always, Self::TriggerSubshellBootstrap => EnablementState::Always,
Self::AddDenylistedSubshellCommand => EnablementState::Always, Self::AddDenylistedSubshellCommand => EnablementState::Always,
Self::RemoveDenylistedSubshellCommand => EnablementState::Always, Self::RemoveDenylistedSubshellCommand => EnablementState::Always,
Self::ToggleSshWarpification => EnablementState::Always, Self::ToggleSshWormholing => EnablementState::Always,
Self::SetSshExtensionInstallMode => EnablementState::Always, Self::SetSshExtensionInstallMode => EnablementState::Always,
Self::SshRemoteServerChoiceDoNotAskAgainToggled => EnablementState::Always, Self::SshRemoteServerChoiceDoNotAskAgainToggled => EnablementState::Always,
Self::WarpifyFooterShown Self::WormholeFooterShown
| Self::AgentToolbarDismissed | Self::AgentToolbarDismissed
| Self::WarpifyFooterAcceptedWarpify => EnablementState::Always, | Self::WormholeFooterAcceptedWormhole => EnablementState::Always,
Self::AddAddedSubshellCommand => EnablementState::Always, Self::AddAddedSubshellCommand => EnablementState::Always,
Self::RemoveAddedSubshellCommand => EnablementState::Always, Self::RemoveAddedSubshellCommand => EnablementState::Always,
Self::ReceivedSubshellRcFileDcs => EnablementState::Always, Self::ReceivedSubshellRcFileDcs => EnablementState::Always,
@@ -5640,7 +5640,7 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
Self::SettingsImportInitiated => EnablementState::Always, Self::SettingsImportInitiated => EnablementState::Always,
Self::InviteTeammates => EnablementState::Always, Self::InviteTeammates => EnablementState::Always,
Self::CopyObjectToClipboard => EnablementState::Always, Self::CopyObjectToClipboard => EnablementState::Always,
Self::OpenAndWarpifyDockerSubshell => EnablementState::Always, Self::OpenAndWormholeDockerSubshell => EnablementState::Always,
Self::UpdateBlockFilterQuery => EnablementState::Always, Self::UpdateBlockFilterQuery => EnablementState::Always,
Self::UpdateBlockFilterQueryContextLines => EnablementState::Always, Self::UpdateBlockFilterQueryContextLines => EnablementState::Always,
Self::ToggleBlockFilterQuery => EnablementState::Always, Self::ToggleBlockFilterQuery => EnablementState::Always,
@@ -6110,14 +6110,14 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
Self::AddAddedSubshellCommand => "Add Added Subshell Command", Self::AddAddedSubshellCommand => "Add Added Subshell Command",
Self::RemoveAddedSubshellCommand => "Remove Added Subshell Command", Self::RemoveAddedSubshellCommand => "Remove Added Subshell Command",
Self::ReceivedSubshellRcFileDcs => "Received Subshell RC File DCS", Self::ReceivedSubshellRcFileDcs => "Received Subshell RC File DCS",
Self::ToggleSshWarpification => "Toggle SSH Warpification", Self::ToggleSshWormholing => "Toggle SSH Wormholing",
Self::SetSshExtensionInstallMode => "Set SSH Extension Install Mode", Self::SetSshExtensionInstallMode => "Set SSH Extension Install Mode",
Self::SshRemoteServerChoiceDoNotAskAgainToggled => { Self::SshRemoteServerChoiceDoNotAskAgainToggled => {
"SSH Remote Server Choice Do Not Ask Again Toggled" "SSH Remote Server Choice Do Not Ask Again Toggled"
} }
Self::WarpifyFooterShown => "Warpify Footer Shown", Self::WormholeFooterShown => "Wormhole Footer Shown",
Self::AgentToolbarDismissed => "Agent Toolbar Dismissed", Self::AgentToolbarDismissed => "Agent Toolbar Dismissed",
Self::WarpifyFooterAcceptedWarpify => "Warpify Footer Accepted Warpify", Self::WormholeFooterAcceptedWormhole => "Wormhole Footer Accepted Wormhole",
Self::ShowAliasExpansionBanner => "Show Alias Expansion Banner", Self::ShowAliasExpansionBanner => "Show Alias Expansion Banner",
Self::DismissAliasExpansionBanner => "Dismiss Alias Expansion Banner", Self::DismissAliasExpansionBanner => "Dismiss Alias Expansion Banner",
Self::EnableAliasExpansionFromBanner => "Enable Alias Expansion From Banner", Self::EnableAliasExpansionFromBanner => "Enable Alias Expansion From Banner",
@@ -6155,7 +6155,7 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
Self::SettingsImportInitiated => "Settings Import Initiated", Self::SettingsImportInitiated => "Settings Import Initiated",
Self::InviteTeammates => "Invited Teammates", Self::InviteTeammates => "Invited Teammates",
Self::CopyObjectToClipboard => "Copy Object To Clipboard", Self::CopyObjectToClipboard => "Copy Object To Clipboard",
Self::OpenAndWarpifyDockerSubshell => "OpenAndWarpifyDockerSubshell", Self::OpenAndWormholeDockerSubshell => "OpenAndWormholeDockerSubshell",
Self::UpdateBlockFilterQuery => "Update Block Filter Query", Self::UpdateBlockFilterQuery => "Update Block Filter Query",
Self::ToggleBlockFilterQuery => "Toggle Block Filter Query", Self::ToggleBlockFilterQuery => "Toggle Block Filter Query",
Self::ToggleBlockFilterCaseSensitivity => "Toggle Block Filter Case Sensitivity", Self::ToggleBlockFilterCaseSensitivity => "Toggle Block Filter Case Sensitivity",
@@ -6790,28 +6790,28 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
"Enabled or disabled preserving the active tab color" "Enabled or disabled preserving the active tab color"
} }
Self::ShowSubshellBanner => { Self::ShowSubshellBanner => {
"Displayed the banner asking whether Warp should Warpify the current session via Warp's subshell wrapper" "Displayed the banner asking whether Galaxy should Wormhole the current session via Galaxy's subshell wrapper"
} }
Self::DeclineSubshellBootstrap => { Self::DeclineSubshellBootstrap => {
"Developer declined the Warp banner to Warpify the current session" "Developer declined the Galaxy banner to Wormhole the current session"
} }
Self::TriggerSubshellBootstrap => { Self::TriggerSubshellBootstrap => {
"Attempted to Warpify the current session via Warp's subshell wrapper" "Attempted to Wormhole the current session via Galaxy's subshell wrapper"
} }
Self::AddDenylistedSubshellCommand => { Self::AddDenylistedSubshellCommand => {
"Explicitly prevent a command from being Warpified via Warp's subshell wrapper" "Explicitly prevent a command from being Wormholed via Galaxy's subshell wrapper"
} }
Self::RemoveDenylistedSubshellCommand => { Self::RemoveDenylistedSubshellCommand => {
"Removed a command from the list of commands to IGNORE when trying to Warpify via Warp's subshell wrapper" "Removed a command from the list of commands to IGNORE when trying to Wormhole via Galaxy's subshell wrapper"
} }
Self::AddAddedSubshellCommand => { Self::AddAddedSubshellCommand => {
"Added a command to be automatically Warpified via Warp's subshell wrapper" "Added a command to be automatically Wormholed via Galaxy's subshell wrapper"
} }
Self::RemoveAddedSubshellCommand => { Self::RemoveAddedSubshellCommand => {
"Removed a command from the list of commands to automatically Warpify via Warp's subshell wrapper" "Removed a command from the list of commands to automatically Wormhole via Galaxy's subshell wrapper"
} }
Self::ReceivedSubshellRcFileDcs => "Spawned a subshell to be automatically Warpified", Self::ReceivedSubshellRcFileDcs => "Spawned a subshell to be automatically Wormholed",
Self::ToggleSshWarpification => "Changed the setting for SSH sessions to be warified", Self::ToggleSshWormholing => "Changed the setting for SSH sessions to be wormholed",
Self::SetSshExtensionInstallMode => { Self::SetSshExtensionInstallMode => {
"Changed the SSH extension install mode (always ask / always allow / always skip)" "Changed the SSH extension install mode (always ask / always allow / always skip)"
} }
@@ -6819,11 +6819,11 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
"Toggled the 'Don't ask me this again' checkbox on the SSH remote-server choice block" "Toggled the 'Don't ask me this again' checkbox on the SSH remote-server choice block"
} }
Self::AgentModeRatedResponse => "User rated an Agent Mode response", Self::AgentModeRatedResponse => "User rated an Agent Mode response",
Self::WarpifyFooterShown => { Self::WormholeFooterShown => {
"Displayed the warpify footer for a detected subshell or SSH session" "Displayed the wormhole footer for a detected subshell or SSH session"
} }
Self::AgentToolbarDismissed => "User dismissed the use-agent toolbar", Self::AgentToolbarDismissed => "User dismissed the use-agent toolbar",
Self::WarpifyFooterAcceptedWarpify => "User clicked Warpify in the warpify footer", Self::WormholeFooterAcceptedWormhole => "User clicked Wormhole in the wormhole footer",
Self::ShowAliasExpansionBanner => { Self::ShowAliasExpansionBanner => {
"Displayed the banner asking whether Warp should automatically expand aliases within the Input Editor" "Displayed the banner asking whether Warp should automatically expand aliases within the Input Editor"
} }
@@ -6903,8 +6903,8 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
Self::SettingsImportInitiated => "Started the import settings flow for new users", Self::SettingsImportInitiated => "Started the import settings flow for new users",
Self::InviteTeammates => "Sent emails to invite teammates to join Warp Drive team", Self::InviteTeammates => "Sent emails to invite teammates to join Warp Drive team",
Self::CopyObjectToClipboard => "Copied an object to the user's keyboard", Self::CopyObjectToClipboard => "Copied an object to the user's keyboard",
Self::OpenAndWarpifyDockerSubshell => { Self::OpenAndWormholeDockerSubshell => {
"Warpifying a docker subshell from using the docker extension" "Wormholing a docker subshell from using the docker extension"
} }
Self::UpdateBlockFilterQuery => "When a new filter is applied to a block", Self::UpdateBlockFilterQuery => "When a new filter is applied to a block",
Self::UpdateBlockFilterQueryContextLines => { Self::UpdateBlockFilterQueryContextLines => {
+2 -2
View File
@@ -33,7 +33,7 @@ use crate::terminal::safe_mode_settings::SafeModeSettings;
use crate::terminal::session_settings::{SessionSettings, SessionSettingsChangedEvent}; use crate::terminal::session_settings::{SessionSettings, SessionSettingsChangedEvent};
use crate::terminal::settings::TerminalSettings; use crate::terminal::settings::TerminalSettings;
use crate::terminal::shared_session::settings::SharedSessionSettings; use crate::terminal::shared_session::settings::SharedSessionSettings;
use crate::terminal::warpify::settings::WarpifySettings; use crate::terminal::wormhole::settings::WormholeSettings;
use crate::terminal::BlockListSettings; use crate::terminal::BlockListSettings;
use crate::undo_close::UndoCloseSettings; use crate::undo_close::UndoCloseSettings;
use crate::window_settings::WindowSettings; use crate::window_settings::WindowSettings;
@@ -86,7 +86,7 @@ pub fn register_all_settings(ctx: &mut AppContext) {
AppIconSettings::register(ctx); AppIconSettings::register(ctx);
AppEditorSettings::register(ctx); AppEditorSettings::register(ctx);
InputSettings::register(ctx); InputSettings::register(ctx);
WarpifySettings::register(ctx); WormholeSettings::register(ctx);
AltScreenReporting::register(ctx); AltScreenReporting::register(ctx);
UndoCloseSettings::register(ctx); UndoCloseSettings::register(ctx);
SshSettings::register(ctx); SshSettings::register(ctx);
+1 -1
View File
@@ -1,6 +1,6 @@
use galaxyui::platform::linux; use galaxyui::platform::linux;
use settings::macros::define_settings_group; use settings::macros::define_settings_group;
use settings::{SupportedPlatforms, SyncToCloud}; use settings::{Setting as _, SupportedPlatforms, SyncToCloud};
define_settings_group!(LinuxAppConfiguration, define_settings_group!(LinuxAppConfiguration,
settings: [ settings: [
+1
View File
@@ -28,6 +28,7 @@ mod onboarding;
mod pane; mod pane;
mod privacy; mod privacy;
mod same_line_prompt_block; mod same_line_prompt_block;
pub mod schema_export;
mod scroll; mod scroll;
mod select; mod select;
mod ssh; mod ssh;
+230
View File
@@ -0,0 +1,230 @@
use std::collections::HashSet;
use std::path::PathBuf;
use anyhow::Context;
use galaxy_core::channel::{Channel, ChannelState};
use galaxy_core::features::{
FeatureFlag, DEBUG_FLAGS, DOGFOOD_FLAGS, PREVIEW_FLAGS, RELEASE_FLAGS,
};
use schemars::SchemaGenerator;
use serde_json::{Map, Value};
use settings::schema::SettingSchemaEntry;
fn strip_numeric_metadata(value: &mut Value) {
match value {
Value::Object(map) => {
let is_numeric = map
.get("type")
.and_then(Value::as_str)
.is_some_and(|value_type| value_type == "integer" || value_type == "number");
if is_numeric {
map.remove("minimum");
map.remove("maximum");
map.remove("format");
}
for value in map.values_mut() {
strip_numeric_metadata(value);
}
}
Value::Array(values) => {
for value in values {
strip_numeric_metadata(value);
}
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
}
fn strip_empty_enum_entries(value: &mut Value) {
match value {
Value::Object(map) => {
if let Some(Value::Array(one_of)) = map.get_mut("oneOf") {
one_of.retain(|entry| {
!matches!(entry, Value::Object(object)
if object.get("enum").is_some_and(|value| value.as_array().is_some_and(|values| values.is_empty())))
});
}
for value in map.values_mut() {
strip_empty_enum_entries(value);
}
}
Value::Array(values) => {
for value in values {
strip_empty_enum_entries(value);
}
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
}
fn active_flags_for_channel(channel: &str) -> HashSet<FeatureFlag> {
let mut flags = HashSet::new();
let flag_lists: &[&[FeatureFlag]] = match channel {
"stable" => &[RELEASE_FLAGS],
"preview" => &[RELEASE_FLAGS, PREVIEW_FLAGS],
"dev" => &[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS],
other => {
log::warn!("Unknown settings schema channel '{other}', defaulting to dev");
&[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS]
}
};
for list in flag_lists {
flags.extend(*list);
}
flags
}
fn ensure_hierarchy<'a>(
root_properties: &'a mut Map<String, Value>,
hierarchy: &str,
) -> &'a mut Map<String, Value> {
let mut current = root_properties;
for segment in hierarchy.split('.') {
let entry = current.entry(segment.to_string()).or_insert_with(|| {
Value::Object({
let mut map = Map::new();
map.insert("type".to_string(), Value::String("object".to_string()));
map.insert("properties".to_string(), Value::Object(Map::new()));
map
})
});
current = entry
.as_object_mut()
.expect("hierarchy node should be an object")
.entry("properties")
.or_insert_with(|| Value::Object(Map::new()))
.as_object_mut()
.expect("properties should be an object");
}
current
}
/// Generates the user-facing JSON schema for Galaxy's TOML settings.
pub fn generate_settings_schema(channel: &str) -> (String, usize) {
let active_flags = active_flags_for_channel(channel);
let mut generator = SchemaGenerator::default();
let mut root_properties = Map::new();
let mut entry_count = 0;
for entry in inventory::iter::<SettingSchemaEntry> {
if entry.is_private {
continue;
}
if let Some(flag) = entry.feature_flag {
if !active_flags.contains(&flag) {
continue;
}
}
let type_schema = (entry.schema_fn)(&mut generator);
let mut schema_value: Value = type_schema.to_value();
let default_json = (entry.file_default_value_fn)();
if let Ok(default_value) = serde_json::from_str::<Value>(&default_json) {
if let Some(object) = schema_value.as_object_mut() {
object.insert("default".to_string(), default_value);
}
}
if !entry.description.is_empty() {
if let Some(object) = schema_value.as_object_mut() {
object.insert(
"description".to_string(),
Value::String(entry.description.to_string()),
);
}
}
let target = if let Some(hierarchy) = entry.hierarchy {
ensure_hierarchy(&mut root_properties, hierarchy)
} else {
&mut root_properties
};
target.insert(entry.storage_key.to_string(), schema_value);
entry_count += 1;
}
let definitions = generator.take_definitions(true);
let mut root = Map::new();
root.insert(
"$schema".to_string(),
Value::String("https://json-schema.org/draft/2020-12/schema".to_string()),
);
root.insert(
"title".to_string(),
Value::String("Galaxy Settings".to_string()),
);
root.insert(
"description".to_string(),
Value::String(format!(
"JSON Schema for Galaxy settings ({channel} channel, {entry_count} settings)"
)),
);
root.insert("type".to_string(), Value::String("object".to_string()));
root.insert("properties".to_string(), Value::Object(root_properties));
if !definitions.is_empty() {
root.insert("$defs".to_string(), Value::Object(definitions));
}
let mut root_value = Value::Object(root);
strip_numeric_metadata(&mut root_value);
strip_empty_enum_entries(&mut root_value);
(
serde_json::to_string_pretty(&root_value).expect("settings schema should serialize"),
entry_count,
)
}
fn runtime_schema_channel() -> &'static str {
match ChannelState::channel() {
Channel::Stable => "stable",
Channel::Preview => "preview",
Channel::Dev | Channel::Local | Channel::Oss | Channel::Integration => "dev",
}
}
/// Returns the bundled settings schema, or generates a current local copy for development runs.
pub fn ensure_runtime_settings_schema() -> anyhow::Result<PathBuf> {
if let Some(schema_path) = galaxy_core::paths::bundled_resources_dir()
.map(|resources| resources.join("settings_schema.json"))
.filter(|path| path.is_file())
{
return Ok(schema_path);
}
let schema_path = galaxy_core::paths::config_local_dir().join("settings_schema.json");
let (schema, _) = generate_settings_schema(runtime_schema_channel());
let existing_schema = std::fs::read_to_string(&schema_path).ok();
if existing_schema.as_deref() != Some(schema.as_str()) {
if let Some(parent) = schema_path.parent() {
std::fs::create_dir_all(parent).with_context(|| {
format!(
"Failed to create settings schema directory {}",
parent.display()
)
})?;
}
std::fs::write(&schema_path, schema).with_context(|| {
format!(
"Failed to write Galaxy settings schema to {}",
schema_path.display()
)
})?;
}
Ok(schema_path)
}
+1 -1
View File
@@ -10,7 +10,7 @@ define_settings_group!(SshSettings,
sync_to_cloud: SyncToCloud::Never, sync_to_cloud: SyncToCloud::Never,
private: false, private: false,
storage_key: "ReuseExistingSshControlMaster", storage_key: "ReuseExistingSshControlMaster",
toml_path: "warpify.ssh.reuse_existing_control_master", toml_path: "wormhole.ssh.reuse_existing_control_master",
description: "Whether the legacy SSH wrapper attaches to an existing SSH ControlMaster for the destination host instead of always creating its own.", description: "Whether the legacy SSH wrapper attaches to an existing SSH ControlMaster for the destination host instead of always creating its own.",
}, },
] ]
+21 -22
View File
@@ -40,7 +40,7 @@ use settings_page::{
HEADER_PADDING, HEADER_PADDING,
}; };
use teams_page::{TeamsPageView, TeamsPageViewEvent}; use teams_page::{TeamsPageView, TeamsPageViewEvent};
use warpify_page::{WarpifyPageAction, WarpifyPageView}; use wormhole_page::{WormholePageAction, WormholePageView};
use self::telemetry::SettingsTelemetryEvent; use self::telemetry::SettingsTelemetryEvent;
use crate::ai::custom_model_routers::CustomModelRouter; use crate::ai::custom_model_routers::CustomModelRouter;
@@ -95,7 +95,7 @@ mod teams_page;
mod telemetry; mod telemetry;
pub mod update_environment_form; pub mod update_environment_form;
mod warp_drive_page; mod warp_drive_page;
mod warpify_page; mod wormhole_page;
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
pub(crate) use ai_page::cli_agent_settings_widget_id; pub(crate) use ai_page::cli_agent_settings_widget_id;
@@ -233,7 +233,7 @@ pub enum SettingsSection {
Scripting, Scripting,
Teams, Teams,
WarpDrive, WarpDrive,
Warpify, Wormhole,
/// Internal backing-page identifier for AISettingsPageView. Multiple subpages /// Internal backing-page identifier for AISettingsPageView. Multiple subpages
/// (WarpAgent, AgentProfiles, Knowledge, ThirdPartyCLIAgents) share this single /// (WarpAgent, AgentProfiles, Knowledge, ThirdPartyCLIAgents) share this single
/// backing page, so this variant is needed as the key in `settings_pages`. /// backing page, so this variant is needed as the key in `settings_pages`.
@@ -286,7 +286,7 @@ impl Display for SettingsSection {
SettingsSection::ProviderChatGPTSubscription => write!(f, "ChatGPT Subscription"), SettingsSection::ProviderChatGPTSubscription => write!(f, "ChatGPT Subscription"),
SettingsSection::ProviderBedrock => write!(f, "Bedrock"), SettingsSection::ProviderBedrock => write!(f, "Bedrock"),
SettingsSection::ProviderACP => write!(f, "ACP"), SettingsSection::ProviderACP => write!(f, "ACP"),
SettingsSection::Warpify => write!(f, "Wormhole"), SettingsSection::Wormhole => write!(f, "Wormhole"),
SettingsSection::CodeIndexing => write!(f, "Indexing and projects"), SettingsSection::CodeIndexing => write!(f, "Indexing and projects"),
SettingsSection::EditorAndCodeReview => write!(f, "Editor and Code Review"), SettingsSection::EditorAndCodeReview => write!(f, "Editor and Code Review"),
_ => write!(f, "{self:?}"), _ => write!(f, "{self:?}"),
@@ -407,7 +407,6 @@ impl FromStr for SettingsSection {
"Privacy" => Ok(Self::Privacy), "Privacy" => Ok(Self::Privacy),
"Galaxy Control" | "Scripting" => Ok(Self::Scripting), "Galaxy Control" | "Scripting" => Ok(Self::Scripting),
"Teams" => Ok(Self::Teams), "Teams" => Ok(Self::Teams),
"Warpify" => Ok(Self::Warpify),
"WarpDrive" | "Galaxy Drive" => Ok(Self::WarpDrive), "WarpDrive" | "Galaxy Drive" => Ok(Self::WarpDrive),
"Oz" | "Warp Agent" | "Galaxy Agent" => Ok(Self::WarpAgent), "Oz" | "Warp Agent" | "Galaxy Agent" => Ok(Self::WarpAgent),
"Profiles" | "AgentProfiles" => Ok(Self::AgentProfiles), "Profiles" | "AgentProfiles" => Ok(Self::AgentProfiles),
@@ -423,7 +422,7 @@ impl FromStr for SettingsSection {
"Indexing and projects" | "CodeIndexing" => Ok(Self::CodeIndexing), "Indexing and projects" | "CodeIndexing" => Ok(Self::CodeIndexing),
"Editor and Code Review" | "EditorAndCodeReview" => Ok(Self::EditorAndCodeReview), "Editor and Code Review" | "EditorAndCodeReview" => Ok(Self::EditorAndCodeReview),
"Experiments" => Ok(Self::Experiments), "Experiments" => Ok(Self::Experiments),
"Wormhole" => Ok(Self::Warpify), "Wormhole" => Ok(Self::Wormhole),
_ => Err(()), _ => Err(()),
} }
} }
@@ -488,7 +487,7 @@ pub mod flags {
pub const SCROLL_REPORTING_CONTEXT_FLAG: &str = "Scroll_Reporting"; pub const SCROLL_REPORTING_CONTEXT_FLAG: &str = "Scroll_Reporting";
pub const FOCUS_REPORTING_CONTEXT_FLAG: &str = "Focus_Reporting"; pub const FOCUS_REPORTING_CONTEXT_FLAG: &str = "Focus_Reporting";
pub const SSH_REUSE_CONTROL_MASTER_CONTEXT_FLAG: &str = "SSH_Reuse_Control_Master"; pub const SSH_REUSE_CONTROL_MASTER_CONTEXT_FLAG: &str = "SSH_Reuse_Control_Master";
pub const SSH_WARPIFICATION_CONTEXT_FLAG: &str = "SSH_Warpification"; pub const SSH_WORMHOLING_CONTEXT_FLAG: &str = "SSH_Wormholing";
pub const NOTIFICATIONS_CONTEXT_FLAG: &str = "Notifications_Enabled"; pub const NOTIFICATIONS_CONTEXT_FLAG: &str = "Notifications_Enabled";
pub const LONG_RUNNING_NOTIFICATIONS_FLAG: &str = "Long_Running_Notifications"; pub const LONG_RUNNING_NOTIFICATIONS_FLAG: &str = "Long_Running_Notifications";
pub const AGENT_TASK_COMPLETED_NOTIFICATIONS_FLAG: &str = "Agent_Task_Completed_Notifications"; pub const AGENT_TASK_COMPLETED_NOTIFICATIONS_FLAG: &str = "Agent_Task_Completed_Notifications";
@@ -610,7 +609,7 @@ pub mod flags {
pub const IS_AUTOINDEXING_ENABLED: &str = "IsAutoIndexingEnabled"; pub const IS_AUTOINDEXING_ENABLED: &str = "IsAutoIndexingEnabled";
pub const LIGATURE_RENDERING_CONTEXT_FLAG: &str = "Ligature_Rendering_Enabled"; pub const LIGATURE_RENDERING_CONTEXT_FLAG: &str = "Ligature_Rendering_Enabled";
pub const HAS_SETTINGS_TO_IMPORT_FLAG: &str = "HasSettingsToImport"; pub const HAS_SETTINGS_TO_IMPORT_FLAG: &str = "HasSettingsToImport";
/// The user's setting enabled UDI, but we may show a classic input (e.g. ssh/subshell warpification) /// The user's setting enabled UDI, but we may show a classic input (e.g. ssh/subshell wormholing)
pub const UNIVERSAL_DEVELOPER_INPUT_ENABLED: &str = "UniversalDeveloperInputEnabled"; pub const UNIVERSAL_DEVELOPER_INPUT_ENABLED: &str = "UniversalDeveloperInputEnabled";
pub const AGENT_MODE_INPUT: &str = "InputAgentMode"; pub const AGENT_MODE_INPUT: &str = "InputAgentMode";
pub const TERMINAL_MODE_INPUT: &str = "InputTerminalMode"; pub const TERMINAL_MODE_INPUT: &str = "InputTerminalMode";
@@ -657,7 +656,7 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
) { ) {
appearance_page::init_actions_from_parent_view(app, context, builder); appearance_page::init_actions_from_parent_view(app, context, builder);
features_page::init_actions_from_parent_view(app, context, builder); features_page::init_actions_from_parent_view(app, context, builder);
warpify_page::init_actions_from_parent_view(app, context, builder); wormhole_page::init_actions_from_parent_view(app, context, builder);
privacy_page::init_actions_from_parent_view(app, context, builder); privacy_page::init_actions_from_parent_view(app, context, builder);
ai_page::init_actions_from_parent_view(app, context, builder); ai_page::init_actions_from_parent_view(app, context, builder);
code_page::init_actions_from_parent_view(app, context, builder); code_page::init_actions_from_parent_view(app, context, builder);
@@ -965,7 +964,7 @@ pub enum SettingsAction {
AI(AISettingsPageAction), AI(AISettingsPageAction),
Code(CodeSettingsPageAction), Code(CodeSettingsPageAction),
WarpDrive(warp_drive_page::WarpDriveSettingsPageAction), WarpDrive(warp_drive_page::WarpDriveSettingsPageAction),
WarpifyPageToggle(WarpifyPageAction), WormholePageToggle(WormholePageAction),
Tab, Tab,
Split(Direction), Split(Direction),
ToggleMaximizePane, ToggleMaximizePane,
@@ -1108,7 +1107,7 @@ macro_rules! update_page {
SettingsPageViewHandle::Appearance(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Appearance(handle) => $ctx.update_view(handle, $update),
SettingsPageViewHandle::Features(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Features(handle) => $ctx.update_view(handle, $update),
SettingsPageViewHandle::Keybindings(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Keybindings(handle) => $ctx.update_view(handle, $update),
SettingsPageViewHandle::Warpify(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Wormhole(handle) => $ctx.update_view(handle, $update),
SettingsPageViewHandle::Privacy(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Privacy(handle) => $ctx.update_view(handle, $update),
SettingsPageViewHandle::Scripting(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Scripting(handle) => $ctx.update_view(handle, $update),
SettingsPageViewHandle::AI(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::AI(handle) => $ctx.update_view(handle, $update),
@@ -1195,9 +1194,9 @@ impl SettingsView {
me.handle_code_page_event(event, ctx); me.handle_code_page_event(event, ctx);
}); });
let warpify_page_handle = ctx.add_typed_action_view(WarpifyPageView::new); let wormhole_page_handle = ctx.add_typed_action_view(WormholePageView::new);
ctx.subscribe_to_view(&warpify_page_handle, |me, _, event, ctx| { ctx.subscribe_to_view(&wormhole_page_handle, |me, _, event, ctx| {
me.handle_warpify_page_event(event, ctx); me.handle_wormhole_page_event(event, ctx);
}); });
// Render the privacy page only if telemetry opt-out is enabled. // Render the privacy page only if telemetry opt-out is enabled.
@@ -1256,7 +1255,7 @@ impl SettingsView {
SettingsPage::new(appearance_page_handle), SettingsPage::new(appearance_page_handle),
SettingsPage::new(features_page_handle), SettingsPage::new(features_page_handle),
SettingsPage::new(keybindings_handle), SettingsPage::new(keybindings_handle),
SettingsPage::new(warpify_page_handle), SettingsPage::new(wormhole_page_handle),
SettingsPage::new(warp_drive_page_handle), SettingsPage::new(warp_drive_page_handle),
]; ];
@@ -1291,7 +1290,7 @@ impl SettingsView {
SettingsNavItem::Page(SettingsSection::Appearance), SettingsNavItem::Page(SettingsSection::Appearance),
SettingsNavItem::Page(SettingsSection::Features), SettingsNavItem::Page(SettingsSection::Features),
SettingsNavItem::Page(SettingsSection::Keybindings), SettingsNavItem::Page(SettingsSection::Keybindings),
SettingsNavItem::Page(SettingsSection::Warpify), SettingsNavItem::Page(SettingsSection::Wormhole),
SettingsNavItem::Page(SettingsSection::WarpDrive), SettingsNavItem::Page(SettingsSection::WarpDrive),
SettingsNavItem::Page(SettingsSection::Privacy), SettingsNavItem::Page(SettingsSection::Privacy),
SettingsNavItem::Page(SettingsSection::About), SettingsNavItem::Page(SettingsSection::About),
@@ -1702,7 +1701,7 @@ impl SettingsView {
} }
} }
fn handle_warpify_page_event( fn handle_wormhole_page_event(
&mut self, &mut self,
event: &SettingsPageEvent, event: &SettingsPageEvent,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
@@ -1938,7 +1937,7 @@ impl SettingsView {
SettingsPageViewHandle::Appearance(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::Appearance(v) => v.as_ref(app).should_render(app),
SettingsPageViewHandle::About(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::About(v) => v.as_ref(app).should_render(app),
SettingsPageViewHandle::Privacy(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::Privacy(v) => v.as_ref(app).should_render(app),
SettingsPageViewHandle::Warpify(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::Wormhole(v) => v.as_ref(app).should_render(app),
SettingsPageViewHandle::Scripting(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::Scripting(v) => v.as_ref(app).should_render(app),
SettingsPageViewHandle::AI(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::AI(v) => v.as_ref(app).should_render(app),
SettingsPageViewHandle::MCPServers(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::MCPServers(v) => v.as_ref(app).should_render(app),
@@ -2518,11 +2517,11 @@ impl TypedActionView for SettingsView {
} }
} }
} }
SettingsAction::WarpifyPageToggle(warpify_action) => { SettingsAction::WormholePageToggle(wormhole_action) => {
if let Some(warpify_page) = self.settings_page(SettingsSection::Warpify) { if let Some(wormhole_page) = self.settings_page(SettingsSection::Wormhole) {
if let SettingsPageViewHandle::Warpify(view) = &warpify_page.view_handle { if let SettingsPageViewHandle::Wormhole(view) = &wormhole_page.view_handle {
view.update(ctx, |view, ctx| { view.update(ctx, |view, ctx| {
view.handle_action(warpify_action, ctx); view.handle_action(wormhole_action, ctx);
}) })
} }
} }
+4 -4
View File
@@ -89,7 +89,7 @@ fn top_level_sections_map_to_themselves() {
SettingsSection::Scripting, SettingsSection::Scripting,
SettingsSection::Teams, SettingsSection::Teams,
SettingsSection::WarpDrive, SettingsSection::WarpDrive,
SettingsSection::Warpify, SettingsSection::Wormhole,
] { ] {
assert!(!section.is_subpage(), "{section:?} should be top-level"); assert!(!section.is_subpage(), "{section:?} should be top-level");
assert_eq!(section.parent_page_section(), section); assert_eq!(section.parent_page_section(), section);
@@ -101,7 +101,7 @@ fn current_settings_display_names_round_trip() {
for (section, display_name) in [ for (section, display_name) in [
(SettingsSection::Scripting, "Galaxy Control"), (SettingsSection::Scripting, "Galaxy Control"),
(SettingsSection::WarpDrive, "Galaxy Drive"), (SettingsSection::WarpDrive, "Galaxy Drive"),
(SettingsSection::Warpify, "Wormhole"), (SettingsSection::Wormhole, "Wormhole"),
(SettingsSection::WarpAgent, "Galaxy Agent"), (SettingsSection::WarpAgent, "Galaxy Agent"),
(SettingsSection::AgentProfiles, "Profiles"), (SettingsSection::AgentProfiles, "Profiles"),
(SettingsSection::AgentMCPServers, "MCP servers"), (SettingsSection::AgentMCPServers, "MCP servers"),
@@ -135,7 +135,7 @@ fn legacy_settings_names_remain_parseable() {
for (name, expected) in [ for (name, expected) in [
("Scripting", SettingsSection::Scripting), ("Scripting", SettingsSection::Scripting),
("WarpDrive", SettingsSection::WarpDrive), ("WarpDrive", SettingsSection::WarpDrive),
("Warpify", SettingsSection::Warpify), ("Wormhole", SettingsSection::Wormhole),
("Oz", SettingsSection::WarpAgent), ("Oz", SettingsSection::WarpAgent),
("Warp Agent", SettingsSection::WarpAgent), ("Warp Agent", SettingsSection::WarpAgent),
("AgentProfiles", SettingsSection::AgentProfiles), ("AgentProfiles", SettingsSection::AgentProfiles),
@@ -191,7 +191,7 @@ fn realistic_nav_items() -> Vec<SettingsNavItem> {
SettingsNavItem::Page(SettingsSection::Appearance), SettingsNavItem::Page(SettingsSection::Appearance),
SettingsNavItem::Page(SettingsSection::Features), SettingsNavItem::Page(SettingsSection::Features),
SettingsNavItem::Page(SettingsSection::Keybindings), SettingsNavItem::Page(SettingsSection::Keybindings),
SettingsNavItem::Page(SettingsSection::Warpify), SettingsNavItem::Page(SettingsSection::Wormhole),
SettingsNavItem::Page(SettingsSection::WarpDrive), SettingsNavItem::Page(SettingsSection::WarpDrive),
SettingsNavItem::Page(SettingsSection::Privacy), SettingsNavItem::Page(SettingsSection::Privacy),
SettingsNavItem::Page(SettingsSection::About), SettingsNavItem::Page(SettingsSection::About),
+3 -3
View File
@@ -37,7 +37,7 @@ use super::privacy_page::PrivacyPageView;
use super::scripting_page::ScriptingSettingsPageView; use super::scripting_page::ScriptingSettingsPageView;
use super::teams_page::TeamsPageView; use super::teams_page::TeamsPageView;
use super::warp_drive_page::WarpDriveSettingsPageView; use super::warp_drive_page::WarpDriveSettingsPageView;
use super::warpify_page::WarpifyPageView; use super::wormhole_page::WormholePageView;
use super::SettingsSection; use super::SettingsSection;
use crate::appearance::Appearance; use crate::appearance::Appearance;
use crate::settings::CloudPreferencesSettings; use crate::settings::CloudPreferencesSettings;
@@ -100,7 +100,7 @@ pub enum SettingsPageViewHandle {
About(ViewHandle<AboutPageView>), About(ViewHandle<AboutPageView>),
Code(ViewHandle<CodeSettingsPageView>), Code(ViewHandle<CodeSettingsPageView>),
Privacy(ViewHandle<PrivacyPageView>), Privacy(ViewHandle<PrivacyPageView>),
Warpify(ViewHandle<WarpifyPageView>), Wormhole(ViewHandle<WormholePageView>),
Scripting(ViewHandle<ScriptingSettingsPageView>), Scripting(ViewHandle<ScriptingSettingsPageView>),
AI(ViewHandle<AISettingsPageView>), AI(ViewHandle<AISettingsPageView>),
MCPServers(ViewHandle<MCPServersSettingsPageView>), MCPServers(ViewHandle<MCPServersSettingsPageView>),
@@ -119,7 +119,7 @@ impl SettingsPageViewHandle {
About(view_handle) => ChildView::new(view_handle).finish(), About(view_handle) => ChildView::new(view_handle).finish(),
Code(view_handle) => ChildView::new(view_handle).finish(), Code(view_handle) => ChildView::new(view_handle).finish(),
Privacy(view_handle) => ChildView::new(view_handle).finish(), Privacy(view_handle) => ChildView::new(view_handle).finish(),
Warpify(view_handle) => ChildView::new(view_handle).finish(), Wormhole(view_handle) => ChildView::new(view_handle).finish(),
Scripting(view_handle) => ChildView::new(view_handle).finish(), Scripting(view_handle) => ChildView::new(view_handle).finish(),
AI(view_handle) => ChildView::new(view_handle).finish(), AI(view_handle) => ChildView::new(view_handle).finish(),
MCPServers(view_handle) => ChildView::new(view_handle).finish(), MCPServers(view_handle) => ChildView::new(view_handle).finish(),
@@ -3,9 +3,7 @@ use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use galaxy_core::features::FeatureFlag; use galaxy_core::features::FeatureFlag;
use galaxyui::elements::{ use galaxyui::elements::{Container, Flex, MouseStateHandle, ParentElement, Text};
Container, Flex, FormattedTextElement, HighlightedHyperlink, MouseStateHandle, ParentElement,
};
use galaxyui::keymap::ContextPredicate; use galaxyui::keymap::ContextPredicate;
use galaxyui::presenter::ChildView; use galaxyui::presenter::ChildView;
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles}; use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
@@ -14,7 +12,6 @@ use galaxyui::{
Action, AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View, Action, AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View,
ViewContext, ViewHandle, ViewContext, ViewHandle,
}; };
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
use regex::Regex; use regex::Regex;
use settings::{Setting, ToggleableSetting}; use settings::{Setting, ToggleableSetting};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -29,9 +26,9 @@ use super::{flags, SettingsAction, SettingsSection, ToggleSettingActionPair};
use crate::appearance::Appearance; use crate::appearance::Appearance;
use crate::server::telemetry::TelemetryEvent; use crate::server::telemetry::TelemetryEvent;
use crate::settings::{ReuseExistingSshControlMaster, SshSettings}; use crate::settings::{ReuseExistingSshControlMaster, SshSettings};
use crate::terminal::warpify::settings::{ use crate::terminal::wormhole::settings::{
EnableSshWarpification, SshExtensionInstallMode, SshExtensionInstallModeSetting, EnableSshWormholing, SshExtensionInstallMode, SshExtensionInstallModeSetting, WormholeSettings,
WarpifySettings, WarpifySettingsChangedEvent, WormholeSettingsChangedEvent,
}; };
use crate::ui_components::blended_colors; use crate::ui_components::blended_colors;
use crate::view_components::dropdown::{Dropdown, DropdownItem}; use crate::view_components::dropdown::{Dropdown, DropdownItem};
@@ -43,19 +40,19 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
context: &ContextPredicate, context: &ContextPredicate,
builder: fn(SettingsAction) -> T, builder: fn(SettingsAction) -> T,
) { ) {
// Add all of the toggle settings from the Warpify Page that you want to show up on the Command Palette here. // Add all of the toggle settings from the Wormhole Page that you want to show up on the Command Palette here.
let mut toggle_binding_pairs = vec![]; let mut toggle_binding_pairs = vec![];
if WarpifySettings::as_ref(app) if WormholeSettings::as_ref(app)
.enable_ssh_warpification .enable_ssh_wormholing
.is_supported_on_current_platform() .is_supported_on_current_platform()
{ {
toggle_binding_pairs.push(ToggleSettingActionPair::new( toggle_binding_pairs.push(ToggleSettingActionPair::new(
"SSH Warpification", "SSH Wormholing",
builder(SettingsAction::WarpifyPageToggle( builder(SettingsAction::WormholePageToggle(
WarpifyPageAction::ToggleSshWarpification, WormholePageAction::ToggleSshWormholing,
)), )),
context, context,
flags::SSH_WARPIFICATION_CONTEXT_FLAG, flags::SSH_WORMHOLING_CONTEXT_FLAG,
)); ));
} }
@@ -68,17 +65,17 @@ const ITEM_VERTICAL_SPACING: f32 = 24.;
const BUILT_IN_TEXT_INPUT_MARGIN: f32 = 10.; const BUILT_IN_TEXT_INPUT_MARGIN: f32 = 10.;
const SPACE_AFTER_TEXT_INPUT: f32 = ITEM_VERTICAL_SPACING - BUILT_IN_TEXT_INPUT_MARGIN; const SPACE_AFTER_TEXT_INPUT: f32 = ITEM_VERTICAL_SPACING - BUILT_IN_TEXT_INPUT_MARGIN;
const SSH_REUSE_CONTROL_MASTER_DESCRIPTION: &str = "Attach to a live SSH ControlMaster you already have configured for the destination host instead of creating a Warp-owned one. Takes effect in new tabs."; const SSH_REUSE_CONTROL_MASTER_DESCRIPTION: &str = "Attach to a live SSH ControlMaster you already have configured for the destination host instead of creating a Galaxy-owned one. Takes effect in new tabs.";
const SSH_EXTENSION_INSTALL_MODE_DESCRIPTION: &str = const SSH_EXTENSION_INSTALL_MODE_DESCRIPTION: &str =
"Controls the installation behavior for Galaxy's SSH extension when a remote host doesn't have it installed."; "Controls how Galaxy installs the Wormhole helper when a remote host doesn't have it.";
/// This page lets users configure when they get asked to warpify a session. Some shell commands /// This page lets users configure when they get asked to wormhole a session. Some shell commands
/// are recognized by default. Users can add new shell commands, or prevent the default ones from /// are recognized by default. Users can add new shell commands, or prevent the default ones from
/// asking. Users can also enable the SSH wrapper, and add hosts to a denylist. /// asking. Users can also enable the SSH wrapper, and add hosts to a denylist.
/// This page is essentially the View for the SubshellSettings model, as well as the SshSettings /// This page is essentially the View for the SubshellSettings model, as well as the SshSettings
/// related to warpification. /// related to wormholing.
pub struct WarpifyPageView { pub struct WormholePageView {
page: PageType<Self>, page: PageType<Self>,
/// This needs to mirror the length of SubshellSettings::added_remove_button_states. /// This needs to mirror the length of SubshellSettings::added_remove_button_states.
remove_added_command_button_states: Vec<MouseStateHandle>, remove_added_command_button_states: Vec<MouseStateHandle>,
@@ -87,19 +84,19 @@ pub struct WarpifyPageView {
remove_denylisted_command_button_states: Vec<MouseStateHandle>, remove_denylisted_command_button_states: Vec<MouseStateHandle>,
add_denylisted_commands_editor: ViewHandle<SubmittableTextInput>, add_denylisted_commands_editor: ViewHandle<SubmittableTextInput>,
ssh_extension_install_mode_dropdown: ViewHandle<Dropdown<WarpifyPageAction>>, ssh_extension_install_mode_dropdown: ViewHandle<Dropdown<WormholePageAction>>,
} }
impl WarpifyPageView { impl WormholePageView {
pub fn new(ctx: &mut ViewContext<Self>) -> Self { pub fn new(ctx: &mut ViewContext<Self>) -> Self {
let warpify_settings_handle = WarpifySettings::handle(ctx); let wormhole_settings_handle = WormholeSettings::handle(ctx);
ctx.observe(&warpify_settings_handle, Self::update_button_states); ctx.observe(&wormhole_settings_handle, Self::update_button_states);
ctx.subscribe_to_model(&warpify_settings_handle, move |me, model, event, ctx| { ctx.subscribe_to_model(&wormhole_settings_handle, move |me, model, event, ctx| {
me.update_button_states(model, ctx); me.update_button_states(model, ctx);
if matches!( if matches!(
event, event,
WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } WormholeSettingsChangedEvent::SshExtensionInstallModeSetting { .. }
) { ) {
me.update_dropdown(ctx); me.update_dropdown(ctx);
} }
@@ -143,20 +140,20 @@ impl WarpifyPageView {
ssh_extension_install_mode_dropdown, ssh_extension_install_mode_dropdown,
}; };
instance.update_button_states(warpify_settings_handle, ctx); instance.update_button_states(wormhole_settings_handle, ctx);
instance instance
} }
fn build_page(ctx: &mut ViewContext<Self>) -> PageType<Self> { fn build_page(ctx: &mut ViewContext<Self>) -> PageType<Self> {
let mut categories = vec![ let mut categories = vec![
Category::new("", vec![Box::new(TitleWidget::default())]), Category::new("", vec![Box::new(TitleWidget)]),
Category::new("Subshells", vec![Box::new(SubshellsWidget::default())]) Category::new("Subshells", vec![Box::new(SubshellsWidget::default())])
.with_subtitle("Subshells supported: bash, zsh, and fish."), .with_subtitle("Subshells supported: bash, zsh, and fish."),
]; ];
let warpify_settings = WarpifySettings::as_ref(ctx); let wormhole_settings = WormholeSettings::as_ref(ctx);
if warpify_settings if wormhole_settings
.enable_ssh_warpification .enable_ssh_wormholing
.is_supported_on_current_platform() .is_supported_on_current_platform()
{ {
categories.push( categories.push(
@@ -171,16 +168,16 @@ impl WarpifyPageView {
/// its delete button in the View. /// its delete button in the View.
fn update_button_states( fn update_button_states(
&mut self, &mut self,
warpify_settings_handle: ModelHandle<WarpifySettings>, wormhole_settings_handle: ModelHandle<WormholeSettings>,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
let warpify_settings = warpify_settings_handle.as_ref(ctx); let wormhole_settings = wormhole_settings_handle.as_ref(ctx);
self.remove_denylisted_command_button_states = warpify_settings self.remove_denylisted_command_button_states = wormhole_settings
.subshell_command_denylist .subshell_command_denylist
.iter() .iter()
.map(|_| Default::default()) .map(|_| Default::default())
.collect(); .collect();
self.remove_added_command_button_states = warpify_settings self.remove_added_command_button_states = wormhole_settings
.added_subshell_commands .added_subshell_commands
.iter() .iter()
.map(|_| Default::default()) .map(|_| Default::default())
@@ -189,16 +186,16 @@ impl WarpifyPageView {
} }
/// Syncs the install-mode dropdown selection with the current /// Syncs the install-mode dropdown selection with the current
/// `WarpifySettings::ssh_extension_install_mode` value (e.g. after it /// `WormholeSettings::ssh_extension_install_mode` value (e.g. after it
/// was changed from the SSH remote server choice view). /// was changed from the SSH remote server choice view).
fn update_dropdown(&mut self, ctx: &mut ViewContext<Self>) { fn update_dropdown(&mut self, ctx: &mut ViewContext<Self>) {
let current_mode = *WarpifySettings::as_ref(ctx) let current_mode = *WormholeSettings::as_ref(ctx)
.ssh_extension_install_mode .ssh_extension_install_mode
.value(); .value();
self.ssh_extension_install_mode_dropdown self.ssh_extension_install_mode_dropdown
.update(ctx, |dropdown, ctx| { .update(ctx, |dropdown, ctx| {
dropdown.set_selected_by_action( dropdown.set_selected_by_action(
WarpifyPageAction::SetSshExtensionInstallMode(current_mode), WormholePageAction::SetSshExtensionInstallMode(current_mode),
ctx, ctx,
); );
}); });
@@ -212,8 +209,8 @@ impl WarpifyPageView {
) { ) {
match event { match event {
SubmittableTextInputEvent::Submit(new_command) => { SubmittableTextInputEvent::Submit(new_command) => {
WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| {
warpify_settings.add_subshell_command(new_command, ctx); wormhole_settings.add_subshell_command(new_command, ctx);
}); });
send_telemetry_from_ctx!(TelemetryEvent::AddAddedSubshellCommand, ctx); send_telemetry_from_ctx!(TelemetryEvent::AddAddedSubshellCommand, ctx);
@@ -230,8 +227,8 @@ impl WarpifyPageView {
) { ) {
match event { match event {
SubmittableTextInputEvent::Submit(new_command) => { SubmittableTextInputEvent::Submit(new_command) => {
WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| {
warpify_settings.denylist_subshell_command(new_command, ctx); wormhole_settings.denylist_subshell_command(new_command, ctx);
}); });
send_telemetry_from_ctx!(TelemetryEvent::AddDenylistedSubshellCommand, ctx); send_telemetry_from_ctx!(TelemetryEvent::AddDenylistedSubshellCommand, ctx);
@@ -242,20 +239,20 @@ impl WarpifyPageView {
fn remove_denylisted_command(&self, index: usize, ctx: &mut ViewContext<Self>) { fn remove_denylisted_command(&self, index: usize, ctx: &mut ViewContext<Self>) {
send_telemetry_from_ctx!(TelemetryEvent::RemoveDenylistedSubshellCommand, ctx); send_telemetry_from_ctx!(TelemetryEvent::RemoveDenylistedSubshellCommand, ctx);
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
warpify.remove_denylisted_subshell_command(index, ctx) wormhole.remove_denylisted_subshell_command(index, ctx)
}); });
} }
fn remove_added_command(&self, index: usize, ctx: &mut ViewContext<Self>) { fn remove_added_command(&self, index: usize, ctx: &mut ViewContext<Self>) {
send_telemetry_from_ctx!(TelemetryEvent::RemoveAddedSubshellCommand, ctx); send_telemetry_from_ctx!(TelemetryEvent::RemoveAddedSubshellCommand, ctx);
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
warpify.remove_added_subshell_command(index, ctx) wormhole.remove_added_subshell_command(index, ctx)
}); });
} }
} }
impl Entity for WarpifyPageView { impl Entity for WormholePageView {
type Event = SettingsPageEvent; type Event = SettingsPageEvent;
} }
@@ -272,25 +269,23 @@ fn build_sub_sub_title(title: &str, appearance: &Appearance) -> Container {
const SSH_EXTENSION_DROPDOWN_WIDTH: f32 = 250.; const SSH_EXTENSION_DROPDOWN_WIDTH: f32 = 250.;
impl WarpifyPageView { impl WormholePageView {
fn create_ssh_extension_install_mode_dropdown( fn create_ssh_extension_install_mode_dropdown(
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) -> ViewHandle<Dropdown<WarpifyPageAction>> { ) -> ViewHandle<Dropdown<WormholePageAction>> {
let items: Vec<DropdownItem<WarpifyPageAction>> = SshExtensionInstallMode::iter() let items: Vec<DropdownItem<WormholePageAction>> = SshExtensionInstallMode::iter()
.map(|mode| { .map(|mode| {
DropdownItem::new( DropdownItem::new(
mode.display_name(), mode.display_name(),
WarpifyPageAction::SetSshExtensionInstallMode(mode), WormholePageAction::SetSshExtensionInstallMode(mode),
) )
}) })
.collect(); .collect();
let current_mode = *WarpifySettings::as_ref(ctx) let current_mode = *WormholeSettings::as_ref(ctx)
.ssh_extension_install_mode .ssh_extension_install_mode
.value(); .value();
let enable_ssh_warpification = *WarpifySettings::as_ref(ctx) let enable_ssh_wormholing = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value();
.enable_ssh_warpification
.value();
ctx.add_typed_action_view(move |ctx| { ctx.add_typed_action_view(move |ctx| {
let mut dropdown = Dropdown::new(ctx); let mut dropdown = Dropdown::new(ctx);
@@ -298,10 +293,10 @@ impl WarpifyPageView {
dropdown.set_menu_width(SSH_EXTENSION_DROPDOWN_WIDTH, ctx); dropdown.set_menu_width(SSH_EXTENSION_DROPDOWN_WIDTH, ctx);
dropdown.add_items(items, ctx); dropdown.add_items(items, ctx);
dropdown.set_selected_by_action( dropdown.set_selected_by_action(
WarpifyPageAction::SetSshExtensionInstallMode(current_mode), WormholePageAction::SetSshExtensionInstallMode(current_mode),
ctx, ctx,
); );
if !enable_ssh_warpification { if !enable_ssh_wormholing {
dropdown.set_disabled(ctx); dropdown.set_disabled(ctx);
} }
dropdown dropdown
@@ -352,9 +347,9 @@ impl WarpifyPageView {
} }
} }
impl View for WarpifyPageView { impl View for WormholePageView {
fn ui_name() -> &'static str { fn ui_name() -> &'static str {
"WarpifyPageView" "WormholePageView"
} }
fn render(&self, app: &AppContext) -> Box<dyn Element> { fn render(&self, app: &AppContext) -> Box<dyn Element> {
@@ -363,41 +358,38 @@ impl View for WarpifyPageView {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum WarpifyPageAction { pub enum WormholePageAction {
RemoveAddedCommand(usize), RemoveAddedCommand(usize),
RemoveDenylistedCommand(usize), RemoveDenylistedCommand(usize),
ToggleSshWarpification, ToggleSshWormholing,
/// Toggles whether the legacy SSH wrapper attaches to an existing /// Toggles whether the legacy SSH wrapper attaches to an existing
/// ControlMaster for the destination host instead of creating its own. /// ControlMaster for the destination host instead of creating its own.
ToggleReuseSshControlMaster, ToggleReuseSshControlMaster,
/// Set the SSH extension installation mode (always ask / always install / always skip). /// Set the SSH extension installation mode (always ask / always install / always skip).
SetSshExtensionInstallMode(SshExtensionInstallMode), SetSshExtensionInstallMode(SshExtensionInstallMode),
OpenUrl(String),
} }
impl TypedActionView for WarpifyPageView { impl TypedActionView for WormholePageView {
type Action = WarpifyPageAction; type Action = WormholePageAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
use WarpifyPageAction::*; use WormholePageAction::*;
match action { match action {
RemoveDenylistedCommand(index) => self.remove_denylisted_command(*index, ctx), RemoveDenylistedCommand(index) => self.remove_denylisted_command(*index, ctx),
RemoveAddedCommand(index) => self.remove_added_command(*index, ctx), RemoveAddedCommand(index) => self.remove_added_command(*index, ctx),
ToggleSshWarpification => { ToggleSshWormholing => {
WarpifySettings::handle(ctx).update(ctx, |ssh_settings, ctx| { WormholeSettings::handle(ctx).update(ctx, |ssh_settings, ctx| {
report_if_error!(ssh_settings report_if_error!(ssh_settings
.enable_ssh_warpification .enable_ssh_wormholing
.toggle_and_save_value(ctx)); .toggle_and_save_value(ctx));
send_telemetry_from_ctx!( send_telemetry_from_ctx!(
TelemetryEvent::ToggleSshWarpification { TelemetryEvent::ToggleSshWormholing {
enabled: *ssh_settings.enable_ssh_warpification.value(), enabled: *ssh_settings.enable_ssh_wormholing.value(),
}, },
ctx ctx
); );
}); });
let enabled = *WarpifySettings::as_ref(ctx) let enabled = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value();
.enable_ssh_warpification
.value();
self.ssh_extension_install_mode_dropdown self.ssh_extension_install_mode_dropdown
.update(ctx, |dropdown, ctx| { .update(ctx, |dropdown, ctx| {
if enabled { if enabled {
@@ -425,8 +417,8 @@ impl TypedActionView for WarpifyPageView {
}); });
} }
SetSshExtensionInstallMode(mode) => { SetSshExtensionInstallMode(mode) => {
WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| {
report_if_error!(warpify_settings report_if_error!(wormhole_settings
.ssh_extension_install_mode .ssh_extension_install_mode
.set_value(*mode, ctx)); .set_value(*mode, ctx));
send_telemetry_from_ctx!( send_telemetry_from_ctx!(
@@ -437,16 +429,13 @@ impl TypedActionView for WarpifyPageView {
); );
}); });
} }
OpenUrl(url) => {
ctx.open_url(url.as_str());
}
} }
} }
} }
impl SettingsPageMeta for WarpifyPageView { impl SettingsPageMeta for WormholePageView {
fn section() -> SettingsSection { fn section() -> SettingsSection {
SettingsSection::Warpify SettingsSection::Wormhole
} }
fn should_render(&self, _ctx: &AppContext) -> bool { fn should_render(&self, _ctx: &AppContext) -> bool {
@@ -466,53 +455,39 @@ impl SettingsPageMeta for WarpifyPageView {
} }
} }
impl From<ViewHandle<WarpifyPageView>> for SettingsPageViewHandle { impl From<ViewHandle<WormholePageView>> for SettingsPageViewHandle {
fn from(view_handle: ViewHandle<WarpifyPageView>) -> Self { fn from(view_handle: ViewHandle<WormholePageView>) -> Self {
SettingsPageViewHandle::Warpify(view_handle) SettingsPageViewHandle::Wormhole(view_handle)
} }
} }
#[derive(Default)] #[derive(Default)]
struct TitleWidget { struct TitleWidget;
learn_more_highlight_index: HighlightedHyperlink,
}
impl TitleWidget { impl TitleWidget {
fn render_top_of_page(&self, appearance: &Appearance, _app: &AppContext) -> Box<dyn Element> { fn render_top_of_page(&self, appearance: &Appearance, _app: &AppContext) -> Box<dyn Element> {
let warpify_description = vec![ let wormhole_description = Text::new(
FormattedTextFragment::plain_text( "Configure whether Galaxy attempts to \u{201c}Wormhole\u{201d} supported shells, adding blocks, full text editing, completions, and other Galaxy features."
"Configure whether Galaxy attempts to \u{201c}Wormhole\u{201d} (add support for blocks, \ .to_string(),
input modes, etc) certain shells. ", appearance.ui_font_family(),
),
FormattedTextFragment::hyperlink(
"Learn more",
"https://docs.warp.dev/terminal/warpify/subshells",
),
];
let warpify_description = FormattedTextElement::new(
FormattedText::new([FormattedTextLine::Line(warpify_description)]),
CONTENT_FONT_SIZE, CONTENT_FONT_SIZE,
appearance.ui_font_family(),
appearance.ui_font_family(),
blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()),
self.learn_more_highlight_index.clone(),
) )
.with_hyperlink_font_color(appearance.theme().accent().into_solid()) .soft_wrap(true)
.register_default_click_handlers(|url, _, ctx| { .with_color(blended_colors::text_sub(
ctx.open_url(&url.url); appearance.theme(),
}) appearance.theme().surface_1(),
))
.finish(); .finish();
Flex::column() Flex::column()
.with_child(render_page_title("Wormhole", HEADER_FONT_SIZE, appearance)) .with_child(render_page_title("Wormhole", HEADER_FONT_SIZE, appearance))
.with_child(warpify_description) .with_child(wormhole_description)
.finish() .finish()
} }
} }
impl SettingsWidget for TitleWidget { impl SettingsWidget for TitleWidget {
type View = WarpifyPageView; type View = WormholePageView;
fn search_terms(&self) -> &str { fn search_terms(&self) -> &str {
"ssh subshell galaxify session" "ssh subshell galaxify session"
@@ -536,20 +511,20 @@ struct SubshellsWidget {}
impl SubshellsWidget { impl SubshellsWidget {
fn render_subshells_section( fn render_subshells_section(
&self, &self,
view: &WarpifyPageView, view: &WormholePageView,
appearance: &Appearance, appearance: &Appearance,
app: &AppContext, app: &AppContext,
) -> Box<dyn Element> { ) -> Box<dyn Element> {
let mut column = Flex::column(); let mut column = Flex::column();
let warpify_settings = WarpifySettings::as_ref(app); let wormhole_settings = WormholeSettings::as_ref(app);
column.add_child( column.add_child(
view.build_input_list( view.build_input_list(
"Added commands", "Added commands",
&warpify_settings.added_subshell_commands, &wormhole_settings.added_subshell_commands,
&view.remove_added_command_button_states, &view.remove_added_command_button_states,
WarpifyPageAction::RemoveAddedCommand, WormholePageAction::RemoveAddedCommand,
&view.add_added_commands_editor, &view.add_added_commands_editor,
appearance, appearance,
) )
@@ -559,9 +534,9 @@ impl SubshellsWidget {
column.add_child( column.add_child(
view.build_input_list( view.build_input_list(
"Denylisted commands", "Denylisted commands",
&warpify_settings.subshell_command_denylist, &wormhole_settings.subshell_command_denylist,
&view.remove_denylisted_command_button_states, &view.remove_denylisted_command_button_states,
WarpifyPageAction::RemoveDenylistedCommand, WormholePageAction::RemoveDenylistedCommand,
&view.add_denylisted_commands_editor, &view.add_denylisted_commands_editor,
appearance, appearance,
) )
@@ -574,7 +549,7 @@ impl SubshellsWidget {
} }
impl SettingsWidget for SubshellsWidget { impl SettingsWidget for SubshellsWidget {
type View = WarpifyPageView; type View = WormholePageView;
fn search_terms(&self) -> &str { fn search_terms(&self) -> &str {
"galaxify subshell" "galaxify subshell"
@@ -594,13 +569,13 @@ impl SettingsWidget for SubshellsWidget {
#[derive(Default)] #[derive(Default)]
struct SSHWidget { struct SSHWidget {
enable_ssh_warpification_switch_state: SwitchStateHandle, enable_ssh_wormholing_switch_state: SwitchStateHandle,
reuse_control_master_switch_state: SwitchStateHandle, reuse_control_master_switch_state: SwitchStateHandle,
local_only_icon_tooltip_states: RefCell<HashMap<String, MouseStateHandle>>, local_only_icon_tooltip_states: RefCell<HashMap<String, MouseStateHandle>>,
} }
impl SettingsWidget for SSHWidget { impl SettingsWidget for SSHWidget {
type View = WarpifyPageView; type View = WormholePageView;
fn search_terms(&self) -> &str { fn search_terms(&self) -> &str {
"galaxify ssh" "galaxify ssh"
@@ -618,31 +593,29 @@ impl SettingsWidget for SSHWidget {
.theme() .theme()
.sub_text_color(appearance.theme().surface_2()); .sub_text_color(appearance.theme().surface_2());
let enable_ssh_warpification = *WarpifySettings::as_ref(app) let enable_ssh_wormholing = *WormholeSettings::as_ref(app).enable_ssh_wormholing.value();
.enable_ssh_warpification
.value();
add_setting( add_setting(
&mut column, &mut column,
&WarpifySettings::as_ref(app).enable_ssh_warpification, &WormholeSettings::as_ref(app).enable_ssh_wormholing,
move || { move || {
render_body_item::<WarpifyPageAction>( render_body_item::<WormholePageAction>(
"Wormhole SSH Sessions".into(), "Wormhole SSH Sessions".into(),
None, None,
LocalOnlyIconState::for_setting( LocalOnlyIconState::for_setting(
EnableSshWarpification::storage_key(), EnableSshWormholing::storage_key(),
EnableSshWarpification::sync_to_cloud(), EnableSshWormholing::sync_to_cloud(),
&mut self.local_only_icon_tooltip_states.borrow_mut(), &mut self.local_only_icon_tooltip_states.borrow_mut(),
app, app,
), ),
ToggleState::Enabled, ToggleState::Enabled,
appearance, appearance,
ui_builder ui_builder
.switch(self.enable_ssh_warpification_switch_state.clone()) .switch(self.enable_ssh_wormholing_switch_state.clone())
.check(enable_ssh_warpification) .check(enable_ssh_wormholing)
.build() .build()
.on_click(move |ctx, _, _| { .on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(WarpifyPageAction::ToggleSshWarpification); ctx.dispatch_typed_action(WormholePageAction::ToggleSshWormholing);
}) })
.finish(), .finish(),
None, None,
@@ -651,18 +624,18 @@ impl SettingsWidget for SSHWidget {
); );
if FeatureFlag::SshRemoteServer.is_enabled() { if FeatureFlag::SshRemoteServer.is_enabled() {
let label_color_override = if !enable_ssh_warpification { let label_color_override = if !enable_ssh_wormholing {
Some(appearance.theme().disabled_ui_text_color()) Some(appearance.theme().disabled_ui_text_color())
} else { } else {
None None
}; };
add_setting( add_setting(
&mut column, &mut column,
&WarpifySettings::as_ref(app).ssh_extension_install_mode, &WormholeSettings::as_ref(app).ssh_extension_install_mode,
move || { move || {
Container::new(render_dropdown_item( Container::new(render_dropdown_item(
appearance, appearance,
"Install SSH extension", "Install Wormhole helper",
Some(SSH_EXTENSION_INSTALL_MODE_DESCRIPTION), Some(SSH_EXTENSION_INSTALL_MODE_DESCRIPTION),
None, None,
LocalOnlyIconState::for_setting( LocalOnlyIconState::for_setting(
@@ -688,7 +661,7 @@ impl SettingsWidget for SSHWidget {
&SshSettings::as_ref(app).reuse_existing_control_master, &SshSettings::as_ref(app).reuse_existing_control_master,
move || { move || {
let mut column = Flex::column(); let mut column = Flex::column();
column.add_child(render_body_item::<WarpifyPageAction>( column.add_child(render_body_item::<WormholePageAction>(
"Reuse existing SSH ControlMaster".into(), "Reuse existing SSH ControlMaster".into(),
None, None,
LocalOnlyIconState::for_setting( LocalOnlyIconState::for_setting(
@@ -697,19 +670,19 @@ impl SettingsWidget for SSHWidget {
&mut self.local_only_icon_tooltip_states.borrow_mut(), &mut self.local_only_icon_tooltip_states.borrow_mut(),
app, app,
), ),
enable_ssh_warpification.into(), enable_ssh_wormholing.into(),
appearance, appearance,
ui_builder ui_builder
.switch(self.reuse_control_master_switch_state.clone()) .switch(self.reuse_control_master_switch_state.clone())
.check(reuse_existing_control_master) .check(reuse_existing_control_master)
.with_disabled(!enable_ssh_warpification) .with_disabled(!enable_ssh_wormholing)
.build() .build()
.on_click(move |ctx, _, _| { .on_click(move |ctx, _, _| {
if !enable_ssh_warpification { if !enable_ssh_wormholing {
return; return;
} }
ctx.dispatch_typed_action( ctx.dispatch_typed_action(
WarpifyPageAction::ToggleReuseSshControlMaster, WormholePageAction::ToggleReuseSshControlMaster,
); );
}) })
.finish(), .finish(),
+2 -2
View File
@@ -58,7 +58,7 @@ use super::view::{
BlocklistAIRenderContext, InlineBannerId, RichContentMetadata, SeparatorId, BlocklistAIRenderContext, InlineBannerId, RichContentMetadata, SeparatorId,
SharedSessionBanners, TerminalEditor, TerminalViewRenderContext, BLOCK_BANNER_HEIGHT, SharedSessionBanners, TerminalEditor, TerminalViewRenderContext, BLOCK_BANNER_HEIGHT,
}; };
use super::warpify::render::{draw_flag_pole, render_subshell_flag}; use super::wormhole::render::{draw_flag_pole, render_subshell_flag};
use super::{heights_approx_eq, TerminalModel, HEIGHT_FUDGE_FACTOR_LINES}; use super::{heights_approx_eq, TerminalModel, HEIGHT_FUDGE_FACTOR_LINES};
use crate::ai::blocklist::agent_view::{agent_view_bg_fill, AgentViewState}; use crate::ai::blocklist::agent_view::{agent_view_bg_fill, AgentViewState};
use crate::ai::blocklist::{ai_brand_color, ATTACH_AS_AGENT_MODE_CONTEXT_TEXT}; use crate::ai::blocklist::{ai_brand_color, ATTACH_AS_AGENT_MODE_CONTEXT_TEXT};
@@ -86,7 +86,7 @@ use crate::terminal::model::selection::{SelectAction, SelectionPoint};
use crate::terminal::model::terminal_model::BlockIndex; use crate::terminal::model::terminal_model::BlockIndex;
use crate::terminal::safe_mode_settings::get_secret_obfuscation_mode; use crate::terminal::safe_mode_settings::get_secret_obfuscation_mode;
use crate::terminal::view::TerminalAction; use crate::terminal::view::TerminalAction;
use crate::terminal::warpify::SubshellSource; use crate::terminal::wormhole::SubshellSource;
use crate::terminal::{grid_renderer, SizeInfo}; use crate::terminal::{grid_renderer, SizeInfo};
use crate::themes::theme::{Fill, WarpTheme}; use crate::themes::theme::{Fill, WarpTheme};
use crate::ui_components::{self, icons as UIIcon}; use crate::ui_components::{self, icons as UIIcon};
+2 -2
View File
@@ -10,7 +10,7 @@ use rand::Rng;
#[cfg(feature = "local_fs")] #[cfg(feature = "local_fs")]
use super::{ use super::{
model::session::{BootstrapSessionType, SessionInfo}, model::session::{BootstrapSessionType, SessionInfo},
warpify::settings::{PIPENV_SUBSHELL_COMMAND_REGEX, POETRY_SUBSHELL_COMMAND_REGEX}, wormhole::settings::{PIPENV_SUBSHELL_COMMAND_REGEX, POETRY_SUBSHELL_COMMAND_REGEX},
}; };
use crate::env_vars::{EnvVar, EnvVarExt}; use crate::env_vars::{EnvVar, EnvVarExt};
use crate::terminal::session_settings::SessionSettings; use crate::terminal::session_settings::SessionSettings;
@@ -99,7 +99,7 @@ pub fn should_use_rc_file_bootstrap_method(
&& shell_type == ShellType::Zsh) && shell_type == ShellType::Zsh)
|| is_msys2 || is_msys2
} }
BootstrapSessionType::WarpifiedRemote => false, BootstrapSessionType::WormholedRemote => false,
} }
} }
@@ -68,7 +68,7 @@ pub struct CLIAgentEvent {
const VERSIONED_PARSERS: &[EventParser] = &[v1::parse]; const VERSIONED_PARSERS: &[EventParser] = &[v1::parse];
/// The current CLI agent protocol version this build of Warp supports. /// The current CLI agent protocol version this build of Warp supports.
/// Exported as the `WARP_CLI_AGENT_PROTOCOL_VERSION` env var on the PTY /// Exported as the `GALAXY_CLI_AGENT_PROTOCOL_VERSION` env var on the PTY
/// so plugins can negotiate a compatible payload format. /// so plugins can negotiate a compatible payload format.
#[cfg_attr(not(feature = "local_tty"), allow(dead_code))] #[cfg_attr(not(feature = "local_tty"), allow(dead_code))]
pub const fn current_protocol_version() -> u32 { pub const fn current_protocol_version() -> u32 {
+1 -1
View File
@@ -130,7 +130,7 @@ pub struct CLIAgentSession {
/// `None` if the plugin predates version reporting or Codex is using OSC9 fallback. /// `None` if the plugin predates version reporting or Codex is using OSC9 fallback.
pub plugin_version: Option<String>, pub plugin_version: Option<String>,
/// `None` when the session is local. /// `None` when the session is local.
/// `Some("user@hostname")` when running over SSH (warpified or legacy). /// `Some("user@hostname")` when running over SSH (wormholed or legacy).
/// Used as a key for per-host plugin install failure tracking. /// Used as a key for per-host plugin install failure tracking.
pub remote_host: Option<String>, pub remote_host: Option<String>,
/// Draft text saved from the rich input composer when it was closed. /// Draft text saved from the rich input composer when it was closed.
+3 -3
View File
@@ -167,9 +167,9 @@ pub enum TerminalMode {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SshLoginStatus { pub enum SshLoginStatus {
/// We have some evidence login is complete but should check again. /// We have some evidence login is complete but should check again.
RecheckBeforeWarpifying, RecheckBeforeWormholing,
/// We have high confidence login is complete. /// We have high confidence login is complete.
ReadyToWarpify, ReadyToWormhole,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -247,7 +247,7 @@ pub enum BlockType {
/// This is a block containing background process output. /// This is a block containing background process output.
Background(Arc<SerializedBlock>), Background(Arc<SerializedBlock>),
/// This is a block containing static/hardcoded content (e.g. the subshell Warpification /// This is a block containing static/hardcoded content (e.g. the subshell Wormholing
/// welcome block). /// welcome block).
Static, Static,
} }
+2 -2
View File
@@ -505,7 +505,7 @@ fn test_multiple_machines() {
SessionInfo::new_for_test() SessionInfo::new_for_test()
.with_id(0) .with_id(0)
.with_shell_type(ShellType::Zsh) .with_shell_type(ShellType::Zsh)
.with_session_type(BootstrapSessionType::WarpifiedRemote) .with_session_type(BootstrapSessionType::WormholedRemote)
.with_hostname("prod".to_string()) .with_hostname("prod".to_string())
.with_user("user".to_string()) .with_user("user".to_string())
.with_ssh_socket_path(PathBuf::from("~/.ssh/12345")) .with_ssh_socket_path(PathBuf::from("~/.ssh/12345"))
@@ -517,7 +517,7 @@ fn test_multiple_machines() {
SessionInfo::new_for_test() SessionInfo::new_for_test()
.with_id(1) .with_id(1)
.with_shell_type(ShellType::Zsh) .with_shell_type(ShellType::Zsh)
.with_session_type(BootstrapSessionType::WarpifiedRemote) .with_session_type(BootstrapSessionType::WormholedRemote)
.with_hostname("dev".to_string()) .with_hostname("dev".to_string())
.with_user("user2".to_string()) .with_user("user2".to_string())
.with_ssh_socket_path(PathBuf::from("~/.ssh/12345")) .with_ssh_socket_path(PathBuf::from("~/.ssh/12345"))
+3 -3
View File
@@ -141,7 +141,7 @@ use super::view::queued_prompts_panel::{QueuedPromptsPanelEvent, QueuedPromptsPa
use super::view::{ use super::view::{
ExecuteCommandEvent, SyncInputType, TerminalAction, PADDING_LEFT as TERMINAL_VIEW_PADDING_LEFT, ExecuteCommandEvent, SyncInputType, TerminalAction, PADDING_LEFT as TERMINAL_VIEW_PADDING_LEFT,
}; };
use super::warpify::SubshellSource; use super::wormhole::SubshellSource;
use super::{prompt, History, HistoryEntry, SizeInfo, TerminalModel, UpArrowHistoryConfig}; use super::{prompt, History, HistoryEntry, SizeInfo, TerminalModel, UpArrowHistoryConfig};
use crate::ai::agent::conversation::AIConversationId; use crate::ai::agent::conversation::AIConversationId;
use crate::ai::agent::{ use crate::ai::agent::{
@@ -11829,13 +11829,13 @@ impl Input {
// CLI agent rich input in shell mode (! prefix) should allow completions // CLI agent rich input in shell mode (! prefix) should allow completions
// even though the active block is a long-running command. // even though the active block is a long-running command.
// However, completions are disabled on warpified remote hosts because // However, completions are disabled on wormholed remote hosts because
// in-band generators don't work in this context (with CLI agent). // in-band generators don't work in this context (with CLI agent).
let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx) let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx)
&& CLIAgentSessionsModel::as_ref(ctx).is_input_open(self.terminal_view_id) && CLIAgentSessionsModel::as_ref(ctx).is_input_open(self.terminal_view_id)
&& !self && !self
.active_session(ctx) .active_session(ctx)
.is_some_and(|s| matches!(s.session_type(), SessionType::WarpifiedRemote { .. })); .is_some_and(|s| matches!(s.session_type(), SessionType::WormholedRemote { .. }));
// If the cursor is in a valid completion position, go into CompletionSuggestions mode // If the cursor is in a valid completion position, go into CompletionSuggestions mode
if (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) { if (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) {
+1 -1
View File
@@ -23,7 +23,7 @@ use crate::terminal::input::common::{
use crate::terminal::input::{get_input_box_top_border_width, InputDropTargetData}; use crate::terminal::input::{get_input_box_top_border_width, InputDropTargetData};
use crate::terminal::settings::{SpacingMode, TerminalSettings}; use crate::terminal::settings::{SpacingMode, TerminalSettings};
use crate::terminal::view::TerminalAction; use crate::terminal::view::TerminalAction;
use crate::terminal::warpify::render::{render_subshell_flag, render_subshell_flag_pole}; use crate::terminal::wormhole::render::{render_subshell_flag, render_subshell_flag_pole};
impl Input { impl Input {
/// Renders the classic input. This is used when the user has 'Honor PS1' enabled in settings, /// Renders the classic input. This is used when the user has 'Honor PS1' enabled in settings,
+2 -2
View File
@@ -31,8 +31,8 @@ pub struct LineEditorStatus {
/// ///
/// When receiving an end prompt marker in zsh, this is used as a proxy to determine if the /// When receiving an end prompt marker in zsh, this is used as a proxy to determine if the
/// session is bootstrapped -- the prompt markers are emitted by zsh regardless of whether or /// session is bootstrapped -- the prompt markers are emitted by zsh regardless of whether or
/// not its a Warpified session, so to in order properly signal downstream that the line editor /// not its a Wormholed session, so to in order properly signal downstream that the line editor
/// (for Warpified sessions) is active, we must check if there was a corresponding precmd /// (for Wormholed sessions) is active, we must check if there was a corresponding precmd
/// emitted prior to the end prompt marker. /// emitted prior to the end prompt marker.
/// ///
/// Precmd is always emitted before prompt markers. /// Precmd is always emitted before prompt markers.
@@ -53,7 +53,7 @@ use crate::terminal::session_settings::{SessionSettings, ToolbarChipSelection};
use crate::terminal::shared_session::sharer::network::Network; use crate::terminal::shared_session::sharer::network::Network;
use crate::terminal::shared_session::{IsSharedSessionCreator, SharedSessionStatus}; use crate::terminal::shared_session::{IsSharedSessionCreator, SharedSessionStatus};
use crate::terminal::shell::ShellName; use crate::terminal::shell::ShellName;
use crate::terminal::warpify::settings::WarpifySettings; use crate::terminal::wormhole::settings::WormholeSettings;
use crate::terminal::writeable_pty::pty_controller::{EventLoopSendError, EventLoopSender}; use crate::terminal::writeable_pty::pty_controller::{EventLoopSendError, EventLoopSender};
use crate::terminal::writeable_pty::terminal_manager_util::{ use crate::terminal::writeable_pty::terminal_manager_util::{
init_pty_controller_model, init_remote_server_controller, wire_up_pty_controller_with_surface, init_pty_controller_model, init_remote_server_controller, wire_up_pty_controller_with_surface,
@@ -740,13 +740,11 @@ impl<S> TerminalManager<S> {
.contains(&ContextChipKind::NodeVersion) .contains(&ContextChipKind::NodeVersion)
}; };
// `enable_ssh_warpification` is the single source of truth for whether the SSH // `enable_ssh_wormholing` is the single source of truth for whether the SSH
// wrapper is active. The bootstrap scripts check `WARP_USE_SSH_WRAPPER` (derived // wrapper is active. The bootstrap scripts check `GALAXY_USE_SSH_WRAPPER` (derived
// from this value) before invoking `warp_ssh_helper`, which spawns the ControlMaster // from this value) before invoking `warp_ssh_helper`, which spawns the ControlMaster
// and opens agent-protocol channels. // and opens agent-protocol channels.
let enable_ssh_wrapper = *WarpifySettings::as_ref(ctx) let enable_ssh_wrapper = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value();
.enable_ssh_warpification
.value();
// Only meaningful when the legacy ControlMaster wrapper is active. // Only meaningful when the legacy ControlMaster wrapper is active.
let reuse_ssh_control_master = enable_ssh_wrapper let reuse_ssh_control_master = enable_ssh_wrapper
+4 -4
View File
@@ -306,7 +306,7 @@ fn build_host_shell_command(
// Whether the SSH wrapper should attach to an existing ControlMaster // Whether the SSH wrapper should attach to an existing ControlMaster
// for the destination host instead of always creating its own. // for the destination host instead of always creating its own.
builder.env( builder.env(
"WARP_SSH_REUSE_CONTROL_MASTER", "GALAXY_SSH_REUSE_CONTROL_MASTER",
if reuse_ssh_control_master { "1" } else { "0" }, if reuse_ssh_control_master { "1" } else { "0" },
); );
@@ -784,8 +784,8 @@ fn build_docker_sandbox_command(
// TODO(advait): audit this list. It currently mirrors what the // TODO(advait): audit this list. It currently mirrors what the
// pre-refactor host-shell `spawn` set when the starter happened to // pre-refactor host-shell `spawn` set when the starter happened to
// be a Docker sandbox, so behaviour is unchanged from before the // be a Docker sandbox, so behaviour is unchanged from before the
// split. Many of these (e.g. `WARP_USE_SSH_WRAPPER`, // split. Many of these (e.g. `GALAXY_USE_SSH_WRAPPER`,
// `SSH_SOCKET_DIR`, `HISTFILESIZE`, `WARP_IS_LOCAL_SHELL_SESSION`) // `SSH_SOCKET_DIR`, `HISTFILESIZE`, `GALAXY_IS_LOCAL_SHELL_SESSION`)
// are set on the *host* `sbx` process and may or may not propagate // are set on the *host* `sbx` process and may or may not propagate
// into the container depending on `sbx`'s env passthrough rules. // into the container depending on `sbx`'s env passthrough rules.
// Once we've validated what the container bootstrap actually needs, // Once we've validated what the container bootstrap actually needs,
@@ -813,7 +813,7 @@ fn build_docker_sandbox_command(
if enable_ssh_wrapper { "1" } else { "0" }, if enable_ssh_wrapper { "1" } else { "0" },
); );
builder.env( builder.env(
"WARP_SSH_REUSE_CONTROL_MASTER", "GALAXY_SSH_REUSE_CONTROL_MASTER",
if reuse_ssh_control_master { "1" } else { "0" }, if reuse_ssh_control_master { "1" } else { "0" },
); );
builder.env("SSH_SOCKET_DIR", ssh_socket_dir()); builder.env("SSH_SOCKET_DIR", ssh_socket_dir());
@@ -18,15 +18,15 @@ use crate::terminal::local_tty::PtyOptions;
const HONOR_PS1_NAME: &str = "WARP_HONOR_PS1"; const HONOR_PS1_NAME: &str = "WARP_HONOR_PS1";
const PROMPT_NODE_VERSION_ENABLED_NAME: &str = "WARP_PROMPT_NODE_VERSION_ENABLED"; const PROMPT_NODE_VERSION_ENABLED_NAME: &str = "WARP_PROMPT_NODE_VERSION_ENABLED";
const INITIAL_WORKING_DIR_NAME: &str = "WARP_INITIAL_WORKING_DIR"; const INITIAL_WORKING_DIR_NAME: &str = "WARP_INITIAL_WORKING_DIR";
const USE_SSH_WRAPPER_NAME: &str = "WARP_USE_SSH_WRAPPER"; const USE_SSH_WRAPPER_NAME: &str = "GALAXY_USE_SSH_WRAPPER";
const SSH_REUSE_CONTROL_MASTER_NAME: &str = "WARP_SSH_REUSE_CONTROL_MASTER"; const SSH_REUSE_CONTROL_MASTER_NAME: &str = "GALAXY_SSH_REUSE_CONTROL_MASTER";
const SHELL_DEBUG_MODE_NAME: &str = "WARP_SHELL_DEBUG_MODE"; const SHELL_DEBUG_MODE_NAME: &str = "WARP_SHELL_DEBUG_MODE";
const TERM_PROGRAM_NAME: &str = "TERM_PROGRAM"; const TERM_PROGRAM_NAME: &str = "TERM_PROGRAM";
const IS_LOCAL_SESSION_NAME: &str = "WARP_IS_LOCAL_SHELL_SESSION"; const IS_LOCAL_SESSION_NAME: &str = "GALAXY_IS_LOCAL_SHELL_SESSION";
const SSH_SOCKET_DIR: &str = "SSH_SOCKET_DIR"; const SSH_SOCKET_DIR: &str = "SSH_SOCKET_DIR";
const PATH_APPEND_NAME: &str = "WARP_PATH_APPEND"; const PATH_APPEND_NAME: &str = "WARP_PATH_APPEND";
const CLIENT_VERSION_NAME: &str = "WARP_CLIENT_VERSION"; const CLIENT_VERSION_NAME: &str = "GALAXY_CLIENT_VERSION";
const CLI_AGENT_PROTOCOL_VERSION_NAME: &str = "WARP_CLI_AGENT_PROTOCOL_VERSION"; const CLI_AGENT_PROTOCOL_VERSION_NAME: &str = "GALAXY_CLI_AGENT_PROTOCOL_VERSION";
const WSLENV: &str = "WSLENV"; const WSLENV: &str = "WSLENV";
const HISTIGNORE: &str = "HISTIGNORE"; const HISTIGNORE: &str = "HISTIGNORE";
+1 -1
View File
@@ -79,8 +79,8 @@ pub mod ssh;
pub mod terminal_manager; pub mod terminal_manager;
mod terminal_size_element; mod terminal_size_element;
pub mod view; pub mod view;
pub mod warpify;
mod waterfall_gap_element; mod waterfall_gap_element;
pub mod wormhole;
mod writeable_pty; mod writeable_pty;
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
pub use writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface}; pub use writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface};
+10 -9
View File
@@ -22,7 +22,7 @@ use crate::terminal::model::block::BlockSection;
use crate::terminal::model::index::{Direction, Point, Side}; use crate::terminal::model::index::{Direction, Point, Side};
use crate::terminal::model::selection::{ExpandedSelectionRange, Selection, SelectionDirection}; use crate::terminal::model::selection::{ExpandedSelectionRange, Selection, SelectionDirection};
use crate::terminal::model::terminal_model::{BlockIndex, WithinBlock}; use crate::terminal::model::terminal_model::{BlockIndex, WithinBlock};
use crate::terminal::warpify::success_block::WarpifySuccessBlock; use crate::terminal::wormhole::success_block::WormholeSuccessBlock;
use crate::terminal::GridType; use crate::terminal::GridType;
/// A selection that can span multiple blocks (and thus grids). Here row is the number of lines from /// A selection that can span multiple blocks (and thus grids). Here row is the number of lines from
@@ -998,12 +998,13 @@ impl BlockList {
} }
if let Some(active_window_id) = app.windows().active_window() { if let Some(active_window_id) = app.windows().active_window() {
if let Some(ssh_block) = app if let Some(ssh_block) = app.view_with_id::<WormholeSuccessBlock>(
.view_with_id::<WarpifySuccessBlock>(active_window_id, *view_id) active_window_id,
{ *view_id,
let warpify_success_block = app.view(&ssh_block); ) {
let wormhole_success_block = app.view(&ssh_block);
if let Some(selected_text) = if let Some(selected_text) =
warpify_success_block.selected_text() wormhole_success_block.selected_text()
{ {
selected_texts.push(selected_text); selected_texts.push(selected_text);
} }
@@ -1123,10 +1124,10 @@ impl BlockList {
} }
if let Some(ssh_block) = if let Some(ssh_block) =
app.view_with_id::<WarpifySuccessBlock>(active_window_id, view_id) app.view_with_id::<WormholeSuccessBlock>(active_window_id, view_id)
{ {
let warpify_success_block = app.view(&ssh_block); let wormhole_success_block = app.view(&ssh_block);
if let Some(selected_text) = warpify_success_block.selected_text() { if let Some(selected_text) = wormhole_success_block.selected_text() {
selected_texts.push(selected_text); selected_texts.push(selected_text);
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@
pub enum RichContentType { pub enum RichContentType {
AIBlock, AIBlock,
EnterAgentView, EnterAgentView,
WarpifySuccessBlock, WormholeSuccessBlock,
InlineAgentViewHeader, InlineAgentViewHeader,
AgentViewZeroState, AgentViewZeroState,
TerminalViewZeroState, TerminalViewZeroState,
+35 -35
View File
@@ -40,7 +40,7 @@ use crate::remote_server::manager::{RemoteServerManager, RemoteServerManagerEven
use crate::server::telemetry::{BootstrappingInfo, TelemetryEvent}; use crate::server::telemetry::{BootstrappingInfo, TelemetryEvent};
use crate::terminal::event::{ExecutedExecutorCommandEvent, RemoteServerSetupState}; use crate::terminal::event::{ExecutedExecutorCommandEvent, RemoteServerSetupState};
use crate::terminal::shell::{Shell, ShellType}; use crate::terminal::shell::{Shell, ShellType};
use crate::terminal::warpify::SubshellSource; use crate::terminal::wormhole::SubshellSource;
use crate::terminal::{History, ShellHost, ShellLaunchData}; use crate::terminal::{History, ShellHost, ShellLaunchData};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@@ -361,7 +361,7 @@ impl Sessions {
let session = Arc::new(session); let session = Arc::new(session);
self.sessions.insert(session.id(), session.clone()); self.sessions.insert(session.id(), session.clone());
// For warpified-remote sessions, pick up the current host_id from // For wormholed-remote sessions, pick up the current host_id from
// the manager so session.remote_host_id() is populated without // the manager so session.remote_host_id() is populated without
// waiting for the next SessionConnected event. The // waiting for the next SessionConnected event. The
// RemoteServerCommandExecutor already has its client baked in, so // RemoteServerCommandExecutor already has its client baked in, so
@@ -370,7 +370,7 @@ impl Sessions {
if FeatureFlag::SshRemoteServer.is_enabled() if FeatureFlag::SshRemoteServer.is_enabled()
&& matches!( && matches!(
session_info.session_type, session_info.session_type,
BootstrapSessionType::WarpifiedRemote BootstrapSessionType::WormholedRemote
) )
{ {
if let Some(host_id) = RemoteServerManager::as_ref(ctx).host_id_for_session(session_id) if let Some(host_id) = RemoteServerManager::as_ref(ctx).host_id_for_session(session_id)
@@ -518,7 +518,7 @@ impl Sessions {
impl From<SessionType> for command_corrections::SessionType { impl From<SessionType> for command_corrections::SessionType {
fn from(session_type: SessionType) -> Self { fn from(session_type: SessionType) -> Self {
match session_type { match session_type {
SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote, SessionType::WormholedRemote { .. } => command_corrections::SessionType::Remote,
SessionType::Local => command_corrections::SessionType::Local, SessionType::Local => command_corrections::SessionType::Local,
} }
} }
@@ -527,20 +527,20 @@ impl From<SessionType> for command_corrections::SessionType {
impl From<&SessionType> for command_corrections::SessionType { impl From<&SessionType> for command_corrections::SessionType {
fn from(session_type: &SessionType) -> Self { fn from(session_type: &SessionType) -> Self {
match session_type { match session_type {
SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote, SessionType::WormholedRemote { .. } => command_corrections::SessionType::Remote,
SessionType::Local => command_corrections::SessionType::Local, SessionType::Local => command_corrections::SessionType::Local,
} }
} }
} }
/// Whether a session was established by Warp's in-band SSH wrapper — the shell function our /// Whether a session was established by Galaxy's in-band SSH wrapper — the shell function our
/// bootstrap injects that intercepts `ssh`, sets up a ControlMaster connection, and bootstraps /// bootstrap injects that intercepts `ssh`, sets up a ControlMaster connection, and bootstraps
/// the remote shell. This applies to all SSH warpification today: the remote-server SSH /// the remote shell. This applies to all SSH wormholing today: the remote-server SSH
/// extension also runs on top of a wrapper session (reusing the ControlMaster socket for its /// extension also runs on top of a wrapper session (reusing the ControlMaster socket for its
/// proxy and for the `RemoteCommandExecutor` fallback). /// proxy and for the `RemoteCommandExecutor` fallback).
/// ///
/// `No` covers local sessions, subshells, and remote sessions warpified *without* the wrapper /// `No` covers local sessions, subshells, and remote sessions wormholed *without* the wrapper
/// (e.g. via the auto-warpify RC snippet inside an unwrapped `ssh` session), which carry no /// (e.g. via the auto-wormhole RC snippet inside an unwrapped `ssh` session), which carry no
/// ControlMaster socket. /// ControlMaster socket.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum IsSSHWrapperSession { pub enum IsSSHWrapperSession {
@@ -550,7 +550,7 @@ pub enum IsSSHWrapperSession {
socket_path: PathBuf, socket_path: PathBuf,
/// `true` when `socket_path` points at a ControlMaster the user /// `true` when `socket_path` points at a ControlMaster the user
/// already had running (the SSH wrapper attached to it instead of /// already had running (the SSH wrapper attached to it instead of
/// creating a Warp-owned one). Warp must not tear down such a /// creating a Galaxy-owned one). Galaxy must not tear down such a
/// master on session exit. /// master on session exit.
external_control_master: bool, external_control_master: bool,
}, },
@@ -651,7 +651,7 @@ impl SessionInfo {
matches!(&is_ssh_wrapper_session, IsSSHWrapperSession::Yes { .. }), matches!(&is_ssh_wrapper_session, IsSSHWrapperSession::Yes { .. }),
); );
let spawning_session_id = if matches!(session_type, BootstrapSessionType::WarpifiedRemote) let spawning_session_id = if matches!(session_type, BootstrapSessionType::WormholedRemote)
|| subshell_info.is_some() || subshell_info.is_some()
{ {
active_block_session_id active_block_session_id
@@ -699,7 +699,7 @@ impl SessionInfo {
{ {
BootstrapSessionType::Local BootstrapSessionType::Local
} else { } else {
BootstrapSessionType::WarpifiedRemote BootstrapSessionType::WormholedRemote
} }
} }
Err(e) => { Err(e) => {
@@ -715,7 +715,7 @@ impl SessionInfo {
_is_ssh_session: bool, _is_ssh_session: bool,
) -> BootstrapSessionType { ) -> BootstrapSessionType {
// When the `remote_tty` feature is enabled--the session is always considered remote. // When the `remote_tty` feature is enabled--the session is always considered remote.
BootstrapSessionType::WarpifiedRemote BootstrapSessionType::WormholedRemote
} }
/// Returns a fully populated [`SessionInfo`] containing data derived from the given /// Returns a fully populated [`SessionInfo`] containing data derived from the given
@@ -859,26 +859,26 @@ impl SessionInfo {
/// which happens *after* the session is bootstrapped. /// which happens *after* the session is bootstrapped.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum BootstrapSessionType { pub enum BootstrapSessionType {
/// The session host is the same host where Warp is running. /// The session host is the same host where Galaxy is running.
Local, Local,
/// The session host is a different host from where Warp is running. /// The session host is a different host from where Galaxy is running.
WarpifiedRemote, WormholedRemote,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum SessionType { pub enum SessionType {
/// The session host is the same host where Warp is running. /// The session host is the same host where Galaxy is running.
Local, Local,
/// The session host is a different host from where Warp is running. /// The session host is a different host from where Galaxy is running.
/// Note that we only know this for sure when we Warpify a block. /// Note that we only know this for sure when we Wormhole a block.
/// ///
/// `host_id` is `Some` when the remote server feature flag is enabled and /// `host_id` is `Some` when the remote server feature flag is enabled and
/// `RemoteServerManager` has completed the connection handshake. It is /// `RemoteServerManager` has completed the connection handshake. It is
/// `None` when the feature flag is off or the connection hasn't been /// `None` when the feature flag is off or the connection hasn't been
/// established yet. /// established yet.
WarpifiedRemote { WormholedRemote {
host_id: Option<galaxy_core::HostId>, host_id: Option<galaxy_core::HostId>,
}, },
} }
@@ -887,7 +887,7 @@ impl From<BootstrapSessionType> for SessionType {
fn from(bst: BootstrapSessionType) -> Self { fn from(bst: BootstrapSessionType) -> Self {
match bst { match bst {
BootstrapSessionType::Local => SessionType::Local, BootstrapSessionType::Local => SessionType::Local,
BootstrapSessionType::WarpifiedRemote => SessionType::WarpifiedRemote { host_id: None }, BootstrapSessionType::WormholedRemote => SessionType::WormholedRemote { host_id: None },
} }
} }
} }
@@ -964,11 +964,11 @@ impl Session {
self.session_type.lock().clone() self.session_type.lock().clone()
} }
/// Updates the `host_id` on a `WarpifiedRemote` session type after the /// Updates the `host_id` on a `WormholedRemote` session type after the
/// remote server handshake completes (or clears it on disconnect). /// remote server handshake completes (or clears it on disconnect).
pub fn set_remote_host_id(&self, host_id: Option<galaxy_core::HostId>) { pub fn set_remote_host_id(&self, host_id: Option<galaxy_core::HostId>) {
let mut st = self.session_type.lock(); let mut st = self.session_type.lock();
if let SessionType::WarpifiedRemote { host_id: ref mut h } = *st { if let SessionType::WormholedRemote { host_id: ref mut h } = *st {
*h = host_id; *h = host_id;
} }
} }
@@ -1012,9 +1012,9 @@ impl Session {
self.info.host_info.clone() self.info.host_info.clone()
} }
/// Returns whether this session was established by Warp's in-band SSH wrapper (see /// Returns whether this session was established by Galaxy's in-band SSH wrapper (see
/// [`IsSSHWrapperSession`]). Note this stays `false` for remote sessions warpified via /// [`IsSSHWrapperSession`]). Note this stays `false` for remote sessions wormholed via
/// the auto-warpify RC snippet inside an unwrapped `ssh` session. /// the auto-wormhole RC snippet inside an unwrapped `ssh` session.
pub fn is_ssh_wrapper_session(&self) -> bool { pub fn is_ssh_wrapper_session(&self) -> bool {
matches!( matches!(
self.info.is_ssh_wrapper_session, self.info.is_ssh_wrapper_session,
@@ -1023,7 +1023,7 @@ impl Session {
} }
pub fn is_subshell_or_ssh(&self) -> bool { pub fn is_subshell_or_ssh(&self) -> bool {
matches!(self.session_type(), SessionType::WarpifiedRemote { .. }) matches!(self.session_type(), SessionType::WormholedRemote { .. })
|| self.is_ssh_wrapper_session() || self.is_ssh_wrapper_session()
|| self.subshell_info().is_some() || self.subshell_info().is_some()
} }
@@ -1539,7 +1539,7 @@ impl Session {
self.read_history_for_local_session(is_kaspersky_running) self.read_history_for_local_session(is_kaspersky_running)
.await .await
} }
BootstrapSessionType::WarpifiedRemote => self.read_history_for_remote_session().await, BootstrapSessionType::WormholedRemote => self.read_history_for_remote_session().await,
} }
} }
@@ -1635,22 +1635,22 @@ impl Session {
/// Converts the given directory into a [`typed_path::TypedPathBuf`]. /// Converts the given directory into a [`typed_path::TypedPathBuf`].
pub fn convert_directory_to_typed_path_buf(&self, pwd: String) -> TypedPathBuf { pub fn convert_directory_to_typed_path_buf(&self, pwd: String) -> TypedPathBuf {
// We need to determine whether this session requires windows file paths // We need to determine whether this session requires windows file paths
// or unix file paths. This needs to be resilient to warpified ssh. Some examples: // or unix file paths. This needs to be resilient to wormholed ssh. Some examples:
// - bash on mac ---> unix // - bash on mac ---> unix
// - powershell on linux ---> unix // - powershell on linux ---> unix
// - powershell on windows ---> windows // - powershell on windows ---> windows
// - wsl on windows ---> unix // - wsl on windows ---> unix
// - warpified zsh --> unix // - wormholed zsh --> unix
// If the host architecture is unix, we can infer unix file paths. This would break // If the host architecture is unix, we can infer unix file paths. This would break
// if we supported warpifying a powershell-on-windows SSH session. // if we supported wormholing a powershell-on-windows SSH session.
if cfg!(unix) { if cfg!(unix) {
return TypedPathBuf::from_unix(pwd); return TypedPathBuf::from_unix(pwd);
} }
// We assume that we're on Windows. // We assume that we're on Windows.
match self.shell_family() { match self.shell_family() {
// Cases: WSL, MSYS2, warpified bash // Cases: WSL, MSYS2, wormholed bash
ShellFamily::Posix => TypedPathBuf::from_unix(pwd), ShellFamily::Posix => TypedPathBuf::from_unix(pwd),
// Cases: powershell sessions // Cases: powershell sessions
ShellFamily::PowerShell => TypedPathBuf::from_windows(pwd), ShellFamily::PowerShell => TypedPathBuf::from_windows(pwd),
@@ -1671,7 +1671,7 @@ impl Display for Session {
} }
} }
/// Returns the hostname for the local machine where Warp is running. /// Returns the hostname for the local machine where Galaxy is running.
pub fn get_local_hostname() -> Result<String> { pub fn get_local_hostname() -> Result<String> {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(not(target_family = "wasm"))] { if #[cfg(not(target_family = "wasm"))] {
@@ -1786,7 +1786,7 @@ pub mod testing {
pub fn with_ssh_socket_path(mut self, socket_path: PathBuf) -> Self { pub fn with_ssh_socket_path(mut self, socket_path: PathBuf) -> Self {
if let BootstrapSessionType::Local = self.session_type { if let BootstrapSessionType::Local = self.session_type {
self.session_type = BootstrapSessionType::WarpifiedRemote; self.session_type = BootstrapSessionType::WormholedRemote;
} }
self.is_ssh_wrapper_session = IsSSHWrapperSession::Yes { self.is_ssh_wrapper_session = IsSSHWrapperSession::Yes {
socket_path, socket_path,
@@ -1856,7 +1856,7 @@ pub mod testing {
pub fn test_remote() -> Self { pub fn test_remote() -> Self {
let info = SessionInfo::new_for_test() let info = SessionInfo::new_for_test()
.with_session_type(BootstrapSessionType::WarpifiedRemote) .with_session_type(BootstrapSessionType::WormholedRemote)
.with_shell_type(ShellType::Bash); // We only support UNIX-based remote sessions. .with_shell_type(ShellType::Bash); // We only support UNIX-based remote sessions.
let session_type = SessionType::from(info.session_type.clone()); let session_type = SessionType::from(info.session_type.clone());
Self { Self {
@@ -100,12 +100,12 @@ impl ActiveSession {
/// the connected host ID. /// the connected host ID.
pub fn location_for_path(&self, path: &str, app: &AppContext) -> Option<LocalOrRemotePath> { pub fn location_for_path(&self, path: &str, app: &AppContext) -> Option<LocalOrRemotePath> {
match self.session_type(app) { match self.session_type(app) {
Some(SessionType::WarpifiedRemote { Some(SessionType::WormholedRemote {
host_id: Some(host_id), host_id: Some(host_id),
}) => StandardizedPath::try_new(path) }) => StandardizedPath::try_new(path)
.ok() .ok()
.map(|path| LocalOrRemotePath::Remote(RemotePath::new(host_id, path))), .map(|path| LocalOrRemotePath::Remote(RemotePath::new(host_id, path))),
Some(SessionType::WarpifiedRemote { host_id: None }) => None, Some(SessionType::WormholedRemote { host_id: None }) => None,
Some(SessionType::Local) | None => { Some(SessionType::Local) | None => {
let path = let path =
dunce::canonicalize(Path::new(path)).unwrap_or_else(|_| PathBuf::from(path)); dunce::canonicalize(Path::new(path)).unwrap_or_else(|_| PathBuf::from(path));
@@ -289,7 +289,7 @@ fn new_command_executor_for_local_tty_session(
} }
} }
} }
BootstrapSessionType::WarpifiedRemote BootstrapSessionType::WormholedRemote
if is_ssh_wrapper_session if is_ssh_wrapper_session
&& !FeatureFlag::InBandGeneratorsForSSH.is_enabled() && !FeatureFlag::InBandGeneratorsForSSH.is_enabled()
&& !force_use_in_band_generators => && !force_use_in_band_generators =>
+2 -2
View File
@@ -114,11 +114,11 @@ fn test_malicious_histfile_path_does_not_execute_injected_commands() {
let malicious_histfile = format!("/tmp/x'; touch {marker}; echo '"); let malicious_histfile = format!("/tmp/x'; touch {marker}; echo '");
let session_info = SessionInfo::new_for_test() let session_info = SessionInfo::new_for_test()
.with_session_type(BootstrapSessionType::WarpifiedRemote) .with_session_type(BootstrapSessionType::WormholedRemote)
.with_histfile(Some(malicious_histfile)); .with_histfile(Some(malicious_histfile));
let session = Session::new(session_info, Arc::new(TestCommandExecutor::default())); let session = Session::new(session_info, Arc::new(TestCommandExecutor::default()));
// read_history for a WarpifiedRemote session calls read_history_from_file, // read_history for a WormholedRemote session calls read_history_from_file,
// which builds `cat '{escaped_path}'` and executes it via TestCommandExecutor // which builds `cat '{escaped_path}'` and executes it via TestCommandExecutor
let _ = session.read_history(false).await; let _ = session.read_history(false).await;
+5 -5
View File
@@ -351,7 +351,7 @@ enum IsReceivingHook {
No, No,
} }
/// Information needed to render a warpify "success" block upon successful subshell bootstrap. /// Information needed to render a wormhole "success" block upon successful subshell bootstrap.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SubshellSuccessBlockInfo { pub struct SubshellSuccessBlockInfo {
/// The ID of the newly bootstrapped subshell session. /// The ID of the newly bootstrapped subshell session.
@@ -2256,7 +2256,7 @@ impl TerminalModel {
/// a line of output that is not a known SSH output, we consider that to be some mild evidence that /// a line of output that is not a known SSH output, we consider that to be some mild evidence that
/// login is complete. Though, because that output line might be a false alarm (i.e., it could be /// login is complete. Though, because that output line might be a false alarm (i.e., it could be
/// an SSH banner OR a line like "Permission denied."), we wait some amount of time and check again /// an SSH banner OR a line like "Permission denied."), we wait some amount of time and check again
/// before indicating we're ready for warpification. /// before indicating we're ready for wormholing.
pub fn check_for_end_of_ssh_login(&mut self, confirmation_check: bool) { pub fn check_for_end_of_ssh_login(&mut self, confirmation_check: bool) {
let Some(mut ssh_login_state) = self.notify_on_end_of_ssh_login.clone() else { let Some(mut ssh_login_state) = self.notify_on_end_of_ssh_login.clone() else {
return; return;
@@ -2279,7 +2279,7 @@ impl TerminalModel {
SshLoginState::LastLogin | SshLoginState::PromptDetected => { SshLoginState::LastLogin | SshLoginState::PromptDetected => {
self.event_proxy self.event_proxy
.send_terminal_event(Event::DetectedEndOfSshLogin( .send_terminal_event(Event::DetectedEndOfSshLogin(
SshLoginStatus::ReadyToWarpify, SshLoginStatus::ReadyToWormhole,
)); ));
ssh_login_state.notification_state = SshLoginNotificationState::Completed; ssh_login_state.notification_state = SshLoginNotificationState::Completed;
@@ -2290,7 +2290,7 @@ impl TerminalModel {
if ssh_login_state.notification_state == SshLoginNotificationState::Monitoring { if ssh_login_state.notification_state == SshLoginNotificationState::Monitoring {
self.event_proxy self.event_proxy
.send_terminal_event(Event::DetectedEndOfSshLogin( .send_terminal_event(Event::DetectedEndOfSshLogin(
SshLoginStatus::RecheckBeforeWarpifying, SshLoginStatus::RecheckBeforeWormholing,
)); ));
// We want to avoid emitting redundant events for the initial check. // We want to avoid emitting redundant events for the initial check.
@@ -2300,7 +2300,7 @@ impl TerminalModel {
} else { } else {
self.event_proxy self.event_proxy
.send_terminal_event(Event::DetectedEndOfSshLogin( .send_terminal_event(Event::DetectedEndOfSshLogin(
SshLoginStatus::ReadyToWarpify, SshLoginStatus::ReadyToWormhole,
)); ));
ssh_login_state.notification_state = SshLoginNotificationState::Completed; ssh_login_state.notification_state = SshLoginNotificationState::Completed;
+1 -1
View File
@@ -381,7 +381,7 @@ pub enum ModelEvent {
ExecutedInBandCommand(ExecutedExecutorCommandEvent), ExecutedInBandCommand(ExecutedExecutorCommandEvent),
/// Sent when a line of output from an interactive ssh session indicates login is complete. /// Sent when a line of output from an interactive ssh session indicates login is complete.
/// A line such as "Last login: Wed Oct 30" for example indicates login is complete. This is /// A line such as "Last login: Wed Oct 30" for example indicates login is complete. This is
/// useful for detecting when an ssh session becomes ready for warpification. /// useful for detecting when an ssh session becomes ready for wormholing.
DetectedEndOfSshLogin(SshLoginStatus), DetectedEndOfSshLogin(SshLoginStatus),
InitSubshell(InitSubshellEvent), InitSubshell(InitSubshellEvent),
/// Emitted when the user's RC file has been executed in a subshell. /// Emitted when the user's RC file has been executed in a subshell.
+1 -1
View File
@@ -21,7 +21,7 @@ pub fn user_and_host_name_string(
) -> Option<String> { ) -> Option<String> {
match session_type { match session_type {
SessionType::Local => None, SessionType::Local => None,
SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:")), SessionType::WormholedRemote { .. } => Some(format!("{user}@{hostname}:")),
} }
} }
+3 -5
View File
@@ -251,13 +251,11 @@ impl PromptRenderHelper {
RemoteServerSetupState::Checking => "Starting shell...".to_string(), RemoteServerSetupState::Checking => "Starting shell...".to_string(),
RemoteServerSetupState::Installing { RemoteServerSetupState::Installing {
progress_percent: Some(p), progress_percent: Some(p),
} => format!("Installing Warp SSH Extension... ({p}%)"), } => format!("Installing Wormhole helper... ({p}%)"),
RemoteServerSetupState::Installing { RemoteServerSetupState::Installing {
progress_percent: None, progress_percent: None,
} => "Installing Warp SSH Extension...".to_string(), } => "Installing Wormhole helper...".to_string(),
RemoteServerSetupState::Updating => { RemoteServerSetupState::Updating => "Updating Wormhole helper...".to_string(),
"Updating Warp SSH Extension...".to_string()
}
RemoteServerSetupState::Initializing => "Initializing...".to_string(), RemoteServerSetupState::Initializing => "Initializing...".to_string(),
RemoteServerSetupState::Ready => "Starting shell...".to_string(), RemoteServerSetupState::Ready => "Starting shell...".to_string(),
// Failed and Unsupported both fall back to the wrapper-only SSH // Failed and Unsupported both fall back to the wrapper-only SSH
+50 -50
View File
@@ -1,9 +1,9 @@
use crate::appearance::Appearance; use crate::appearance::Appearance;
use crate::terminal::model::ansi::WarpificationUnavailableReason; use crate::terminal::model::ansi::WormholingUnavailableReason;
use crate::terminal::warpify; use crate::terminal::wormhole;
use crate::terminal::warpify::render::apply_spacing_styles; use crate::terminal::wormhole::render::apply_spacing_styles;
use crate::terminal::warpify::render::build_description_row; use crate::terminal::wormhole::render::build_description_row;
use crate::terminal::warpify::settings::WarpifySettings; use crate::terminal::wormhole::settings::WormholeSettings;
use crate::ui_components::icons::Icon as UiIcon; use crate::ui_components::icons::Icon as UiIcon;
use galaxy_core::channel::ChannelState; use galaxy_core::channel::ChannelState;
use galaxy_core::ui::theme::GalaxyTheme; use galaxy_core::ui::theme::GalaxyTheme;
@@ -35,7 +35,7 @@ const UNSUPPORTED_TMUX_VERSION_ERROR: &str =
"The tmux version available on the remote machine is below 3.0. Please install tmux 3.0 or greater using a different method and try again."; "The tmux version available on the remote machine is below 3.0. Please install tmux 3.0 or greater using a different method and try again.";
const TMUX_FAILED_ERROR: &str = const TMUX_FAILED_ERROR: &str =
"tmux failed to execute on the remote machine. Please re-install tmux and try again."; "tmux failed to execute on the remote machine. Please re-install tmux and try again.";
const WARPIFY_TIMEOUT_ERROR: &str = "Wormholing the session hit a timeout."; const WORMHOLE_TIMEOUT_ERROR: &str = "Wormholing the session hit a timeout.";
const UNSUPPORTED_SHELL_ERROR: &str = const UNSUPPORTED_SHELL_ERROR: &str =
"Unsupported shell. Please set bash, zsh, or fish as your default shell and try again."; "Unsupported shell. Please set bash, zsh, or fish as your default shell and try again.";
const TMUX_INSTALL_FAILED_ERROR: &str = const TMUX_INSTALL_FAILED_ERROR: &str =
@@ -55,28 +55,28 @@ fn get_ssh_github_issue_url(title: &str) -> String {
format!("{url}&title={title}") format!("{url}&title={title}")
} }
impl WarpificationUnavailableReason { impl WormholingUnavailableReason {
fn error_message(&self) -> &'static str { fn error_message(&self) -> &'static str {
match self { match self {
WarpificationUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR, WormholingUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR,
WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => { WormholingUnavailableReason::UnsupportedTmuxVersion { .. } => {
UNSUPPORTED_TMUX_VERSION_ERROR UNSUPPORTED_TMUX_VERSION_ERROR
} }
WarpificationUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR, WormholingUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR,
WarpificationUnavailableReason::Timeout { .. } => WARPIFY_TIMEOUT_ERROR, WormholingUnavailableReason::Timeout { .. } => WORMHOLE_TIMEOUT_ERROR,
WarpificationUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR, WormholingUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR,
WarpificationUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR, WormholingUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR,
} }
} }
fn error_title(&self) -> &'static str { fn error_title(&self) -> &'static str {
match self { match self {
WarpificationUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed", WormholingUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed",
WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => { WormholingUnavailableReason::UnsupportedTmuxVersion { .. } => {
"Unsupported Tmux Version" "Unsupported Tmux Version"
} }
WarpificationUnavailableReason::TmuxFailed => "tmux Failed", WormholingUnavailableReason::TmuxFailed => "tmux Failed",
WarpificationUnavailableReason::Timeout { WormholingUnavailableReason::Timeout {
is_tmux_install, .. is_tmux_install, ..
} => { } => {
if *is_tmux_install { if *is_tmux_install {
@@ -85,34 +85,34 @@ impl WarpificationUnavailableReason {
"SSH Wormhole Timeout" "SSH Wormhole Timeout"
} }
} }
WarpificationUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell", WormholingUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell",
WarpificationUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed", WormholingUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed",
} }
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SshErrorBlockEvent { pub enum SshErrorBlockEvent {
ContinueWithoutWarpification, ContinueWithoutWormholing,
WarpifyWithoutTmux, WormholeWithoutTmux,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SshErrorBlockAction { pub enum SshErrorBlockAction {
ContinueWithoutWarpification, ContinueWithoutWormholing,
WarpifyWithoutTmux, WormholeWithoutTmux,
OpenUrl(String), OpenUrl(String),
AddSshHostToDenylist(String), AddSshHostToDenylist(String),
Focus, Focus,
} }
pub struct SshErrorBlock { pub struct SshErrorBlock {
error_reason: WarpificationUnavailableReason, error_reason: WormholingUnavailableReason,
ssh_host: Option<String>, ssh_host: Option<String>,
warpify_without_tmux_button_mouse_state: MouseStateHandle, wormhole_without_tmux_button_mouse_state: MouseStateHandle,
continue_button_mouse_state: MouseStateHandle, continue_button_mouse_state: MouseStateHandle,
report_link_highlight_index: HighlightedHyperlink, report_link_highlight_index: HighlightedHyperlink,
never_warpify_mouse_state_handle: MouseStateHandle, never_wormhole_mouse_state_handle: MouseStateHandle,
block_mouse_state: MouseStateHandle, block_mouse_state: MouseStateHandle,
is_focused: bool, is_focused: bool,
} }
@@ -123,17 +123,17 @@ pub fn init(app: &mut AppContext) {
app.register_fixed_bindings([ app.register_fixed_bindings([
FixedBinding::new( FixedBinding::new(
"enter", "enter",
SshErrorBlockAction::WarpifyWithoutTmux, SshErrorBlockAction::WormholeWithoutTmux,
id!(SshErrorBlock::ui_name()), id!(SshErrorBlock::ui_name()),
), ),
FixedBinding::new( FixedBinding::new(
"escape", "escape",
SshErrorBlockAction::ContinueWithoutWarpification, SshErrorBlockAction::ContinueWithoutWormholing,
id!(SshErrorBlock::ui_name()), id!(SshErrorBlock::ui_name()),
), ),
FixedBinding::new( FixedBinding::new(
"ctrl-c", "ctrl-c",
SshErrorBlockAction::ContinueWithoutWarpification, SshErrorBlockAction::ContinueWithoutWormholing,
id!(SshErrorBlock::ui_name()), id!(SshErrorBlock::ui_name()),
), ),
]); ]);
@@ -141,14 +141,14 @@ pub fn init(app: &mut AppContext) {
impl SshErrorBlock { impl SshErrorBlock {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new(error_reason: WarpificationUnavailableReason, ssh_host: Option<String>) -> Self { pub fn new(error_reason: WormholingUnavailableReason, ssh_host: Option<String>) -> Self {
Self { Self {
error_reason, error_reason,
ssh_host, ssh_host,
warpify_without_tmux_button_mouse_state: Default::default(), wormhole_without_tmux_button_mouse_state: Default::default(),
continue_button_mouse_state: Default::default(), continue_button_mouse_state: Default::default(),
report_link_highlight_index: Default::default(), report_link_highlight_index: Default::default(),
never_warpify_mouse_state_handle: Default::default(), never_wormhole_mouse_state_handle: Default::default(),
block_mouse_state: Default::default(), block_mouse_state: Default::default(),
is_focused: false, is_focused: false,
} }
@@ -162,8 +162,8 @@ impl SshErrorBlock {
fn should_show_report_to_warp_button(&self) -> bool { fn should_show_report_to_warp_button(&self) -> bool {
matches!( matches!(
self.error_reason, self.error_reason,
WarpificationUnavailableReason::Timeout { .. } WormholingUnavailableReason::Timeout { .. }
| WarpificationUnavailableReason::TmuxInstallFailed { .. } | WormholingUnavailableReason::TmuxInstallFailed { .. }
) )
} }
@@ -173,7 +173,7 @@ impl SshErrorBlock {
theme: &GalaxyTheme, theme: &GalaxyTheme,
appearance: &Appearance, appearance: &Appearance,
) -> Box<dyn Element> { ) -> Box<dyn Element> {
let header_contents = warpify::render::build_header_row( let header_contents = wormhole::render::build_header_row(
"Error Wormholing session", "Error Wormholing session",
Icon::new(UiIcon::AlertTriangle.into(), theme.ui_error_color()), Icon::new(UiIcon::AlertTriangle.into(), theme.ui_error_color()),
theme, theme,
@@ -182,11 +182,11 @@ impl SshErrorBlock {
.with_margin_right(8.) .with_margin_right(8.)
.finish(); .finish();
let right_hand_size = warpify::render::render_never_warpify_ssh_link( let right_hand_size = wormhole::render::render_never_wormhole_ssh_link(
&self.ssh_host, &self.ssh_host,
app, app,
appearance, appearance,
self.never_warpify_mouse_state_handle.clone(), self.never_wormhole_mouse_state_handle.clone(),
move |ctx, ssh_host| { move |ctx, ssh_host| {
ctx.dispatch_typed_action(SshErrorBlockAction::AddSshHostToDenylist( ctx.dispatch_typed_action(SshErrorBlockAction::AddSshHostToDenylist(
ssh_host.to_owned(), ssh_host.to_owned(),
@@ -204,7 +204,7 @@ impl SshErrorBlock {
row.add_child(right_hand_size); row.add_child(right_hand_size);
} }
warpify::render::apply_spacing_styles(Container::new(row.finish())).finish() wormhole::render::apply_spacing_styles(Container::new(row.finish())).finish()
} }
} }
@@ -227,7 +227,7 @@ impl View for SshErrorBlock {
content.add_child(self.render_title_ui(app, theme, appearance)); content.add_child(self.render_title_ui(app, theme, appearance));
content.add_child(warpify::render::description_row( content.add_child(wormhole::render::description_row(
self.error_reason.error_message(), self.error_reason.error_message(),
theme, theme,
appearance, appearance,
@@ -256,7 +256,7 @@ impl View for SshErrorBlock {
ui_builder ui_builder
.button( .button(
ButtonVariant::Accent, ButtonVariant::Accent,
self.warpify_without_tmux_button_mouse_state.clone(), self.wormhole_without_tmux_button_mouse_state.clone(),
) )
.with_centered_text_label("Wormhole without TMUX".into()) .with_centered_text_label("Wormhole without TMUX".into())
.with_style(UiComponentStyles { .with_style(UiComponentStyles {
@@ -266,7 +266,7 @@ impl View for SshErrorBlock {
.build() .build()
.with_cursor(Cursor::PointingHand) .with_cursor(Cursor::PointingHand)
.on_click(move |ctx, _, _| { .on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(SshErrorBlockAction::WarpifyWithoutTmux) ctx.dispatch_typed_action(SshErrorBlockAction::WormholeWithoutTmux)
}) })
.finish(), .finish(),
) )
@@ -287,7 +287,7 @@ impl View for SshErrorBlock {
.build() .build()
.with_cursor(Cursor::PointingHand) .with_cursor(Cursor::PointingHand)
.on_click(move |ctx, _, _| { .on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutWarpification) ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutWormholing)
}) })
.finish(), .finish(),
); );
@@ -331,21 +331,21 @@ impl TypedActionView for SshErrorBlock {
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action { match action {
SshErrorBlockAction::WarpifyWithoutTmux => { SshErrorBlockAction::WormholeWithoutTmux => {
ctx.emit(SshErrorBlockEvent::WarpifyWithoutTmux) ctx.emit(SshErrorBlockEvent::WormholeWithoutTmux)
} }
SshErrorBlockAction::ContinueWithoutWarpification => { SshErrorBlockAction::ContinueWithoutWormholing => {
ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification) ctx.emit(SshErrorBlockEvent::ContinueWithoutWormholing)
} }
SshErrorBlockAction::OpenUrl(url) => { SshErrorBlockAction::OpenUrl(url) => {
ctx.open_url(url); ctx.open_url(url);
} }
SshErrorBlockAction::AddSshHostToDenylist(ssh_host) => { SshErrorBlockAction::AddSshHostToDenylist(ssh_host) => {
let settings = WarpifySettings::handle(ctx); let settings = WormholeSettings::handle(ctx);
settings.update(ctx, |warpify, ctx| { settings.update(ctx, |wormhole, ctx| {
warpify.denylist_ssh_host(ssh_host, ctx); wormhole.denylist_ssh_host(ssh_host, ctx);
}); });
ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification); ctx.emit(SshErrorBlockEvent::ContinueWithoutWormholing);
ctx.notify() ctx.notify()
} }
SshErrorBlockAction::Focus => { SshErrorBlockAction::Focus => {
+30 -47
View File
@@ -6,14 +6,13 @@ use crate::ai::blocklist::inline_action::requested_script::{RequestedScriptStatu
use crate::appearance::Appearance; use crate::appearance::Appearance;
use crate::terminal::model::ansi::SystemDetails; use crate::terminal::model::ansi::SystemDetails;
use crate::terminal::model::escape_sequences; use crate::terminal::model::escape_sequences;
use crate::terminal::warpify::render; use crate::terminal::wormhole::render;
use crate::terminal::warpify::settings::WarpifySettings; use crate::terminal::wormhole::settings::WormholeSettings;
use crate::ui_components::blended_colors; use crate::ui_components::blended_colors;
use crate::ui_components::icons::Icon as UiIcon; use crate::ui_components::icons::Icon as UiIcon;
use galaxy_core::ui::theme::GalaxyTheme; use galaxy_core::ui::theme::GalaxyTheme;
use galaxyui::elements::{ use galaxyui::elements::{
FormattedTextElement, HighlightedHyperlink, Hoverable, Icon, MainAxisAlignment, MainAxisSize, Hoverable, Icon, MainAxisAlignment, MainAxisSize, MouseStateHandle, Text,
MouseStateHandle,
}; };
use galaxyui::keymap::FixedBinding; use galaxyui::keymap::FixedBinding;
use galaxyui::ui_components::toggle_menu::ToggleMenuStateHandle; use galaxyui::ui_components::toggle_menu::ToggleMenuStateHandle;
@@ -22,10 +21,6 @@ use galaxyui::{
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext,
}; };
use galaxyui::{BlurContext, FocusContext}; use galaxyui::{BlurContext, FocusContext};
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
pub const WHY_INSTALL_TMUX_URL: &str =
"https://docs.warp.dev/terminal/warpify/ssh#why-do-i-need-tmux-on-the-remote-machine";
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TmuxInstallMethod { pub struct TmuxInstallMethod {
@@ -35,7 +30,7 @@ pub struct TmuxInstallMethod {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SshInstallTmuxBlockEvent { pub enum SshInstallTmuxBlockEvent {
InstallTmuxAndWarpify(TmuxInstallMethod), InstallTmuxAndWormhole(TmuxInstallMethod),
ToggleScriptVisibility, ToggleScriptVisibility,
Cancel, Cancel,
Interrupt, Interrupt,
@@ -88,15 +83,14 @@ impl SshKeyEvent {
pub struct SshInstallTmuxBlock { pub struct SshInstallTmuxBlock {
requested_script_mouse_states: RequestedScriptMouseStates, requested_script_mouse_states: RequestedScriptMouseStates,
why_install_tmux_highlight_index: HighlightedHyperlink, never_wormhole_mouse_state_handle: MouseStateHandle,
never_warpify_mouse_state_handle: MouseStateHandle,
block_mouse_state: MouseStateHandle, block_mouse_state: MouseStateHandle,
is_focused: bool, is_focused: bool,
is_collapsed: bool, is_collapsed: bool,
show_tmux_install_block: bool, show_tmux_install_block: bool,
script_status: RequestedScriptStatus, script_status: RequestedScriptStatus,
system_details: SystemDetails, system_details: SystemDetails,
/// The script to install tmux locally, in a ~/.warp directory /// The script to install tmux locally, in a ~/.galaxy directory
tmux_local_install_script: String, tmux_local_install_script: String,
ssh_host: Option<String>, ssh_host: Option<String>,
ssh_command: String, ssh_command: String,
@@ -166,8 +160,7 @@ impl SshInstallTmuxBlock {
) -> Self { ) -> Self {
Self { Self {
requested_script_mouse_states: Default::default(), requested_script_mouse_states: Default::default(),
why_install_tmux_highlight_index: Default::default(), never_wormhole_mouse_state_handle: Default::default(),
never_warpify_mouse_state_handle: Default::default(),
block_mouse_state: Default::default(), block_mouse_state: Default::default(),
is_focused: false, is_focused: false,
is_collapsed: true, is_collapsed: true,
@@ -220,7 +213,7 @@ impl SshInstallTmuxBlock {
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
self.script_status = RequestedScriptStatus::Running; self.script_status = RequestedScriptStatus::Running;
ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndWarpify( ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndWormhole(
install_method, install_method,
)); ));
ctx.notify() ctx.notify()
@@ -261,7 +254,7 @@ impl SshInstallTmuxBlock {
content: tmux_system_install_script.to_string(), content: tmux_system_install_script.to_string(),
}, },
TitledScript { TitledScript {
title: "Install to ~/.warp".to_string(), title: "Install to ~/.galaxy".to_string(),
content: self.tmux_local_install_script.clone(), content: self.tmux_local_install_script.clone(),
}, },
*is_first_script_active, *is_first_script_active,
@@ -320,7 +313,7 @@ impl SshInstallTmuxBlock {
) -> Box<dyn Element> { ) -> Box<dyn Element> {
let header_contents = render::build_header_row( let header_contents = render::build_header_row(
"Install tmux?", "Install tmux?",
Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()), Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail()),
theme, theme,
appearance, appearance,
) )
@@ -331,11 +324,11 @@ impl SshInstallTmuxBlock {
let right_hand_size = is_awaiting_action let right_hand_size = is_awaiting_action
.then(|| { .then(|| {
render::render_never_warpify_ssh_link( render::render_never_wormhole_ssh_link(
&self.ssh_host, &self.ssh_host,
app, app,
appearance, appearance,
self.never_warpify_mouse_state_handle.clone(), self.never_wormhole_mouse_state_handle.clone(),
move |ctx, ssh_host| { move |ctx, ssh_host| {
ctx.dispatch_typed_action(SshInstallTmuxBlockAction::AddSshHostToDenylist( ctx.dispatch_typed_action(SshInstallTmuxBlockAction::AddSshHostToDenylist(
ssh_host.to_owned(), ssh_host.to_owned(),
@@ -382,30 +375,20 @@ impl View for SshInstallTmuxBlock {
"In order to Wormhole your SSH session, tmux must be installed. " "In order to Wormhole your SSH session, tmux must be installed. "
}; };
let warpify_description = vec![
FormattedTextFragment::plain_text(explanation),
FormattedTextFragment::hyperlink("Why do I need tmux?", WHY_INSTALL_TMUX_URL),
];
let text_color = let text_color =
blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()); blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1());
let warpify_description = FormattedTextElement::new( let wormhole_description = Text::new(
FormattedText::new([FormattedTextLine::Line(warpify_description)]), explanation.to_string(),
appearance.monospace_font_family(),
appearance.monospace_font_size(), appearance.monospace_font_size(),
appearance.monospace_font_family(),
appearance.monospace_font_family(),
text_color,
self.why_install_tmux_highlight_index.clone(),
) )
.with_hyperlink_font_color(appearance.theme().accent().into_solid()) .soft_wrap(true)
.register_default_click_handlers(|url, _, ctx| { .with_color(text_color)
ctx.open_url(&url.url);
})
.finish(); .finish();
content content
.add_child(render::apply_spacing_styles(Container::new(warpify_description)).finish()); .add_child(render::apply_spacing_styles(Container::new(wormhole_description)).finish());
if let Some(root_install_state) = &self.system_install_state { if let Some(root_install_state) = &self.system_install_state {
content.add_child(self.render_system_install_ui(root_install_state, app)); content.add_child(self.render_system_install_ui(root_install_state, app));
@@ -490,9 +473,9 @@ impl TypedActionView for SshInstallTmuxBlock {
ctx.emit(SshInstallTmuxBlockEvent::Interrupt); ctx.emit(SshInstallTmuxBlockEvent::Interrupt);
} }
(SshInstallTmuxBlockAction::AddSshHostToDenylist(ssh_host), true) => { (SshInstallTmuxBlockAction::AddSshHostToDenylist(ssh_host), true) => {
let settings = WarpifySettings::handle(ctx); let settings = WormholeSettings::handle(ctx);
settings.update(ctx, |warpify, ctx| { settings.update(ctx, |wormhole, ctx| {
warpify.denylist_ssh_host(ssh_host, ctx); wormhole.denylist_ssh_host(ssh_host, ctx);
}); });
ctx.emit(SshInstallTmuxBlockEvent::Cancel); ctx.emit(SshInstallTmuxBlockEvent::Cancel);
ctx.notify(); ctx.notify();
@@ -519,16 +502,16 @@ pub fn install_tmux_script(system: &SystemDetails, app: &AppContext) -> Option<S
system.shell.as_str(), system.shell.as_str(),
) { ) {
("Linux", _, "bash" | "zsh") => { ("Linux", _, "bash" | "zsh") => {
bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_linux.sh") bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_linux.sh")
} }
("Linux", _, "fish") => { ("Linux", _, "fish") => {
bundled_asset!("ssh/fish/install_tmux_and_warpify_linux.sh") bundled_asset!("ssh/fish/install_tmux_and_wormhole_linux.sh")
} }
("Darwin", "homebrew", "bash" | "zsh") => { ("Darwin", "homebrew", "bash" | "zsh") => {
bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_brew.sh") bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_brew.sh")
} }
("Darwin", "homebrew", "fish") => { ("Darwin", "homebrew", "fish") => {
bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh") bundled_asset!("ssh/fish/install_tmux_and_wormhole_brew.sh")
} }
_ => return None, _ => return None,
}; };
@@ -555,19 +538,19 @@ pub fn install_root_tmux_script(
system.shell.as_str(), system.shell.as_str(),
) { ) {
("Linux", "apt", "bash" | "zsh") => { ("Linux", "apt", "bash" | "zsh") => {
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_apt.sh") bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_apt.sh")
} }
("Linux", "dnf", "bash" | "zsh") => { ("Linux", "dnf", "bash" | "zsh") => {
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_dnf.sh") bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_dnf.sh")
} }
("Linux", "pacman", "bash" | "zsh") => { ("Linux", "pacman", "bash" | "zsh") => {
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_pacman.sh") bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_pacman.sh")
} }
("Linux", "yum", "bash" | "zsh") => { ("Linux", "yum", "bash" | "zsh") => {
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_yum.sh") bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_yum.sh")
} }
("Linux", "zypper", "bash" | "zsh") => { ("Linux", "zypper", "bash" | "zsh") => {
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_zypper.sh") bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_zypper.sh")
} }
_ => return None, _ => return None,
}; };
+12 -12
View File
@@ -2,7 +2,7 @@ use galaxy_core::{features::FeatureFlag, settings::Setting};
use galaxy_util::path::ShellFamily; use galaxy_util::path::ShellFamily;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::terminal::warpify::settings::WarpifySettings; use crate::terminal::wormhole::settings::WormholeSettings;
/// The different possible outcomes of detecting an interactive SSH session. /// The different possible outcomes of detecting an interactive SSH session.
/// Also the payload for the [`crate::server::telemetry::TelemetryEvent::SshInteractiveSessionDetected`] event. /// Also the payload for the [`crate::server::telemetry::TelemetryEvent::SshInteractiveSessionDetected`] event.
@@ -12,8 +12,8 @@ pub enum SshInteractiveSessionDetected {
FeatureDisabled, FeatureDisabled,
#[serde(rename = "host_denylisted")] #[serde(rename = "host_denylisted")]
HostDenylisted, HostDenylisted,
#[serde(rename = "warpify_prompt")] #[serde(rename = "wormhole_prompt")]
ShouldPromptWarpification { ShouldPromptWormholing {
#[serde(skip)] #[serde(skip)]
command: String, command: String,
#[serde(skip)] #[serde(skip)]
@@ -21,17 +21,17 @@ pub enum SshInteractiveSessionDetected {
}, },
} }
/// Determines whether a host could be warpified. /// Determines whether a host could be wormholed.
pub fn evaluate_warpify_ssh_host( pub fn evaluate_wormhole_ssh_host(
command: &str, command: &str,
ssh_host: Option<&str>, ssh_host: Option<&str>,
shell_family: ShellFamily, shell_family: ShellFamily,
warpify_settings: &WarpifySettings, wormhole_settings: &WormholeSettings,
) -> SshInteractiveSessionDetected { ) -> SshInteractiveSessionDetected {
let should_prompt_ssh_tmux_wrapper = *warpify_settings.enable_ssh_warpification.value() let should_prompt_ssh_tmux_wrapper = *wormhole_settings.enable_ssh_wormholing.value()
&& *warpify_settings.use_ssh_tmux_wrapper.value(); && *wormhole_settings.use_ssh_tmux_wrapper.value();
let matches_subshell = warpify_settings.is_denylisted_subshell_command(command) let matches_subshell = wormhole_settings.is_denylisted_subshell_command(command)
|| warpify_settings.is_compatible_subshell_command(command, shell_family); || wormhole_settings.is_compatible_subshell_command(command, shell_family);
if !should_prompt_ssh_tmux_wrapper if !should_prompt_ssh_tmux_wrapper
|| matches_subshell || matches_subshell
|| !FeatureFlag::SSHTmuxWrapper.is_enabled() || !FeatureFlag::SSHTmuxWrapper.is_enabled()
@@ -40,12 +40,12 @@ pub fn evaluate_warpify_ssh_host(
} }
if let Some(ssh_host) = ssh_host { if let Some(ssh_host) = ssh_host {
if warpify_settings.is_ssh_host_denylisted(ssh_host) { if wormhole_settings.is_ssh_host_denylisted(ssh_host) {
return SshInteractiveSessionDetected::HostDenylisted; return SshInteractiveSessionDetected::HostDenylisted;
} }
} }
SshInteractiveSessionDetected::ShouldPromptWarpification { SshInteractiveSessionDetected::ShouldPromptWormholing {
host: ssh_host.map(|host| host.to_owned()), host: ssh_host.map(|host| host.to_owned()),
command: command.to_string(), command: command.to_string(),
} }
+18 -16
View File
@@ -78,7 +78,7 @@ pub fn check_ssh_login_state(block_output: &str) -> SshLoginState {
} }
/// Represents the parsed components of an interactive SSH command. /// Represents the parsed components of an interactive SSH command.
/// For some [`SshWarpifyCommand`]s, we do not support parsing /// For some [`SshWormholeCommand`]s, we do not support parsing
/// a host or port In these cases, we can still parse to a valid /// a host or port In these cases, we can still parse to a valid
/// empty `InteractiveSshCommand` to indicate that we did /// empty `InteractiveSshCommand` to indicate that we did
/// successfully detect an interactive SSH command. /// successfully detect an interactive SSH command.
@@ -150,25 +150,25 @@ pub enum SshLikeCommand {
/// Represents the different kinds of commands we recognize as starting an interactive SSH /// Represents the different kinds of commands we recognize as starting an interactive SSH
/// session. `Ssh` means a literal `ssh` command, where all other commands (e.g. `gcloud /// session. `Ssh` means a literal `ssh` command, where all other commands (e.g. `gcloud
/// compute ssh`) are categorized as SSH-like commands. /// compute ssh`) are categorized as SSH-like commands.
pub enum SshWarpifyCommand { pub enum SshWormholeCommand {
Ssh, Ssh,
SshLike(SshLikeCommand), SshLike(SshLikeCommand),
} }
impl SshWarpifyCommand { impl SshWormholeCommand {
/// Not a literal `ssh` command, but another command that starts an interactive SSH /// Not a literal `ssh` command, but another command that starts an interactive SSH
/// session. /// session.
pub fn is_ssh_like_command(&self) -> bool { pub fn is_ssh_like_command(&self) -> bool {
matches!(self, SshWarpifyCommand::SshLike(_)) matches!(self, SshWormholeCommand::SshLike(_))
} }
} }
impl SshWarpifyCommand { impl SshWormholeCommand {
pub fn matches(command: &str) -> Option<SshWarpifyCommand> { pub fn matches(command: &str) -> Option<SshWormholeCommand> {
let tokens = normalized_command_tokens(command)?; let tokens = normalized_command_tokens(command)?;
match tokens.as_slice() { match tokens.as_slice() {
[command, arguments @ ..] if command == "ssh" && !arguments.is_empty() => { [command, arguments @ ..] if command == "ssh" && !arguments.is_empty() => {
Some(SshWarpifyCommand::Ssh) Some(SshWormholeCommand::Ssh)
} }
[command, compute, ssh, arguments @ ..] [command, compute, ssh, arguments @ ..]
if command == "gcloud" if command == "gcloud"
@@ -176,12 +176,14 @@ impl SshWarpifyCommand {
&& ssh == "ssh" && ssh == "ssh"
&& !arguments.is_empty() => && !arguments.is_empty() =>
{ {
Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud)) Some(SshWormholeCommand::SshLike(SshLikeCommand::Gcloud))
} }
[command, ssh, arguments @ ..] [command, ssh, arguments @ ..]
if command == "eb" && ssh == "ssh" && !arguments.is_empty() => if command == "eb" && ssh == "ssh" && !arguments.is_empty() =>
{ {
Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) Some(SshWormholeCommand::SshLike(
SshLikeCommand::ElasticBeanstalk,
))
} }
[command, compute, ssh, arguments @ ..] [command, compute, ssh, arguments @ ..]
if command == "doctl" if command == "doctl"
@@ -189,7 +191,7 @@ impl SshWarpifyCommand {
&& ssh == "ssh" && ssh == "ssh"
&& !arguments.is_empty() => && !arguments.is_empty() =>
{ {
Some(SshWarpifyCommand::SshLike( Some(SshWormholeCommand::SshLike(
SshLikeCommand::DigitalOceanDroplet, SshLikeCommand::DigitalOceanDroplet,
)) ))
} }
@@ -199,15 +201,15 @@ impl SshWarpifyCommand {
} }
pub fn parse_interactive_ssh_command(command: &str) -> Option<InteractiveSshCommand> { pub fn parse_interactive_ssh_command(command: &str) -> Option<InteractiveSshCommand> {
match SshWarpifyCommand::matches(command) { match SshWormholeCommand::matches(command) {
Some(SshWarpifyCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command), Some(SshWormholeCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command),
Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud)) => { Some(SshWormholeCommand::SshLike(SshLikeCommand::Gcloud)) => {
Some(InteractiveSshCommand::default()) Some(InteractiveSshCommand::default())
} }
Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => { Some(SshWormholeCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => {
Some(InteractiveSshCommand::default()) Some(InteractiveSshCommand::default())
} }
Some(SshWarpifyCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => { Some(SshWormholeCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => {
Some(InteractiveSshCommand::default()) Some(InteractiveSshCommand::default())
} }
None => None, None => None,
@@ -299,7 +301,7 @@ fn executable_name(executable: &str) -> String {
.to_ascii_lowercase() .to_ascii_lowercase()
} }
/// Creates an sftp command that copies a given local file into the pwd in the warpified ssh session. /// Creates an sftp command that copies a given local file into the pwd in the wormholed ssh session.
pub fn transfer_file_sftp_command( pub fn transfer_file_sftp_command(
local_file_path: String, local_file_path: String,
ssh_host: String, ssh_host: String,
@@ -6,8 +6,7 @@ use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
use crate::ai::blocklist::inline_action::requested_action::RenderableAction; use crate::ai::blocklist::inline_action::requested_action::RenderableAction;
use crate::appearance::Appearance; use crate::appearance::Appearance;
use crate::terminal::shell::ShellType; use crate::terminal::shell::ShellType;
use crate::terminal::warpify; use crate::terminal::wormhole;
use crate::terminal::warpify::render::SSH_DOCS_URL;
use crate::ui_components::icons::Icon as UiIcon; use crate::ui_components::icons::Icon as UiIcon;
use galaxyui::elements::{HighlightedHyperlink, Hoverable, Icon, MouseStateHandle}; use galaxyui::elements::{HighlightedHyperlink, Hoverable, Icon, MouseStateHandle};
use galaxyui::keymap::FixedBinding; use galaxyui::keymap::FixedBinding;
@@ -18,19 +17,19 @@ use galaxyui::{
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SshWarpifyBlockEvent { pub enum SshWormholeBlockEvent {
WarpifySession, WormholeSession,
Cancel, Cancel,
Interrupt, Interrupt,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum SshWarpifyBlockAction { pub enum SshWormholeBlockAction {
Interrupt, Interrupt,
Focus, Focus,
} }
pub struct SshWarpifyBlock { pub struct SshWormholeBlock {
block_mouse_state: MouseStateHandle, block_mouse_state: MouseStateHandle,
ssh_command: String, ssh_command: String,
} }
@@ -40,12 +39,12 @@ pub fn init(app: &mut AppContext) {
app.register_fixed_bindings([FixedBinding::new( app.register_fixed_bindings([FixedBinding::new(
"ctrl-c", "ctrl-c",
SshWarpifyBlockAction::Interrupt, SshWormholeBlockAction::Interrupt,
id!(SshWarpifyBlock::ui_name()), id!(SshWormholeBlock::ui_name()),
)]); )]);
} }
impl SshWarpifyBlock { impl SshWormholeBlock {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new(ssh_command: String) -> Self { pub fn new(ssh_command: String) -> Self {
Self { Self {
@@ -60,18 +59,18 @@ impl SshWarpifyBlock {
} }
} }
impl Entity for SshWarpifyBlock { impl Entity for SshWormholeBlock {
type Event = SshWarpifyBlockEvent; type Event = SshWormholeBlockEvent;
} }
impl SshWarpifyBlock { impl SshWormholeBlock {
fn render_title_ui(&self, theme: &GalaxyTheme, appearance: &Appearance) -> Box<dyn Element> { fn render_title_ui(&self, theme: &GalaxyTheme, appearance: &Appearance) -> Box<dyn Element> {
let icon = Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()); let icon = Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail());
warpify::render::header_row("Wormholing SSH Session...", icon, theme, appearance) wormhole::render::header_row("Wormholing SSH Session...", icon, theme, appearance)
} }
} }
pub fn warpify_description( pub fn wormhole_description(
app: &AppContext, app: &AppContext,
hyperlink_index: &HighlightedHyperlink, hyperlink_index: &HighlightedHyperlink,
) -> Box<dyn Element> { ) -> Box<dyn Element> {
@@ -80,21 +79,16 @@ pub fn warpify_description(
let description = FormattedText::new(vec![FormattedTextLine::Line(vec![ let description = FormattedText::new(vec![FormattedTextLine::Line(vec![
FormattedTextFragment::plain_text( FormattedTextFragment::plain_text(
"Bring Galaxy's features to your remote session. Blocks, full text editing, auto-complete, Oz, and more. " "Bring Galaxy's features to your remote session: blocks, full text editing, completions, Oz, and more."
), ),
FormattedTextFragment::hyperlink("Learn more", SSH_DOCS_URL),
])]); ])]);
warpify::render::build_description_row(description, theme, appearance, hyperlink_index.clone()) wormhole::render::build_description_row(description, theme, appearance, hyperlink_index.clone())
.with_hyperlink_font_color(appearance.theme().accent().into_solid())
.register_default_click_handlers(|url, _, ctx| {
ctx.open_url(&url.url);
})
.finish() .finish()
} }
impl View for SshWarpifyBlock { impl View for SshWormholeBlock {
fn ui_name() -> &'static str { fn ui_name() -> &'static str {
"SshWarpifyBlock" "SshWormholeBlock"
} }
fn render(&self, app: &AppContext) -> Box<dyn Element> { fn render(&self, app: &AppContext) -> Box<dyn Element> {
@@ -124,39 +118,39 @@ impl View for SshWarpifyBlock {
.finish() .finish()
}) })
.on_click(|ctx, _, _| { .on_click(|ctx, _, _| {
ctx.dispatch_typed_action(SshWarpifyBlockAction::Focus); ctx.dispatch_typed_action(SshWormholeBlockAction::Focus);
}) })
.finish() .finish()
} }
} }
impl TypedActionView for SshWarpifyBlock { impl TypedActionView for SshWormholeBlock {
type Action = SshWarpifyBlockAction; type Action = SshWormholeBlockAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action { match action {
SshWarpifyBlockAction::Interrupt => { SshWormholeBlockAction::Interrupt => {
ctx.emit(SshWarpifyBlockEvent::Interrupt); ctx.emit(SshWormholeBlockEvent::Interrupt);
} }
SshWarpifyBlockAction::Focus => { SshWormholeBlockAction::Focus => {
self.focus(ctx); self.focus(ctx);
} }
} }
} }
} }
/// Convert the begin_warpify_ssh_session script into a string. /// Convert the begin_wormhole_ssh_session script into a string.
pub fn begin_warpify_ssh_session_command(app: &AppContext) -> String { pub fn begin_wormhole_ssh_session_command(app: &AppContext) -> String {
let asset = bundled_asset!("bootstrap/unknown_init_subshell.sh"); let asset = bundled_asset!("bootstrap/unknown_init_subshell.sh");
match AssetCache::as_ref(app).load_asset::<String>(asset) { match AssetCache::as_ref(app).load_asset::<String>(asset) {
AssetState::Loaded { data } => data.to_string().replace("HOOK_NAME", "InitSsh"), AssetState::Loaded { data } => data.to_string().replace("HOOK_NAME", "InitSsh"),
_ => panic!("ssh begin warpify script should be available as a string"), _ => panic!("ssh begin wormhole script should be available as a string"),
} }
} }
/// Convert the warpify_ssh_session script into a string. /// Convert the wormhole_ssh_session script into a string.
pub fn warpify_ssh_session_command( pub fn wormhole_ssh_session_command(
uname: &str, uname: &str,
shell_type: ShellType, shell_type: ShellType,
app: &AppContext, app: &AppContext,
@@ -164,14 +158,14 @@ pub fn warpify_ssh_session_command(
let asset = match (uname, shell_type) { let asset = match (uname, shell_type) {
// Mac scripts must be less than 1020 characters due to macOS 15+ pty issue // Mac scripts must be less than 1020 characters due to macOS 15+ pty issue
("Darwin", ShellType::Zsh | ShellType::Bash) => { ("Darwin", ShellType::Zsh | ShellType::Bash) => {
bundled_asset!("ssh/bash_zsh/warpify_ssh_session_mac.sh") bundled_asset!("ssh/bash_zsh/wormhole_ssh_session_mac.sh")
} }
// Mac scripts must be less than 1020 characters due to macOS 15+ pty issue // Mac scripts must be less than 1020 characters due to macOS 15+ pty issue
("Darwin", ShellType::Fish) => bundled_asset!("ssh/fish/warpify_ssh_session_mac.sh"), ("Darwin", ShellType::Fish) => bundled_asset!("ssh/fish/wormhole_ssh_session_mac.sh"),
(_, ShellType::Zsh | ShellType::Bash) => { (_, ShellType::Zsh | ShellType::Bash) => {
bundled_asset!("ssh/bash_zsh/warpify_ssh_session.sh") bundled_asset!("ssh/bash_zsh/wormhole_ssh_session.sh")
} }
(_, ShellType::Fish) => bundled_asset!("ssh/fish/warpify_ssh_session.sh"), (_, ShellType::Fish) => bundled_asset!("ssh/fish/wormhole_ssh_session.sh"),
// PowerShell is not supported yet. // PowerShell is not supported yet.
(_, ShellType::PowerShell) => return None, (_, ShellType::PowerShell) => return None,
}; };
@@ -179,9 +173,9 @@ pub fn warpify_ssh_session_command(
// Todo(Jack): look into avoiding an allocation here. // Todo(Jack): look into avoiding an allocation here.
match AssetCache::as_ref(app).load_asset::<String>(asset) { match AssetCache::as_ref(app).load_asset::<String>(asset) {
AssetState::Loaded { data } => Some(data.to_string()), AssetState::Loaded { data } => Some(data.to_string()),
_ => panic!("ssh warpify script should be available as a string"), _ => panic!("ssh wormhole script should be available as a string"),
} }
} }
#[cfg(test)] #[cfg(test)]
#[path = "warpify_test.rs"] #[path = "wormhole_test.rs"]
mod tests; mod tests;
@@ -37,50 +37,50 @@ fn get_script(asset_source: AssetSource, ctx: &AppContext) -> String {
#[cfg_attr(windows, ignore = "TODO(CORE-3626)")] #[cfg_attr(windows, ignore = "TODO(CORE-3626)")]
#[test] #[test]
/// See [assert_script_is_short_enough_mac] for more information. /// See [assert_script_is_short_enough_mac] for more information.
fn test_mac_warpification_script_size() { fn test_mac_wormholing_script_size() {
App::test(Assets, |mut app| async move { App::test(Assets, |mut app| async move {
initialize_app(&mut app); initialize_app(&mut app);
app.read(|ctx| { app.read(|ctx| {
assert_script_is_short_enough_mac( assert_script_is_short_enough_mac(
&begin_warpify_ssh_session_command(ctx), &begin_wormhole_ssh_session_command(ctx),
"unknown_init_subshell.sh", "unknown_init_subshell.sh",
false, false,
); );
assert_script_is_short_enough_mac( assert_script_is_short_enough_mac(
&get_script( &get_script(
bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_brew.sh"), bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_brew.sh"),
ctx, ctx,
), ),
"install_tmux_and_warpify_brew.sh", "install_tmux_and_wormhole_brew.sh",
false, false,
); );
assert_script_is_short_enough_mac( assert_script_is_short_enough_mac(
&get_script( &get_script(
bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh"), bundled_asset!("ssh/fish/install_tmux_and_wormhole_brew.sh"),
ctx, ctx,
), ),
"fish/install_tmux_and_warpify_brew.sh", "fish/install_tmux_and_wormhole_brew.sh",
false, false,
); );
assert_script_is_short_enough_mac( assert_script_is_short_enough_mac(
&warpify_ssh_session_command("Darwin", ShellType::Zsh, ctx) &wormhole_ssh_session_command("Darwin", ShellType::Zsh, ctx)
.expect("Should get Darwin zsh script"), .expect("Should get Darwin zsh script"),
"zsh warpify", "zsh wormhole",
true, true,
); );
assert_script_is_short_enough_mac( assert_script_is_short_enough_mac(
&warpify_ssh_session_command("Darwin", ShellType::Bash, ctx) &wormhole_ssh_session_command("Darwin", ShellType::Bash, ctx)
.expect("Should get Darwin bash script"), .expect("Should get Darwin bash script"),
"bash warpify", "bash wormhole",
true, true,
); );
assert_script_is_short_enough_mac( assert_script_is_short_enough_mac(
&warpify_ssh_session_command("Darwin", ShellType::Fish, ctx) &wormhole_ssh_session_command("Darwin", ShellType::Fish, ctx)
.expect("Should get Darwin fish script"), .expect("Should get Darwin fish script"),
"fish warpify", "fish wormhole",
true, true,
) )
}); });
@@ -126,23 +126,23 @@ impl AtContextMenuDisabledReason {
let session_type = session.session_type(); let session_type = session.session_type();
let has_connected_remote_server = matches!( let has_connected_remote_server = matches!(
session_type, session_type,
SessionType::WarpifiedRemote { host_id: Some(_) } SessionType::WormholedRemote { host_id: Some(_) }
); );
// The @ menu requires repo metadata which is only available for: // The @ menu requires repo metadata which is only available for:
// - Local sessions // - Local sessions
// - WarpifiedRemote sessions with a connected remote server (host_id is Some) // - WormholedRemote sessions with a connected remote server (host_id is Some)
// //
// Block when: // Block when:
// - SSH wrapper session without a remote server upgrade // - SSH wrapper session without a remote server upgrade
// - WarpifiedRemote still connecting (host_id is None) // - WormholedRemote still connecting (host_id is None)
// //
// Note: is_ssh_wrapper_session() is set at bootstrap time and stays true // Note: is_ssh_wrapper_session() is set at bootstrap time and stays true
// even after the session transitions to WarpifiedRemote with a host_id. // even after the session transitions to WormholedRemote with a host_id.
// So we must check has_connected_remote_server first to avoid // So we must check has_connected_remote_server first to avoid
// incorrectly blocking upgraded sessions. // incorrectly blocking upgraded sessions.
let is_ssh_without_remote_server = !has_connected_remote_server let is_ssh_without_remote_server = !has_connected_remote_server
&& (session.is_ssh_wrapper_session() && (session.is_ssh_wrapper_session()
|| matches!(session_type, SessionType::WarpifiedRemote { host_id: None })); || matches!(session_type, SessionType::WormholedRemote { host_id: None }));
let is_subshell = session.subshell_info().is_some(); let is_subshell = session.subshell_info().is_some();
(is_ssh_without_remote_server, is_subshell) (is_ssh_without_remote_server, is_subshell)
}) })
+144 -158
View File
@@ -67,13 +67,13 @@ use std::sync::Arc;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use std::time::Duration; use std::time::Duration;
use action::RememberForWarpification; use action::RememberForWormholing;
pub use action::{AgentOnboardingVersion, OnboardingIntention, OnboardingVersion, TerminalAction}; pub use action::{AgentOnboardingVersion, OnboardingIntention, OnboardingVersion, TerminalAction};
use ai::api_keys::{ApiKeyManager, AwsCredentialsState}; use ai::api_keys::{ApiKeyManager, AwsCredentialsState};
use ai::index::full_source_code_embedding::manager::{BuildSource, CodebaseIndexManager}; use ai::index::full_source_code_embedding::manager::{BuildSource, CodebaseIndexManager};
use async_channel::{Receiver, Sender}; use async_channel::{Receiver, Sender};
use base64::Engine as _; use base64::Engine as _;
use block_banner::{render_warpification_banner, WarpifyBannerState}; use block_banner::{render_wormholing_banner, WormholeBannerState};
pub use block_banner::{WithinBlockBanner, BLOCK_BANNER_HEIGHT}; pub use block_banner::{WithinBlockBanner, BLOCK_BANNER_HEIGHT};
use block_onboarding::onboarding_drive_sharing_block::OnboardingDriveSharingBlock; use block_onboarding::onboarding_drive_sharing_block::OnboardingDriveSharingBlock;
use bookmarks::render_floating_block_snapshot; use bookmarks::render_floating_block_snapshot;
@@ -192,10 +192,9 @@ use super::model::secrets::RichContentSecretTooltipInfo;
use super::model::selection::ExpandedSelectionRange; use super::model::selection::ExpandedSelectionRange;
use super::model::session::SessionBootstrappedEvent; use super::model::session::SessionBootstrappedEvent;
use super::settings::AltScreenPaddingMode; use super::settings::AltScreenPaddingMode;
use super::ssh::util::{parse_interactive_ssh_command, InteractiveSshCommand, SshWarpifyCommand}; use super::ssh::util::{parse_interactive_ssh_command, InteractiveSshCommand, SshWormholeCommand};
use super::warpify::success_block::{WarpifySuccessBlock, WarpifySuccessBlockEvent}; use super::wormhole::success_block::{WormholeSuccessBlock, WormholeSuccessBlockEvent};
use super::warpify::trigger_state::{SshBlockState, WarpifyState}; use super::wormhole::trigger_state::{SshBlockState, WormholeState};
use super::warpify::WarpificationSource;
use super::{cli_agent, CLIAgent, GridType}; use super::{cli_agent, CLIAgent, GridType};
use crate::ai::agent::api::ServerConversationToken; use crate::ai::agent::api::ServerConversationToken;
use crate::ai::agent::conversation::{AIConversation, AIConversationId, ConversationStatus}; use crate::ai::agent::conversation::{AIConversation, AIConversationId, ConversationStatus};
@@ -484,10 +483,10 @@ use crate::terminal::view::ssh_tmux_deprecation_banner::{
}; };
use crate::terminal::view::telemetry::PromptSuggestionFallbackReason; use crate::terminal::view::telemetry::PromptSuggestionFallbackReason;
use crate::terminal::view::zero_state_block::TerminalViewZeroStateBlock; use crate::terminal::view::zero_state_block::TerminalViewZeroStateBlock;
use crate::terminal::warpify::render::render_subshell_separator;
use crate::terminal::warpify::settings::WarpifySettings;
use crate::terminal::warpify::SubshellSource;
use crate::terminal::waterfall_gap_element::WaterfallGapElement; use crate::terminal::waterfall_gap_element::WaterfallGapElement;
use crate::terminal::wormhole::render::render_subshell_separator;
use crate::terminal::wormhole::settings::WormholeSettings;
use crate::terminal::wormhole::SubshellSource;
use crate::terminal::writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface}; use crate::terminal::writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface};
use crate::terminal::{ use crate::terminal::{
block_list_element::BlockHoverAction, block_list_element::BlockHoverAction,
@@ -634,10 +633,6 @@ const KNOWN_ISSUES_URL: &str =
const PROMPT_COMPATIBILITY_URL: &str = const PROMPT_COMPATIBILITY_URL: &str =
"https://docs.warp.dev/terminal/appearance/prompt#custom-prompt-compatibility-table"; "https://docs.warp.dev/terminal/appearance/prompt#custom-prompt-compatibility-table";
/// Link to troubleshooting steps for ControlMaster errors.
const CONTROLMASTER_ISSUES_URL: &str =
"https://docs.warp.dev/terminal/warpify/ssh-legacy#troubleshooting";
/// Link to instructions on how to update p10k. /// Link to instructions on how to update p10k.
const P10K_UPDATE_INSTRUCTIONS_URL: &str = const P10K_UPDATE_INSTRUCTIONS_URL: &str =
"https://github.com/romkatv/powerlevel10k#how-do-i-update-powerlevel10k"; "https://github.com/romkatv/powerlevel10k#how-do-i-update-powerlevel10k";
@@ -676,10 +671,10 @@ enum Osc52ClipboardBlockedType {
/// Key used in user defaults to save whether the user has seen the banner. /// Key used in user defaults to save whether the user has seen the banner.
pub const ALIAS_EXPANSION_BANNER_SEEN_KEY: &str = "AliasExpansionBannerSeen"; pub const ALIAS_EXPANSION_BANNER_SEEN_KEY: &str = "AliasExpansionBannerSeen";
/// Delay between receiving preexec hook for a command we want to auto-warpify /// Delay between receiving preexec hook for a command we want to auto-wormhole
/// and triggering the warpification (subshell bootstrapping). /// and triggering the wormholing (subshell bootstrapping).
/// Reached this number after experimenting with different values to find a reliable delay. /// Reached this number after experimenting with different values to find a reliable delay.
const AUTO_WARPIFY_DELAY: u64 = 1000; const AUTO_WORMHOLE_DELAY: u64 = 1000;
/// Binding names to be customized if the user indicates they prefer /// Binding names to be customized if the user indicates they prefer
/// Emacs-style keybindings instead of IDE-style keybindings. /// Emacs-style keybindings instead of IDE-style keybindings.
@@ -2755,7 +2750,7 @@ pub struct TerminalView {
onboarding_callout_view: Option<ViewHandle<onboarding::OnboardingCalloutView>>, onboarding_callout_view: Option<ViewHandle<onboarding::OnboardingCalloutView>>,
/// The type of the subshell that we will bootstrap/"warpify"" on the next [`AfterBlockStarted`] /// The type of the subshell that we will bootstrap/"wormhole"" on the next [`AfterBlockStarted`]
/// terminal model event. Will only be `Some` with a [`ShellType`] we can bootstrap. /// terminal model event. Will only be `Some` with a [`ShellType`] we can bootstrap.
pending_auto_bootstrap_shell_type: Option<ShellType>, pending_auto_bootstrap_shell_type: Option<ShellType>,
env_vars: Vec<EnvVar>, env_vars: Vec<EnvVar>,
@@ -2817,7 +2812,7 @@ pub struct TerminalView {
find_model: ModelHandle<TerminalFindModel>, find_model: ModelHandle<TerminalFindModel>,
warpify_state: WarpifyState, wormhole_state: WormholeState,
/// The keystroke bound to canceling a command. /// The keystroke bound to canceling a command.
/// ///
@@ -3947,12 +3942,12 @@ impl TerminalView {
let control_master_error_banner = ctx.add_typed_action_view(|_| { let control_master_error_banner = ctx.add_typed_action_view(|_| {
Banner::new_permanently_dismissible(BannerTextContent::formatted_text(vec![ Banner::new_permanently_dismissible(BannerTextContent::formatted_text(vec![
FormattedTextFragment::plain_text("Seems like your completions are not working ("), FormattedTextFragment::plain_text(
FormattedTextFragment::hyperlink("more info", CONTROLMASTER_ISSUES_URL), "Your completions may not be working. Enabling the Wormhole helper in ",
FormattedTextFragment::plain_text("). Enabling the SSH extension in "), ),
FormattedTextFragment::hyperlink_action( FormattedTextFragment::hyperlink_action(
"settings", "settings",
TerminalAction::ShowWarpifySettings, TerminalAction::ShowWormholeSettings,
), ),
FormattedTextFragment::plain_text(" may resolve this issue."), FormattedTextFragment::plain_text(" may resolve this issue."),
])) ]))
@@ -4436,7 +4431,7 @@ impl TerminalView {
input_position_id, input_position_id,
input_hoverable_handle: Default::default(), input_hoverable_handle: Default::default(),
find_model, find_model,
warpify_state: Default::default(), wormhole_state: Default::default(),
cancel_command_keystroke: keybinding_name_to_keystroke(CANCEL_COMMAND_KEYBINDING, ctx), cancel_command_keystroke: keybinding_name_to_keystroke(CANCEL_COMMAND_KEYBINDING, ctx),
is_file_drop_target: false, is_file_drop_target: false,
is_ssh_file_uploader: false, is_ssh_file_uploader: false,
@@ -4582,7 +4577,7 @@ impl TerminalView {
me.show_ssh_remote_server_failed_banner( me.show_ssh_remote_server_failed_banner(
*session_id, *session_id,
remote_server::transport::UserFacingError { remote_server::transport::UserFacingError {
body: "Failed to start SSH extension".into(), body: "Failed to start Wormhole helper".into(),
detail: if error.is_empty() { detail: if error.is_empty() {
None None
} else { } else {
@@ -9116,7 +9111,7 @@ impl TerminalView {
/// events, allow it to handle the event. /// events, allow it to handle the event.
/// ///
/// TODO(CORE-3415): We should probably remove the FixedBindings for ctrl-c /// TODO(CORE-3415): We should probably remove the FixedBindings for ctrl-c
/// in the SSH warpification blocks and handle them here as well. /// in the SSH wormholing blocks and handle them here as well.
fn maybe_handle_ctrl_c_in_rich_content_block(&mut self, ctx: &mut ViewContext<Self>) { fn maybe_handle_ctrl_c_in_rich_content_block(&mut self, ctx: &mut ViewContext<Self>) {
if self.active_ai_block(ctx).is_some() { if self.active_ai_block(ctx).is_some() {
self.cancel_active_conversation_via_status_bar(ctx); self.cancel_active_conversation_via_status_bar(ctx);
@@ -9192,7 +9187,7 @@ impl TerminalView {
/// the workspace to derive `PendingRemoteSession` without storing /// the workspace to derive `PendingRemoteSession` without storing
/// mutable state on the workspace itself. /// mutable state on the workspace itself.
pub fn has_pending_ssh_command(&self) -> bool { pub fn has_pending_ssh_command(&self) -> bool {
self.warpify_state.get_pending_ssh_host().is_some() && self.is_long_running() self.wormhole_state.get_pending_ssh_host().is_some() && self.is_long_running()
} }
/// Like `is_long_running`, but also requires the user to be in control of the command /// Like `is_long_running`, but also requires the user to be in control of the command
@@ -9779,7 +9774,7 @@ impl TerminalView {
.is_some_and(|session| { .is_some_and(|session| {
matches!( matches!(
session.session_type(), session.session_type(),
SessionType::WarpifiedRemote { SessionType::WormholedRemote {
host_id: Some(_), host_id: Some(_),
.. ..
} }
@@ -9839,7 +9834,7 @@ impl TerminalView {
triggered_by_rc_file_snippet: bool, triggered_by_rc_file_snippet: bool,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
self.dismiss_warpify_banner(&RememberForWarpification::DoNotRememberSubshellCommand, ctx); self.dismiss_wormhole_banner(&RememberForWormholing::DoNotRememberSubshellCommand, ctx);
// Record the active long-running block so we can hide it later once the remote // Record the active long-running block so we can hide it later once the remote
// actually confirms subshell bootstrap is in progress. // actually confirms subshell bootstrap is in progress.
@@ -9852,7 +9847,7 @@ impl TerminalView {
.is_active_and_long_running() .is_active_and_long_running()
{ {
let block_id = model.block_list().active_block_id().clone(); let block_id = model.block_list().active_block_id().clone();
self.warpify_state.set_block_id(block_id); self.wormhole_state.set_block_id(block_id);
} }
} }
@@ -9875,7 +9870,7 @@ impl TerminalView {
/// Util method to update the ssh block, with a lock /// Util method to update the ssh block, with a lock
fn update_long_running_ssh_block_with_lock(&self, f: impl FnOnce(&mut Block)) -> bool { fn update_long_running_ssh_block_with_lock(&self, f: impl FnOnce(&mut Block)) -> bool {
if let Some(block_id) = self.warpify_state.block_id() { if let Some(block_id) = self.wormhole_state.block_id() {
if let Some(block) = self if let Some(block) = self
.model .model
.lock() .lock()
@@ -9897,15 +9892,15 @@ impl TerminalView {
} }
fn clear_ssh_blocks(&mut self, ctx: &mut ViewContext<Self>) { fn clear_ssh_blocks(&mut self, ctx: &mut ViewContext<Self>) {
self.dismiss_warpify_banner(&RememberForWarpification::DoNotRememberSSHHost, ctx); self.dismiss_wormhole_banner(&RememberForWormholing::DoNotRememberSSHHost, ctx);
if let Some(ssh_block) = self.warpify_state.ssh_block_state() { if let Some(ssh_block) = self.wormhole_state.ssh_block_state() {
let view_id = ssh_block.get_block_view_id(); let view_id = ssh_block.get_block_view_id();
self.remove_ssh_block_by_id(view_id); self.remove_ssh_block_by_id(view_id);
self.redetermine_global_focus(ctx); self.redetermine_global_focus(ctx);
self.warpify_state.clear_ssh_block_state(); self.wormhole_state.clear_ssh_block_state();
} }
} }
@@ -9915,7 +9910,6 @@ impl TerminalView {
spawning_command, spawning_command,
subshell_info, subshell_info,
shell, shell,
session_type,
.. ..
}: SessionBootstrappedEvent, }: SessionBootstrappedEvent,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
@@ -9929,18 +9923,8 @@ impl TerminalView {
}); });
} }
let warpification_source = match session_type {
BootstrapSessionType::WarpifiedRemote => WarpificationSource::Ssh,
BootstrapSessionType::Local => WarpificationSource::Subshell,
};
let ssh_success_block_handle = ctx.add_typed_action_view(|ctx| { let ssh_success_block_handle = ctx.add_typed_action_view(|ctx| {
WarpifySuccessBlock::new( WormholeSuccessBlock::new(spawning_command, subshell_info, shell, ctx)
warpification_source,
spawning_command,
subshell_info,
shell,
ctx,
)
}); });
ctx.subscribe_to_view(&ssh_success_block_handle, move |me, _, event, ctx| { ctx.subscribe_to_view(&ssh_success_block_handle, move |me, _, event, ctx| {
me.handle_ssh_success_block_events(event, ctx); me.handle_ssh_success_block_events(event, ctx);
@@ -9948,9 +9932,9 @@ impl TerminalView {
self.clear_ssh_blocks(ctx); self.clear_ssh_blocks(ctx);
self.insert_rich_content( self.insert_rich_content(
Some(RichContentType::WarpifySuccessBlock), Some(RichContentType::WormholeSuccessBlock),
ssh_success_block_handle.clone(), ssh_success_block_handle.clone(),
Some(RichContentMetadata::WarpifySuccessBlock { Some(RichContentMetadata::WormholeSuccessBlock {
bootstrap_success_block_handle: ssh_success_block_handle.clone(), bootstrap_success_block_handle: ssh_success_block_handle.clone(),
}), }),
RichContentInsertionPosition::Append { RichContentInsertionPosition::Append {
@@ -9958,30 +9942,30 @@ impl TerminalView {
}, },
ctx, ctx,
); );
self.warpify_state self.wormhole_state
.set_ssh_block_state(SshBlockState::WarpifySuccess { .set_ssh_block_state(SshBlockState::WormholeSuccess {
handle: ssh_success_block_handle, handle: ssh_success_block_handle,
}); });
let active_session_id = self.active_block_session_id(); let active_session_id = self.active_block_session_id();
self.warpify_state.on_warpify_start(active_session_id); self.wormhole_state.on_wormhole_start(active_session_id);
self.refresh_warp_prompt(ctx); self.refresh_warp_prompt(ctx);
} }
fn handle_ssh_success_block_events( fn handle_ssh_success_block_events(
&mut self, &mut self,
event: &WarpifySuccessBlockEvent, event: &WormholeSuccessBlockEvent,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
match event { match event {
WarpifySuccessBlockEvent::OpenWarpifySettings => { WormholeSuccessBlockEvent::OpenWormholeSettings => {
ctx.emit(Event::OpenSettings(SettingsSection::Warpify)); ctx.emit(Event::OpenSettings(SettingsSection::Wormhole));
} }
} }
} }
fn dismiss_warpify_banner( fn dismiss_wormhole_banner(
&mut self, &mut self,
remember_command: &RememberForWarpification, remember_command: &RememberForWormholing,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
{ {
@@ -9989,54 +9973,54 @@ impl TerminalView {
model.block_list_mut().set_active_block_banner(None); model.block_list_mut().set_active_block_banner(None);
} }
// Also clear the warpify footer so it doesn't linger after warpification // Also clear the wormhole footer so it doesn't linger after wormholing
// starts, fails, or is cancelled. // starts, fails, or is cancelled.
if FeatureFlag::WarpifyFooter.is_enabled() { if FeatureFlag::WormholeFooter.is_enabled() {
self.use_agent_footer.update(ctx, |footer, ctx| { self.use_agent_footer.update(ctx, |footer, ctx| {
footer.clear_warpify(ctx); footer.clear_wormhole(ctx);
}); });
} }
match remember_command { match remember_command {
RememberForWarpification::RememberSubshellCommand(command) => { RememberForWormholing::RememberSubshellCommand(command) => {
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
warpify.denylist_subshell_command(command, ctx); wormhole.denylist_subshell_command(command, ctx);
}); });
} }
RememberForWarpification::RememberSSHHost(host) => { RememberForWormholing::RememberSSHHost(host) => {
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
warpify.denylist_ssh_host(host, ctx); wormhole.denylist_ssh_host(host, ctx);
}); });
} }
RememberForWarpification::DoNotRememberSubshellCommand RememberForWormholing::DoNotRememberSubshellCommand
| RememberForWarpification::DoNotRememberSSHHost => {} | RememberForWormholing::DoNotRememberSSHHost => {}
} }
} }
fn show_warpify_banner( fn show_wormhole_banner(
&mut self, &mut self,
command: String, command: String,
title: &str, title: &str,
lowercase_title: &str, lowercase_title: &str,
warpify_keybinding: Option<Keystroke>, wormhole_keybinding: Option<Keystroke>,
telemetry_event: TelemetryEvent, telemetry_event: TelemetryEvent,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
if FeatureFlag::WarpifyFooter.is_enabled() { if FeatureFlag::WormholeFooter.is_enabled() {
return; return;
} }
let mut model = self.model.lock(); let mut model = self.model.lock();
// Shared session viewers can't initiate warpification currently. // Shared session viewers can't initiate wormholing currently.
// Don't show the warpify banner when an agent is monitoring the command either. // Don't show the wormhole banner when an agent is monitoring the command either.
if model.shared_session_status().is_viewer() if model.shared_session_status().is_viewer()
|| model.block_list().active_block().is_agent_monitoring() || model.block_list().active_block().is_agent_monitoring()
{ {
return; return;
} }
let a11y_message = match &warpify_keybinding { let a11y_message = match &wormhole_keybinding {
Some(keystroke) => format!( Some(keystroke) => format!(
"You can press {} to Wormhole this {} for more Galaxy features.", "You can press {} to Wormhole this {} for more Galaxy features.",
keystroke.displayed(), keystroke.displayed(),
@@ -10047,8 +10031,8 @@ impl TerminalView {
model model
.block_list_mut() .block_list_mut()
.set_active_block_banner(Some(WithinBlockBanner::WarpifyBanner( .set_active_block_banner(Some(WithinBlockBanner::WormholeBanner(
WarpifyBannerState::new(command, warpify_keybinding), WormholeBannerState::new(command, wormhole_keybinding),
))); )));
let a11y_content = AccessibilityContent::new( let a11y_content = AccessibilityContent::new(
@@ -11310,7 +11294,7 @@ impl TerminalView {
/// Returns true if the block is considered remote. /// Returns true if the block is considered remote.
/// ///
/// Note that we don't know for sure if a block is remote, because we can only detect /// Note that we don't know for sure if a block is remote, because we can only detect
/// warpified remote blocks. /// wormholed remote blocks.
/// ///
/// For some organizations, we accept a regex list that we run against commands to /// For some organizations, we accept a regex list that we run against commands to
/// further make the determination. /// further make the determination.
@@ -11320,7 +11304,7 @@ impl TerminalView {
command: Option<&str>, command: Option<&str>,
app: &AppContext, app: &AppContext,
) -> bool { ) -> bool {
let is_warpified_remote = session_id let is_wormholed_remote = session_id
.map(|id| { .map(|id| {
self.sessions self.sessions
.as_ref(app) .as_ref(app)
@@ -11330,7 +11314,7 @@ impl TerminalView {
}) })
.unwrap_or_default(); .unwrap_or_default();
if is_warpified_remote { if is_wormholed_remote {
return true; return true;
} }
@@ -11980,7 +11964,8 @@ impl TerminalView {
// If this block ran a possible subshell command, and it exited before the 1s timer // If this block ran a possible subshell command, and it exited before the 1s timer
// completed, abort showing the banner. // completed, abort showing the banner.
if let Some(abort_handle) = self.warpify_state.take_subshell_banner_abort_handle() { if let Some(abort_handle) = self.wormhole_state.take_subshell_banner_abort_handle()
{
abort_handle.abort(); abort_handle.abort();
} }
@@ -12022,9 +12007,9 @@ impl TerminalView {
self.on_user_block_completed(&block_completed_event.block_id, ctx); self.on_user_block_completed(&block_completed_event.block_id, ctx);
} }
// Clear any stale warpify footer so it doesn't leak into the next command's footer rendering. // Clear any stale wormhole footer so it doesn't leak into the next command's footer rendering.
self.use_agent_footer.update(ctx, |footer, ctx| { self.use_agent_footer.update(ctx, |footer, ctx| {
footer.clear_warpify(ctx); footer.clear_wormhole(ctx);
}); });
self.hide_use_agent_footer_in_blocklist(ctx); self.hide_use_agent_footer_in_blocklist(ctx);
if matches!(block_completed_event.block_type, BlockType::User(_)) { if matches!(block_completed_event.block_type, BlockType::User(_)) {
@@ -12109,7 +12094,7 @@ impl TerminalView {
self.drop_hidden_passive_ai_blocks(ctx); self.drop_hidden_passive_ai_blocks(ctx);
// If the first word of the command is a shell alias, expand it // If the first word of the command is a shell alias, expand it
// for subshell/SSH detection. This enables warpification for // for subshell/SSH detection. This enables wormholing for
// aliased SSH commands (e.g. `alias myssh='ssh user@host'`). // aliased SSH commands (e.g. `alias myssh='ssh user@host'`).
let expanded_command = self let expanded_command = self
.active_block_session_id() .active_block_session_id()
@@ -12119,19 +12104,19 @@ impl TerminalView {
let alias_value = session.alias_value(first_word)?; let alias_value = session.alias_value(first_word)?;
Some(format!("{alias_value}{rest}")) Some(format!("{alias_value}{rest}"))
}); });
let warpify_command = expanded_command.as_deref().unwrap_or(command.as_str()); let wormhole_command = expanded_command.as_deref().unwrap_or(command.as_str());
// Check if the current running command spawns a subshell eligible for Warpification. // Check if the current running command spawns a subshell eligible for Wormholing.
let shell_family = self.shell_family(ctx); let shell_family = self.shell_family(ctx);
let warpify_settings = WarpifySettings::as_ref(ctx); let wormhole_settings = WormholeSettings::as_ref(ctx);
let is_compatible_subshell_command = warpify_settings let is_compatible_subshell_command = wormhole_settings
.is_compatible_subshell_command(command, shell_family) .is_compatible_subshell_command(command, shell_family)
|| warpify_settings || wormhole_settings
.is_compatible_subshell_command(warpify_command, shell_family); .is_compatible_subshell_command(wormhole_command, shell_family);
let command_is_denylisted = warpify_settings let command_is_denylisted = wormhole_settings
.is_denylisted_subshell_command(command) .is_denylisted_subshell_command(command)
|| warpify_settings.is_denylisted_subshell_command(warpify_command); || wormhole_settings.is_denylisted_subshell_command(wormhole_command);
// Never warpify or surface warpification for agent-requested commands. // Never wormhole or surface wormholing for agent-requested commands.
let has_ai_metadata = self let has_ai_metadata = self
.model .model
.lock() .lock()
@@ -12142,30 +12127,30 @@ impl TerminalView {
if is_compatible_subshell_command { if is_compatible_subshell_command {
if command_is_denylisted || has_ai_metadata { if command_is_denylisted || has_ai_metadata {
// Don't auto-warpify or surface warpification for these commands. // Don't auto-wormhole or surface wormholing for these commands.
} else if let Some(shell_type) = self.pending_auto_bootstrap_shell_type.take() { } else if let Some(shell_type) = self.pending_auto_bootstrap_shell_type.take() {
// If there is a subshell we're waiting to bootstrap until we receive // If there is a subshell we're waiting to bootstrap until we receive
// the preexec hook, now we can bootstrap it. // the preexec hook, now we can bootstrap it.
let auto_warpify_abort_handle = ctx.spawn_abortable( let auto_wormhole_abort_handle = ctx.spawn_abortable(
Timer::after(Duration::from_millis(AUTO_WARPIFY_DELAY)), Timer::after(Duration::from_millis(AUTO_WORMHOLE_DELAY)),
move |me, _, ctx| { move |me, _, ctx| {
me.trigger_subshell_bootstrap(Some(shell_type), false, ctx); me.trigger_subshell_bootstrap(Some(shell_type), false, ctx);
}, },
|_, _| (), |_, _| (),
); );
self.warpify_state self.wormhole_state
.add_auto_warpify_abort_handle(auto_warpify_abort_handle); .add_auto_wormhole_abort_handle(auto_wormhole_abort_handle);
} else { } else {
// Wait 1 second before showing the banner, just to make sure the // Wait 1 second before showing the banner, just to make sure the
// command stays running for a bit. If the command fails instantly, // command stays running for a bit. If the command fails instantly,
// we don't want to flicker the banner away so quickly. // we don't want to flicker the banner away so quickly.
let command = command.clone(); let command = command.clone();
self.warpify_state self.wormhole_state
.add_subshell_banner_abort_handle(ctx.spawn_abortable( .add_subshell_banner_abort_handle(ctx.spawn_abortable(
Timer::after(*SUBSHELL_BANNER_DELAY_DURATION), Timer::after(*SUBSHELL_BANNER_DELAY_DURATION),
|view, _, ctx| { |view, _, ctx| {
if FeatureFlag::WarpifyFooter.is_enabled() { if FeatureFlag::WormholeFooter.is_enabled() {
view.show_warpify_footer(ctx); view.show_wormhole_footer(ctx);
} else { } else {
view.handle_action( view.handle_action(
&TerminalAction::ShowSubshellBanner(command), &TerminalAction::ShowSubshellBanner(command),
@@ -12179,14 +12164,14 @@ impl TerminalView {
} else { } else {
if !has_ai_metadata { if !has_ai_metadata {
if let Some(ssh_host) = if let Some(ssh_host) =
parse_interactive_ssh_command(warpify_command).map(|cmd| cmd.host) parse_interactive_ssh_command(wormhole_command).map(|cmd| cmd.host)
{ {
self.warpify_state self.wormhole_state
.set_pending_ssh_host(warpify_command.to_string(), ssh_host); .set_pending_ssh_host(wormhole_command.to_string(), ssh_host);
self.model.lock().start_notify_on_end_of_ssh_login(); self.model.lock().start_notify_on_end_of_ssh_login();
ctx.emit(Event::TerminalViewStateChanged); ctx.emit(Event::TerminalViewStateChanged);
} else { } else {
self.warpify_state.clear_pending_ssh_host(); self.wormhole_state.clear_pending_ssh_host();
ctx.spawn( ctx.spawn(
Timer::after(Duration::from_millis( Timer::after(Duration::from_millis(
@@ -12284,14 +12269,14 @@ impl TerminalView {
cloud_workflow_id, cloud_workflow_id,
cloud_env_var_collection_id, cloud_env_var_collection_id,
}) => { }) => {
// To automatically warpify a subshell, we run the relevant command // To automatically wormhole a subshell, we run the relevant command
// subshell and create a future to delay bootstrapping the subshell long enough for // subshell and create a future to delay bootstrapping the subshell long enough for
// the command to complete. We receive AfterBlockCompleted if the subshell command // the command to complete. We receive AfterBlockCompleted if the subshell command
// returns an error or the user exits the subshell. Here we abort the future to // returns an error or the user exits the subshell. Here we abort the future to
// avoid an attempt to trigger bootstrapping if the subshell command failed. If the // avoid an attempt to trigger bootstrapping if the subshell command failed. If the
// future already resolved, abort has no effect. We handle this as early as possible // future already resolved, abort has no effect. We handle this as early as possible
// because the abort is time sensitive. // because the abort is time sensitive.
self.warpify_state.abort_auto_warpify(); self.wormhole_state.abort_auto_wormhole();
let active_session = self let active_session = self
.active_block_session_id() .active_block_session_id()
@@ -12366,14 +12351,14 @@ impl TerminalView {
} }
let active_session_id = self.active_block_session_id(); let active_session_id = self.active_block_session_id();
if let Some(block_id) = self if let Some(block_id) = self
.warpify_state .wormhole_state
.get_completed_warpify_session_id(active_session_id, ctx) .get_completed_wormhole_session_id(active_session_id, ctx)
{ {
self.remove_ssh_block_by_id(block_id); self.remove_ssh_block_by_id(block_id);
} }
self.dismiss_warpify_banner( self.dismiss_wormhole_banner(
&RememberForWarpification::DoNotRememberSubshellCommand, &RememberForWormholing::DoNotRememberSubshellCommand,
ctx, ctx,
); );
@@ -12868,7 +12853,7 @@ impl TerminalView {
.active_block() .active_block()
.agent_interaction_metadata() .agent_interaction_metadata()
.is_some(); .is_some();
// Never warpify for agent-requested commands. // Never wormhole for agent-requested commands.
if has_ai_metadata { if has_ai_metadata {
return; return;
} }
@@ -13063,8 +13048,8 @@ impl TerminalView {
me.remove_ssh_remote_server_choice_block(session_id, ctx); me.remove_ssh_remote_server_choice_block(session_id, ctx);
ctx.emit(Event::RemoteServerSkipRequested { session_id }); ctx.emit(Event::RemoteServerSkipRequested { session_id });
} }
SshRemoteServerChoiceViewEvent::OpenWarpifySettings => { SshRemoteServerChoiceViewEvent::OpenWormholeSettings => {
ctx.emit(Event::OpenSettings(SettingsSection::Warpify)); ctx.emit(Event::OpenSettings(SettingsSection::Wormhole));
} }
}); });
@@ -13259,7 +13244,7 @@ impl TerminalView {
// Clear the pending flag up front so the notice is shown at most once, even if the // Clear the pending flag up front so the notice is shown at most once, even if the
// banner is dismissed without interaction or the session ends early. // banner is dismissed without interaction or the session ends early.
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| { WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
settings.mark_tmux_deprecation_notice_shown(ctx); settings.mark_tmux_deprecation_notice_shown(ctx);
}); });
@@ -13690,7 +13675,7 @@ impl TerminalView {
self.update_incompatible_configuration_banner(session.shell().plugins(), ctx); self.update_incompatible_configuration_banner(session.shell().plugins(), ctx);
if let Some(subshell_info) = session.subshell_info() { if let Some(subshell_info) = session.subshell_info() {
self.warpify_state self.wormhole_state
.add_subshell_separator(subshell_info, self.model.clone(), ctx); .add_subshell_separator(subshell_info, self.model.clone(), ctx);
} }
@@ -13772,22 +13757,23 @@ impl TerminalView {
.spawn(async move { session_clone2.load_all_builtins().await }) .spawn(async move { session_clone2.load_all_builtins().await })
.detach(); .detach();
// If we were waiting for a successful warpification, it's come. Stop the timeout. // If we were waiting for a successful wormholing, it's come. Stop the timeout.
self.warpify_state.abort_ssh_warpify_timeout(); self.wormhole_state.abort_ssh_wormhole_timeout();
let is_warpified_remote = matches!( let is_wormholed_remote = matches!(
bootstrap_event.session_type, bootstrap_event.session_type,
BootstrapSessionType::WarpifiedRemote BootstrapSessionType::WormholedRemote
); );
if bootstrap_event.subshell_info.is_some() { if bootstrap_event.subshell_info.is_some() {
self.add_bootstrap_success_block(bootstrap_event, ctx); self.add_bootstrap_success_block(bootstrap_event, ctx);
} }
// Show the one-time tmux deprecation notice when an SSH session successfully // Show the one-time tmux deprecation notice when an SSH session successfully
// warpifies. The end-of-ssh-login path (`handle_detected_end_of_ssh_login`) only // wormholes. The end-of-ssh-login path (`handle_detected_end_of_ssh_login`) only
// fires for sessions that stay unwarpified, since warpification replaces the // fires for sessions that stay unwormholed, since wormholing replaces the
// original ssh block before login detection can confirm completion. // original ssh block before login detection can confirm completion.
if is_warpified_remote && WarpifySettings::as_ref(ctx).should_show_tmux_deprecation_notice() if is_wormholed_remote
&& WormholeSettings::as_ref(ctx).should_show_tmux_deprecation_notice()
{ {
self.show_ssh_tmux_deprecation_banner(session_id, ctx); self.show_ssh_tmux_deprecation_banner(session_id, ctx);
} }
@@ -15201,7 +15187,7 @@ impl TerminalView {
// https://github.com/warpdotdev/command-corrections/blob/df7848d4fb3da7883623e959889a296a07d88053/src/rules/cd/mod.rs#L31-L36 // https://github.com/warpdotdev/command-corrections/blob/df7848d4fb3da7883623e959889a296a07d88053/src/rules/cd/mod.rs#L31-L36
// We don't currently support dynamic rules over SSH, so we should not attempt to correct commands if // We don't currently support dynamic rules over SSH, so we should not attempt to correct commands if
// inside ssh session. // inside ssh session.
let is_ssh_command = SshWarpifyCommand::matches(input).is_some(); let is_ssh_command = SshWormholeCommand::matches(input).is_some();
if is_ssh_command { if is_ssh_command {
return vec![]; return vec![];
} }
@@ -19105,7 +19091,7 @@ impl TerminalView {
.and_then(|id| self.sessions.as_ref(ctx).get(id)) .and_then(|id| self.sessions.as_ref(ctx).get(id))
{ {
if let Some(info) = session.subshell_info() { if let Some(info) = session.subshell_info() {
self.warpify_state self.wormhole_state
.add_subshell_separator(info, self.model.clone(), ctx); .add_subshell_separator(info, self.model.clone(), ctx);
} }
} }
@@ -20152,9 +20138,9 @@ impl TerminalView {
env_var_collection_block.clear_selection(ctx); env_var_collection_block.clear_selection(ctx);
}); });
} }
Some(RichContentMetadata::WarpifySuccessBlock { .. }) => { Some(RichContentMetadata::WormholeSuccessBlock { .. }) => {
// TODO(Simon): We should be checking for WarpifySuccessBlocks here as well. // TODO(Simon): We should be checking for WormholeSuccessBlocks here as well.
// The `WarpifySuccessBlock` implements a `SelectableArea`. // The `WormholeSuccessBlock` implements a `SelectableArea`.
} }
_ => {} _ => {}
} }
@@ -23365,7 +23351,7 @@ impl TerminalView {
} else { } else {
// Remote session: pair CWD with the session's host_id. // Remote session: pair CWD with the session's host_id.
let host_id = match session.session_type() { let host_id = match session.session_type() {
SessionType::WarpifiedRemote { host_id } => host_id, SessionType::WormholedRemote { host_id } => host_id,
SessionType::Local => return None, SessionType::Local => return None,
}?; }?;
let std_path = StandardizedPath::try_new(cwd_str).ok()?; let std_path = StandardizedPath::try_new(cwd_str).ok()?;
@@ -24110,7 +24096,7 @@ impl TerminalView {
let mut subshell_separators = HashMap::new(); let mut subshell_separators = HashMap::new();
for (id, command) in self.warpify_state.get_subshell_separators() { for (id, command) in self.wormhole_state.get_subshell_separators() {
subshell_separators.insert(*id, render_subshell_separator(command.clone(), appearance)); subshell_separators.insert(*id, render_subshell_separator(command.clone(), appearance));
} }
@@ -24122,8 +24108,8 @@ impl TerminalView {
.active_block() .active_block()
.block_banner() .block_banner()
.map(|banner| match banner { .map(|banner| match banner {
WithinBlockBanner::WarpifyBanner(state) => { WithinBlockBanner::WormholeBanner(state) => {
render_warpification_banner(state, appearance) render_wormholing_banner(state, appearance)
} }
}); });
@@ -25397,7 +25383,7 @@ impl TerminalView {
} }
/// Replace the terminal input buffer with the given command that is meant to open a subshell. /// Replace the terminal input buffer with the given command that is meant to open a subshell.
/// Set a flag that we should automatically bootstrap AKA "warpify" the subshell when we /// Set a flag that we should automatically bootstrap AKA "wormhole" the subshell when we
/// receive the [`AfterBlockStarted`] event. /// receive the [`AfterBlockStarted`] event.
pub fn insert_subshell_command_and_bootstrap_if_supported( pub fn insert_subshell_command_and_bootstrap_if_supported(
&mut self, &mut self,
@@ -25631,7 +25617,7 @@ impl TerminalView {
shell_type: ShellType, shell_type: ShellType,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
// Attempt to auto warpify the subshell when bootstrapped // Attempt to auto wormhole the subshell when bootstrapped
self.pending_auto_bootstrap_shell_type = Some(shell_type); self.pending_auto_bootstrap_shell_type = Some(shell_type);
self.input.update(ctx, |input, ctx| { self.input.update(ctx, |input, ctx| {
@@ -25853,7 +25839,7 @@ impl TerminalView {
ctx: &mut ViewContext<TerminalView>, ctx: &mut ViewContext<TerminalView>,
) { ) {
match check_type { match check_type {
SshLoginStatus::RecheckBeforeWarpifying => { SshLoginStatus::RecheckBeforeWormholing => {
// After we receive a line of output from ssh that is NOT prompting for user input (unlike "Enter passphrase: "), // After we receive a line of output from ssh that is NOT prompting for user input (unlike "Enter passphrase: "),
// we wait and repeat the check after a small delay in case the state returned to something that's user-input bound. // we wait and repeat the check after a small delay in case the state returned to something that's user-input bound.
// For example, say the output that kicked off this event was "Permission denied, please try again." and // For example, say the output that kicked off this event was "Permission denied, please try again." and
@@ -25875,11 +25861,11 @@ impl TerminalView {
}, },
); );
} }
SshLoginStatus::ReadyToWarpify => { SshLoginStatus::ReadyToWormhole => {
// The tmux-based SSH warpification flow has been removed in favor of the // The tmux-based SSH wormholing flow has been removed in favor of the
// remote-server SSH extension. If this user had previously opted into the tmux // remote-server SSH extension. If this user had previously opted into the tmux
// wrapper, show them a one-time deprecation notice on their next SSH session. // wrapper, show them a one-time deprecation notice on their next SSH session.
if WarpifySettings::as_ref(ctx).should_show_tmux_deprecation_notice() { if WormholeSettings::as_ref(ctx).should_show_tmux_deprecation_notice() {
if let Some(session_id) = self.active_block_session_id() { if let Some(session_id) = self.active_block_session_id() {
self.show_ssh_tmux_deprecation_banner(session_id, ctx); self.show_ssh_tmux_deprecation_banner(session_id, ctx);
} }
@@ -25922,22 +25908,22 @@ impl TerminalView {
let alias_value = session.alias_value(first_word)?; let alias_value = session.alias_value(first_word)?;
Some(format!("{alias_value}{rest}")) Some(format!("{alias_value}{rest}"))
}); });
let warpify_command = expanded_command.as_deref().unwrap_or(command); let wormhole_command = expanded_command.as_deref().unwrap_or(command);
let shell_family = self.shell_family_for_password_prompt_polling(ctx); let shell_family = self.shell_family_for_password_prompt_polling(ctx);
let warpify_settings = WarpifySettings::as_ref(ctx); let wormhole_settings = WormholeSettings::as_ref(ctx);
let is_compatible_subshell_command = warpify_settings let is_compatible_subshell_command = wormhole_settings
.is_compatible_subshell_command(command, shell_family) .is_compatible_subshell_command(command, shell_family)
|| warpify_settings.is_compatible_subshell_command(warpify_command, shell_family); || wormhole_settings.is_compatible_subshell_command(wormhole_command, shell_family);
!is_compatible_subshell_command !is_compatible_subshell_command
} }
/// Shows the warpify footer for a detected subshell command. /// Shows the wormhole footer for a detected subshell command.
fn show_warpify_footer(&mut self, ctx: &mut ViewContext<Self>) { fn show_wormhole_footer(&mut self, ctx: &mut ViewContext<Self>) {
let model = self.model.lock(); let model = self.model.lock();
// Shared session viewers can't initiate warpification currently. // Shared session viewers can't initiate wormholing currently.
// Don't show the warpify footer when an agent is monitoring the command either. // Don't show the wormhole footer when an agent is monitoring the command either.
if model.shared_session_status().is_viewer() if model.shared_session_status().is_viewer()
|| model.block_list().active_block().is_agent_monitoring() || model.block_list().active_block().is_agent_monitoring()
{ {
@@ -25946,11 +25932,11 @@ impl TerminalView {
drop(model); drop(model);
self.use_agent_footer.update(ctx, |footer, ctx| { self.use_agent_footer.update(ctx, |footer, ctx| {
footer.show_warpify(ctx); footer.show_wormhole(ctx);
}); });
self.maybe_show_use_agent_footer_in_blocklist(ctx); self.maybe_show_use_agent_footer_in_blocklist(ctx);
send_telemetry_from_ctx!(TelemetryEvent::WarpifyFooterShown { is_ssh: false }, ctx); send_telemetry_from_ctx!(TelemetryEvent::WormholeFooterShown { is_ssh: false }, ctx);
} }
fn show_initialization_block(&mut self) { fn show_initialization_block(&mut self) {
@@ -26330,7 +26316,7 @@ impl TypedActionView for TerminalView {
"Showed initialization block", "Showed initialization block",
GalaxyA11yRole::TextareaRole, GalaxyA11yRole::TextareaRole,
)), )),
ShowWarpifySettings => Custom(AccessibilityContent::new_without_help( ShowWormholeSettings => Custom(AccessibilityContent::new_without_help(
"Opened Wormhole Settings", "Opened Wormhole Settings",
GalaxyA11yRole::ButtonRole, GalaxyA11yRole::ButtonRole,
)), )),
@@ -26380,7 +26366,7 @@ impl TypedActionView for TerminalView {
| ControlSequence(_) | ControlSequence(_)
| TriggerSubshellBootstrap | TriggerSubshellBootstrap
| ShowSubshellBanner(_) | ShowSubshellBanner(_)
| DismissWarpifyBanner(_) | DismissWormholeBanner(_)
| OpenBlockListContextMenu | OpenBlockListContextMenu
| AliasExpansionBanner(_) | AliasExpansionBanner(_)
| VimModeBanner(_) | VimModeBanner(_)
@@ -26891,21 +26877,21 @@ impl TypedActionView for TerminalView {
TriggerSubshellBootstrap => self.trigger_subshell_bootstrap(None, false, ctx), TriggerSubshellBootstrap => self.trigger_subshell_bootstrap(None, false, ctx),
ShowSubshellBanner(command) => { ShowSubshellBanner(command) => {
// Abort handle is no longer needed since we've waited the 1s already. // Abort handle is no longer needed since we've waited the 1s already.
self.warpify_state.take_subshell_banner_abort_handle(); self.wormhole_state.take_subshell_banner_abort_handle();
let warpify_keybinding = let wormhole_keybinding =
keybinding_name_to_keystroke("terminal:warpify_subshell", ctx); keybinding_name_to_keystroke("terminal:wormhole_subshell", ctx);
self.show_warpify_banner( self.show_wormhole_banner(
command.to_owned(), command.to_owned(),
"Subshell", "Subshell",
"subshell", "subshell",
warpify_keybinding, wormhole_keybinding,
TelemetryEvent::ShowSubshellBanner, TelemetryEvent::ShowSubshellBanner,
ctx, ctx,
); );
} }
DismissWarpifyBanner(remember) => { DismissWormholeBanner(remember) => {
self.dismiss_warpify_banner(remember, ctx); self.dismiss_wormhole_banner(remember, ctx);
if !remember.is_ssh() { if !remember.is_ssh() {
send_telemetry_from_ctx!( send_telemetry_from_ctx!(
TelemetryEvent::DeclineSubshellBootstrap { TelemetryEvent::DeclineSubshellBootstrap {
@@ -27142,7 +27128,7 @@ impl TypedActionView for TerminalView {
LoadAgentModeConversation => { LoadAgentModeConversation => {
self.load_agent_mode_conversation(ctx); self.load_agent_mode_conversation(ctx);
} }
ShowWarpifySettings => ctx.emit(Event::OpenSettings(SettingsSection::Warpify)), ShowWormholeSettings => ctx.emit(Event::OpenSettings(SettingsSection::Wormhole)),
DeleteAttachment { index } => { DeleteAttachment { index } => {
self.ai_context_model.update(ctx, |context_model, ctx| { self.ai_context_model.update(ctx, |context_model, ctx| {
context_model.remove_pending_attachment(*index, ctx); context_model.remove_pending_attachment(*index, ctx);
@@ -28374,15 +28360,15 @@ impl View for TerminalView {
context.set.insert(init::ROOT_CLOUD_MODE_PANE_KEY); context.set.insert(init::ROOT_CLOUD_MODE_PANE_KEY);
} }
if let Some(WithinBlockBanner::WarpifyBanner(_)) = if let Some(WithinBlockBanner::WormholeBanner(_)) =
model_lock.block_list().active_block().block_banner() model_lock.block_list().active_block().block_banner()
{ {
context.set.insert("SubshellBanner"); context.set.insert("SubshellBanner");
} }
// Also set the warpify context when the footer (flag-gated replacement // Also set the wormhole context when the footer (flag-gated replacement
// for the in-block banner) is active, so the ctrl-i keybinding works. // for the in-block banner) is active, so the ctrl-i keybinding works.
if self.use_agent_footer.as_ref(app).is_warpify_active(app) { if self.use_agent_footer.as_ref(app).is_wormhole_active(app) {
context.set.insert("SubshellBanner"); context.set.insert("SubshellBanner");
} }
+15 -15
View File
@@ -67,7 +67,7 @@ pub enum OnboardingVersion {
/// This represents whether entering a subshell for a particular command should become automatic in /// This represents whether entering a subshell for a particular command should become automatic in
/// the future, or to ask again. /// the future, or to ask again.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum RememberForWarpification { pub enum RememberForWormholing {
/// If yes, need to transmit the command itself so it can be persisted to user-defaults /// If yes, need to transmit the command itself so it can be persisted to user-defaults
RememberSubshellCommand(String), RememberSubshellCommand(String),
RememberSSHHost(String), RememberSSHHost(String),
@@ -75,22 +75,22 @@ pub enum RememberForWarpification {
DoNotRememberSSHHost, DoNotRememberSSHHost,
} }
impl RememberForWarpification { impl RememberForWormholing {
pub fn as_bool(&self) -> bool { pub fn as_bool(&self) -> bool {
match self { match self {
RememberForWarpification::RememberSubshellCommand(_) => true, RememberForWormholing::RememberSubshellCommand(_) => true,
RememberForWarpification::RememberSSHHost(_) => true, RememberForWormholing::RememberSSHHost(_) => true,
RememberForWarpification::DoNotRememberSubshellCommand => false, RememberForWormholing::DoNotRememberSubshellCommand => false,
RememberForWarpification::DoNotRememberSSHHost => false, RememberForWormholing::DoNotRememberSSHHost => false,
} }
} }
pub fn is_ssh(&self) -> bool { pub fn is_ssh(&self) -> bool {
match self { match self {
RememberForWarpification::RememberSSHHost(_) => true, RememberForWormholing::RememberSSHHost(_) => true,
RememberForWarpification::DoNotRememberSSHHost => true, RememberForWormholing::DoNotRememberSSHHost => true,
RememberForWarpification::RememberSubshellCommand(_) => false, RememberForWormholing::RememberSubshellCommand(_) => false,
RememberForWarpification::DoNotRememberSubshellCommand => false, RememberForWormholing::DoNotRememberSubshellCommand => false,
} }
} }
} }
@@ -284,8 +284,8 @@ pub enum TerminalAction {
}, },
/// Starts a subshell in the active session. /// Starts a subshell in the active session.
TriggerSubshellBootstrap, TriggerSubshellBootstrap,
/// If the user says "no" to Warpification, possibly requesting not to be asked again /// If the user says "no" to Wormholing, possibly requesting not to be asked again
DismissWarpifyBanner(RememberForWarpification), DismissWormholeBanner(RememberForWormholing),
/// Triggers the banner asking to turn the running block into a subshell. The String is the /// Triggers the banner asking to turn the running block into a subshell. The String is the
/// command that the user entered. /// command that the user entered.
ShowSubshellBanner(String), ShowSubshellBanner(String),
@@ -342,7 +342,7 @@ pub enum TerminalAction {
GenerateCodebaseIndex, GenerateCodebaseIndex,
/// This is for debugging, dev only for now /// This is for debugging, dev only for now
LoadAgentModeConversation, LoadAgentModeConversation,
ShowWarpifySettings, ShowWormholeSettings,
/// Removes a pending attachment (image or file) by index in the unified list. /// Removes a pending attachment (image or file) by index in the unified list.
DeleteAttachment { DeleteAttachment {
index: usize, index: usize,
@@ -622,7 +622,7 @@ impl fmt::Debug for TerminalAction {
OpenBlockListContextMenu => f.write_str("OpenBlockListContextMenu"), OpenBlockListContextMenu => f.write_str("OpenBlockListContextMenu"),
AskAIAssistant { block_index } => write!(f, "AskAIAssistant({block_index:?})"), AskAIAssistant { block_index } => write!(f, "AskAIAssistant({block_index:?})"),
TriggerSubshellBootstrap => f.write_str("TriggerSubshellBootstrap"), TriggerSubshellBootstrap => f.write_str("TriggerSubshellBootstrap"),
DismissWarpifyBanner(remember) => write!(f, "DismissWarpifyBanner({remember:?})"), DismissWormholeBanner(remember) => write!(f, "DismissWormholeBanner({remember:?})"),
ShowSubshellBanner(_) => f.write_str("ShowSubshellBanner"), ShowSubshellBanner(_) => f.write_str("ShowSubshellBanner"),
InsertMostRecentCommandCorrection => f.write_str("InsertMostRecentCommandCorrection"), InsertMostRecentCommandCorrection => f.write_str("InsertMostRecentCommandCorrection"),
AliasExpansionBanner(action) => write!(f, "AliasExpansionBanner({action:?}"), AliasExpansionBanner(action) => write!(f, "AliasExpansionBanner({action:?}"),
@@ -682,7 +682,7 @@ impl fmt::Debug for TerminalAction {
ShowInitializationBlock => write!(f, "ShowInitializationBlock"), ShowInitializationBlock => write!(f, "ShowInitializationBlock"),
GenerateCodebaseIndex => write!(f, "GenerateIndexForRepo"), GenerateCodebaseIndex => write!(f, "GenerateIndexForRepo"),
LoadAgentModeConversation => write!(f, "LoadAgentModeConversation"), LoadAgentModeConversation => write!(f, "LoadAgentModeConversation"),
ShowWarpifySettings => write!(f, "ShowWarpifySettings"), ShowWormholeSettings => write!(f, "ShowWormholeSettings"),
DeleteAttachment { index } => write!(f, "DeleteAttachment({index:?})"), DeleteAttachment { index } => write!(f, "DeleteAttachment({index:?})"),
OpenAttachmentLightbox { index } => { OpenAttachmentLightbox { index } => {
write!(f, "OpenAttachmentLightbox({index:?})") write!(f, "OpenAttachmentLightbox({index:?})")
+4 -4
View File
@@ -6,14 +6,14 @@
//! without a LayoutContext. Use the exported BLOCK_BANNER_HEIGHT const when the banner height //! without a LayoutContext. Use the exported BLOCK_BANNER_HEIGHT const when the banner height
//! needs to be taken into account. //! needs to be taken into account.
mod warpify; mod wormhole;
use galaxyui::elements::{ use galaxyui::elements::{
ConstrainedBox, Container, CornerRadius, Hoverable, MouseState, MouseStateHandle, ConstrainedBox, Container, CornerRadius, Hoverable, MouseState, MouseStateHandle,
ParentElement, Radius, Stack, ParentElement, Radius, Stack,
}; };
use galaxyui::Element; use galaxyui::Element;
pub use warpify::*; pub use wormhole::*;
use crate::themes::theme::GalaxyTheme; use crate::themes::theme::GalaxyTheme;
@@ -25,13 +25,13 @@ const BANNER_H_PADDING: f32 = 8.;
pub const BLOCK_BANNER_HEIGHT: f32 = CONSTRAINED_BANNER_HEIGHT + BANNER_TOP_MARGIN; pub const BLOCK_BANNER_HEIGHT: f32 = CONSTRAINED_BANNER_HEIGHT + BANNER_TOP_MARGIN;
pub enum WithinBlockBanner { pub enum WithinBlockBanner {
WarpifyBanner(WarpifyBannerState), WormholeBanner(WormholeBannerState),
} }
impl WithinBlockBanner { impl WithinBlockBanner {
pub fn banner_height(&self) -> f32 { pub fn banner_height(&self) -> f32 {
match self { match self {
WithinBlockBanner::WarpifyBanner(_) => BLOCK_BANNER_HEIGHT, WithinBlockBanner::WormholeBanner(_) => BLOCK_BANNER_HEIGHT,
} }
} }
} }
@@ -10,14 +10,14 @@ use pathfinder_color::ColorU;
use super::render_block_banner; use super::render_block_banner;
use crate::appearance::Appearance; use crate::appearance::Appearance;
use crate::terminal::view::{RememberForWarpification, TerminalAction}; use crate::terminal::view::{RememberForWormholing, TerminalAction};
use crate::themes::theme::Fill; use crate::themes::theme::Fill;
use crate::ui_components::blended_colors; use crate::ui_components::blended_colors;
const CLOSE_BUTTON_DIAMETER: f32 = 20.0; const CLOSE_BUTTON_DIAMETER: f32 = 20.0;
const STANDARD_PADDING: f32 = 8.0; const STANDARD_PADDING: f32 = 8.0;
pub struct WarpifyBannerState { pub struct WormholeBannerState {
/// The subshell command that triggered the banner. /// The subshell command that triggered the banner.
pub command: String, pub command: String,
pub height: f32, pub height: f32,
@@ -25,19 +25,19 @@ pub struct WarpifyBannerState {
pub dont_ask_button_mouse_state: MouseStateHandle, pub dont_ask_button_mouse_state: MouseStateHandle,
pub dismiss_button_mouse_state: MouseStateHandle, pub dismiss_button_mouse_state: MouseStateHandle,
/// This keybinding gets rendered in the Warpification banner, but we can't look it up /// This keybinding gets rendered in the Wormholing banner, but we can't look it up
/// during render as a &mut AppContext is not available then. This needs to get /// during render as a &mut AppContext is not available then. This needs to get
/// looked up during action handling and cached here. /// looked up during action handling and cached here.
pub initialize_warpify_keybinding: Option<Keystroke>, pub initialize_wormhole_keybinding: Option<Keystroke>,
pub hover_state: MouseStateHandle, pub hover_state: MouseStateHandle,
} }
impl WarpifyBannerState { impl WormholeBannerState {
pub fn new(command: String, initialize_warpify_keybinding: Option<Keystroke>) -> Self { pub fn new(command: String, initialize_wormhole_keybinding: Option<Keystroke>) -> Self {
Self { Self {
command, command,
height: 0.0, height: 0.0,
initialize_warpify_keybinding, initialize_wormhole_keybinding,
accept_button_mouse_state: Default::default(), accept_button_mouse_state: Default::default(),
dont_ask_button_mouse_state: Default::default(), dont_ask_button_mouse_state: Default::default(),
dismiss_button_mouse_state: Default::default(), dismiss_button_mouse_state: Default::default(),
@@ -46,18 +46,18 @@ impl WarpifyBannerState {
} }
pub fn title(&self) -> &str { pub fn title(&self) -> &str {
"Warpify subshell" "Wormhole subshell"
} }
pub fn action(&self) -> TerminalAction { pub fn action(&self) -> TerminalAction {
TerminalAction::TriggerSubshellBootstrap TerminalAction::TriggerSubshellBootstrap
} }
fn remember_for_warpification(&self, should_remember: bool) -> RememberForWarpification { fn remember_for_wormholing(&self, should_remember: bool) -> RememberForWormholing {
if should_remember { if should_remember {
RememberForWarpification::RememberSubshellCommand(self.command.to_owned()) RememberForWormholing::RememberSubshellCommand(self.command.to_owned())
} else { } else {
RememberForWarpification::DoNotRememberSubshellCommand RememberForWormholing::DoNotRememberSubshellCommand
} }
} }
} }
@@ -65,18 +65,18 @@ impl WarpifyBannerState {
/// This banner is shown when the user runs a command which is recognized as a subshell-compatible /// This banner is shown when the user runs a command which is recognized as a subshell-compatible
/// command. It asks if they want to bootstrap a subshell and, if so, whether we should ask again /// command. It asks if they want to bootstrap a subshell and, if so, whether we should ask again
/// next time they run the same command. /// next time they run the same command.
pub fn render_warpification_banner( pub fn render_wormholing_banner(
state: &WarpifyBannerState, state: &WormholeBannerState,
appearance: &Appearance, appearance: &Appearance,
) -> Box<dyn Element> { ) -> Box<dyn Element> {
let yes_button = render_yes_button( let yes_button = render_yes_button(
state, state,
&state.initialize_warpify_keybinding, &state.initialize_wormhole_keybinding,
&state.accept_button_mouse_state, &state.accept_button_mouse_state,
appearance, appearance,
); );
let remember = state.remember_for_warpification(true); let remember = state.remember_for_wormholing(true);
let dont_ask_button = Container::new( let dont_ask_button = Container::new(
appearance appearance
.ui_builder() .ui_builder()
@@ -87,7 +87,7 @@ pub fn render_warpification_banner(
.with_text_label("Do not show again".to_owned()) .with_text_label("Do not show again".to_owned())
.build() .build()
.on_click(move |ctx, _, _| { .on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner( ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner(
remember.to_owned(), remember.to_owned(),
)); ));
}) })
@@ -96,7 +96,7 @@ pub fn render_warpification_banner(
.with_margin_right(16.) .with_margin_right(16.)
.finish(); .finish();
let do_not_remember = state.remember_for_warpification(false); let do_not_remember = state.remember_for_wormholing(false);
let close_button = appearance let close_button = appearance
.ui_builder() .ui_builder()
.close_button( .close_button(
@@ -105,7 +105,7 @@ pub fn render_warpification_banner(
) )
.build() .build()
.on_click(move |ctx, _, _| { .on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner( ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner(
do_not_remember.to_owned(), do_not_remember.to_owned(),
)); ));
}) })
@@ -132,12 +132,12 @@ pub fn render_warpification_banner(
} }
fn render_yes_button( fn render_yes_button(
state: &WarpifyBannerState, state: &WormholeBannerState,
initialize_warpification_keybinding: &Option<Keystroke>, initialize_wormholing_keybinding: &Option<Keystroke>,
mouse_state: &MouseStateHandle, mouse_state: &MouseStateHandle,
appearance: &Appearance, appearance: &Appearance,
) -> Box<dyn Element> { ) -> Box<dyn Element> {
let yes_button = match initialize_warpification_keybinding { let yes_button = match initialize_wormholing_keybinding {
Some(keystroke) => appearance Some(keystroke) => appearance
.ui_builder() .ui_builder()
.keyboard_shortcut_button(state.title().to_owned(), keystroke, mouse_state.clone()) .keyboard_shortcut_button(state.title().to_owned(), keystroke, mouse_state.clone())
+3 -3
View File
@@ -81,8 +81,8 @@ pub fn init(app: &mut AppContext) {
app.register_binding_validator::<TerminalView>(is_binding_pty_compliant); app.register_binding_validator::<TerminalView>(is_binding_pty_compliant);
init_overlapping_keybindings(app); init_overlapping_keybindings(app);
// Register input mode bindings before warpify bindings so ctrl-i warpifies // Register input mode bindings before wormhole bindings so ctrl-i wormholes
// instead of opening inline agent when a warpify banner is visible. // instead of opening inline agent when a wormhole banner is visible.
register_input_mode_bindings(app); register_input_mode_bindings(app);
app.register_fixed_bindings([ app.register_fixed_bindings([
@@ -320,7 +320,7 @@ pub fn init(app: &mut AppContext) {
| (id!("Terminal") & !id!("IMEOpen") & id!(flags::CLI_AGENT_RICH_INPUT_OPEN)), | (id!("Terminal") & !id!("IMEOpen") & id!(flags::CLI_AGENT_RICH_INPUT_OPEN)),
), ),
EditableBinding::new( EditableBinding::new(
"terminal:warpify_subshell", "terminal:wormhole_subshell",
"Wormhole subshell", "Wormhole subshell",
TerminalAction::TriggerSubshellBootstrap, TerminalAction::TriggerSubshellBootstrap,
) )
+3 -3
View File
@@ -18,7 +18,7 @@ use crate::terminal::view::init_environment::InitEnvironmentBlock;
use crate::terminal::view::ssh_remote_server_choice_view::SshRemoteServerChoiceView; use crate::terminal::view::ssh_remote_server_choice_view::SshRemoteServerChoiceView;
use crate::terminal::view::ssh_remote_server_failed_banner::SshRemoteServerFailedBanner; use crate::terminal::view::ssh_remote_server_failed_banner::SshRemoteServerFailedBanner;
use crate::terminal::view::ssh_tmux_deprecation_banner::SshTmuxDeprecationBanner; use crate::terminal::view::ssh_tmux_deprecation_banner::SshTmuxDeprecationBanner;
use crate::terminal::warpify::success_block::WarpifySuccessBlock; use crate::terminal::wormhole::success_block::WormholeSuccessBlock;
use crate::terminal::TerminalView; use crate::terminal::TerminalView;
/// Specifies where to insert rich content in the blocklist. /// Specifies where to insert rich content in the blocklist.
@@ -249,8 +249,8 @@ pub enum RichContentMetadata {
SshTmuxDeprecationBanner { SshTmuxDeprecationBanner {
handle: ViewHandle<SshTmuxDeprecationBanner>, handle: ViewHandle<SshTmuxDeprecationBanner>,
}, },
WarpifySuccessBlock { WormholeSuccessBlock {
bootstrap_success_block_handle: ViewHandle<WarpifySuccessBlock>, bootstrap_success_block_handle: ViewHandle<WormholeSuccessBlock>,
}, },
TelemetryBanner { TelemetryBanner {
telemetry_banner_handle: ViewHandle<TelemetryBanner>, telemetry_banner_handle: ViewHandle<TelemetryBanner>,
+1 -1
View File
@@ -187,7 +187,7 @@ impl FileUpload {
} }
} }
/// Creates an sftp command that copies a given local file into the PWD of the warpified ssh session, if any. /// Creates an sftp command that copies a given local file into the PWD of the wormholed ssh session, if any.
fn transfer_file_sftp_command(&self, file_upload: &FileUploadInfo) -> String { fn transfer_file_sftp_command(&self, file_upload: &FileUploadInfo) -> String {
// "sftp " // "sftp "
let mut command = String::from("sftp "); let mut command = String::from("sftp ");
@@ -1,14 +1,14 @@
//! Inline block view that asks the user whether they want to install //! Inline block view that asks the user whether they want to install
//! Warp's SSH extension on the remote host the shell just connected to, //! Wormhole's remote helper on the host the shell just connected to,
//! or continue without installing (falling back to the existing //! or continue without installing (falling back to the existing
//! ControlMaster warpification path). //! ControlMaster wormholing path).
//! //!
//! Designed from frame 6050:2448 of the Figma file //! Designed from frame 6050:2448 of the Figma file
//! [Remote session initialization](https://www.figma.com/design/r0BO9cTZCK6pDE6qerg2K0/Remote-session-initialization). //! [Remote session initialization](https://www.figma.com/design/r0BO9cTZCK6pDE6qerg2K0/Remote-session-initialization).
//! //!
//! The view owns: //! The view owns:
//! - a child [`KeyboardNavigableButtons`] handle for the two selectable //! - a child [`KeyboardNavigableButtons`] handle for the two selectable
//! cards ("Install Warp's SSH extension" / "Continue without installing"), //! cards ("Install Wormhole helper" / "Continue without installing"),
//! - the [`SessionId`] this prompt is scoped to (used for event forwarding), //! - the [`SessionId`] this prompt is scoped to (used for event forwarding),
//! - the current "Don't ask me this again" checked state (purely local to //! - the current "Don't ask me this again" checked state (purely local to
//! this prompt instance; persisted to `ssh_extension_install_mode` only //! this prompt instance; persisted to `ssh_extension_install_mode` only
@@ -37,7 +37,7 @@ use crate::ai::blocklist::inline_action::inline_action_header::{
}; };
use crate::server::telemetry::TelemetryEvent; use crate::server::telemetry::TelemetryEvent;
use crate::terminal::model::session::SessionId; use crate::terminal::model::session::SessionId;
use crate::terminal::warpify::settings::{SshExtensionInstallMode, WarpifySettings}; use crate::terminal::wormhole::settings::{SshExtensionInstallMode, WormholeSettings};
use crate::ui_components::blended_colors; use crate::ui_components::blended_colors;
use crate::{send_telemetry_from_ctx, Appearance}; use crate::{send_telemetry_from_ctx, Appearance};
@@ -48,14 +48,14 @@ pub enum SshRemoteServerChoiceViewAction {
Install, Install,
Skip, Skip,
ToggleDoNotAskAgain, ToggleDoNotAskAgain,
OpenWarpifySettings, OpenWormholeSettings,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SshRemoteServerChoiceViewEvent { pub enum SshRemoteServerChoiceViewEvent {
Install, Install,
Skip, Skip,
OpenWarpifySettings, OpenWormholeSettings,
} }
/// Choice block prompting the user to install the remote-server binary on the remote host or skip. /// Choice block prompting the user to install the remote-server binary on the remote host or skip.
@@ -74,7 +74,7 @@ impl SshRemoteServerChoiceView {
let buttons = ctx.add_typed_action_view(|_| { let buttons = ctx.add_typed_action_view(|_| {
KeyboardNavigableButtons::new(vec![ KeyboardNavigableButtons::new(vec![
rich_navigation_button( rich_navigation_button(
"Install Galaxy's SSH extension".to_string(), "Install Wormhole helper".to_string(),
Some( Some(
"Install Galaxy's extension to enable agent features like file browsing, \ "Install Galaxy's extension to enable agent features like file browsing, \
code review, and intelligent command completions in this session." code review, and intelligent command completions in this session."
@@ -171,14 +171,16 @@ impl SshRemoteServerChoiceView {
.with_child(Container::new(checkbox_label).with_margin_left(4.).finish()) .with_child(Container::new(checkbox_label).with_margin_left(4.).finish())
.finish(); .finish();
// Right: "Manage Warpify settings" link. // Right: "Manage Wormhole settings" link.
let manage_settings_link = appearance let manage_settings_link = appearance
.ui_builder() .ui_builder()
.link( .link(
"Manage Wormhole settings".into(), "Manage Wormhole settings".into(),
None, None,
Some(Box::new(|ctx| { Some(Box::new(|ctx| {
ctx.dispatch_typed_action(SshRemoteServerChoiceViewAction::OpenWarpifySettings); ctx.dispatch_typed_action(
SshRemoteServerChoiceViewAction::OpenWormholeSettings,
);
})), })),
self.manage_settings_mouse_state.clone(), self.manage_settings_mouse_state.clone(),
) )
@@ -264,7 +266,7 @@ impl TypedActionView for SshRemoteServerChoiceView {
SshRemoteServerChoiceViewAction::Install => { SshRemoteServerChoiceViewAction::Install => {
if self.do_not_ask_again { if self.do_not_ask_again {
let mode = SshExtensionInstallMode::AlwaysInstall; let mode = SshExtensionInstallMode::AlwaysInstall;
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| { WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) { if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
log::error!("Failed to persist ssh_extension_install_mode: {e}"); log::error!("Failed to persist ssh_extension_install_mode: {e}");
} }
@@ -281,7 +283,7 @@ impl TypedActionView for SshRemoteServerChoiceView {
SshRemoteServerChoiceViewAction::Skip => { SshRemoteServerChoiceViewAction::Skip => {
if self.do_not_ask_again { if self.do_not_ask_again {
let mode = SshExtensionInstallMode::NeverInstall; let mode = SshExtensionInstallMode::NeverInstall;
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| { WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) { if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
log::error!("Failed to persist ssh_extension_install_mode: {e}"); log::error!("Failed to persist ssh_extension_install_mode: {e}");
} }
@@ -305,8 +307,8 @@ impl TypedActionView for SshRemoteServerChoiceView {
); );
ctx.notify(); ctx.notify();
} }
SshRemoteServerChoiceViewAction::OpenWarpifySettings => { SshRemoteServerChoiceViewAction::OpenWormholeSettings => {
ctx.emit(SshRemoteServerChoiceViewEvent::OpenWarpifySettings); ctx.emit(SshRemoteServerChoiceViewEvent::OpenWormholeSettings);
} }
} }
} }
@@ -1,5 +1,5 @@
//! Banner shown when the remote-server binary check, installation, or connection fails on the remote host. //! Banner shown when the remote-server binary check, installation, or connection fails on the remote host.
//! We fall back to the existing Warpification behavior and display this banner so the user knows why advanced features are unavailable. //! We fall back to the existing Wormholing behavior and display this banner so the user knows why advanced features are unavailable.
use galaxy_core::ui::theme::color::internal_colors; use galaxy_core::ui::theme::color::internal_colors;
use galaxy_core::ui::theme::AnsiColorIdentifier; use galaxy_core::ui::theme::AnsiColorIdentifier;
@@ -15,11 +15,11 @@ use crate::terminal::model::session::SessionId;
use crate::ui_components::icons::Icon; use crate::ui_components::icons::Icon;
use crate::Appearance; use crate::Appearance;
const BANNER_TITLE: &str = "Couldn't connect to the Warp SSH extension"; const BANNER_TITLE: &str = "Couldn't connect to the Wormhole helper";
const BANNER_BODY: &str = const BANNER_BODY: &str =
"While advanced features like file browsing and code review are currently \ "While advanced features like file browsing and code review are currently \
disabled, the rest of your Warpified experience is fully available."; disabled, the rest of your Wormholed experience is fully available.";
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SshRemoteServerFailedBannerAction { pub enum SshRemoteServerFailedBannerAction {
@@ -1,6 +1,6 @@
//! One-time inline banner shown to users who had previously opted into the now-deprecated //! One-time inline banner shown to users who had previously opted into the now-deprecated
//! tmux-based SSH warpification flow. It explains that tmux SSH warpification has been turned //! tmux-based SSH wormholing flow. It explains that tmux SSH wormholing has been turned
//! off in favor of Warp's SSH extension (remote server) and links to the docs. //! off in favor of Galaxy's SSH extension (remote server).
//! //!
//! The banner is shown at most once per affected user: it is gated on the //! The banner is shown at most once per affected user: it is gated on the
//! `ssh_tmux_deprecation_notice_pending` setting, which is set by a one-time migration and //! `ssh_tmux_deprecation_notice_pending` setting, which is set by a one-time migration and
@@ -8,29 +8,25 @@
use galaxy_core::ui::theme::color::internal_colors; use galaxy_core::ui::theme::color::internal_colors;
use warpui::elements::{ use warpui::elements::{
Align, ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment, ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment,
MainAxisSize, MouseStateHandle, ParentElement, Shrinkable, Text, MainAxisSize, MouseStateHandle, ParentElement, Shrinkable, Text,
}; };
use warpui::platform::Cursor; use warpui::platform::Cursor;
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext}; use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
use crate::terminal::model::session::SessionId; use crate::terminal::model::session::SessionId;
use crate::terminal::warpify::render::SSH_DOCS_URL;
use crate::ui_components::icons::Icon; use crate::ui_components::icons::Icon;
use crate::Appearance; use crate::Appearance;
const BANNER_TITLE: &str = "Tmux SSH warpification has been deprecated"; const BANNER_TITLE: &str = "Legacy tmux SSH wormholing has been retired";
const BANNER_BODY: &str = "Warp now connects to remote sessions using the SSH extension, which is \ const BANNER_BODY: &str =
"Galaxy now connects to remote sessions using the SSH extension, which is \
more robust than the tmux-based flow. The tmux option has been removed."; more robust than the tmux-based flow. The tmux option has been removed.";
const LEARN_MORE_LABEL: &str = "Learn more";
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SshTmuxDeprecationBannerAction { pub enum SshTmuxDeprecationBannerAction {
Dismiss, Dismiss,
LearnMore,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -40,7 +36,6 @@ pub enum SshTmuxDeprecationBannerEvent {
pub struct SshTmuxDeprecationBanner { pub struct SshTmuxDeprecationBanner {
session_id: SessionId, session_id: SessionId,
learn_more_mouse_state: MouseStateHandle,
close_mouse_state: MouseStateHandle, close_mouse_state: MouseStateHandle,
} }
@@ -48,7 +43,6 @@ impl SshTmuxDeprecationBanner {
pub fn new(session_id: SessionId) -> Self { pub fn new(session_id: SessionId) -> Self {
Self { Self {
session_id, session_id,
learn_more_mouse_state: MouseStateHandle::default(),
close_mouse_state: MouseStateHandle::default(), close_mouse_state: MouseStateHandle::default(),
} }
} }
@@ -72,13 +66,12 @@ impl View for SshTmuxDeprecationBanner {
let theme = appearance.theme(); let theme = appearance.theme();
let fg_color = theme.foreground().into_solid(); let fg_color = theme.foreground().into_solid();
let muted_color = internal_colors::neutral_5(theme); let muted_color = internal_colors::neutral_5(theme);
let accent_color = theme.accent().into_solid();
let font_size = appearance.monospace_font_size(); let font_size = appearance.monospace_font_size();
let small_font_size = font_size - 2.; let small_font_size = font_size - 2.;
// Warp icon to match the other warpification blocks. // Galaxy icon to match the other wormholing blocks.
let icon = Container::new( let icon = Container::new(
ConstrainedBox::new(Icon::Warp.to_warpui_icon(fg_color.into()).finish()) ConstrainedBox::new(Icon::GalaxyLogo.to_warpui_icon(fg_color.into()).finish())
.with_width(16.) .with_width(16.)
.with_height(16.) .with_height(16.)
.finish(), .finish(),
@@ -103,26 +96,6 @@ impl View for SshTmuxDeprecationBanner {
.with_color(muted_color) .with_color(muted_color)
.finish(); .finish();
let learn_more = appearance
.ui_builder()
.link(
LEARN_MORE_LABEL.into(),
None,
Some(Box::new(|ctx| {
ctx.dispatch_typed_action(SshTmuxDeprecationBannerAction::LearnMore);
})),
self.learn_more_mouse_state.clone(),
)
.soft_wrap(false)
.with_style(UiComponentStyles {
font_size: Some(small_font_size),
font_family_id: Some(appearance.ui_font_family()),
font_color: Some(accent_color),
..Default::default()
})
.build()
.finish();
// Close (X) button // Close (X) button
let close_icon_color = muted_color; let close_icon_color = muted_color;
let close = Hoverable::new(self.close_mouse_state.clone(), move |_| { let close = Hoverable::new(self.close_mouse_state.clone(), move |_| {
@@ -158,26 +131,17 @@ impl View for SshTmuxDeprecationBanner {
.with_child(close_container) .with_child(close_container)
.finish(); .finish();
// Body text + learn more link, indented past the icon to align with the title. // Body text, indented past the icon to align with the title.
let body_container = Container::new(body) let body_container = Container::new(body)
.with_margin_top(2.) .with_margin_top(2.)
.with_margin_left(24.) .with_margin_left(24.)
.finish(); .finish();
// Wrap the link in a left-aligned `Align` so its hover/underline region hugs the
// link text instead of stretching to the full banner width (the parent column uses
// `CrossAxisAlignment::Stretch`).
let learn_more_container = Container::new(Align::new(learn_more).left().finish())
.with_margin_top(4.)
.with_margin_left(24.)
.finish();
let content = Flex::column() let content = Flex::column()
.with_main_axis_size(MainAxisSize::Min) .with_main_axis_size(MainAxisSize::Min)
.with_cross_axis_alignment(CrossAxisAlignment::Stretch) .with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_child(header_row) .with_child(header_row)
.with_child(body_container) .with_child(body_container)
.with_child(learn_more_container)
.finish(); .finish();
Container::new(content) Container::new(content)
@@ -195,10 +159,6 @@ impl TypedActionView for SshTmuxDeprecationBanner {
SshTmuxDeprecationBannerAction::Dismiss => { SshTmuxDeprecationBannerAction::Dismiss => {
ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed); ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed);
} }
SshTmuxDeprecationBannerAction::LearnMore => {
ctx.open_url(SSH_DOCS_URL);
ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed);
}
} }
} }
} }
+36 -36
View File
@@ -16,7 +16,7 @@ use crate::terminal::shared_session::{
SharedSessionActionSource, SharedSessionScrollbackType, SharedSessionSource, SharedSessionActionSource, SharedSessionScrollbackType, SharedSessionSource,
}; };
use crate::util::image::{infer_mime_type, MAX_IMAGE_SIZE_BYTES_FOR_CLI_AGENT, MIME_SNIFF_BYTES}; use crate::util::image::{infer_mime_type, MAX_IMAGE_SIZE_BYTES_FOR_CLI_AGENT, MIME_SNIFF_BYTES};
mod warpify_footer; mod wormhole_footer;
use std::path::Path; use std::path::Path;
use std::sync::{Arc, LazyLock}; use std::sync::{Arc, LazyLock};
@@ -44,7 +44,7 @@ use galaxyui::{
}; };
use parking_lot::FairMutex; use parking_lot::FairMutex;
use pathfinder_color::ColorU; use pathfinder_color::ColorU;
use warpify_footer::{WarpifyFooterView, WarpifyFooterViewEvent}; use wormhole_footer::{WormholeFooterView, WormholeFooterViewEvent};
use super::{RichContentInsertionPosition, TerminalAction, TerminalView}; use super::{RichContentInsertionPosition, TerminalAction, TerminalView};
use crate::ai::blocklist::agent_view::agent_view_bg_fill; use crate::ai::blocklist::agent_view::agent_view_bg_fill;
@@ -267,11 +267,11 @@ impl TerminalView {
UseAgentToolbarEvent::HideRichInput => { UseAgentToolbarEvent::HideRichInput => {
self.close_cli_agent_rich_input_and_disable_auto_toggle(ctx); self.close_cli_agent_rich_input_and_disable_auto_toggle(ctx);
} }
UseAgentToolbarEvent::Warpify => { UseAgentToolbarEvent::Wormhole => {
self.hide_use_agent_footer_in_blocklist(ctx); self.hide_use_agent_footer_in_blocklist(ctx);
self.handle_action(&TerminalAction::TriggerSubshellBootstrap, ctx); self.handle_action(&TerminalAction::TriggerSubshellBootstrap, ctx);
send_telemetry_from_ctx!( send_telemetry_from_ctx!(
TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh: false }, TelemetryEvent::WormholeFooterAcceptedWormhole { is_ssh: false },
ctx ctx
); );
} }
@@ -295,8 +295,8 @@ impl TerminalView {
) -> bool { ) -> bool {
let ai_settings = AISettings::as_ref(app); let ai_settings = AISettings::as_ref(app);
// If the warpify footer is active, a subshell was detected and we should show the footer. // If the wormhole footer is active, a subshell was detected and we should show the footer.
if self.use_agent_footer.as_ref(app).is_warpify_active(app) { if self.use_agent_footer.as_ref(app).is_wormhole_active(app) {
return true; return true;
} }
@@ -421,7 +421,7 @@ impl TerminalView {
if !self.model.lock().is_alt_screen_active() { if !self.model.lock().is_alt_screen_active() {
self.use_agent_footer.update(ctx, |footer, ctx| { self.use_agent_footer.update(ctx, |footer, ctx| {
footer.clear_warpify(ctx); footer.clear_wormhole(ctx);
}); });
self.hide_use_agent_footer_in_blocklist(ctx); self.hide_use_agent_footer_in_blocklist(ctx);
} }
@@ -1046,8 +1046,8 @@ pub struct UseAgentToolbar {
// Shared agent input footer (renders CLI agent mode when a CLI session is active). // Shared agent input footer (renders CLI agent mode when a CLI session is active).
agent_input_footer: ViewHandle<AgentInputFooter>, agent_input_footer: ViewHandle<AgentInputFooter>,
// Warpify footer UI (shown when a subshell/SSH command is detected). // Wormhole footer UI (shown when a subshell/SSH command is detected).
warpify_footer_view: ViewHandle<WarpifyFooterView>, wormhole_footer_view: ViewHandle<WormholeFooterView>,
// `true` if the user has dismissed the footer. // `true` if the user has dismissed the footer.
// //
@@ -1120,11 +1120,11 @@ impl UseAgentToolbar {
me.handle_agent_input_footer_event(event, ctx); me.handle_agent_input_footer_event(event, ctx);
}); });
let warpify_footer_view = let wormhole_footer_view =
ctx.add_typed_action_view(|ctx| WarpifyFooterView::new(terminal_model.clone(), ctx)); ctx.add_typed_action_view(|ctx| WormholeFooterView::new(terminal_model.clone(), ctx));
ctx.subscribe_to_view(&warpify_footer_view, |me, _, event, ctx| { ctx.subscribe_to_view(&wormhole_footer_view, |me, _, event, ctx| {
me.handle_warpify_footer_event(event, ctx); me.handle_wormhole_footer_event(event, ctx);
}); });
ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| { ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| {
@@ -1150,7 +1150,7 @@ impl UseAgentToolbar {
dismiss_button, dismiss_button,
dont_show_again_button, dont_show_again_button,
agent_input_footer, agent_input_footer,
warpify_footer_view, wormhole_footer_view,
terminal_model, terminal_model,
did_user_dismiss: false, did_user_dismiss: false,
} }
@@ -1186,19 +1186,19 @@ impl UseAgentToolbar {
} }
} }
fn handle_warpify_footer_event( fn handle_wormhole_footer_event(
&mut self, &mut self,
event: &WarpifyFooterViewEvent, event: &WormholeFooterViewEvent,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) { ) {
match event { match event {
WarpifyFooterViewEvent::Warpify => { WormholeFooterViewEvent::Wormhole => {
ctx.emit(UseAgentToolbarEvent::Warpify); ctx.emit(UseAgentToolbarEvent::Wormhole);
} }
WarpifyFooterViewEvent::UseAgent => { WormholeFooterViewEvent::UseAgent => {
ctx.emit(UseAgentToolbarEvent::UseAgent); ctx.emit(UseAgentToolbarEvent::UseAgent);
} }
WarpifyFooterViewEvent::Dismiss => { WormholeFooterViewEvent::Dismiss => {
ctx.emit(UseAgentToolbarEvent::Dismiss); ctx.emit(UseAgentToolbarEvent::Dismiss);
} }
} }
@@ -1207,7 +1207,7 @@ impl UseAgentToolbar {
pub(in crate::terminal) fn notify_and_notify_children(&mut self, ctx: &mut ViewContext<Self>) { pub(in crate::terminal) fn notify_and_notify_children(&mut self, ctx: &mut ViewContext<Self>) {
ctx.notify(); ctx.notify();
self.agent_input_footer.update(ctx, |_, ctx| ctx.notify()); self.agent_input_footer.update(ctx, |_, ctx| ctx.notify());
self.warpify_footer_view.update(ctx, |_, ctx| ctx.notify()); self.wormhole_footer_view.update(ctx, |_, ctx| ctx.notify());
self.button.update(ctx, |_, ctx| ctx.notify()); self.button.update(ctx, |_, ctx| ctx.notify());
self.give_control_back_button self.give_control_back_button
.update(ctx, |_, ctx| ctx.notify()); .update(ctx, |_, ctx| ctx.notify());
@@ -1227,26 +1227,26 @@ impl UseAgentToolbar {
.map(|session| session.agent) .map(|session| session.agent)
} }
/// Activates the warpify footer. When active, the footer shows the /// Activates the wormhole footer. When active, the footer shows the
/// warpify view instead of the CLI agent or regular "Use agent" views. /// wormhole view instead of the CLI agent or regular "Use agent" views.
pub(in crate::terminal) fn show_warpify(&mut self, ctx: &mut ViewContext<Self>) { pub(in crate::terminal) fn show_wormhole(&mut self, ctx: &mut ViewContext<Self>) {
self.warpify_footer_view.update(ctx, |view, ctx| { self.wormhole_footer_view.update(ctx, |view, ctx| {
view.show(ctx); view.show(ctx);
}); });
ctx.notify(); ctx.notify();
} }
/// Deactivates the warpify footer so it reverts to its default behavior. /// Deactivates the wormhole footer so it reverts to its default behavior.
pub(in crate::terminal) fn clear_warpify(&mut self, ctx: &mut ViewContext<Self>) { pub(in crate::terminal) fn clear_wormhole(&mut self, ctx: &mut ViewContext<Self>) {
self.warpify_footer_view.update(ctx, |view, ctx| { self.wormhole_footer_view.update(ctx, |view, ctx| {
view.clear(ctx); view.clear(ctx);
}); });
ctx.notify(); ctx.notify();
} }
/// Returns whether the warpify footer is currently active. /// Returns whether the wormhole footer is currently active.
pub(in crate::terminal) fn is_warpify_active(&self, app: &AppContext) -> bool { pub(in crate::terminal) fn is_wormhole_active(&self, app: &AppContext) -> bool {
self.warpify_footer_view.as_ref(app).is_active() self.wormhole_footer_view.as_ref(app).is_active()
} }
/// Returns whether there's a current CLI agent (like Claude Code). /// Returns whether there's a current CLI agent (like Claude Code).
@@ -1272,8 +1272,8 @@ pub enum UseAgentToolbarEvent {
OpenRichInput, OpenRichInput,
/// Hide the rich input editor (same as Escape). /// Hide the rich input editor (same as Escape).
HideRichInput, HideRichInput,
/// User chose to warpify the subshell. /// User chose to wormhole the subshell.
Warpify, Wormhole,
/// User chose to use the agent. /// User chose to use the agent.
UseAgent, UseAgent,
StartRemoteControl { StartRemoteControl {
@@ -1292,9 +1292,9 @@ impl View for UseAgentToolbar {
} }
fn render(&self, app: &AppContext) -> Box<dyn Element> { fn render(&self, app: &AppContext) -> Box<dyn Element> {
// If the warpify footer is active, delegate rendering to the warpify footer view. // If the wormhole footer is active, delegate rendering to the wormhole footer view.
if self.warpify_footer_view.as_ref(app).is_active() { if self.wormhole_footer_view.as_ref(app).is_active() {
return ChildView::new(&self.warpify_footer_view).finish(); return ChildView::new(&self.wormhole_footer_view).finish();
} }
// Hide the toolbar entirely when CLI rich input is open, // Hide the toolbar entirely when CLI rich input is open,
@@ -15,28 +15,28 @@ use crate::view_components::action_button::{
}; };
/// Footer view rendered for detected subshell commands, offering both /// Footer view rendered for detected subshell commands, offering both
/// "Warpify" and "Use agent" buttons in a horizontal row. /// "Wormhole" and "Use agent" buttons in a horizontal row.
pub(super) struct WarpifyFooterView { pub(super) struct WormholeFooterView {
terminal_model: Arc<FairMutex<TerminalModel>>, terminal_model: Arc<FairMutex<TerminalModel>>,
warpify_button: ViewHandle<ActionButton>, wormhole_button: ViewHandle<ActionButton>,
use_agent_button: ViewHandle<ActionButton>, use_agent_button: ViewHandle<ActionButton>,
dismiss_button: ViewHandle<ActionButton>, dismiss_button: ViewHandle<ActionButton>,
/// Whether the footer is currently offering subshell warpification. /// Whether the footer is currently offering subshell wormholing.
is_active: bool, is_active: bool,
} }
impl WarpifyFooterView { impl WormholeFooterView {
pub fn new(terminal_model: Arc<FairMutex<TerminalModel>>, ctx: &mut ViewContext<Self>) -> Self { pub fn new(terminal_model: Arc<FairMutex<TerminalModel>>, ctx: &mut ViewContext<Self>) -> Self {
let button_size = ButtonSize::XSmall; let button_size = ButtonSize::XSmall;
let warpify_button = ctx.add_typed_action_view(|_ctx| { let wormhole_button = ctx.add_typed_action_view(|_ctx| {
ActionButton::new("Wormhole subshell", AgentFooterButtonTheme::new(None)) ActionButton::new("Wormhole subshell", AgentFooterButtonTheme::new(None))
.with_icon(Icon::Warp) .with_icon(Icon::GalaxyLogo)
.with_size(button_size) .with_size(button_size)
.with_tooltip("Enable Galaxy shell integration in this session") .with_tooltip("Enable Galaxy shell integration in this session")
.with_tooltip_alignment(TooltipAlignment::Left) .with_tooltip_alignment(TooltipAlignment::Left)
.on_click(|ctx| { .on_click(|ctx| {
ctx.dispatch_typed_action(WarpifyFooterViewAction::Warpify); ctx.dispatch_typed_action(WormholeFooterViewAction::Wormhole);
}) })
}); });
@@ -48,7 +48,7 @@ impl WarpifyFooterView {
.with_tooltip("Ask the Galaxy agent to assist") .with_tooltip("Ask the Galaxy agent to assist")
.with_tooltip_alignment(TooltipAlignment::Left) .with_tooltip_alignment(TooltipAlignment::Left)
.on_click(|ctx| { .on_click(|ctx| {
ctx.dispatch_typed_action(WarpifyFooterViewAction::UseAgent); ctx.dispatch_typed_action(WormholeFooterViewAction::UseAgent);
}) })
}); });
@@ -56,24 +56,24 @@ impl WarpifyFooterView {
ActionButton::new("Dismiss", AgentFooterButtonTheme::new(None)) ActionButton::new("Dismiss", AgentFooterButtonTheme::new(None))
.with_size(button_size) .with_size(button_size)
.on_click(|ctx| { .on_click(|ctx| {
ctx.dispatch_typed_action(WarpifyFooterViewAction::Dismiss); ctx.dispatch_typed_action(WormholeFooterViewAction::Dismiss);
}) })
}); });
Self { Self {
terminal_model, terminal_model,
warpify_button, wormhole_button,
use_agent_button, use_agent_button,
dismiss_button, dismiss_button,
is_active: false, is_active: false,
} }
} }
/// Activates the footer so it offers subshell warpification. /// Activates the footer so it offers subshell wormholing.
pub fn show(&mut self, ctx: &mut ViewContext<Self>) { pub fn show(&mut self, ctx: &mut ViewContext<Self>) {
self.warpify_button.update(ctx, |button, ctx| { self.wormhole_button.update(ctx, |button, ctx| {
button.set_keybinding( button.set_keybinding(
Some(KeystrokeSource::Binding("terminal:warpify_subshell")), Some(KeystrokeSource::Binding("terminal:wormhole_subshell")),
ctx, ctx,
); );
}); });
@@ -81,7 +81,7 @@ impl WarpifyFooterView {
ctx.notify(); ctx.notify();
} }
/// Returns whether the footer is currently offering subshell warpification. /// Returns whether the footer is currently offering subshell wormholing.
pub fn is_active(&self) -> bool { pub fn is_active(&self) -> bool {
self.is_active self.is_active
} }
@@ -89,7 +89,7 @@ impl WarpifyFooterView {
/// Deactivates the footer. /// Deactivates the footer.
pub fn clear(&mut self, ctx: &mut ViewContext<Self>) { pub fn clear(&mut self, ctx: &mut ViewContext<Self>) {
self.is_active = false; self.is_active = false;
self.warpify_button.update(ctx, |button, ctx| { self.wormhole_button.update(ctx, |button, ctx| {
button.set_keybinding(None, ctx); button.set_keybinding(None, ctx);
}); });
ctx.notify(); ctx.notify();
@@ -97,25 +97,25 @@ impl WarpifyFooterView {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum WarpifyFooterViewAction { pub enum WormholeFooterViewAction {
Warpify, Wormhole,
UseAgent, UseAgent,
Dismiss, Dismiss,
} }
pub enum WarpifyFooterViewEvent { pub enum WormholeFooterViewEvent {
Warpify, Wormhole,
UseAgent, UseAgent,
Dismiss, Dismiss,
} }
impl Entity for WarpifyFooterView { impl Entity for WormholeFooterView {
type Event = WarpifyFooterViewEvent; type Event = WormholeFooterViewEvent;
} }
impl View for WarpifyFooterView { impl View for WormholeFooterView {
fn ui_name() -> &'static str { fn ui_name() -> &'static str {
"WarpifyFooterView" "WormholeFooterView"
} }
fn render(&self, _app: &AppContext) -> Box<dyn Element> { fn render(&self, _app: &AppContext) -> Box<dyn Element> {
@@ -125,7 +125,7 @@ impl View for WarpifyFooterView {
.with_spacing(4.) .with_spacing(4.)
.with_main_axis_size(MainAxisSize::Max) .with_main_axis_size(MainAxisSize::Max)
.with_cross_axis_alignment(CrossAxisAlignment::Center) .with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(ChildView::new(&self.warpify_button).finish()) .with_child(ChildView::new(&self.wormhole_button).finish())
.with_child(ChildView::new(&self.use_agent_button).finish()) .with_child(ChildView::new(&self.use_agent_button).finish())
.with_child(Expanded::new(1., Empty::new().finish()).finish()) .with_child(Expanded::new(1., Empty::new().finish()).finish())
.with_child(ChildView::new(&self.dismiss_button).finish()); .with_child(ChildView::new(&self.dismiss_button).finish());
@@ -144,24 +144,24 @@ impl View for WarpifyFooterView {
} }
} }
impl TypedActionView for WarpifyFooterView { impl TypedActionView for WormholeFooterView {
type Action = WarpifyFooterViewAction; type Action = WormholeFooterViewAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action { match action {
WarpifyFooterViewAction::Warpify => { WormholeFooterViewAction::Wormhole => {
if self.is_active { if self.is_active {
self.clear(ctx); self.clear(ctx);
ctx.emit(WarpifyFooterViewEvent::Warpify); ctx.emit(WormholeFooterViewEvent::Wormhole);
} }
} }
WarpifyFooterViewAction::UseAgent => { WormholeFooterViewAction::UseAgent => {
self.clear(ctx); self.clear(ctx);
ctx.emit(WarpifyFooterViewEvent::UseAgent); ctx.emit(WormholeFooterViewEvent::UseAgent);
} }
WarpifyFooterViewAction::Dismiss => { WormholeFooterViewAction::Dismiss => {
self.clear(ctx); self.clear(ctx);
ctx.emit(WarpifyFooterViewEvent::Dismiss); ctx.emit(WormholeFooterViewEvent::Dismiss);
} }
} }
} }
@@ -10,12 +10,6 @@ use crate::terminal::model::terminal_model::SubshellInitializationInfo;
use crate::terminal::shell::ShellType; use crate::terminal::shell::ShellType;
use crate::ASSETS; use crate::ASSETS;
#[derive(Debug)]
pub enum WarpificationSource {
Ssh,
Subshell,
}
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub enum SubshellSource { pub enum SubshellSource {
Command(String), Command(String),
@@ -34,7 +28,7 @@ fn get_subshell_bootstrap_success_block_path(shell_type: ShellType) -> Option<&'
} }
} }
/// Returns OutputGrid bytes to be rendered in the hardcoded "Warpified subshell" block that's added /// Returns OutputGrid bytes to be rendered in the hardcoded "Wormholed subshell" block that's added
/// to the blocklist upon successful subshell bootstrap. /// to the blocklist upon successful subshell bootstrap.
/// ///
/// The exact block contents varies based on whether or not the session is local or remote, in /// The exact block contents varies based on whether or not the session is local or remote, in
@@ -13,7 +13,7 @@ use pathfinder_color::ColorU;
use pathfinder_geometry::rect::RectF; use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::vector::Vector2F;
use super::settings::WarpifySettings; use super::settings::WormholeSettings;
use super::SubshellSource; use super::SubshellSource;
use crate::ai::blocklist::inline_action::inline_action_icons; use crate::ai::blocklist::inline_action::inline_action_icons;
use crate::ui_components::blended_colors; use crate::ui_components::blended_colors;
@@ -31,8 +31,6 @@ const WARP_DRIVE_ENV_VAR_COLLECTION_ICON_COLOR: u32 = 0xC464FFFF;
const ICON_MARGIN: f32 = 4.; const ICON_MARGIN: f32 = 4.;
const TERMINAL_ICON: &str = "bundled/svg/terminal.svg"; const TERMINAL_ICON: &str = "bundled/svg/terminal.svg";
pub const HORIZONTAL_TEXT_MARGIN: f32 = 20.; pub const HORIZONTAL_TEXT_MARGIN: f32 = 20.;
pub const SSH_DOCS_URL: &str = "https://docs.warp.dev/terminal/warpify/ssh";
pub const SUBSHELL_DOCS_URL: &str = "https://docs.warp.dev/terminal/warpify/subshells";
/// Errored blocks have a red stripe, and subshells have a gray one. /// Errored blocks have a red stripe, and subshells have a gray one.
pub const LEFT_STRIPE_WIDTH: f32 = 5.; pub const LEFT_STRIPE_WIDTH: f32 = 5.;
@@ -92,7 +90,7 @@ fn green_check_icon(appearance: &Appearance, size: f32) -> Box<dyn Element> {
.finish() .finish()
} }
/// UI helper to render the ssh command that caused the warpification prompt. /// UI helper to render the ssh command that caused the wormholing prompt.
pub fn build_command_row( pub fn build_command_row(
command: String, command: String,
theme: &GalaxyTheme, theme: &GalaxyTheme,
@@ -164,21 +162,21 @@ pub fn description_row(
.finish() .finish()
} }
/// Renders a "Never Warpify this host" link or nothing. /// Renders a "Never Wormhole this host" link or nothing.
pub fn render_never_warpify_ssh_link( pub fn render_never_wormhole_ssh_link(
ssh_host: &Option<String>, ssh_host: &Option<String>,
app: &AppContext, app: &AppContext,
appearance: &Appearance, appearance: &Appearance,
mouse_state_handle: MouseStateHandle, mouse_state_handle: MouseStateHandle,
on_never_warpify: fn(&mut EventContext<'_>, ssh_host: String), on_never_wormhole: fn(&mut EventContext<'_>, ssh_host: String),
) -> Option<Box<dyn Element>> { ) -> Option<Box<dyn Element>> {
let Some(ssh_host) = ssh_host else { let Some(ssh_host) = ssh_host else {
return None; return None;
}; };
let settings = WarpifySettings::handle(app); let settings = WormholeSettings::handle(app);
if settings.as_ref(app).is_ssh_host_denylisted(ssh_host) { if settings.as_ref(app).is_ssh_host_denylisted(ssh_host) {
// Should only happen if user manually attempts to Warpify a denylisted host. // Should only happen if user manually attempts to Wormhole a denylisted host.
return None; return None;
} }
@@ -189,7 +187,7 @@ pub fn render_never_warpify_ssh_link(
None, None,
Some(Box::new({ Some(Box::new({
let ssh_host = ssh_host.clone(); let ssh_host = ssh_host.clone();
move |ctx| on_never_warpify(ctx, ssh_host.to_owned()) move |ctx| on_never_wormhole(ctx, ssh_host.to_owned())
})), })),
mouse_state_handle, mouse_state_handle,
) )
@@ -9,65 +9,65 @@ use settings::{
}; };
use strum_macros::EnumIter; use strum_macros::EnumIter;
use crate::terminal::ssh::util::{parse_interactive_ssh_command, SshWarpifyCommand}; use crate::terminal::ssh::util::{parse_interactive_ssh_command, SshWormholeCommand};
// Cannot directly use Vec<Regex> here b/c Regex doesn't impl Eq, Serialize, and Deserialize. // Cannot directly use Vec<Regex> here b/c Regex doesn't impl Eq, Serialize, and Deserialize.
maybe_define_setting!(AddedSubshellCommands, group: WarpifySettings, { maybe_define_setting!(AddedSubshellCommands, group: WormholeSettings, {
type: Vec<String>, type: Vec<String>,
default: Vec::new(), default: Vec::new(),
supported_platforms: SupportedPlatforms::ALL, supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never, sync_to_cloud: SyncToCloud::Never,
private: false, private: false,
toml_path: "warpify.subshells.added_subshell_commands", toml_path: "wormhole.subshells.added_subshell_commands",
description: "Additional regex patterns for commands that should be recognized as subshells.", description: "Additional regex patterns for commands that should be recognized as subshells.",
}); });
maybe_define_setting!(SubshellCommandsDenylist, group: WarpifySettings, { maybe_define_setting!(SubshellCommandsDenylist, group: WormholeSettings, {
type: Vec<String>, type: Vec<String>,
default: Vec::new(), default: Vec::new(),
supported_platforms: SupportedPlatforms::ALL, supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never, sync_to_cloud: SyncToCloud::Never,
private: false, private: false,
toml_path: "warpify.subshells.subshell_commands_denylist", toml_path: "wormhole.subshells.subshell_commands_denylist",
description: "Commands that should not trigger the subshell warpification prompt.", description: "Commands that should not trigger the subshell wormholing prompt.",
}); });
maybe_define_setting!(SshHostsDenylist, group: WarpifySettings, { maybe_define_setting!(SshHostsDenylist, group: WormholeSettings, {
type: Vec<String>, type: Vec<String>,
default: Vec::new(), default: Vec::new(),
supported_platforms: SupportedPlatforms::ALL, supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never, sync_to_cloud: SyncToCloud::Never,
private: false, private: false,
toml_path: "warpify.ssh.ssh_hosts_denylist", toml_path: "wormhole.ssh.ssh_hosts_denylist",
description: "SSH hosts that should not trigger the warpification prompt.", description: "SSH hosts that should not trigger the wormholing prompt.",
}); });
maybe_define_setting!(EnableSshWarpification, group: WarpifySettings, { maybe_define_setting!(EnableSshWormholing, group: WormholeSettings, {
type: bool, type: bool,
default: true, default: true,
supported_platforms: SupportedPlatforms::ALL, supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never, sync_to_cloud: SyncToCloud::Never,
private: false, private: false,
toml_path: "warpify.ssh.enable_ssh_warpification", toml_path: "wormhole.ssh.enable_ssh_wormholing",
description: "Whether to enable Galaxy features in SSH sessions.", description: "Whether to enable Galaxy features in SSH sessions.",
}); });
// NOTE: This setting has been unified into `enable_ssh_warpification` and is no // NOTE: This setting has been unified into `enable_ssh_wormholing` and is no
// longer surfaced in the UI or used to gate any behavior. It is retained only // longer surfaced in the UI or used to gate any behavior. It is retained only
// so the one-time migration (see `register`) can read a user's previous value // so the one-time migration (see `register`) can read a user's previous value
// and forward it to `enable_ssh_warpification`. It can be deleted in a future // and forward it to `enable_ssh_wormholing`. It can be deleted in a future
// release once the migration has shipped to all users. // release once the migration has shipped to all users.
// The storage key and TOML path are intentionally kept identical to the old // The storage key and TOML path are intentionally kept identical to the old
// `SshSettings::enable_ssh_wrapper` field for backward compatibility. // `SshSettings::enable_ssh_wrapper` field for backward compatibility.
maybe_define_setting!(EnableSshWrapper, group: WarpifySettings, { maybe_define_setting!(EnableSshWrapper, group: WormholeSettings, {
type: bool, type: bool,
default: true, default: true,
supported_platforms: SupportedPlatforms::ALL, supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false, private: false,
storage_key: "EnableSSHWrapper", storage_key: "EnableSSHWrapper",
toml_path: "warpify.ssh.enable_legacy_ssh_wrapper", toml_path: "wormhole.ssh.enable_legacy_ssh_wrapper",
description: "Deprecated: unified into enable_ssh_warpification. Retained only for one-time migration.", description: "Deprecated: unified into enable_ssh_wormholing. Retained only for one-time migration.",
}); });
// NOTE: The tmux-based SSH wrapper is deprecated in favor of the remote-server SSH // NOTE: The tmux-based SSH wrapper is deprecated in favor of the remote-server SSH
@@ -75,31 +75,31 @@ maybe_define_setting!(EnableSshWrapper, group: WarpifySettings, {
// it is retained only so the one-time deprecation migration (see `register`) can read a // it is retained only so the one-time deprecation migration (see `register`) can read a
// user's previous opt-in and reset it. It can be deleted in a future release once the // user's previous opt-in and reset it. It can be deleted in a future release once the
// migration has shipped to all users. // migration has shipped to all users.
maybe_define_setting!(UseSshTmuxWrapper, group: WarpifySettings, { maybe_define_setting!(UseSshTmuxWrapper, group: WormholeSettings, {
type: bool, type: bool,
default: false, default: false,
supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()), supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()),
sync_to_cloud: SyncToCloud::Never, sync_to_cloud: SyncToCloud::Never,
private: false, private: false,
toml_path: "warpify.ssh.use_ssh_tmux_wrapper", toml_path: "wormhole.ssh.use_ssh_tmux_wrapper",
description: "Deprecated: whether to use a tmux-based wrapper for SSH warpification.", description: "Deprecated: whether to use a tmux-based wrapper for SSH wormholing.",
}); });
// When set, the user previously opted into the now-deprecated tmux SSH wrapper and should // When set, the user previously opted into the now-deprecated tmux SSH wrapper and should
// be shown a one-time inline banner pointing them to the remote-server SSH extension on // be shown a one-time inline banner pointing them to the remote-server SSH extension on
// their next interactive SSH session. Set by the migration in `register`; cleared once the // their next interactive SSH session. Set by the migration in `register`; cleared once the
// banner has been shown. // banner has been shown.
maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WarpifySettings, { maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WormholeSettings, {
type: bool, type: bool,
default: false, default: false,
supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()), supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()),
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false, private: false,
toml_path: "warpify.ssh.ssh_tmux_deprecation_notice_pending", toml_path: "wormhole.ssh.ssh_tmux_deprecation_notice_pending",
description: "Internal: whether to show the one-time tmux SSH deprecation notice.", description: "Internal: whether to show the one-time tmux SSH deprecation notice.",
}); });
/// Controls how Warp handles the SSH extension (remote server binary) when connecting /// Controls how Galaxy handles the SSH extension (remote server binary) when connecting
/// to a remote host that does not already have it installed. /// to a remote host that does not already have it installed.
#[derive( #[derive(
Default, Default,
@@ -115,7 +115,7 @@ maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WarpifySettings, {
)] )]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[schemars( #[schemars(
description = "Controls SSH extension installation behavior.", description = "Controls Wormhole helper installation behavior.",
rename_all = "snake_case" rename_all = "snake_case"
)] )]
pub enum SshExtensionInstallMode { pub enum SshExtensionInstallMode {
@@ -124,18 +124,18 @@ pub enum SshExtensionInstallMode {
AlwaysAsk, AlwaysAsk,
/// Automatically install and connect without prompting. /// Automatically install and connect without prompting.
AlwaysInstall, AlwaysInstall,
/// Never install; fall back to wrapper-only SSH warpification. /// Never install; fall back to wrapper-only SSH wormholing.
NeverInstall, NeverInstall,
} }
maybe_define_setting!(SshExtensionInstallModeSetting, group: WarpifySettings, { maybe_define_setting!(SshExtensionInstallModeSetting, group: WormholeSettings, {
type: SshExtensionInstallMode, type: SshExtensionInstallMode,
default: SshExtensionInstallMode::default(), default: SshExtensionInstallMode::default(),
supported_platforms: SupportedPlatforms::ALL, supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
private: false, private: false,
toml_path: "warpify.ssh.ssh_extension_install_mode", toml_path: "wormhole.ssh.ssh_extension_install_mode",
description: "Controls SSH extension installation behavior.", description: "Controls Wormhole helper installation behavior.",
}); });
impl SshExtensionInstallMode { impl SshExtensionInstallMode {
@@ -151,7 +151,7 @@ impl SshExtensionInstallMode {
/// Normally we use the define_settings_group! macro for singleton models of settings like this. /// Normally we use the define_settings_group! macro for singleton models of settings like this.
/// However, this model needs to do some extra processing on the added_subshell_commands and store /// However, this model needs to do some extra processing on the added_subshell_commands and store
/// an enriched representation in parsed_added_subshell_commands. /// an enriched representation in parsed_added_subshell_commands.
pub struct WarpifySettings { pub struct WormholeSettings {
/// A list of regexes that users can add to define new subshell-compatible commands. This /// A list of regexes that users can add to define new subshell-compatible commands. This
/// represents the raw, serialized value. Therefore, it is Vec<String>. /// represents the raw, serialized value. Therefore, it is Vec<String>.
pub added_subshell_commands: AddedSubshellCommands, pub added_subshell_commands: AddedSubshellCommands,
@@ -161,9 +161,9 @@ pub struct WarpifySettings {
/// needs to be kept up-to-date as added_subshell_commands changes. See the Self::register /// needs to be kept up-to-date as added_subshell_commands changes. See the Self::register
/// method for how this is done. /// method for how this is done.
pub parsed_added_subshell_commands: Vec<Result<Regex, regex::Error>>, pub parsed_added_subshell_commands: Vec<Result<Regex, regex::Error>>,
/// A list of commands that we shouldn't attempt to warpify. These can be added either b/c the /// A list of commands that we shouldn't attempt to wormhole. These can be added either b/c the
/// "don't ask again" button was clicked in the trigger banner, or it was added explicitly on /// "don't ask again" button was clicked in the trigger banner, or it was added explicitly on
/// the Warpify settings page. This represents the raw, serialized value. /// the Wormhole settings page. This represents the raw, serialized value.
pub subshell_command_denylist: SubshellCommandsDenylist, pub subshell_command_denylist: SubshellCommandsDenylist,
/// This is subshell_command_denylist compiled to actual executable Regex. This is a Result as we /// This is subshell_command_denylist compiled to actual executable Regex. This is a Result as we
/// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering /// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering
@@ -172,11 +172,11 @@ pub struct WarpifySettings {
/// method for how this is done. /// method for how this is done.
pub parsed_subshell_command_denylist: Vec<Result<Regex, regex::Error>>, pub parsed_subshell_command_denylist: Vec<Result<Regex, regex::Error>>,
/// A list of hosts that we shouldn't attempt to warpify. This supports regex. /// A list of hosts that we shouldn't attempt to wormhole. This supports regex.
/// These can be added either b/c the "don't ask again" button was clicked in the trigger banner, /// These can be added either b/c the "don't ask again" button was clicked in the trigger banner,
/// or it was added explicitly on the Warpify settings page. /// or it was added explicitly on the Wormhole settings page.
/// While this could live in the `SshSettings` group, the custom processing shared with the other /// While this could live in the `SshSettings` group, the custom processing shared with the other
/// subshell logic better justifies it living in the `WarpifySettings` group. /// subshell logic better justifies it living in the `WormholeSettings` group.
pub ssh_hosts_denylist: SshHostsDenylist, pub ssh_hosts_denylist: SshHostsDenylist,
/// This is ssh_hosts_denylist compiled to actual executable Regex. This is a Result as we /// This is ssh_hosts_denylist compiled to actual executable Regex. This is a Result as we
/// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering /// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering
@@ -185,10 +185,10 @@ pub struct WarpifySettings {
/// method for how this is done. /// method for how this is done.
pub parsed_ssh_hosts_denylist: Vec<Result<Regex, regex::Error>>, pub parsed_ssh_hosts_denylist: Vec<Result<Regex, regex::Error>>,
/// This setting controls whether we should ever warpify ssh sessions. /// This setting controls whether we should ever wormhole ssh sessions.
pub enable_ssh_warpification: EnableSshWarpification, pub enable_ssh_wormholing: EnableSshWormholing,
/// Deprecated: unified into `enable_ssh_warpification`. Retained only so the one-time /// Deprecated: unified into `enable_ssh_wormholing`. Retained only so the one-time
/// migration in `register` can read and forward a user's previous opt-out. Not used to /// migration in `register` can read and forward a user's previous opt-out. Not used to
/// gate any behavior. /// gate any behavior.
pub enable_ssh_wrapper: EnableSshWrapper, pub enable_ssh_wrapper: EnableSshWrapper,
@@ -238,7 +238,7 @@ lazy_static! {
// Matches commands that spawn a pipenv subshell. // Matches commands that spawn a pipenv subshell.
PIPENV_SUBSHELL_COMMAND_REGEX.clone(), PIPENV_SUBSHELL_COMMAND_REGEX.clone(),
// https://github.com/warpdotdev/Warp/issues/2736 // Matches aws-vault's subshell-spawning exec command.
Regex::new(r"^aws-vault\s+exec\b").expect("aws-vault regex invalid"), Regex::new(r"^aws-vault\s+exec\b").expect("aws-vault regex invalid"),
// https://flox.dev/docs/reference/command-reference/flox-activate/ // https://flox.dev/docs/reference/command-reference/flox-activate/
@@ -251,7 +251,7 @@ lazy_static! {
/// define_settings_group! macro, which is the basic template for user-defaults-backed settings. /// define_settings_group! macro, which is the basic template for user-defaults-backed settings.
/// I have separated this stuff from the other impl block, which contains the subshell-specific /// I have separated this stuff from the other impl block, which contains the subshell-specific
/// logic, because this is basically boilerplate. /// logic, because this is basically boilerplate.
impl WarpifySettings { impl WormholeSettings {
fn new_from_storage(ctx: &mut ModelContext<Self>) -> Self { fn new_from_storage(ctx: &mut ModelContext<Self>) -> Self {
let added_subshell_commands = AddedSubshellCommands::new_from_storage(ctx); let added_subshell_commands = AddedSubshellCommands::new_from_storage(ctx);
let subshell_command_denylist = SubshellCommandsDenylist::new_from_storage(ctx); let subshell_command_denylist = SubshellCommandsDenylist::new_from_storage(ctx);
@@ -267,7 +267,7 @@ impl WarpifySettings {
subshell_command_denylist, subshell_command_denylist,
parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist), parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist),
ssh_hosts_denylist, ssh_hosts_denylist,
enable_ssh_warpification: EnableSshWarpification::new_from_storage(ctx), enable_ssh_wormholing: EnableSshWormholing::new_from_storage(ctx),
enable_ssh_wrapper: EnableSshWrapper::new_from_storage(ctx), enable_ssh_wrapper: EnableSshWrapper::new_from_storage(ctx),
use_ssh_tmux_wrapper: UseSshTmuxWrapper::new_from_storage(ctx), use_ssh_tmux_wrapper: UseSshTmuxWrapper::new_from_storage(ctx),
ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new_from_storage( ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new_from_storage(
@@ -294,7 +294,7 @@ impl WarpifySettings {
subshell_command_denylist, subshell_command_denylist,
parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist), parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist),
ssh_hosts_denylist, ssh_hosts_denylist,
enable_ssh_warpification: EnableSshWarpification::new(None), enable_ssh_wormholing: EnableSshWormholing::new(None),
enable_ssh_wrapper: EnableSshWrapper::new(None), enable_ssh_wrapper: EnableSshWrapper::new(None),
use_ssh_tmux_wrapper: UseSshTmuxWrapper::new(None), use_ssh_tmux_wrapper: UseSshTmuxWrapper::new(None),
ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new(None), ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new(None),
@@ -309,37 +309,37 @@ impl WarpifySettings {
let handle = ctx.add_singleton_model(Self::new_from_storage); let handle = ctx.add_singleton_model(Self::new_from_storage);
ctx.subscribe_to_model(&handle, |settings, event, ctx| { ctx.subscribe_to_model(&handle, |settings, event, ctx| {
settings.update(ctx, |me, _| match event { settings.update(ctx, |me, _| match event {
WarpifySettingsChangedEvent::AddedSubshellCommands { .. } => { WormholeSettingsChangedEvent::AddedSubshellCommands { .. } => {
me.parsed_added_subshell_commands = me.parsed_added_subshell_commands =
Self::parse_added_subshell_commands(&me.added_subshell_commands) Self::parse_added_subshell_commands(&me.added_subshell_commands)
} }
WarpifySettingsChangedEvent::SubshellCommandsDenylist { .. } => { WormholeSettingsChangedEvent::SubshellCommandsDenylist { .. } => {
me.parsed_subshell_command_denylist = me.parsed_subshell_command_denylist =
Self::parse_subshell_command_denylist(&me.subshell_command_denylist) Self::parse_subshell_command_denylist(&me.subshell_command_denylist)
} }
WarpifySettingsChangedEvent::SshHostsDenylist { .. } => { WormholeSettingsChangedEvent::SshHostsDenylist { .. } => {
me.parsed_ssh_hosts_denylist = me.parsed_ssh_hosts_denylist =
Self::parse_ssh_hosts_denylist(&me.ssh_hosts_denylist) Self::parse_ssh_hosts_denylist(&me.ssh_hosts_denylist)
} }
WarpifySettingsChangedEvent::EnableSshWarpification { .. } => {} WormholeSettingsChangedEvent::EnableSshWormholing { .. } => {}
WarpifySettingsChangedEvent::EnableSshWrapper { .. } => {} WormholeSettingsChangedEvent::EnableSshWrapper { .. } => {}
WarpifySettingsChangedEvent::UseSshTmuxWrapper { .. } => {} WormholeSettingsChangedEvent::UseSshTmuxWrapper { .. } => {}
WarpifySettingsChangedEvent::SshTmuxDeprecationNoticePending { .. } => {} WormholeSettingsChangedEvent::SshTmuxDeprecationNoticePending { .. } => {}
WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {} WormholeSettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {}
}); });
}); });
// One-time migration: if the user had explicitly set the legacy `enable_ssh_wrapper` // One-time migration: if the user had explicitly set the legacy `enable_ssh_wrapper`
// setting to `false` (via `warpify.ssh.enable_legacy_ssh_wrapper = false` in their // setting to `false` (via `wormhole.ssh.enable_legacy_ssh_wrapper = false` in their
// TOML config or the old `EnableSSHWrapper` storage key), honour that intent by // TOML config or the old `EnableSSHWrapper` storage key), honour that intent by
// disabling `enable_ssh_warpification` — the canonical setting that now controls the // disabling `enable_ssh_wormholing` — the canonical setting that now controls the
// same behaviour. Resetting `enable_ssh_wrapper` back to its default (`true`) ensures // same behaviour. Resetting `enable_ssh_wrapper` back to its default (`true`) ensures
// the migration does not run again on subsequent launches. // the migration does not run again on subsequent launches.
handle.update(ctx, |me, ctx| { handle.update(ctx, |me, ctx| {
if me.enable_ssh_wrapper.is_value_explicitly_set() && !*me.enable_ssh_wrapper.value() { if me.enable_ssh_wrapper.is_value_explicitly_set() && !*me.enable_ssh_wrapper.value() {
if let Err(e) = me.enable_ssh_warpification.set_value(false, ctx) { if let Err(e) = me.enable_ssh_wormholing.set_value(false, ctx) {
log::error!( log::error!(
"Failed to migrate enable_ssh_wrapper → enable_ssh_warpification: {e}" "Failed to migrate enable_ssh_wrapper → enable_ssh_wormholing: {e}"
); );
} }
if let Err(e) = me.enable_ssh_wrapper.set_value(true, ctx) { if let Err(e) = me.enable_ssh_wrapper.set_value(true, ctx) {
@@ -366,7 +366,7 @@ impl WarpifySettings {
}); });
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
added_subshell_commands, added_subshell_commands,
AddedSubshellCommands, AddedSubshellCommands,
handle.clone(), handle.clone(),
@@ -374,7 +374,7 @@ impl WarpifySettings {
); );
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
subshell_command_denylist, subshell_command_denylist,
SubshellCommandsDenylist, SubshellCommandsDenylist,
handle.clone(), handle.clone(),
@@ -382,15 +382,15 @@ impl WarpifySettings {
); );
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
enable_ssh_warpification, enable_ssh_wormholing,
EnableSshWarpification, EnableSshWormholing,
handle.clone(), handle.clone(),
ctx ctx
); );
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
enable_ssh_wrapper, enable_ssh_wrapper,
EnableSshWrapper, EnableSshWrapper,
handle.clone(), handle.clone(),
@@ -398,7 +398,7 @@ impl WarpifySettings {
); );
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
use_ssh_tmux_wrapper, use_ssh_tmux_wrapper,
UseSshTmuxWrapper, UseSshTmuxWrapper,
handle.clone(), handle.clone(),
@@ -406,7 +406,7 @@ impl WarpifySettings {
); );
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
ssh_tmux_deprecation_notice_pending, ssh_tmux_deprecation_notice_pending,
SshTmuxDeprecationNoticePending, SshTmuxDeprecationNoticePending,
handle.clone(), handle.clone(),
@@ -414,7 +414,7 @@ impl WarpifySettings {
); );
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
ssh_extension_install_mode, ssh_extension_install_mode,
SshExtensionInstallModeSetting, SshExtensionInstallModeSetting,
handle.clone(), handle.clone(),
@@ -422,7 +422,7 @@ impl WarpifySettings {
); );
register_settings_events!( register_settings_events!(
WarpifySettings, WormholeSettings,
ssh_hosts_denylist, ssh_hosts_denylist,
SshHostsDenylist, SshHostsDenylist,
handle, handle,
@@ -432,9 +432,9 @@ impl WarpifySettings {
} }
/// This is also something that would normally be generated by /// This is also something that would normally be generated by
/// define_settings_group!(WarpifySettings). Since we didn't use that macro we define it manually /// define_settings_group!(WormholeSettings). Since we didn't use that macro we define it manually
/// here. It's the event emitted by the setter methods when a setting value changes. /// here. It's the event emitted by the setter methods when a setting value changes.
pub enum WarpifySettingsChangedEvent { pub enum WormholeSettingsChangedEvent {
AddedSubshellCommands { AddedSubshellCommands {
change_event_reason: ChangeEventReason, change_event_reason: ChangeEventReason,
}, },
@@ -444,7 +444,7 @@ pub enum WarpifySettingsChangedEvent {
SshHostsDenylist { SshHostsDenylist {
change_event_reason: ChangeEventReason, change_event_reason: ChangeEventReason,
}, },
EnableSshWarpification { EnableSshWormholing {
change_event_reason: ChangeEventReason, change_event_reason: ChangeEventReason,
}, },
EnableSshWrapper { EnableSshWrapper {
@@ -461,15 +461,15 @@ pub enum WarpifySettingsChangedEvent {
}, },
} }
impl Entity for WarpifySettings { impl Entity for WormholeSettings {
type Event = WarpifySettingsChangedEvent; type Event = WormholeSettingsChangedEvent;
} }
impl SingletonEntity for WarpifySettings {} impl SingletonEntity for WormholeSettings {}
/// This is the other impl block for this model. This one contains the actual subshell-specific /// This is the other impl block for this model. This one contains the actual subshell-specific
/// logic. /// logic.
impl WarpifySettings { impl WormholeSettings {
fn is_built_in_subshell_match(command: &str) -> bool { fn is_built_in_subshell_match(command: &str) -> bool {
for command_regex in SUBSHELL_COMMAND_REGEXES.iter() { for command_regex in SUBSHELL_COMMAND_REGEXES.iter() {
if command_regex.is_match(command) { if command_regex.is_match(command) {
@@ -494,7 +494,7 @@ impl WarpifySettings {
return true; return true;
} }
if SshWarpifyCommand::matches(command).is_some_and(|command| command.is_ssh_like_command()) if SshWormholeCommand::matches(command).is_some_and(|command| command.is_ssh_like_command())
{ {
return true; return true;
} }
@@ -505,8 +505,8 @@ impl WarpifySettings {
} }
} }
// While in-band generators are our best option for warpifying ssh sessions from powershell, hard-code // While in-band generators are our best option for wormholing ssh sessions from powershell, hard-code
// the warpify subshell banner to show up. // the wormhole subshell banner to show up.
if matches!(shell_family, ShellFamily::PowerShell) if matches!(shell_family, ShellFamily::PowerShell)
&& parse_interactive_ssh_command(command).is_some() && parse_interactive_ssh_command(command).is_some()
{ {
@@ -602,7 +602,7 @@ impl WarpifySettings {
new_added_commands_list.push(command_to_add.trim().to_owned()); new_added_commands_list.push(command_to_add.trim().to_owned());
// The set_value method generated by the maybe_define_setting! macro will take // The set_value method generated by the maybe_define_setting! macro will take
// care of emitting the WarpifySettingsChangedEvent::AddedSubshellCommands event to keep // care of emitting the WormholeSettingsChangedEvent::AddedSubshellCommands event to keep
// parsed_added_subshell_commands in sync. // parsed_added_subshell_commands in sync.
self.added_subshell_commands self.added_subshell_commands
.set_value(new_added_commands_list, ctx) .set_value(new_added_commands_list, ctx)
@@ -611,7 +611,7 @@ impl WarpifySettings {
ctx.notify(); ctx.notify();
} }
/// Check if the user has asked us to remember a command and avoid asking to warpify a subshell. /// Check if the user has asked us to remember a command and avoid asking to wormhole a subshell.
pub fn is_denylisted_subshell_command(&self, command: &str) -> bool { pub fn is_denylisted_subshell_command(&self, command: &str) -> bool {
let command = command.trim(); let command = command.trim();
self.parsed_subshell_command_denylist self.parsed_subshell_command_denylist
@@ -1,7 +1,7 @@
use settings::Setting; use settings::Setting;
use warpui::{App, SingletonEntity}; use warpui::{App, SingletonEntity};
use super::WarpifySettings; use super::WormholeSettings;
use crate::test_util::settings::initialize_settings_for_tests; use crate::test_util::settings::initialize_settings_for_tests;
#[test] #[test]
@@ -10,12 +10,12 @@ fn test_parsed_subshell_commands_updated_via_self_subscription() {
initialize_settings_for_tests(&mut app); initialize_settings_for_tests(&mut app);
app.read(|ctx| { app.read(|ctx| {
assert!(WarpifySettings::as_ref(ctx) assert!(WormholeSettings::as_ref(ctx)
.parsed_added_subshell_commands .parsed_added_subshell_commands
.is_empty()); .is_empty());
}); });
WarpifySettings::handle(&app).update(&mut app, |settings, ctx| { WormholeSettings::handle(&app).update(&mut app, |settings, ctx| {
settings settings
.added_subshell_commands .added_subshell_commands
.set_value(vec!["^my-custom-shell$".to_string()], ctx) .set_value(vec!["^my-custom-shell$".to_string()], ctx)
@@ -24,7 +24,7 @@ fn test_parsed_subshell_commands_updated_via_self_subscription() {
// The parsed field must now contain the compiled regex. // The parsed field must now contain the compiled regex.
app.read(|ctx| { app.read(|ctx| {
let parsed = &WarpifySettings::as_ref(ctx).parsed_added_subshell_commands; let parsed = &WormholeSettings::as_ref(ctx).parsed_added_subshell_commands;
assert_eq!( assert_eq!(
parsed.len(), parsed.len(),
1, 1,
@@ -41,14 +41,14 @@ fn test_parsed_subshell_commands_updated_via_self_subscription() {
/// Verify that a user who previously set `enable_legacy_ssh_wrapper = false` /// Verify that a user who previously set `enable_legacy_ssh_wrapper = false`
/// (old `SshSettings::enable_ssh_wrapper`) has that opt-out forwarded to /// (old `SshSettings::enable_ssh_wrapper`) has that opt-out forwarded to
/// `enable_ssh_warpification` on first launch after the migration. /// `enable_ssh_wormholing` on first launch after the migration.
#[test] #[test]
fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() { fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_wormholing_false() {
App::test((), |mut app| async move { App::test((), |mut app| async move {
initialize_settings_for_tests(&mut app); initialize_settings_for_tests(&mut app);
// Simulate a user who had explicitly opted out of the legacy SSH wrapper. // Simulate a user who had explicitly opted out of the legacy SSH wrapper.
WarpifySettings::handle(&app).update(&mut app, |settings, ctx| { WormholeSettings::handle(&app).update(&mut app, |settings, ctx| {
settings settings
.enable_ssh_wrapper .enable_ssh_wrapper
.set_value(false, ctx) .set_value(false, ctx)
@@ -63,13 +63,13 @@ fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() {
// Simpler approach: confirm the migration logic produces the right state // Simpler approach: confirm the migration logic produces the right state
// by applying it explicitly here. // by applying it explicitly here.
app.update(|ctx| { app.update(|ctx| {
WarpifySettings::handle(ctx).update(ctx, |me, ctx| { WormholeSettings::handle(ctx).update(ctx, |me, ctx| {
if me.enable_ssh_wrapper.is_value_explicitly_set() if me.enable_ssh_wrapper.is_value_explicitly_set()
&& !*me.enable_ssh_wrapper.value() && !*me.enable_ssh_wrapper.value()
{ {
me.enable_ssh_warpification me.enable_ssh_wormholing
.set_value(false, ctx) .set_value(false, ctx)
.expect("migration set enable_ssh_warpification"); .expect("migration set enable_ssh_wormholing");
me.enable_ssh_wrapper me.enable_ssh_wrapper
.set_value(true, ctx) .set_value(true, ctx)
.expect("migration reset enable_ssh_wrapper"); .expect("migration reset enable_ssh_wrapper");
@@ -78,10 +78,10 @@ fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() {
}); });
app.read(|ctx| { app.read(|ctx| {
let settings = WarpifySettings::as_ref(ctx); let settings = WormholeSettings::as_ref(ctx);
assert!( assert!(
!*settings.enable_ssh_warpification.value(), !*settings.enable_ssh_wormholing.value(),
"enable_ssh_warpification should be false after migration" "enable_ssh_wormholing should be false after migration"
); );
// The wrapper is reset to true so the migration condition // The wrapper is reset to true so the migration condition
// (`!*enable_ssh_wrapper.value()`) won't fire again on the next launch. // (`!*enable_ssh_wrapper.value()`) won't fire again on the next launch.
@@ -94,22 +94,22 @@ fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() {
} }
/// Verify that the default state (no legacy setting present) does not /// Verify that the default state (no legacy setting present) does not
/// spuriously disable `enable_ssh_warpification`. /// spuriously disable `enable_ssh_wormholing`.
#[test] #[test]
fn test_enable_ssh_wrapper_default_does_not_affect_enable_ssh_warpification() { fn test_enable_ssh_wrapper_default_does_not_affect_enable_ssh_wormholing() {
App::test((), |mut app| async move { App::test((), |mut app| async move {
initialize_settings_for_tests(&mut app); initialize_settings_for_tests(&mut app);
app.read(|ctx| { app.read(|ctx| {
let settings = WarpifySettings::as_ref(ctx); let settings = WormholeSettings::as_ref(ctx);
// Neither setting should be explicitly set — both default to true. // Neither setting should be explicitly set — both default to true.
assert!( assert!(
!settings.enable_ssh_wrapper.is_value_explicitly_set(), !settings.enable_ssh_wrapper.is_value_explicitly_set(),
"enable_ssh_wrapper should not be explicitly set in a fresh install" "enable_ssh_wrapper should not be explicitly set in a fresh install"
); );
assert!( assert!(
*settings.enable_ssh_warpification.value(), *settings.enable_ssh_wormholing.value(),
"enable_ssh_warpification should remain true when no migration is needed" "enable_ssh_wormholing should remain true when no migration is needed"
); );
}); });
}); });
@@ -133,7 +133,7 @@ fn test_wsl_subshell_detection_success() {
.iter() .iter()
.for_each(|cmd| { .for_each(|cmd| {
assert!( assert!(
WarpifySettings::is_built_in_subshell_match(cmd), WormholeSettings::is_built_in_subshell_match(cmd),
"{} failed to match", "{} failed to match",
*cmd *cmd
) )
@@ -164,7 +164,7 @@ fn test_wsl_subshell_detection_fail() {
.iter() .iter()
.for_each(|cmd| { .for_each(|cmd| {
assert!( assert!(
!WarpifySettings::is_built_in_subshell_match(cmd), !WormholeSettings::is_built_in_subshell_match(cmd),
"{} accidentally matched", "{} accidentally matched",
*cmd *cmd
) )
@@ -7,14 +7,13 @@ use galaxy_core::ui::theme::GalaxyTheme;
use parking_lot::RwLock; use parking_lot::RwLock;
use warpui::elements::{ use warpui::elements::{
Border, Container, CrossAxisAlignment, Flex, Icon, MainAxisAlignment, MainAxisSize, Border, Container, CrossAxisAlignment, Flex, Icon, MainAxisAlignment, MainAxisSize,
MouseStateHandle, ParentElement, SelectableArea, SelectionHandle, Text, ParentElement, SelectableArea, SelectionHandle, Text,
}; };
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext}; use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
use super::render::{HORIZONTAL_TEXT_MARGIN, SSH_DOCS_URL, SUBSHELL_DOCS_URL}; use super::render::HORIZONTAL_TEXT_MARGIN;
use super::settings::WarpifySettings; use super::settings::WormholeSettings;
use super::{render, subshell_bootstrap_success_block_bytes, WarpificationSource}; use super::{render, subshell_bootstrap_success_block_bytes};
use crate::ai::agent::ProgrammingLanguage; use crate::ai::agent::ProgrammingLanguage;
use crate::ai::blocklist::code_block::{render_runnable_code_snippet, CodeSnippetButtonHandles}; use crate::ai::blocklist::code_block::{render_runnable_code_snippet, CodeSnippetButtonHandles};
use crate::appearance::Appearance; use crate::appearance::Appearance;
@@ -27,20 +26,19 @@ use crate::workspace::WorkspaceAction;
const VERTICAL_TEXT_MARGIN: f32 = 16.; const VERTICAL_TEXT_MARGIN: f32 = 16.;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum WarpifySuccessBlockEvent { pub enum WormholeSuccessBlockEvent {
OpenWarpifySettings, OpenWormholeSettings,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum WarpifySuccessBlockAction { pub enum WormholeSuccessBlockAction {
ClearAutoWarpifySnippet, ClearAutoWormholeSnippet,
OpenWarpifySettings, OpenWormholeSettings,
OpenUrl(String),
} }
struct AutoWarpifySnippet { struct AutoWormholeSnippet {
/// On subshell initialization, this will contain the output grid to display, /// On subshell initialization, this will contain the output grid to display,
/// containing info like how to auto-warpify the subshell. /// containing info like how to auto-wormhole the subshell.
output_grid: Cow<'static, str>, output_grid: Cow<'static, str>,
/// The output grid needs to be selectable to allow users to copy the command to their clipboard. /// The output grid needs to be selectable to allow users to copy the command to their clipboard.
selection_handle: SelectionHandle, selection_handle: SelectionHandle,
@@ -52,23 +50,20 @@ struct AutoWarpifySnippet {
can_write_to_rc: bool, can_write_to_rc: bool,
} }
pub struct WarpifySuccessBlock { pub struct WormholeSuccessBlock {
source: WarpificationSource,
spawning_command: String, spawning_command: String,
learn_more_link_mouse_states: MouseStateHandle, auto_wormhole_snippet: Option<AutoWormholeSnippet>,
auto_warpify_snippet: Option<AutoWarpifySnippet>,
} }
impl WarpifySuccessBlock { impl WormholeSuccessBlock {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new( pub fn new(
source: WarpificationSource,
spawning_command: String, spawning_command: String,
subshell_info: Option<SubshellInitializationInfo>, subshell_info: Option<SubshellInitializationInfo>,
shell: Shell, shell: Shell,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
ctx.subscribe_to_model(&WarpifySettings::handle(ctx), move |_, _, _, ctx| { ctx.subscribe_to_model(&WormholeSettings::handle(ctx), move |_, _, _, ctx| {
ctx.notify(); ctx.notify();
}); });
@@ -76,17 +71,17 @@ impl WarpifySuccessBlock {
// getting the OS to write to the correct RC file. // getting the OS to write to the correct RC file.
let remote_os = TargetOS::Linux; let remote_os = TargetOS::Linux;
let is_auto_warpify_configured = subshell_info let is_auto_wormhole_configured = subshell_info
.as_ref() .as_ref()
.map(|info| info.was_triggered_by_rc_file_snippet) .map(|info| info.was_triggered_by_rc_file_snippet)
.unwrap_or_default(); .unwrap_or_default();
let auto_warpify_snippet = if is_auto_warpify_configured { let auto_wormhole_snippet = if is_auto_wormhole_configured {
None None
} else { } else {
subshell_info.and_then(|subshell_info| { subshell_info.and_then(|subshell_info| {
// If warpification wasn't triggered automatically, show a snippet about // If wormholing wasn't triggered automatically, show a snippet about
// how to automatically warpify. // how to automatically wormhole.
(!subshell_info.was_triggered_by_rc_file_snippet).then(|| { (!subshell_info.was_triggered_by_rc_file_snippet).then(|| {
let (command, is_executable) = subshell_bootstrap_success_block_bytes( let (command, is_executable) = subshell_bootstrap_success_block_bytes(
&subshell_info, &subshell_info,
@@ -108,8 +103,8 @@ impl WarpifySuccessBlock {
}) })
}) })
}; };
let auto_warpify_snippet = auto_warpify_snippet.map(|(output_grid, can_write_to_rc)| { let auto_wormhole_snippet = auto_wormhole_snippet.map(|(output_grid, can_write_to_rc)| {
AutoWarpifySnippet { AutoWormholeSnippet {
description: (if !output_grid.is_empty() { description: (if !output_grid.is_empty() {
"Run the following to automatically Wormhole in the future:" "Run the following to automatically Wormhole in the future:"
} else { } else {
@@ -125,15 +120,13 @@ impl WarpifySuccessBlock {
}); });
Self { Self {
source,
learn_more_link_mouse_states: Default::default(),
spawning_command, spawning_command,
auto_warpify_snippet, auto_wormhole_snippet,
} }
} }
pub fn selected_text(&self) -> Option<String> { pub fn selected_text(&self) -> Option<String> {
self.auto_warpify_snippet self.auto_wormhole_snippet
.as_ref() .as_ref()
.and_then(|snippet| snippet.selected_text.read().clone()) .and_then(|snippet| snippet.selected_text.read().clone())
} }
@@ -156,18 +149,12 @@ impl WarpifySuccessBlock {
) -> Box<dyn Element> { ) -> Box<dyn Element> {
let header_contents = render::build_header_row( let header_contents = render::build_header_row(
"Session Wormholed", "Session Wormholed",
Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()), Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail()),
theme, theme,
appearance, appearance,
) )
.with_margin_right(8.) .with_margin_right(8.)
.finish(); .finish();
let header_contents = Container::new(
Flex::row()
.with_children([header_contents, self.render_learn_more_link(appearance)])
.finish(),
)
.finish();
Container::new( Container::new(
Flex::row() Flex::row()
@@ -182,45 +169,13 @@ impl WarpifySuccessBlock {
.finish() .finish()
} }
fn render_learn_more_link(&self, appearance: &Appearance) -> Box<dyn Element> { /// Fired when a block ends and we are not in a Wormholed session.
let url = match self.source { pub fn on_wormholed_session_complete(&mut self, ctx: &mut ViewContext<Self>) {
WarpificationSource::Ssh => SSH_DOCS_URL, self.clear_auto_wormhole_snippet(ctx);
WarpificationSource::Subshell => SUBSHELL_DOCS_URL,
};
let font_family_id = appearance.monospace_font_family();
let font_size = appearance.monospace_font_size();
appearance
.ui_builder()
.link(
"Learn more".into(),
None,
Some(Box::new({
move |ctx| {
ctx.dispatch_typed_action(WarpifySuccessBlockAction::OpenUrl(
url.to_owned(),
));
}
})),
self.learn_more_link_mouse_states.clone(),
)
.soft_wrap(false)
.with_style(UiComponentStyles {
font_size: Some(font_size),
font_family_id: Some(font_family_id),
..Default::default()
})
.build()
.finish()
} }
/// Fired when a block ends and we are not in a Warpified session. pub fn clear_auto_wormhole_snippet(&mut self, ctx: &mut ViewContext<Self>) {
pub fn on_warpified_session_complete(&mut self, ctx: &mut ViewContext<Self>) { self.auto_wormhole_snippet = None;
self.clear_auto_warpify_snippet(ctx);
}
pub fn clear_auto_warpify_snippet(&mut self, ctx: &mut ViewContext<Self>) {
self.auto_warpify_snippet = None;
ctx.notify(); ctx.notify();
} }
@@ -231,16 +186,16 @@ impl WarpifySuccessBlock {
appearance: &Appearance, appearance: &Appearance,
) -> Option<Box<dyn Element>> { ) -> Option<Box<dyn Element>> {
let theme = appearance.theme(); let theme = appearance.theme();
let auto_warpify_snippet = self.auto_warpify_snippet.as_ref()?; let auto_wormhole_snippet = self.auto_wormhole_snippet.as_ref()?;
if auto_warpify_snippet.output_grid.is_empty() { if auto_wormhole_snippet.output_grid.is_empty() {
return None; return None;
} }
let shell_language = ProgrammingLanguage::Shell(auto_warpify_snippet.shell_type); let shell_language = ProgrammingLanguage::Shell(auto_wormhole_snippet.shell_type);
let runnable_command = render_runnable_code_snippet( let runnable_command = render_runnable_code_snippet(
&auto_warpify_snippet.output_grid, &auto_wormhole_snippet.output_grid,
if auto_warpify_snippet.can_write_to_rc { if auto_wormhole_snippet.can_write_to_rc {
Some(&shell_language) Some(&shell_language)
} else { } else {
None None
@@ -251,7 +206,7 @@ impl WarpifySuccessBlock {
code_snippet.to_string(), code_snippet.to_string(),
)); ));
ctx.dispatch_typed_action(WarpifySuccessBlockAction::ClearAutoWarpifySnippet); ctx.dispatch_typed_action(WormholeSuccessBlockAction::ClearAutoWormholeSnippet);
} }
})), })),
Some(Box::new({ Some(Box::new({
@@ -259,19 +214,19 @@ impl WarpifySuccessBlock {
ctx.dispatch_typed_action(WorkspaceAction::CopyTextToClipboard(code_snippet)); ctx.dispatch_typed_action(WorkspaceAction::CopyTextToClipboard(code_snippet));
} }
})), })),
Some(auto_warpify_snippet.code_snippet_handles.clone()), Some(auto_wormhole_snippet.code_snippet_handles.clone()),
app, app,
); );
let semantic_selection = SemanticSelection::as_ref(app); let semantic_selection = SemanticSelection::as_ref(app);
let selected_text = auto_warpify_snippet.selected_text.clone(); let selected_text = auto_wormhole_snippet.selected_text.clone();
// TODO(Simon): Implement full selection and copying functionality for the WarpifySuccessBlock. // TODO(Simon): Implement full selection and copying functionality for the WormholeSuccessBlock.
// Look to the `EnvVarCollectionBlock` for the existing implementation paradigm. We don't // Look to the `EnvVarCollectionBlock` for the existing implementation paradigm. We don't
// yet have a robust way of ensuring that every aspect of text selection is implemented // yet have a robust way of ensuring that every aspect of text selection is implemented
// properly, so be extra careful not to miss any details! // properly, so be extra careful not to miss any details!
let output_grid = SelectableArea::new( let output_grid = SelectableArea::new(
auto_warpify_snippet.selection_handle.clone(), auto_wormhole_snippet.selection_handle.clone(),
move |selection_args, _, _| { move |selection_args, _, _| {
*selected_text.write() = selection_args.selection; *selected_text.write() = selection_args.selection;
}, },
@@ -285,7 +240,7 @@ impl WarpifySuccessBlock {
.with_child( .with_child(
Container::new( Container::new(
Text::new( Text::new(
auto_warpify_snippet.description.clone(), auto_wormhole_snippet.description.clone(),
appearance.monospace_font_family(), appearance.monospace_font_family(),
appearance.monospace_font_size(), appearance.monospace_font_size(),
) )
@@ -307,15 +262,15 @@ impl WarpifySuccessBlock {
} }
} }
impl Entity for WarpifySuccessBlock { impl Entity for WormholeSuccessBlock {
type Event = WarpifySuccessBlockEvent; type Event = WormholeSuccessBlockEvent;
} }
pub const WARPIFY_SUCCESS_BLOCK_VISIBLE_KEY: &str = "WarpifySuccessBlockVisible"; pub const WORMHOLE_SUCCESS_BLOCK_VISIBLE_KEY: &str = "WormholeSuccessBlockVisible";
impl View for WarpifySuccessBlock { impl View for WormholeSuccessBlock {
fn ui_name() -> &'static str { fn ui_name() -> &'static str {
"WarpifySuccessBlock" "WormholeSuccessBlock"
} }
fn render(&self, app: &AppContext) -> Box<dyn Element> { fn render(&self, app: &AppContext) -> Box<dyn Element> {
@@ -340,19 +295,16 @@ impl View for WarpifySuccessBlock {
} }
} }
impl TypedActionView for WarpifySuccessBlock { impl TypedActionView for WormholeSuccessBlock {
type Action = WarpifySuccessBlockAction; type Action = WormholeSuccessBlockAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action { match action {
WarpifySuccessBlockAction::OpenWarpifySettings => { WormholeSuccessBlockAction::OpenWormholeSettings => {
ctx.emit(WarpifySuccessBlockEvent::OpenWarpifySettings); ctx.emit(WormholeSuccessBlockEvent::OpenWormholeSettings);
} }
WarpifySuccessBlockAction::OpenUrl(url) => { WormholeSuccessBlockAction::ClearAutoWormholeSnippet => {
ctx.open_url(url); self.clear_auto_wormhole_snippet(ctx);
}
WarpifySuccessBlockAction::ClearAutoWarpifySnippet => {
self.clear_auto_warpify_snippet(ctx);
} }
} }
} }
@@ -6,7 +6,7 @@ use galaxyui::r#async::SpawnedFutureHandle;
use galaxyui::{EntityId, SingletonEntity as _, ViewContext, ViewHandle}; use galaxyui::{EntityId, SingletonEntity as _, ViewContext, ViewHandle};
use parking_lot::FairMutex; use parking_lot::FairMutex;
use super::success_block::WarpifySuccessBlock; use super::success_block::WormholeSuccessBlock;
use crate::terminal::model::block::BlockId; use crate::terminal::model::block::BlockId;
use crate::terminal::model::session::SessionId; use crate::terminal::model::session::SessionId;
use crate::terminal::model::terminal_model::SubshellInitializationInfo; use crate::terminal::model::terminal_model::SubshellInitializationInfo;
@@ -40,8 +40,8 @@ impl SubshellSeparatorState {
#[derive(Debug)] #[derive(Debug)]
pub enum SshBlockState { pub enum SshBlockState {
WarpifySuccess { WormholeSuccess {
handle: ViewHandle<WarpifySuccessBlock>, handle: ViewHandle<WormholeSuccessBlock>,
}, },
} }
@@ -52,18 +52,18 @@ impl SshBlockState {
pub fn get_block_view_id(&self) -> EntityId { pub fn get_block_view_id(&self) -> EntityId {
match self { match self {
SshBlockState::WarpifySuccess { handle, .. } => handle.id(), SshBlockState::WormholeSuccess { handle, .. } => handle.id(),
} }
} }
pub fn on_warpified_session_complete( pub fn on_wormholed_session_complete(
&self, &self,
ctx: &mut ViewContext<TerminalView>, ctx: &mut ViewContext<TerminalView>,
) -> Option<EntityId> { ) -> Option<EntityId> {
match self { match self {
SshBlockState::WarpifySuccess { handle } => { SshBlockState::WormholeSuccess { handle } => {
handle.update(ctx, |block, ctx| { handle.update(ctx, |block, ctx| {
block.on_warpified_session_complete(ctx); block.on_wormholed_session_complete(ctx);
}); });
} }
} }
@@ -71,29 +71,29 @@ impl SshBlockState {
} }
} }
/// Temporary state used to trigger Warpification. /// Temporary state used to trigger Wormholing.
#[derive(Default)] #[derive(Default)]
struct WarpifyTriggerState { struct WormholeTriggerState {
block_id: Option<BlockId>, block_id: Option<BlockId>,
/// Lets us abort an attempt to auto warpify if the subshell command /// Lets us abort an attempt to auto wormhole if the subshell command
/// hasn't completed. /// hasn't completed.
auto_warpify_abort_handle: Option<SpawnedFutureHandle>, auto_wormhole_abort_handle: Option<SpawnedFutureHandle>,
/// The subshell banner waits 1s before showing. This is to see that the command stays running /// The subshell banner waits 1s before showing. This is to see that the command stays running
/// for a while without exiting. We store the abort handle here so that the /// for a while without exiting. We store the abort handle here so that the
/// TerminalEvent::BlockCompleted event can abort the banner. /// TerminalEvent::BlockCompleted event can abort the banner.
subshell_banner_abort_handle: Option<SpawnedFutureHandle>, subshell_banner_abort_handle: Option<SpawnedFutureHandle>,
/// The command which may trigger ssh Warpification /// The command which may trigger ssh Wormholing
pending_command: Option<String>, pending_command: Option<String>,
/// The Host which may trigger ssh Warpification /// The Host which may trigger ssh Wormholing
pending_warpify_ssh_host: Option<String>, pending_wormhole_ssh_host: Option<String>,
/// Which, if any, SSH block is currently added to the blocklist. /// Which, if any, SSH block is currently added to the blocklist.
ssh_block_state: Option<SshBlockState>, ssh_block_state: Option<SshBlockState>,
ssh_warpify_timeout_handle: Option<SpawnedFutureHandle>, ssh_wormhole_timeout_handle: Option<SpawnedFutureHandle>,
shell_type: Option<ShellType>, shell_type: Option<ShellType>,
@@ -101,17 +101,17 @@ struct WarpifyTriggerState {
} }
#[derive(Default)] #[derive(Default)]
pub struct WarpifyState { pub struct WormholeState {
session_id: Option<SessionId>, session_id: Option<SessionId>,
pending_state: Option<WarpifyTriggerState>, pending_state: Option<WormholeTriggerState>,
/// Stores the metadata needed to render any separators above the first block of a subshell. /// Stores the metadata needed to render any separators above the first block of a subshell.
subshell_separator_state: SubshellSeparatorState, subshell_separator_state: SubshellSeparatorState,
/// A unique-enough ID that is used to validate that a timeout is still valid. /// A unique-enough ID that is used to validate that a timeout is still valid.
timeout_id: u8, timeout_id: u8,
} }
impl WarpifyState { impl WormholeState {
pub fn delete_state(&mut self) { pub fn delete_state(&mut self) {
self.pending_state.take(); self.pending_state.take();
} }
@@ -180,32 +180,32 @@ impl WarpifyState {
.and_then(|state| state.subshell_banner_abort_handle.take()) .and_then(|state| state.subshell_banner_abort_handle.take())
} }
pub fn add_auto_warpify_abort_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) { pub fn add_auto_wormhole_abort_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) {
let pending_state = self.pending_state.get_or_insert_with(Default::default); let pending_state = self.pending_state.get_or_insert_with(Default::default);
pending_state.auto_warpify_abort_handle = Some(spawned_future_handle); pending_state.auto_wormhole_abort_handle = Some(spawned_future_handle);
} }
pub fn abort_auto_warpify(&mut self) { pub fn abort_auto_wormhole(&mut self) {
if let Some(abort_handle) = self if let Some(abort_handle) = self
.pending_state .pending_state
.as_mut() .as_mut()
.and_then(|state| state.auto_warpify_abort_handle.take()) .and_then(|state| state.auto_wormhole_abort_handle.take())
{ {
abort_handle.abort(); abort_handle.abort();
}; };
} }
pub fn add_ssh_warpify_timeout_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) { pub fn add_ssh_wormhole_timeout_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) {
let pending_state = self.pending_state.get_or_insert_with(Default::default); let pending_state = self.pending_state.get_or_insert_with(Default::default);
pending_state.ssh_warpify_timeout_handle = Some(spawned_future_handle); pending_state.ssh_wormhole_timeout_handle = Some(spawned_future_handle);
} }
pub fn abort_ssh_warpify_timeout(&mut self) { pub fn abort_ssh_wormhole_timeout(&mut self) {
self.replace_timeout_id(); self.replace_timeout_id();
if let Some(handle) = self if let Some(handle) = self
.pending_state .pending_state
.as_mut() .as_mut()
.and_then(|state| state.ssh_warpify_timeout_handle.take()) .and_then(|state| state.ssh_wormhole_timeout_handle.take())
{ {
handle.abort(); handle.abort();
}; };
@@ -231,31 +231,31 @@ impl WarpifyState {
pub fn get_pending_ssh_host(&self) -> Option<String> { pub fn get_pending_ssh_host(&self) -> Option<String> {
self.pending_state self.pending_state
.as_ref() .as_ref()
.and_then(|state: &WarpifyTriggerState| state.pending_warpify_ssh_host.clone()) .and_then(|state: &WormholeTriggerState| state.pending_wormhole_ssh_host.clone())
} }
pub fn get_pending_ssh_command(&self) -> Option<String> { pub fn get_pending_ssh_command(&self) -> Option<String> {
self.pending_state self.pending_state
.as_ref() .as_ref()
.and_then(|state: &WarpifyTriggerState| state.pending_command.clone()) .and_then(|state: &WormholeTriggerState| state.pending_command.clone())
} }
pub fn take_pending_ssh_host(&mut self) -> Option<String> { pub fn take_pending_ssh_host(&mut self) -> Option<String> {
self.pending_state self.pending_state
.as_mut() .as_mut()
.and_then(|state: &mut WarpifyTriggerState| state.pending_warpify_ssh_host.take()) .and_then(|state: &mut WormholeTriggerState| state.pending_wormhole_ssh_host.take())
} }
pub fn clear_pending_ssh_host(&mut self) { pub fn clear_pending_ssh_host(&mut self) {
if let Some(ref mut pending_state) = self.pending_state.as_mut() { if let Some(ref mut pending_state) = self.pending_state.as_mut() {
pending_state.pending_warpify_ssh_host = None; pending_state.pending_wormhole_ssh_host = None;
} }
} }
pub fn set_pending_ssh_host(&mut self, command: String, ssh_host: Option<String>) { pub fn set_pending_ssh_host(&mut self, command: String, ssh_host: Option<String>) {
let pending_state = self.pending_state.get_or_insert_with(Default::default); let pending_state = self.pending_state.get_or_insert_with(Default::default);
pending_state.pending_command = Some(command); pending_state.pending_command = Some(command);
pending_state.pending_warpify_ssh_host = ssh_host; pending_state.pending_wormhole_ssh_host = ssh_host;
} }
pub fn set_block_id(&mut self, block_id: BlockId) { pub fn set_block_id(&mut self, block_id: BlockId) {
@@ -290,10 +290,10 @@ impl WarpifyState {
} }
/// Called once whenever we get a local block completed, as opposed to a remote ssh block /// Called once whenever we get a local block completed, as opposed to a remote ssh block
/// and we have a Warpify Success block. /// and we have a Wormhole Success block.
fn on_warpified_session_complete( fn on_wormholed_session_complete(
&mut self, &mut self,
state: WarpifyTriggerState, state: WormholeTriggerState,
ctx: &mut ViewContext<TerminalView>, ctx: &mut ViewContext<TerminalView>,
) -> Option<EntityId> { ) -> Option<EntityId> {
self.clear_ssh_block_state(); self.clear_ssh_block_state();
@@ -301,16 +301,16 @@ impl WarpifyState {
let Some(block) = &state.ssh_block_state else { let Some(block) = &state.ssh_block_state else {
return None; return None;
}; };
block.on_warpified_session_complete(ctx) block.on_wormholed_session_complete(ctx)
} }
pub fn on_warpify_start(&mut self, active_session_id: Option<SessionId>) { pub fn on_wormhole_start(&mut self, active_session_id: Option<SessionId>) {
self.session_id = active_session_id; self.session_id = active_session_id;
} }
/// Called whenever a block is completed, to determine whether a Warpified session /// Called whenever a block is completed, to determine whether a Wormholed session
/// has been completed. /// has been completed.
pub fn get_completed_warpify_session_id( pub fn get_completed_wormhole_session_id(
&mut self, &mut self,
active_session_id: Option<SessionId>, active_session_id: Option<SessionId>,
ctx: &mut ViewContext<TerminalView>, ctx: &mut ViewContext<TerminalView>,
@@ -319,7 +319,7 @@ impl WarpifyState {
return None; return None;
} }
if let Some(state) = self.pending_state.take() { if let Some(state) = self.pending_state.take() {
return self.on_warpified_session_complete(state, ctx); return self.on_wormholed_session_complete(state, ctx);
}; };
None None
} }
@@ -20,7 +20,7 @@ use crate::server::server_api::ServerApiProvider;
use crate::settings::PrivacySettings; use crate::settings::PrivacySettings;
use crate::terminal::model::session::{IsSSHWrapperSession, SessionInfo}; use crate::terminal::model::session::{IsSSHWrapperSession, SessionInfo};
use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher}; use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher};
use crate::terminal::warpify::settings::{SshExtensionInstallMode, WarpifySettings}; use crate::terminal::wormhole::settings::{SshExtensionInstallMode, WormholeSettings};
use crate::{send_telemetry_from_ctx, TelemetryEvent}; use crate::{send_telemetry_from_ctx, TelemetryEvent};
/// Per-SSH-init state machine. Encoding the state as an enum makes invalid /// Per-SSH-init state machine. Encoding the state as an enum makes invalid
@@ -310,7 +310,7 @@ impl<T: EventLoopSender> RemoteServerController<T> {
}); });
} }
Ok(false) => { Ok(false) => {
let install_mode = *WarpifySettings::as_ref(ctx) let install_mode = *WormholeSettings::as_ref(ctx)
.ssh_extension_install_mode .ssh_extension_install_mode
.value(); .value();
match install_mode { match install_mode {
+2 -2
View File
@@ -46,7 +46,7 @@ pub fn initialize_settings_for_tests_with_mode(
use crate::terminal::session_settings::SessionSettings; use crate::terminal::session_settings::SessionSettings;
use crate::terminal::settings::TerminalSettings; use crate::terminal::settings::TerminalSettings;
use crate::terminal::shared_session::settings::SharedSessionSettings; use crate::terminal::shared_session::settings::SharedSessionSettings;
use crate::terminal::warpify::settings::WarpifySettings; use crate::terminal::wormhole::settings::WormholeSettings;
use crate::terminal::BlockListSettings; use crate::terminal::BlockListSettings;
use crate::undo_close::UndoCloseSettings; use crate::undo_close::UndoCloseSettings;
use crate::user_config::WarpConfig; use crate::user_config::WarpConfig;
@@ -104,7 +104,7 @@ pub fn initialize_settings_for_tests_with_mode(
ScrollSettings::register(app); ScrollSettings::register(app);
SelectionSettings::register(app); SelectionSettings::register(app);
app.update(|ctx| { app.update(|ctx| {
WarpifySettings::register(ctx); WormholeSettings::register(ctx);
}); });
SessionSettings::register(app); SessionSettings::register(app);
SshSettings::register(app); SshSettings::register(app);

Some files were not shown because too many files have changed in this diff Show More