diff --git a/AGENTS.md b/AGENTS.md index 6d6473ca..2b92b3bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -338,7 +338,7 @@ The code editor has full LSP-powered autocompletion with documentation resolutio **Behavior:** - Auto-completes as you type (triggered by alphanumeric/underscore with 50ms debounce) - 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 - 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`) diff --git a/app/Cargo.toml b/app/Cargo.toml index fbf22885..4c744edf 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -591,7 +591,7 @@ default = [ "revert_to_checkpoints", "rewind_slash_command", "hoa_code_review", - "warpify_footer", + "wormhole_footer", "hoa_notifications", "hoa_onboarding_flow", "agent_toolbar_editor", @@ -936,7 +936,7 @@ solo_user_byok = [] billing_and_usage_page_v2 = [] gpt_configurable_context_window = [] configurable_toolbar = [] -warpify_footer = [] +wormhole_footer = [] hoa_onboarding_flow = [] git_operations_in_code_review = [] hoa_remote_control = [] diff --git a/app/assets/bundled/bootstrap/bash_body.sh b/app/assets/bundled/bootstrap/bash_body.sh index 5a9bff0b..c1f4b266 100644 --- a/app/assets/bundled/bootstrap/bash_body.sh +++ b/app/assets/bundled/bootstrap/bash_body.sh @@ -371,7 +371,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then 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" else 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 # 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 # creation of a non-interactive session or otherwise would conflict with # 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_master_mode="yes" 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') case "$user_control_path" in "" | none) @@ -1064,9 +1064,9 @@ export TERM_PROGRAM='WarpTerminal' # body can distinguish it from local shells. Used to gate the ExitShell # hook which tears down the remote-server-proxy subprocess. export WARP_IS_SSH='1' -test -n '$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). -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")'" 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}}" # 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="" if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x" diff --git a/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt b/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt index 66d98af5..34edcc6b 100644 --- a/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt +++ b/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt @@ -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'\'' ' >> % diff --git a/app/assets/bundled/bootstrap/fish.sh b/app/assets/bundled/bootstrap/fish.sh index e32e64ac..a2b12a36 100644 --- a/app/assets/bundled/bootstrap/fish.sh +++ b/app/assets/bundled/bootstrap/fish.sh @@ -603,7 +603,7 @@ end # The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping # recursive SSH sessions. -if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1" +if test "$GALAXY_IS_LOCAL_SHELL_SESSION" = "1" function is_interactive_ssh_session # 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. @@ -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_master_mode "yes" 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') # Skip when no ControlPath is configured, and reject resolved # paths containing characters we cannot safely embed in the SSH @@ -681,9 +681,9 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1" -t $argv \ " 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). -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")'" printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'" @@ -753,7 +753,7 @@ esac if is_interactive_ssh_session $argv 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 set -g TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE "-x" else diff --git a/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt b/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt index b20c5f18..0693a79f 100644 --- a/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt +++ b/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt @@ -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'\'' ' >> % diff --git a/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 b/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 index 851d6cac..139a999d 100644 --- a/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 +++ b/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 @@ -7,7 +7,7 @@ if ($PSEdition -eq 'Desktop' -or $IsWindows) { $EP = [Microsoft.PowerShell.ExecutionPolicy] # 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) { - 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) { $global:_warp_PSProcessExecPolicy = $(Get-ExecutionPolicy -Scope Process) Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force diff --git a/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt b/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt index 7b56c6a8..0930ad77 100644 --- a/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt +++ b/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt @@ -1 +1 @@ -Success! This subshell spawned by % has been Warpified. \ No newline at end of file +Success! This subshell spawned by % has been Wormholed. \ No newline at end of file diff --git a/app/assets/bundled/bootstrap/zsh_body.sh b/app/assets/bundled/bootstrap/zsh_body.sh index bb0a0557..9f8da042 100644 --- a/app/assets/bundled/bootstrap/zsh_body.sh +++ b/app/assets/bundled/bootstrap/zsh_body.sh @@ -583,7 +583,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # set the WARP_DISABLE_AUTO_TITLE flag. [[ "${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" else 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 # 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 # creation of a non-interactive session or otherwise would conflict with # 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_master_mode="yes" 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') case "$user_control_path" in "" | none) @@ -991,9 +991,9 @@ export TERM_PROGRAM='WarpTerminal' # body can distinguish it from local shells. Used to gate the ExitShell # hook which tears down the remote-server-proxy subprocess. export WARP_IS_SSH='1' -test -n '$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). -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")'" 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}}" # 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="" if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x" diff --git a/app/src/ai/agent/api/impl.rs b/app/src/ai/agent/api/impl.rs index e75f2342..23afc664 100644 --- a/app/src/ai/agent/api/impl.rs +++ b/app/src/ai/agent/api/impl.rs @@ -71,7 +71,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec { 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 // through RemoteServerClient. The host_id is only populated // after a successful connection handshake, so its presence is a @@ -81,7 +81,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec { supported_tools.push(api::ToolType::SearchCodebase); } } - Some(SessionType::WarpifiedRemote { host_id: None }) => {} + Some(SessionType::WormholedRemote { host_id: None }) => {} } if FeatureFlag::ListSkills.is_enabled() { @@ -113,13 +113,13 @@ fn get_supported_cli_agent_tools(params: &RequestParams) -> Vec { supported_cli_agent_tools .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); if FeatureFlag::RemoteCodebaseIndexing.is_enabled() { supported_cli_agent_tools.push(api::ToolType::SearchCodebase); } } - Some(SessionType::WarpifiedRemote { host_id: None }) => {} + Some(SessionType::WormholedRemote { host_id: None }) => {} } supported_cli_agent_tools diff --git a/app/src/ai/agent/api/impl_tests.rs b/app/src/ai/agent/api/impl_tests.rs index 0338120f..efc107e0 100644 --- a/app/src/ai/agent/api/impl_tests.rs +++ b/app/src/ai/agent/api/impl_tests.rs @@ -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) -> RequestParams { let mut params = request_params_with_ask_user_question_enabled(false); 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, })); params diff --git a/app/src/ai/agent_tips.rs b/app/src/ai/agent_tips.rs index 74093fa5..c712fc86 100644 --- a/app/src/ai/agent_tips.rs +++ b/app/src/ai/agent_tips.rs @@ -289,7 +289,7 @@ static DEFAULT_TIPS: LazyLock> = LazyLock::new(|| { }, AgentTip { 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, action: None, kind: AgentTipKind::General, diff --git a/app/src/ai/blocklist/action_model/execute/read_files.rs b/app/src/ai/blocklist/action_model/execute/read_files.rs index afa5a5c7..2a668e76 100644 --- a/app/src/ai/blocklist/action_model/execute/read_files.rs +++ b/app/src/ai/blocklist/action_model/execute/read_files.rs @@ -115,7 +115,7 @@ impl ReadFilesExecutor { // Check if this is a remote session with a connected host. let session_type = self.active_session.as_ref(ctx).session_type(ctx); let host_request_handle = match &session_type { - Some(SessionType::WarpifiedRemote { + Some(SessionType::WormholedRemote { host_id: Some(host_id), }) => Some( remote_server::manager::RemoteServerManager::as_ref(ctx) @@ -127,7 +127,7 @@ impl ReadFilesExecutor { // Remote session without a usable remote server connection. File reading // requires either local access or a connected remote server, neither // of which is available. - if matches!(session_type, Some(SessionType::WarpifiedRemote { .. })) + if matches!(session_type, Some(SessionType::WormholedRemote { .. })) && host_request_handle.is_none() { return ActionExecution::Sync(AIAgentActionResultType::ReadFiles( diff --git a/app/src/ai/blocklist/action_model/execute/read_skill_tests.rs b/app/src/ai/blocklist/action_model/execute/read_skill_tests.rs index e3209acc..2ccd6552 100644 --- a/app/src/ai/blocklist/action_model/execute/read_skill_tests.rs +++ b/app/src/ai/blocklist/action_model/execute/read_skill_tests.rs @@ -154,7 +154,7 @@ fn disconnected_remote_session_does_not_fall_back_to_client_global_bundled_skill sessions.register_session_for_test( SessionInfo::new_for_test() .with_id(session_id) - .with_session_type(BootstrapSessionType::WarpifiedRemote), + .with_session_type(BootstrapSessionType::WormholedRemote), ); }); 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( SessionInfo::new_for_test() .with_id(session_id) - .with_session_type(BootstrapSessionType::WarpifiedRemote), + .with_session_type(BootstrapSessionType::WormholedRemote), ); }); let session = sessions diff --git a/app/src/ai/blocklist/action_model/execute/request_file_edits.rs b/app/src/ai/blocklist/action_model/execute/request_file_edits.rs index 1be790c7..af07abbb 100644 --- a/app/src/ai/blocklist/action_model/execute/request_file_edits.rs +++ b/app/src/ai/blocklist/action_model/execute/request_file_edits.rs @@ -370,7 +370,7 @@ impl RequestFileEditsExecutor { }) .collect(); 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), }) => DiffSessionType::Remote(host_id.clone()), _ => DiffSessionType::Local, diff --git a/app/src/ai/blocklist/agent_view/zero_state_block.rs b/app/src/ai/blocklist/agent_view/zero_state_block.rs index a178416f..7ad53f3c 100644 --- a/app/src/ai/blocklist/agent_view/zero_state_block.rs +++ b/app/src/ai/blocklist/agent_view/zero_state_block.rs @@ -360,7 +360,7 @@ fn format_session_location(session: &Session, working_directory: Option<&str>) - let hostname = session.hostname(); match session_type { 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| { matches!( pending_session_info.session_type, - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::WormholedRemote ) }); (!terminal_model.block_list().is_bootstrapped() && !is_bootstrapping_remote_shell) diff --git a/app/src/ai/blocklist/block/status_bar.rs b/app/src/ai/blocklist/block/status_bar.rs index e702080b..2bdb7e87 100644 --- a/app/src/ai/blocklist/block/status_bar.rs +++ b/app/src/ai/blocklist/block/status_bar.rs @@ -65,7 +65,7 @@ use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher}; use crate::terminal::view::ambient_agent::{ 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::{ TerminalModel, CANCEL_COMMAND_KEYBINDING, TOGGLE_AUTOEXECUTE_MODE_KEYBINDING, TOGGLE_HIDE_CLI_RESPONSES_KEYBINDING, TOGGLE_QUEUE_NEXT_PROMPT_KEYBINDING, diff --git a/app/src/ai/blocklist/controller.rs b/app/src/ai/blocklist/controller.rs index 68bb4b1c..ce43a1cc 100644 --- a/app/src/ai/blocklist/controller.rs +++ b/app/src/ai/blocklist/controller.rs @@ -314,11 +314,11 @@ impl SessionContext { &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`. pub fn host_id(&self) -> Option<&galaxy_core::HostId> { 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, } } @@ -326,17 +326,17 @@ impl SessionContext { /// Returns `true` if this is a remote session (regardless of whether /// the remote server client is connected). 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 { match &self.session_type { - Some(SessionType::WarpifiedRemote { + Some(SessionType::WormholedRemote { host_id: Some(host_id), }) => SkillPathOrigin::Remote { 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, } } diff --git a/app/src/ai/blocklist/inline_action/requested_action.rs b/app/src/ai/blocklist/inline_action/requested_action.rs index 1826099b..82c13055 100644 --- a/app/src/ai/blocklist/inline_action/requested_action.rs +++ b/app/src/ai/blocklist/inline_action/requested_action.rs @@ -1,6 +1,6 @@ //! 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, -//! 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 //! actions. Moreover, the header rendering functions here don't make use of the HeaderConfig. diff --git a/app/src/ai/blocklist/passive_suggestions/legacy.rs b/app/src/ai/blocklist/passive_suggestions/legacy.rs index 0f942aaf..a43d7e5a 100644 --- a/app/src/ai/blocklist/passive_suggestions/legacy.rs +++ b/app/src/ai/blocklist/passive_suggestions/legacy.rs @@ -408,7 +408,7 @@ impl PassiveSuggestionsModel { .active_session .as_ref(ctx) .session_type(ctx) - .map(|session_type| matches!(session_type, SessionType::WarpifiedRemote { .. })) + .map(|session_type| matches!(session_type, SessionType::WormholedRemote { .. })) .unwrap_or(true); if !can_read_file || should_skip_for_remote { let reason = if !can_read_file { diff --git a/app/src/ai/persisted_workspace.rs b/app/src/ai/persisted_workspace.rs index 1dc17b9d..a47f2991 100644 --- a/app/src/ai/persisted_workspace.rs +++ b/app/src/ai/persisted_workspace.rs @@ -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) { + 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. /// This looks up the workspace root and starts any servers that are enabled but not yet running. #[cfg(feature = "local_fs")] diff --git a/app/src/autoupdate/linux.rs b/app/src/autoupdate/linux.rs index 286f3ce2..31347790 100644 --- a/app/src/autoupdate/linux.rs +++ b/app/src/autoupdate/linux.rs @@ -159,12 +159,14 @@ mod appimage { } mod package_manager { + use anyhow::{bail, Result}; use galaxyui::elements::{Container, FormattedTextElement, HighlightedHyperlink}; use galaxyui::{Element, SingletonEntity as _}; use markdown_parser::{ FormattedText, FormattedTextFragment, FormattedTextHeader, FormattedTextLine, }; + use super::{PackageManager, CURRENT_EXE}; use crate::appearance::Appearance; pub struct AutoupdateContextBlock { diff --git a/app/src/bin/generate_settings_schema.rs b/app/src/bin/generate_settings_schema.rs index dc33d9b3..31d36d41 100644 --- a/app/src/bin/generate_settings_schema.rs +++ b/app/src/bin/generate_settings_schema.rs @@ -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: //! ``` //! cargo run --bin generate_settings_schema -- [--channel dev|preview|stable] [output_path] //! ``` -use std::collections::HashSet; use std::io::Write; -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; +use galaxy::settings::schema_export::generate_settings_schema; /// Ensures all `inventory::submit!` registrations from the app crate's /// 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() { 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 { - 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, - hierarchy: &str, -) -> &'a mut Map { - 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() { ensure_settings_linked(); let args: Vec = std::env::args().collect(); - let mut channel = "dev"; let mut output_path: Option<&str> = None; - let mut i = 1; - while i < args.len() { - match args[i].as_str() { + let mut index = 1; + + while index < args.len() { + match args[index].as_str() { "--channel" => { - i += 1; - if i < args.len() { - channel = &args[i]; + index += 1; + if index < args.len() { + channel = &args[index]; } } arg if !arg.starts_with('-') => { @@ -165,101 +39,16 @@ fn main() { std::process::exit(1); } } - i += 1; + index += 1; } - 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:: { - // 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::(&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"); + let (output, entry_count) = generate_settings_schema(channel); if let Some(path) = output_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()) - .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}"); } else { println!("{output}"); diff --git a/app/src/code/completion.rs b/app/src/code/completion.rs index d0d35607..cec2ee6f 100644 --- a/app/src/code/completion.rs +++ b/app/src/code/completion.rs @@ -187,6 +187,13 @@ fn fuzzy_match(target: &str, query: &str) -> bool { true } +fn completion_documentation(item: &CompletionItem) -> Option { + match item.documentation.as_ref()? { + lsp_types::Documentation::String(documentation) => Some(documentation.clone()), + lsp_types::Documentation::MarkupContent(documentation) => Some(documentation.value.clone()), + } +} + impl LocalCodeEditorView { pub(super) fn is_completion_enabled() -> bool { FeatureFlag::LspCompletion.is_enabled() @@ -254,12 +261,13 @@ impl LocalCodeEditorView { }; 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( &mut self, + request_offset: CharOffset, trigger_offset: CharOffset, trigger: CompletionTrigger, ctx: &mut ViewContext, @@ -279,7 +287,7 @@ impl LocalCodeEditorView { let lsp_position = self .editor() .as_ref(ctx) - .offset_to_lsp_position(trigger_offset, ctx); + .offset_to_lsp_position(request_offset, ctx); let future = match lsp_server @@ -318,7 +326,7 @@ impl LocalCodeEditorView { } 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`. @@ -440,7 +448,7 @@ impl LocalCodeEditorView { /// Resolve documentation for the currently selected completion item. pub(super) fn resolve_selected_completion_docs(&mut self, ctx: &mut ViewContext) { - let raw_item = match &self.completion_state { + let (item_index, raw_item) = match &self.completion_state { CompletionState::Showing { items, filtered_indices, @@ -458,14 +466,22 @@ impl LocalCodeEditorView { { return; } - items[item_idx].raw_item.clone() + (item_idx, items[item_idx].raw_item.clone()) } _ => 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 { return; }; + if !lsp_server.as_ref(ctx).supports_completion_resolve() { + return; + } let future = match lsp_server.as_ref(ctx).completion_resolve(raw_item) { Ok(future) => future, @@ -473,8 +489,8 @@ impl LocalCodeEditorView { }; let abort_handle = ctx - .spawn(future, |me, result, ctx| { - me.handle_completion_resolve_response(result, ctx); + .spawn(future, move |me, result, ctx| { + me.handle_completion_resolve_response(item_index, result, ctx); }) .abort_handle(); @@ -492,6 +508,7 @@ impl LocalCodeEditorView { fn handle_completion_resolve_response( &mut self, + item_index: usize, result: anyhow::Result, ctx: &mut ViewContext, ) { @@ -500,22 +517,29 @@ impl LocalCodeEditorView { Err(_) => return, }; - let doc_string = match resolved_item.documentation { - Some(lsp_types::Documentation::String(s)) => s, - Some(lsp_types::Documentation::MarkupContent(m)) => m.value, - None => return, + let Some(documentation) = completion_documentation(&resolved_item) else { + 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, + ) { + if documentation.trim().is_empty() { return; } - let formatted = match markdown_parser::parse_markdown(&doc_string) { + let formatted = match markdown_parser::parse_markdown(&documentation) { Ok(text) => text, Err(_) => { use markdown_parser::{FormattedTextFragment, FormattedTextLine}; FormattedText::new([FormattedTextLine::Line(vec![ - FormattedTextFragment::plain_text(doc_string), + FormattedTextFragment::plain_text(documentation), ])]) } }; @@ -529,9 +553,9 @@ impl LocalCodeEditorView { } = &mut self.completion_state { *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 { - item_index: item_idx, + item_index, text: formatted, 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) { if !Self::is_completion_enabled() { return; @@ -562,7 +586,7 @@ impl LocalCodeEditorView { } let cursor_offset = self.editor().as_ref(ctx).cursor_head_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> { diff --git a/app/src/code/editor/view.rs b/app/src/code/editor/view.rs index 0c557ff5..ed33bd57 100644 --- a/app/src/code/editor/view.rs +++ b/app/src/code/editor/view.rs @@ -158,6 +158,8 @@ pub enum CodeEditorEvent { CompletionNavigateDown, /// Emitted when Tab/Enter is pressed and completion_intercept_keys is active. CompletionConfirm, + /// Emitted when the manual completion keybinding is pressed. + CompletionTrigger, } /// Store all states related to displaying the editor content. diff --git a/app/src/code/editor/view/actions.rs b/app/src/code/editor/view/actions.rs index b89d4d61..6dd3735e 100644 --- a/app/src/code/editor/view/actions.rs +++ b/app/src/code/editor/view/actions.rs @@ -78,6 +78,11 @@ pub fn init(app: &mut AppContext) { CodeEditorViewAction::VimShiftEnter, text_entry.clone() & id!("Vim"), ), + FixedBinding::new( + "ctrl-space", + CodeEditorViewAction::TriggerCompletion, + editable_state.clone(), + ), FixedBinding::new( "backspace", CodeEditorViewAction::Backspace, @@ -711,6 +716,7 @@ pub enum CodeEditorViewAction { ShiftTab, ShowFindBar, ShowGoToLine, + TriggerCompletion, Escape, VimEnter, VimTab, @@ -795,6 +801,7 @@ impl CodeEditorViewAction { | Self::Copy | Self::ShowFindBar | Self::ShowGoToLine + | Self::TriggerCompletion | Self::Escape | Self::HiddenSectionExpansion { .. } | Self::AddDiffHunkContext { .. } @@ -1064,6 +1071,7 @@ impl TypedActionView for CodeEditorView { ShowFindBar => self.show_find_bar(ctx), ShowGoToLine => self.show_goto_line(ctx), + TriggerCompletion => ctx.emit(CodeEditorEvent::CompletionTrigger), Escape => self.escape(ctx), HiddenSectionExpansion { line_range, diff --git a/app/src/code/file_tree/view.rs b/app/src/code/file_tree/view.rs index 846d58d0..c2e0ef7c 100644 --- a/app/src/code/file_tree/view.rs +++ b/app/src/code/file_tree/view.rs @@ -2965,7 +2965,7 @@ impl View for FileTreeView { if let CodingPanelEnablementState::RemoteSession { has_remote_server } = self.enablement { // 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 // (tmux, subshell) no data will arrive, so show the disabled // error instead. diff --git a/app/src/code/global_buffer_model.rs b/app/src/code/global_buffer_model.rs index ee870086..d83cd1b5 100644 --- a/app/src/code/global_buffer_model.rs +++ b/app/src/code/global_buffer_model.rs @@ -1562,13 +1562,23 @@ impl GlobalBufferModel { .flatten(); // 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| { - last_synced.is_none() || last_synced.is_some_and(|synced| synced < 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) + }); 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( LspServerLogLevel::Info, format!( diff --git a/app/src/code/local_code_editor.rs b/app/src/code/local_code_editor.rs index 0d0130e0..9c308a48 100644 --- a/app/src/code/local_code_editor.rs +++ b/app/src/code/local_code_editor.rs @@ -114,11 +114,6 @@ pub fn init(app: &mut AppContext) { LocalCodeEditorAction::StartRename, id!("LocalCodeEditorView"), ), - FixedBinding::new( - "ctrl-alt-space", - LocalCodeEditorAction::TriggerCompletion, - id!("LocalCodeEditorView"), - ), ]); } @@ -216,8 +211,6 @@ pub enum LocalCodeEditorAction { OpenCodeActions, /// Start LSP rename at cursor (F2). StartRename, - /// Manually trigger completion (Ctrl+Alt+Space). - TriggerCompletion, /// Hover over a completion item by display index. CompletionHoverItem(usize), /// Confirm completion via mouse click. @@ -510,6 +503,9 @@ impl LocalCodeEditorView { CodeEditorEvent::CompletionConfirm => { me.confirm_completion(ctx); } + CodeEditorEvent::CompletionTrigger => { + me.trigger_completion_manually(ctx); + } CodeEditorEvent::VimGotoDefinition | CodeEditorEvent::VimFindReferences | CodeEditorEvent::VimShowHover => { @@ -995,9 +991,12 @@ impl LocalCodeEditorView { // If the LSP is not registered, try to start it via PersistedWorkspace. #[cfg(feature = "local_fs")] { - use crate::ai::persisted_workspace::LspTask; PersistedWorkspace::handle(ctx).update(ctx, |workspace, ctx| { - workspace.execute_lsp_task(LspTask::Spawn { file_path: path }, 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); + } }); } return; @@ -2487,9 +2486,6 @@ impl TypedActionView for LocalCodeEditorView { LocalCodeEditorAction::StartRename => { self.start_rename(ctx); } - LocalCodeEditorAction::TriggerCompletion => { - self.trigger_completion_manually(ctx); - } LocalCodeEditorAction::CompletionHoverItem(display_index) => { self.handle_completion_hover_item(*display_index, ctx); } diff --git a/app/src/coding_panel_enablement_state.rs b/app/src/coding_panel_enablement_state.rs index a3f4ee39..c1d3cb89 100644 --- a/app/src/coding_panel_enablement_state.rs +++ b/app/src/coding_panel_enablement_state.rs @@ -9,7 +9,7 @@ pub(crate) enum CodingPanelEnablementState { /// The active session is on a remote host. /// /// `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 /// a loading state. When `false` (tmux or subshell SSH), no data will /// arrive and the file tree should show a disabled message. diff --git a/app/src/completer/mod.rs b/app/src/completer/mod.rs index bfc5e1ab..0f9fda8c 100644 --- a/app/src/completer/mod.rs +++ b/app/src/completer/mod.rs @@ -101,7 +101,7 @@ impl SessionContext { .filter_map(|res| res.and_then(EngineDirEntry::try_from).ok()) .collect::>() } - SessionType::WarpifiedRemote { .. } => { + SessionType::WormholedRemote { .. } => { let env_vars = self .session .path() diff --git a/app/src/context_chips/builtins.rs b/app/src/context_chips/builtins.rs index cd6271da..8fefce15 100644 --- a/app/src/context_chips/builtins.rs +++ b/app/src/context_chips/builtins.rs @@ -91,7 +91,7 @@ pub fn ssh_session(ctx: &GeneratorContext) -> Option { if session.is_ssh_wrapper_session() || matches!( session.session_type(), - crate::terminal::model::session::SessionType::WarpifiedRemote { .. } + crate::terminal::model::session::SessionType::WormholedRemote { .. } ) { let user = session.user(); diff --git a/app/src/context_chips/builtins_tests.rs b/app/src/context_chips/builtins_tests.rs index 6720793d..96050786 100644 --- a/app/src/context_chips/builtins_tests.rs +++ b/app/src/context_chips/builtins_tests.rs @@ -43,7 +43,7 @@ fn test_remote_sessions() { let local_session = Session::test(); let remote_session = Session::new( SessionInfo::new_for_test() - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::WormholedRemote) .with_hostname("remote-host".to_string()) .with_user("remote-user".to_string()), Arc::new(TestCommandExecutor {}), diff --git a/app/src/context_chips/display_chip.rs b/app/src/context_chips/display_chip.rs index 152ce7c4..10060909 100644 --- a/app/src/context_chips/display_chip.rs +++ b/app/src/context_chips/display_chip.rs @@ -1638,10 +1638,10 @@ impl DisplayChip { .as_ref() .map(|ctx| match ctx.session.session_type() { SessionType::Local => true, - SessionType::WarpifiedRemote { host_id: Some(_) } => { + SessionType::WormholedRemote { host_id: Some(_) } => { FeatureFlag::RemoteCodeReview.is_enabled() } - SessionType::WarpifiedRemote { host_id: None } => false, + SessionType::WormholedRemote { host_id: None } => false, }) .unwrap_or(false); diff --git a/app/src/features.rs b/app/src/features.rs index e7371bd6..67b80f1a 100644 --- a/app/src/features.rs +++ b/app/src/features.rs @@ -465,8 +465,8 @@ fn enabled_features() -> HashSet { FeatureFlag::CLIAgentRichInput, #[cfg(feature = "transfer_control_tool")] FeatureFlag::TransferControlTool, - #[cfg(feature = "warpify_footer")] - FeatureFlag::WarpifyFooter, + #[cfg(feature = "wormhole_footer")] + FeatureFlag::WormholeFooter, #[cfg(feature = "solo_user_byok")] FeatureFlag::SoloUserByok, #[cfg(feature = "billing_and_usage_page_v2")] diff --git a/app/src/integration_testing/subshell/step.rs b/app/src/integration_testing/subshell/step.rs index fbc13696..8295e729 100644 --- a/app/src/integration_testing/subshell/step.rs +++ b/app/src/integration_testing/subshell/step.rs @@ -92,7 +92,7 @@ pub fn enter_local_subshell_command(shell: &str) -> 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| { let terminal_view = single_terminal_view(app, window_id); terminal_view.read(app, |view, _ctx| { @@ -102,7 +102,7 @@ pub fn assert_subshell_banner_is_showing() -> TestStep { .block_list_mut() .active_block() .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 { - Some(RichContentType::WarpifySuccessBlock) => {} + Some(RichContentType::WormholeSuccessBlock) => {} _ => { 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(), ); } } diff --git a/app/src/remote_server/ssh_transport.rs b/app/src/remote_server/ssh_transport.rs index 4fdb3ff9..da45d599 100644 --- a/app/src/remote_server/ssh_transport.rs +++ b/app/src/remote_server/ssh_transport.rs @@ -152,7 +152,19 @@ impl RemoteTransport for SshTransport { fn check_binary(&self) -> Pin> + Send>> { let socket_path = self.socket_path.clone(); 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}"); let output = remote_server::ssh::run_ssh_command( &socket_path, @@ -161,16 +173,18 @@ impl RemoteTransport for SshTransport { ) .await?; // ` --version` exits 0 when present, executable, and - // functional. Exit 127 means the binary was not found, and 126 - // means it exists 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. + // functional. Static helpers additionally compare their bundled + // build marker, where exit 1 means the remote copy is stale. + // Exit 127 means the binary was not found, and 126 means it exists + // 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 stdout = String::from_utf8_lossy(&output.stdout); log::info!("Binary check result: exit={code:?} stdout={stdout}"); match code { Some(0) => Ok(true), - Some(126) | Some(127) => Ok(false), + Some(1) | Some(126) | Some(127) => Ok(false), Some(code) => { let stderr = String::from_utf8_lossy(&output.stderr); Err(Error::Other(anyhow::anyhow!( diff --git a/app/src/remote_server/ssh_transport/installation.rs b/app/src/remote_server/ssh_transport/installation.rs index 5af280d1..97df4ea4 100644 --- a/app/src/remote_server/ssh_transport/installation.rs +++ b/app/src/remote_server/ssh_transport/installation.rs @@ -4,6 +4,8 @@ mod scp_fallback; use std::path::Path; use anyhow::Result; +use galaxy_core::channel::{Channel, ChannelState}; +use remote_server::setup::RemotePlatform; use remote_server::ssh::SshCommandError; use remote_server::transport::{Error, InstallOutcome, InstallSource}; @@ -13,28 +15,38 @@ use remote_server::transport::{Error, InstallOutcome, InstallSource}; pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome { let binary_path = remote_server::setup::remote_server_binary(); log::info!("Installing remote server binary to {binary_path}"); - let mut outcome = match install_on_server(socket_path).await { - Ok(()) => InstallOutcome { - source: Some(InstallSource::Server), - result: Ok(()), - }, - Err(server_err) => { - if scp_fallback::should_try_install(&server_err) { - log::info!("Remote server install failed; falling back to SCP upload"); - match scp_fallback::install(socket_path).await { - Ok(()) => InstallOutcome { - source: Some(InstallSource::Client), - result: Ok(()), - }, - Err(e) => InstallOutcome { - source: Some(InstallSource::Client), - result: Err(e), - }, - } - } else { - InstallOutcome { - source: Some(InstallSource::Server), - result: Err(server_err), + 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 { + source: Some(InstallSource::Server), + result: Ok(()), + }, + Err(server_err) => { + if scp_fallback::should_try_install(&server_err) { + log::info!("Remote server install failed; falling back to SCP upload"); + match scp_fallback::install(socket_path).await { + Ok(()) => InstallOutcome { + source: Some(InstallSource::Client), + result: Ok(()), + }, + Err(e) => InstallOutcome { + source: Some(InstallSource::Client), + result: Err(e), + }, + } + } else { + InstallOutcome { + source: Some(InstallSource::Server), + result: Err(server_err), + } } } } @@ -73,6 +85,13 @@ pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome { 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 { + scp_fallback::local_helper_version(platform).await +} + /// Runs the install script on the remote host to download and install the /// binary directly from the CDN. async fn install_on_server(socket_path: &Path) -> Result<(), Error> { diff --git a/app/src/remote_server/ssh_transport/installation/scp_fallback.rs b/app/src/remote_server/ssh_transport/installation/scp_fallback.rs index 68f82907..e16bb5e5 100644 --- a/app/src/remote_server/ssh_transport/installation/scp_fallback.rs +++ b/app/src/remote_server/ssh_transport/installation/scp_fallback.rs @@ -8,6 +8,9 @@ use remote_server::setup::RemotePlatform; use remote_server::transport::Error; 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; // 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) } +/// 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. /// /// 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) .await .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 install_dir = remote_server::setup::remote_server_dir(); 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}"); remote_server::ssh::scp_upload( socket_path, - &client_tarball_path, + client_tarball_path, &remote_tarball_path, timeout, ) @@ -88,6 +118,58 @@ pub(super) async fn install(socket_path: &Path) -> Result<(), Error> { } } +fn helper_relative_path(platform: &RemotePlatform) -> Option { + 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 { + 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 { + 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 { + 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 { galaxy_core::paths::cache_dir() .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 /// tarball into the cache and returns the newly cached path. async fn cached_remote_server_tarball(platform: &RemotePlatform) -> anyhow::Result { + 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); if is_valid_cached_tarball(&cache_path).await { log::info!( diff --git a/app/src/root_view.rs b/app/src/root_view.rs index 6646c431..b51f182d 100644 --- a/app/src/root_view.rs +++ b/app/src/root_view.rs @@ -2749,7 +2749,7 @@ impl RootView { } /// 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. pub fn insert_subshell_command_and_bootstrap_if_supported( &mut self, diff --git a/app/src/server/telemetry/events.rs b/app/src/server/telemetry/events.rs index 62c7b7bc..1802e77a 100644 --- a/app/src/server/telemetry/events.rs +++ b/app/src/server/telemetry/events.rs @@ -1739,7 +1739,7 @@ pub enum TelemetryEvent { AddAddedSubshellCommand, RemoveAddedSubshellCommand, ReceivedSubshellRcFileDcs, - ToggleSshWarpification { + ToggleSshWormholing { enabled: bool, }, /// User changed the SSH extension install mode. @@ -1751,11 +1751,11 @@ pub enum TelemetryEvent { SshRemoteServerChoiceDoNotAskAgainToggled { checked: bool, }, - WarpifyFooterShown { + WormholeFooterShown { is_ssh: bool, }, AgentToolbarDismissed, - WarpifyFooterAcceptedWarpify { + WormholeFooterAcceptedWormhole { is_ssh: bool, }, ShowAliasExpansionBanner, @@ -1861,7 +1861,7 @@ pub enum TelemetryEvent { team_uid: ServerId, }, CopyObjectToClipboard(TelemetryCloudObjectType), - OpenAndWarpifyDockerSubshell { + OpenAndWormholeDockerSubshell { /// Some variant if we support this shell type, and None otherwise. shell_type: Option, }, @@ -3438,8 +3438,8 @@ impl TelemetryEvent { Some(json!({ "remember": remember })) } TelemetryEvent::AgentToolbarDismissed => None, - TelemetryEvent::WarpifyFooterShown { is_ssh } - | TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh } => { + TelemetryEvent::WormholeFooterShown { is_ssh } + | TelemetryEvent::WormholeFooterAcceptedWormhole { is_ssh } => { Some(json!({ "is_ssh": is_ssh })) } TelemetryEvent::ToggleSameLinePrompt { enabled } => Some(json!({ "enabled": enabled })), @@ -3512,7 +3512,7 @@ impl TelemetryEvent { TelemetryEvent::CopyObjectToClipboard(object_type) => { Some(json!({ "object_type": object_type })) } - TelemetryEvent::OpenAndWarpifyDockerSubshell { shell_type } => { + TelemetryEvent::OpenAndWormholeDockerSubshell { shell_type } => { Some(json!({ "shell_type": shell_type })) } TelemetryEvent::ToggleBlockFilterQuery { enabled, source } => { @@ -3536,7 +3536,7 @@ impl TelemetryEvent { TelemetryEvent::ToggleNewWindowsAtCustomSize { 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::SshRemoteServerChoiceDoNotAskAgainToggled { checked } => { Some(json!({"checked": checked})) @@ -5053,9 +5053,9 @@ impl TelemetryEvent { | TelemetryEvent::AddAddedSubshellCommand | TelemetryEvent::RemoveAddedSubshellCommand | TelemetryEvent::ReceivedSubshellRcFileDcs - | TelemetryEvent::WarpifyFooterShown { .. } + | TelemetryEvent::WormholeFooterShown { .. } | TelemetryEvent::AgentToolbarDismissed - | TelemetryEvent::WarpifyFooterAcceptedWarpify { .. } + | TelemetryEvent::WormholeFooterAcceptedWormhole { .. } | TelemetryEvent::ShowAliasExpansionBanner | TelemetryEvent::EnableAliasExpansionFromBanner | TelemetryEvent::DismissAliasExpansionBanner @@ -5099,7 +5099,7 @@ impl TelemetryEvent { | TelemetryEvent::LogOut | TelemetryEvent::InviteTeammates { .. } | TelemetryEvent::CopyObjectToClipboard(_) - | TelemetryEvent::OpenAndWarpifyDockerSubshell { .. } + | TelemetryEvent::OpenAndWormholeDockerSubshell { .. } | TelemetryEvent::UpdateBlockFilterQuery | TelemetryEvent::UpdateBlockFilterQueryContextLines { .. } | TelemetryEvent::ToggleBlockFilterQuery { .. } @@ -5174,7 +5174,7 @@ impl TelemetryEvent { | TelemetryEvent::MCPServerSpawned { .. } | TelemetryEvent::MCPToolCallAccepted { .. } | TelemetryEvent::ExecutedWarpDrivePrompt { .. } - | TelemetryEvent::ToggleSshWarpification { .. } + | TelemetryEvent::ToggleSshWormholing { .. } | TelemetryEvent::SetSshExtensionInstallMode { .. } | TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { .. } | TelemetryEvent::SettingsImportInitiated @@ -5604,12 +5604,12 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants { Self::TriggerSubshellBootstrap => EnablementState::Always, Self::AddDenylistedSubshellCommand => EnablementState::Always, Self::RemoveDenylistedSubshellCommand => EnablementState::Always, - Self::ToggleSshWarpification => EnablementState::Always, + Self::ToggleSshWormholing => EnablementState::Always, Self::SetSshExtensionInstallMode => EnablementState::Always, Self::SshRemoteServerChoiceDoNotAskAgainToggled => EnablementState::Always, - Self::WarpifyFooterShown + Self::WormholeFooterShown | Self::AgentToolbarDismissed - | Self::WarpifyFooterAcceptedWarpify => EnablementState::Always, + | Self::WormholeFooterAcceptedWormhole => EnablementState::Always, Self::AddAddedSubshellCommand => EnablementState::Always, Self::RemoveAddedSubshellCommand => EnablementState::Always, Self::ReceivedSubshellRcFileDcs => EnablementState::Always, @@ -5640,7 +5640,7 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants { Self::SettingsImportInitiated => EnablementState::Always, Self::InviteTeammates => EnablementState::Always, Self::CopyObjectToClipboard => EnablementState::Always, - Self::OpenAndWarpifyDockerSubshell => EnablementState::Always, + Self::OpenAndWormholeDockerSubshell => EnablementState::Always, Self::UpdateBlockFilterQuery => EnablementState::Always, Self::UpdateBlockFilterQueryContextLines => EnablementState::Always, Self::ToggleBlockFilterQuery => EnablementState::Always, @@ -6110,14 +6110,14 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants { Self::AddAddedSubshellCommand => "Add Added Subshell Command", Self::RemoveAddedSubshellCommand => "Remove Added Subshell Command", 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::SshRemoteServerChoiceDoNotAskAgainToggled => { "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::WarpifyFooterAcceptedWarpify => "Warpify Footer Accepted Warpify", + Self::WormholeFooterAcceptedWormhole => "Wormhole Footer Accepted Wormhole", Self::ShowAliasExpansionBanner => "Show Alias Expansion Banner", Self::DismissAliasExpansionBanner => "Dismiss Alias Expansion Banner", Self::EnableAliasExpansionFromBanner => "Enable Alias Expansion From Banner", @@ -6155,7 +6155,7 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants { Self::SettingsImportInitiated => "Settings Import Initiated", Self::InviteTeammates => "Invited Teammates", Self::CopyObjectToClipboard => "Copy Object To Clipboard", - Self::OpenAndWarpifyDockerSubshell => "OpenAndWarpifyDockerSubshell", + Self::OpenAndWormholeDockerSubshell => "OpenAndWormholeDockerSubshell", Self::UpdateBlockFilterQuery => "Update Block Filter Query", Self::ToggleBlockFilterQuery => "Toggle Block Filter Query", Self::ToggleBlockFilterCaseSensitivity => "Toggle Block Filter Case Sensitivity", @@ -6790,28 +6790,28 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants { "Enabled or disabled preserving the active tab color" } 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 => { - "Developer declined the Warp banner to Warpify the current session" + "Developer declined the Galaxy banner to Wormhole the current session" } 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 => { - "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 => { - "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 => { - "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 => { - "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::ToggleSshWarpification => "Changed the setting for SSH sessions to be warified", + Self::ReceivedSubshellRcFileDcs => "Spawned a subshell to be automatically Wormholed", + Self::ToggleSshWormholing => "Changed the setting for SSH sessions to be wormholed", Self::SetSshExtensionInstallMode => { "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" } Self::AgentModeRatedResponse => "User rated an Agent Mode response", - Self::WarpifyFooterShown => { - "Displayed the warpify footer for a detected subshell or SSH session" + Self::WormholeFooterShown => { + "Displayed the wormhole footer for a detected subshell or SSH session" } 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 => { "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::InviteTeammates => "Sent emails to invite teammates to join Warp Drive team", Self::CopyObjectToClipboard => "Copied an object to the user's keyboard", - Self::OpenAndWarpifyDockerSubshell => { - "Warpifying a docker subshell from using the docker extension" + Self::OpenAndWormholeDockerSubshell => { + "Wormholing a docker subshell from using the docker extension" } Self::UpdateBlockFilterQuery => "When a new filter is applied to a block", Self::UpdateBlockFilterQueryContextLines => { diff --git a/app/src/settings/init.rs b/app/src/settings/init.rs index f3f152be..8583fc40 100644 --- a/app/src/settings/init.rs +++ b/app/src/settings/init.rs @@ -33,7 +33,7 @@ use crate::terminal::safe_mode_settings::SafeModeSettings; use crate::terminal::session_settings::{SessionSettings, SessionSettingsChangedEvent}; use crate::terminal::settings::TerminalSettings; 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::undo_close::UndoCloseSettings; use crate::window_settings::WindowSettings; @@ -86,7 +86,7 @@ pub fn register_all_settings(ctx: &mut AppContext) { AppIconSettings::register(ctx); AppEditorSettings::register(ctx); InputSettings::register(ctx); - WarpifySettings::register(ctx); + WormholeSettings::register(ctx); AltScreenReporting::register(ctx); UndoCloseSettings::register(ctx); SshSettings::register(ctx); diff --git a/app/src/settings/linux.rs b/app/src/settings/linux.rs index 7a64e2eb..0a8fc9fb 100644 --- a/app/src/settings/linux.rs +++ b/app/src/settings/linux.rs @@ -1,6 +1,6 @@ use galaxyui::platform::linux; use settings::macros::define_settings_group; -use settings::{SupportedPlatforms, SyncToCloud}; +use settings::{Setting as _, SupportedPlatforms, SyncToCloud}; define_settings_group!(LinuxAppConfiguration, settings: [ diff --git a/app/src/settings/mod.rs b/app/src/settings/mod.rs index 34a2a6c0..fe33845d 100644 --- a/app/src/settings/mod.rs +++ b/app/src/settings/mod.rs @@ -28,6 +28,7 @@ mod onboarding; mod pane; mod privacy; mod same_line_prompt_block; +pub mod schema_export; mod scroll; mod select; mod ssh; diff --git a/app/src/settings/schema_export.rs b/app/src/settings/schema_export.rs new file mode 100644 index 00000000..72a9c878 --- /dev/null +++ b/app/src/settings/schema_export.rs @@ -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 { + 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, + hierarchy: &str, +) -> &'a mut Map { + 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:: { + 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::(&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 { + 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) +} diff --git a/app/src/settings/ssh.rs b/app/src/settings/ssh.rs index c945578a..07435df3 100644 --- a/app/src/settings/ssh.rs +++ b/app/src/settings/ssh.rs @@ -10,7 +10,7 @@ define_settings_group!(SshSettings, sync_to_cloud: SyncToCloud::Never, private: false, 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.", }, ] diff --git a/app/src/settings_view/mod.rs b/app/src/settings_view/mod.rs index a11fa726..cc54e032 100644 --- a/app/src/settings_view/mod.rs +++ b/app/src/settings_view/mod.rs @@ -40,7 +40,7 @@ use settings_page::{ HEADER_PADDING, }; use teams_page::{TeamsPageView, TeamsPageViewEvent}; -use warpify_page::{WarpifyPageAction, WarpifyPageView}; +use wormhole_page::{WormholePageAction, WormholePageView}; use self::telemetry::SettingsTelemetryEvent; use crate::ai::custom_model_routers::CustomModelRouter; @@ -95,7 +95,7 @@ mod teams_page; mod telemetry; pub mod update_environment_form; mod warp_drive_page; -mod warpify_page; +mod wormhole_page; #[cfg(not(target_family = "wasm"))] pub(crate) use ai_page::cli_agent_settings_widget_id; @@ -233,7 +233,7 @@ pub enum SettingsSection { Scripting, Teams, WarpDrive, - Warpify, + Wormhole, /// Internal backing-page identifier for AISettingsPageView. Multiple subpages /// (WarpAgent, AgentProfiles, Knowledge, ThirdPartyCLIAgents) share this single /// 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::ProviderBedrock => write!(f, "Bedrock"), SettingsSection::ProviderACP => write!(f, "ACP"), - SettingsSection::Warpify => write!(f, "Wormhole"), + SettingsSection::Wormhole => write!(f, "Wormhole"), SettingsSection::CodeIndexing => write!(f, "Indexing and projects"), SettingsSection::EditorAndCodeReview => write!(f, "Editor and Code Review"), _ => write!(f, "{self:?}"), @@ -407,7 +407,6 @@ impl FromStr for SettingsSection { "Privacy" => Ok(Self::Privacy), "Galaxy Control" | "Scripting" => Ok(Self::Scripting), "Teams" => Ok(Self::Teams), - "Warpify" => Ok(Self::Warpify), "WarpDrive" | "Galaxy Drive" => Ok(Self::WarpDrive), "Oz" | "Warp Agent" | "Galaxy Agent" => Ok(Self::WarpAgent), "Profiles" | "AgentProfiles" => Ok(Self::AgentProfiles), @@ -423,7 +422,7 @@ impl FromStr for SettingsSection { "Indexing and projects" | "CodeIndexing" => Ok(Self::CodeIndexing), "Editor and Code Review" | "EditorAndCodeReview" => Ok(Self::EditorAndCodeReview), "Experiments" => Ok(Self::Experiments), - "Wormhole" => Ok(Self::Warpify), + "Wormhole" => Ok(Self::Wormhole), _ => Err(()), } } @@ -488,7 +487,7 @@ pub mod flags { pub const SCROLL_REPORTING_CONTEXT_FLAG: &str = "Scroll_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_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 LONG_RUNNING_NOTIFICATIONS_FLAG: &str = "Long_Running_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 LIGATURE_RENDERING_CONTEXT_FLAG: &str = "Ligature_Rendering_Enabled"; 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 AGENT_MODE_INPUT: &str = "InputAgentMode"; pub const TERMINAL_MODE_INPUT: &str = "InputTerminalMode"; @@ -657,7 +656,7 @@ pub fn init_actions_from_parent_view( ) { appearance_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); ai_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), Code(CodeSettingsPageAction), WarpDrive(warp_drive_page::WarpDriveSettingsPageAction), - WarpifyPageToggle(WarpifyPageAction), + WormholePageToggle(WormholePageAction), Tab, Split(Direction), ToggleMaximizePane, @@ -1108,7 +1107,7 @@ macro_rules! update_page { SettingsPageViewHandle::Appearance(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Features(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::Scripting(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); }); - let warpify_page_handle = ctx.add_typed_action_view(WarpifyPageView::new); - ctx.subscribe_to_view(&warpify_page_handle, |me, _, event, ctx| { - me.handle_warpify_page_event(event, ctx); + let wormhole_page_handle = ctx.add_typed_action_view(WormholePageView::new); + ctx.subscribe_to_view(&wormhole_page_handle, |me, _, event, ctx| { + me.handle_wormhole_page_event(event, ctx); }); // Render the privacy page only if telemetry opt-out is enabled. @@ -1256,7 +1255,7 @@ impl SettingsView { SettingsPage::new(appearance_page_handle), SettingsPage::new(features_page_handle), SettingsPage::new(keybindings_handle), - SettingsPage::new(warpify_page_handle), + SettingsPage::new(wormhole_page_handle), SettingsPage::new(warp_drive_page_handle), ]; @@ -1291,7 +1290,7 @@ impl SettingsView { SettingsNavItem::Page(SettingsSection::Appearance), SettingsNavItem::Page(SettingsSection::Features), SettingsNavItem::Page(SettingsSection::Keybindings), - SettingsNavItem::Page(SettingsSection::Warpify), + SettingsNavItem::Page(SettingsSection::Wormhole), SettingsNavItem::Page(SettingsSection::WarpDrive), SettingsNavItem::Page(SettingsSection::Privacy), SettingsNavItem::Page(SettingsSection::About), @@ -1702,7 +1701,7 @@ impl SettingsView { } } - fn handle_warpify_page_event( + fn handle_wormhole_page_event( &mut self, event: &SettingsPageEvent, ctx: &mut ViewContext, @@ -1938,7 +1937,7 @@ impl SettingsView { SettingsPageViewHandle::Appearance(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::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::AI(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) => { - if let Some(warpify_page) = self.settings_page(SettingsSection::Warpify) { - if let SettingsPageViewHandle::Warpify(view) = &warpify_page.view_handle { + SettingsAction::WormholePageToggle(wormhole_action) => { + if let Some(wormhole_page) = self.settings_page(SettingsSection::Wormhole) { + if let SettingsPageViewHandle::Wormhole(view) = &wormhole_page.view_handle { view.update(ctx, |view, ctx| { - view.handle_action(warpify_action, ctx); + view.handle_action(wormhole_action, ctx); }) } } diff --git a/app/src/settings_view/mod_tests.rs b/app/src/settings_view/mod_tests.rs index 40b3a63b..172f59ac 100644 --- a/app/src/settings_view/mod_tests.rs +++ b/app/src/settings_view/mod_tests.rs @@ -89,7 +89,7 @@ fn top_level_sections_map_to_themselves() { SettingsSection::Scripting, SettingsSection::Teams, SettingsSection::WarpDrive, - SettingsSection::Warpify, + SettingsSection::Wormhole, ] { assert!(!section.is_subpage(), "{section:?} should be top-level"); assert_eq!(section.parent_page_section(), section); @@ -101,7 +101,7 @@ fn current_settings_display_names_round_trip() { for (section, display_name) in [ (SettingsSection::Scripting, "Galaxy Control"), (SettingsSection::WarpDrive, "Galaxy Drive"), - (SettingsSection::Warpify, "Wormhole"), + (SettingsSection::Wormhole, "Wormhole"), (SettingsSection::WarpAgent, "Galaxy Agent"), (SettingsSection::AgentProfiles, "Profiles"), (SettingsSection::AgentMCPServers, "MCP servers"), @@ -135,7 +135,7 @@ fn legacy_settings_names_remain_parseable() { for (name, expected) in [ ("Scripting", SettingsSection::Scripting), ("WarpDrive", SettingsSection::WarpDrive), - ("Warpify", SettingsSection::Warpify), + ("Wormhole", SettingsSection::Wormhole), ("Oz", SettingsSection::WarpAgent), ("Warp Agent", SettingsSection::WarpAgent), ("AgentProfiles", SettingsSection::AgentProfiles), @@ -191,7 +191,7 @@ fn realistic_nav_items() -> Vec { SettingsNavItem::Page(SettingsSection::Appearance), SettingsNavItem::Page(SettingsSection::Features), SettingsNavItem::Page(SettingsSection::Keybindings), - SettingsNavItem::Page(SettingsSection::Warpify), + SettingsNavItem::Page(SettingsSection::Wormhole), SettingsNavItem::Page(SettingsSection::WarpDrive), SettingsNavItem::Page(SettingsSection::Privacy), SettingsNavItem::Page(SettingsSection::About), diff --git a/app/src/settings_view/settings_page.rs b/app/src/settings_view/settings_page.rs index 66f9fb63..2a513408 100644 --- a/app/src/settings_view/settings_page.rs +++ b/app/src/settings_view/settings_page.rs @@ -37,7 +37,7 @@ use super::privacy_page::PrivacyPageView; use super::scripting_page::ScriptingSettingsPageView; use super::teams_page::TeamsPageView; use super::warp_drive_page::WarpDriveSettingsPageView; -use super::warpify_page::WarpifyPageView; +use super::wormhole_page::WormholePageView; use super::SettingsSection; use crate::appearance::Appearance; use crate::settings::CloudPreferencesSettings; @@ -100,7 +100,7 @@ pub enum SettingsPageViewHandle { About(ViewHandle), Code(ViewHandle), Privacy(ViewHandle), - Warpify(ViewHandle), + Wormhole(ViewHandle), Scripting(ViewHandle), AI(ViewHandle), MCPServers(ViewHandle), @@ -119,7 +119,7 @@ impl SettingsPageViewHandle { About(view_handle) => ChildView::new(view_handle).finish(), Code(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(), AI(view_handle) => ChildView::new(view_handle).finish(), MCPServers(view_handle) => ChildView::new(view_handle).finish(), diff --git a/app/src/settings_view/warpify_page.rs b/app/src/settings_view/wormhole_page.rs similarity index 73% rename from app/src/settings_view/warpify_page.rs rename to app/src/settings_view/wormhole_page.rs index 7dc96a80..f3eed999 100644 --- a/app/src/settings_view/warpify_page.rs +++ b/app/src/settings_view/wormhole_page.rs @@ -3,9 +3,7 @@ use std::collections::HashMap; use std::fmt::Display; use galaxy_core::features::FeatureFlag; -use galaxyui::elements::{ - Container, Flex, FormattedTextElement, HighlightedHyperlink, MouseStateHandle, ParentElement, -}; +use galaxyui::elements::{Container, Flex, MouseStateHandle, ParentElement, Text}; use galaxyui::keymap::ContextPredicate; use galaxyui::presenter::ChildView; use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles}; @@ -14,7 +12,6 @@ use galaxyui::{ Action, AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle, }; -use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine}; use regex::Regex; use settings::{Setting, ToggleableSetting}; use strum::IntoEnumIterator; @@ -29,9 +26,9 @@ use super::{flags, SettingsAction, SettingsSection, ToggleSettingActionPair}; use crate::appearance::Appearance; use crate::server::telemetry::TelemetryEvent; use crate::settings::{ReuseExistingSshControlMaster, SshSettings}; -use crate::terminal::warpify::settings::{ - EnableSshWarpification, SshExtensionInstallMode, SshExtensionInstallModeSetting, - WarpifySettings, WarpifySettingsChangedEvent, +use crate::terminal::wormhole::settings::{ + EnableSshWormholing, SshExtensionInstallMode, SshExtensionInstallModeSetting, WormholeSettings, + WormholeSettingsChangedEvent, }; use crate::ui_components::blended_colors; use crate::view_components::dropdown::{Dropdown, DropdownItem}; @@ -43,19 +40,19 @@ pub fn init_actions_from_parent_view( context: &ContextPredicate, 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![]; - if WarpifySettings::as_ref(app) - .enable_ssh_warpification + if WormholeSettings::as_ref(app) + .enable_ssh_wormholing .is_supported_on_current_platform() { toggle_binding_pairs.push(ToggleSettingActionPair::new( - "SSH Warpification", - builder(SettingsAction::WarpifyPageToggle( - WarpifyPageAction::ToggleSshWarpification, + "SSH Wormholing", + builder(SettingsAction::WormholePageToggle( + WormholePageAction::ToggleSshWormholing, )), 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 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 = - "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 /// 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 -/// related to warpification. -pub struct WarpifyPageView { +/// related to wormholing. +pub struct WormholePageView { page: PageType, /// This needs to mirror the length of SubshellSettings::added_remove_button_states. remove_added_command_button_states: Vec, @@ -87,19 +84,19 @@ pub struct WarpifyPageView { remove_denylisted_command_button_states: Vec, add_denylisted_commands_editor: ViewHandle, - ssh_extension_install_mode_dropdown: ViewHandle>, + ssh_extension_install_mode_dropdown: ViewHandle>, } -impl WarpifyPageView { +impl WormholePageView { pub fn new(ctx: &mut ViewContext) -> 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.subscribe_to_model(&warpify_settings_handle, move |me, model, event, ctx| { + ctx.observe(&wormhole_settings_handle, Self::update_button_states); + ctx.subscribe_to_model(&wormhole_settings_handle, move |me, model, event, ctx| { me.update_button_states(model, ctx); if matches!( event, - WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } + WormholeSettingsChangedEvent::SshExtensionInstallModeSetting { .. } ) { me.update_dropdown(ctx); } @@ -143,20 +140,20 @@ impl WarpifyPageView { ssh_extension_install_mode_dropdown, }; - instance.update_button_states(warpify_settings_handle, ctx); + instance.update_button_states(wormhole_settings_handle, ctx); instance } fn build_page(ctx: &mut ViewContext) -> PageType { 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())]) .with_subtitle("Subshells supported: bash, zsh, and fish."), ]; - let warpify_settings = WarpifySettings::as_ref(ctx); - if warpify_settings - .enable_ssh_warpification + let wormhole_settings = WormholeSettings::as_ref(ctx); + if wormhole_settings + .enable_ssh_wormholing .is_supported_on_current_platform() { categories.push( @@ -171,16 +168,16 @@ impl WarpifyPageView { /// its delete button in the View. fn update_button_states( &mut self, - warpify_settings_handle: ModelHandle, + wormhole_settings_handle: ModelHandle, ctx: &mut ViewContext, ) { - let warpify_settings = warpify_settings_handle.as_ref(ctx); - self.remove_denylisted_command_button_states = warpify_settings + let wormhole_settings = wormhole_settings_handle.as_ref(ctx); + self.remove_denylisted_command_button_states = wormhole_settings .subshell_command_denylist .iter() .map(|_| Default::default()) .collect(); - self.remove_added_command_button_states = warpify_settings + self.remove_added_command_button_states = wormhole_settings .added_subshell_commands .iter() .map(|_| Default::default()) @@ -189,16 +186,16 @@ impl WarpifyPageView { } /// 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). fn update_dropdown(&mut self, ctx: &mut ViewContext) { - let current_mode = *WarpifySettings::as_ref(ctx) + let current_mode = *WormholeSettings::as_ref(ctx) .ssh_extension_install_mode .value(); self.ssh_extension_install_mode_dropdown .update(ctx, |dropdown, ctx| { dropdown.set_selected_by_action( - WarpifyPageAction::SetSshExtensionInstallMode(current_mode), + WormholePageAction::SetSshExtensionInstallMode(current_mode), ctx, ); }); @@ -212,8 +209,8 @@ impl WarpifyPageView { ) { match event { SubmittableTextInputEvent::Submit(new_command) => { - WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { - warpify_settings.add_subshell_command(new_command, ctx); + WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| { + wormhole_settings.add_subshell_command(new_command, ctx); }); send_telemetry_from_ctx!(TelemetryEvent::AddAddedSubshellCommand, ctx); @@ -230,8 +227,8 @@ impl WarpifyPageView { ) { match event { SubmittableTextInputEvent::Submit(new_command) => { - WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { - warpify_settings.denylist_subshell_command(new_command, ctx); + WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| { + wormhole_settings.denylist_subshell_command(new_command, ctx); }); send_telemetry_from_ctx!(TelemetryEvent::AddDenylistedSubshellCommand, ctx); @@ -242,20 +239,20 @@ impl WarpifyPageView { fn remove_denylisted_command(&self, index: usize, ctx: &mut ViewContext) { send_telemetry_from_ctx!(TelemetryEvent::RemoveDenylistedSubshellCommand, ctx); - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.remove_denylisted_subshell_command(index, ctx) + WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| { + wormhole.remove_denylisted_subshell_command(index, ctx) }); } fn remove_added_command(&self, index: usize, ctx: &mut ViewContext) { send_telemetry_from_ctx!(TelemetryEvent::RemoveAddedSubshellCommand, ctx); - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.remove_added_subshell_command(index, ctx) + WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| { + wormhole.remove_added_subshell_command(index, ctx) }); } } -impl Entity for WarpifyPageView { +impl Entity for WormholePageView { type Event = SettingsPageEvent; } @@ -272,25 +269,23 @@ fn build_sub_sub_title(title: &str, appearance: &Appearance) -> Container { const SSH_EXTENSION_DROPDOWN_WIDTH: f32 = 250.; -impl WarpifyPageView { +impl WormholePageView { fn create_ssh_extension_install_mode_dropdown( ctx: &mut ViewContext, - ) -> ViewHandle> { - let items: Vec> = SshExtensionInstallMode::iter() + ) -> ViewHandle> { + let items: Vec> = SshExtensionInstallMode::iter() .map(|mode| { DropdownItem::new( mode.display_name(), - WarpifyPageAction::SetSshExtensionInstallMode(mode), + WormholePageAction::SetSshExtensionInstallMode(mode), ) }) .collect(); - let current_mode = *WarpifySettings::as_ref(ctx) + let current_mode = *WormholeSettings::as_ref(ctx) .ssh_extension_install_mode .value(); - let enable_ssh_warpification = *WarpifySettings::as_ref(ctx) - .enable_ssh_warpification - .value(); + let enable_ssh_wormholing = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value(); ctx.add_typed_action_view(move |ctx| { let mut dropdown = Dropdown::new(ctx); @@ -298,10 +293,10 @@ impl WarpifyPageView { dropdown.set_menu_width(SSH_EXTENSION_DROPDOWN_WIDTH, ctx); dropdown.add_items(items, ctx); dropdown.set_selected_by_action( - WarpifyPageAction::SetSshExtensionInstallMode(current_mode), + WormholePageAction::SetSshExtensionInstallMode(current_mode), ctx, ); - if !enable_ssh_warpification { + if !enable_ssh_wormholing { dropdown.set_disabled(ctx); } dropdown @@ -352,9 +347,9 @@ impl WarpifyPageView { } } -impl View for WarpifyPageView { +impl View for WormholePageView { fn ui_name() -> &'static str { - "WarpifyPageView" + "WormholePageView" } fn render(&self, app: &AppContext) -> Box { @@ -363,41 +358,38 @@ impl View for WarpifyPageView { } #[derive(Clone, Debug, PartialEq)] -pub enum WarpifyPageAction { +pub enum WormholePageAction { RemoveAddedCommand(usize), RemoveDenylistedCommand(usize), - ToggleSshWarpification, + ToggleSshWormholing, /// Toggles whether the legacy SSH wrapper attaches to an existing /// ControlMaster for the destination host instead of creating its own. ToggleReuseSshControlMaster, /// Set the SSH extension installation mode (always ask / always install / always skip). SetSshExtensionInstallMode(SshExtensionInstallMode), - OpenUrl(String), } -impl TypedActionView for WarpifyPageView { - type Action = WarpifyPageAction; +impl TypedActionView for WormholePageView { + type Action = WormholePageAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { - use WarpifyPageAction::*; + use WormholePageAction::*; match action { RemoveDenylistedCommand(index) => self.remove_denylisted_command(*index, ctx), RemoveAddedCommand(index) => self.remove_added_command(*index, ctx), - ToggleSshWarpification => { - WarpifySettings::handle(ctx).update(ctx, |ssh_settings, ctx| { + ToggleSshWormholing => { + WormholeSettings::handle(ctx).update(ctx, |ssh_settings, ctx| { report_if_error!(ssh_settings - .enable_ssh_warpification + .enable_ssh_wormholing .toggle_and_save_value(ctx)); send_telemetry_from_ctx!( - TelemetryEvent::ToggleSshWarpification { - enabled: *ssh_settings.enable_ssh_warpification.value(), + TelemetryEvent::ToggleSshWormholing { + enabled: *ssh_settings.enable_ssh_wormholing.value(), }, ctx ); }); - let enabled = *WarpifySettings::as_ref(ctx) - .enable_ssh_warpification - .value(); + let enabled = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value(); self.ssh_extension_install_mode_dropdown .update(ctx, |dropdown, ctx| { if enabled { @@ -425,8 +417,8 @@ impl TypedActionView for WarpifyPageView { }); } SetSshExtensionInstallMode(mode) => { - WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { - report_if_error!(warpify_settings + WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| { + report_if_error!(wormhole_settings .ssh_extension_install_mode .set_value(*mode, 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 { - SettingsSection::Warpify + SettingsSection::Wormhole } fn should_render(&self, _ctx: &AppContext) -> bool { @@ -466,53 +455,39 @@ impl SettingsPageMeta for WarpifyPageView { } } -impl From> for SettingsPageViewHandle { - fn from(view_handle: ViewHandle) -> Self { - SettingsPageViewHandle::Warpify(view_handle) +impl From> for SettingsPageViewHandle { + fn from(view_handle: ViewHandle) -> Self { + SettingsPageViewHandle::Wormhole(view_handle) } } #[derive(Default)] -struct TitleWidget { - learn_more_highlight_index: HighlightedHyperlink, -} +struct TitleWidget; impl TitleWidget { fn render_top_of_page(&self, appearance: &Appearance, _app: &AppContext) -> Box { - let warpify_description = vec![ - FormattedTextFragment::plain_text( - "Configure whether Galaxy attempts to \u{201c}Wormhole\u{201d} (add support for blocks, \ - input modes, etc) certain shells. ", - ), - FormattedTextFragment::hyperlink( - "Learn more", - "https://docs.warp.dev/terminal/warpify/subshells", - ), - ]; - - let warpify_description = FormattedTextElement::new( - FormattedText::new([FormattedTextLine::Line(warpify_description)]), + let wormhole_description = Text::new( + "Configure whether Galaxy attempts to \u{201c}Wormhole\u{201d} supported shells, adding blocks, full text editing, completions, and other Galaxy features." + .to_string(), + appearance.ui_font_family(), 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()) - .register_default_click_handlers(|url, _, ctx| { - ctx.open_url(&url.url); - }) + .soft_wrap(true) + .with_color(blended_colors::text_sub( + appearance.theme(), + appearance.theme().surface_1(), + )) .finish(); Flex::column() .with_child(render_page_title("Wormhole", HEADER_FONT_SIZE, appearance)) - .with_child(warpify_description) + .with_child(wormhole_description) .finish() } } impl SettingsWidget for TitleWidget { - type View = WarpifyPageView; + type View = WormholePageView; fn search_terms(&self) -> &str { "ssh subshell galaxify session" @@ -536,20 +511,20 @@ struct SubshellsWidget {} impl SubshellsWidget { fn render_subshells_section( &self, - view: &WarpifyPageView, + view: &WormholePageView, appearance: &Appearance, app: &AppContext, ) -> Box { let mut column = Flex::column(); - let warpify_settings = WarpifySettings::as_ref(app); + let wormhole_settings = WormholeSettings::as_ref(app); column.add_child( view.build_input_list( "Added commands", - &warpify_settings.added_subshell_commands, + &wormhole_settings.added_subshell_commands, &view.remove_added_command_button_states, - WarpifyPageAction::RemoveAddedCommand, + WormholePageAction::RemoveAddedCommand, &view.add_added_commands_editor, appearance, ) @@ -559,9 +534,9 @@ impl SubshellsWidget { column.add_child( view.build_input_list( "Denylisted commands", - &warpify_settings.subshell_command_denylist, + &wormhole_settings.subshell_command_denylist, &view.remove_denylisted_command_button_states, - WarpifyPageAction::RemoveDenylistedCommand, + WormholePageAction::RemoveDenylistedCommand, &view.add_denylisted_commands_editor, appearance, ) @@ -574,7 +549,7 @@ impl SubshellsWidget { } impl SettingsWidget for SubshellsWidget { - type View = WarpifyPageView; + type View = WormholePageView; fn search_terms(&self) -> &str { "galaxify subshell" @@ -594,13 +569,13 @@ impl SettingsWidget for SubshellsWidget { #[derive(Default)] struct SSHWidget { - enable_ssh_warpification_switch_state: SwitchStateHandle, + enable_ssh_wormholing_switch_state: SwitchStateHandle, reuse_control_master_switch_state: SwitchStateHandle, local_only_icon_tooltip_states: RefCell>, } impl SettingsWidget for SSHWidget { - type View = WarpifyPageView; + type View = WormholePageView; fn search_terms(&self) -> &str { "galaxify ssh" @@ -618,31 +593,29 @@ impl SettingsWidget for SSHWidget { .theme() .sub_text_color(appearance.theme().surface_2()); - let enable_ssh_warpification = *WarpifySettings::as_ref(app) - .enable_ssh_warpification - .value(); + let enable_ssh_wormholing = *WormholeSettings::as_ref(app).enable_ssh_wormholing.value(); add_setting( &mut column, - &WarpifySettings::as_ref(app).enable_ssh_warpification, + &WormholeSettings::as_ref(app).enable_ssh_wormholing, move || { - render_body_item::( + render_body_item::( "Wormhole SSH Sessions".into(), None, LocalOnlyIconState::for_setting( - EnableSshWarpification::storage_key(), - EnableSshWarpification::sync_to_cloud(), + EnableSshWormholing::storage_key(), + EnableSshWormholing::sync_to_cloud(), &mut self.local_only_icon_tooltip_states.borrow_mut(), app, ), ToggleState::Enabled, appearance, ui_builder - .switch(self.enable_ssh_warpification_switch_state.clone()) - .check(enable_ssh_warpification) + .switch(self.enable_ssh_wormholing_switch_state.clone()) + .check(enable_ssh_wormholing) .build() .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(WarpifyPageAction::ToggleSshWarpification); + ctx.dispatch_typed_action(WormholePageAction::ToggleSshWormholing); }) .finish(), None, @@ -651,18 +624,18 @@ impl SettingsWidget for SSHWidget { ); 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()) } else { None }; add_setting( &mut column, - &WarpifySettings::as_ref(app).ssh_extension_install_mode, + &WormholeSettings::as_ref(app).ssh_extension_install_mode, move || { Container::new(render_dropdown_item( appearance, - "Install SSH extension", + "Install Wormhole helper", Some(SSH_EXTENSION_INSTALL_MODE_DESCRIPTION), None, LocalOnlyIconState::for_setting( @@ -688,7 +661,7 @@ impl SettingsWidget for SSHWidget { &SshSettings::as_ref(app).reuse_existing_control_master, move || { let mut column = Flex::column(); - column.add_child(render_body_item::( + column.add_child(render_body_item::( "Reuse existing SSH ControlMaster".into(), None, LocalOnlyIconState::for_setting( @@ -697,19 +670,19 @@ impl SettingsWidget for SSHWidget { &mut self.local_only_icon_tooltip_states.borrow_mut(), app, ), - enable_ssh_warpification.into(), + enable_ssh_wormholing.into(), appearance, ui_builder .switch(self.reuse_control_master_switch_state.clone()) .check(reuse_existing_control_master) - .with_disabled(!enable_ssh_warpification) + .with_disabled(!enable_ssh_wormholing) .build() .on_click(move |ctx, _, _| { - if !enable_ssh_warpification { + if !enable_ssh_wormholing { return; } ctx.dispatch_typed_action( - WarpifyPageAction::ToggleReuseSshControlMaster, + WormholePageAction::ToggleReuseSshControlMaster, ); }) .finish(), diff --git a/app/src/terminal/block_list_element.rs b/app/src/terminal/block_list_element.rs index aa156b70..213b899f 100644 --- a/app/src/terminal/block_list_element.rs +++ b/app/src/terminal/block_list_element.rs @@ -58,7 +58,7 @@ use super::view::{ BlocklistAIRenderContext, InlineBannerId, RichContentMetadata, SeparatorId, 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 crate::ai::blocklist::agent_view::{agent_view_bg_fill, AgentViewState}; 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::safe_mode_settings::get_secret_obfuscation_mode; use crate::terminal::view::TerminalAction; -use crate::terminal::warpify::SubshellSource; +use crate::terminal::wormhole::SubshellSource; use crate::terminal::{grid_renderer, SizeInfo}; use crate::themes::theme::{Fill, WarpTheme}; use crate::ui_components::{self, icons as UIIcon}; diff --git a/app/src/terminal/bootstrap.rs b/app/src/terminal/bootstrap.rs index d731f319..31215bb2 100644 --- a/app/src/terminal/bootstrap.rs +++ b/app/src/terminal/bootstrap.rs @@ -10,7 +10,7 @@ use rand::Rng; #[cfg(feature = "local_fs")] use super::{ 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::terminal::session_settings::SessionSettings; @@ -99,7 +99,7 @@ pub fn should_use_rc_file_bootstrap_method( && shell_type == ShellType::Zsh) || is_msys2 } - BootstrapSessionType::WarpifiedRemote => false, + BootstrapSessionType::WormholedRemote => false, } } diff --git a/app/src/terminal/cli_agent_sessions/event/mod.rs b/app/src/terminal/cli_agent_sessions/event/mod.rs index 768f3010..deb3dba5 100644 --- a/app/src/terminal/cli_agent_sessions/event/mod.rs +++ b/app/src/terminal/cli_agent_sessions/event/mod.rs @@ -68,7 +68,7 @@ pub struct CLIAgentEvent { const VERSIONED_PARSERS: &[EventParser] = &[v1::parse]; /// 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. #[cfg_attr(not(feature = "local_tty"), allow(dead_code))] pub const fn current_protocol_version() -> u32 { diff --git a/app/src/terminal/cli_agent_sessions/mod.rs b/app/src/terminal/cli_agent_sessions/mod.rs index df86bf46..ee7bb700 100644 --- a/app/src/terminal/cli_agent_sessions/mod.rs +++ b/app/src/terminal/cli_agent_sessions/mod.rs @@ -130,7 +130,7 @@ pub struct CLIAgentSession { /// `None` if the plugin predates version reporting or Codex is using OSC9 fallback. pub plugin_version: Option, /// `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. pub remote_host: Option, /// Draft text saved from the rich input composer when it was closed. diff --git a/app/src/terminal/event.rs b/app/src/terminal/event.rs index 2877465c..9bb49908 100644 --- a/app/src/terminal/event.rs +++ b/app/src/terminal/event.rs @@ -167,9 +167,9 @@ pub enum TerminalMode { #[derive(Clone, Debug)] pub enum SshLoginStatus { /// We have some evidence login is complete but should check again. - RecheckBeforeWarpifying, + RecheckBeforeWormholing, /// We have high confidence login is complete. - ReadyToWarpify, + ReadyToWormhole, } #[derive(Clone, Debug)] @@ -247,7 +247,7 @@ pub enum BlockType { /// This is a block containing background process output. Background(Arc), - /// 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). Static, } diff --git a/app/src/terminal/history_tests.rs b/app/src/terminal/history_tests.rs index a84b2e2f..cc2242a6 100644 --- a/app/src/terminal/history_tests.rs +++ b/app/src/terminal/history_tests.rs @@ -505,7 +505,7 @@ fn test_multiple_machines() { SessionInfo::new_for_test() .with_id(0) .with_shell_type(ShellType::Zsh) - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::WormholedRemote) .with_hostname("prod".to_string()) .with_user("user".to_string()) .with_ssh_socket_path(PathBuf::from("~/.ssh/12345")) @@ -517,7 +517,7 @@ fn test_multiple_machines() { SessionInfo::new_for_test() .with_id(1) .with_shell_type(ShellType::Zsh) - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::WormholedRemote) .with_hostname("dev".to_string()) .with_user("user2".to_string()) .with_ssh_socket_path(PathBuf::from("~/.ssh/12345")) diff --git a/app/src/terminal/input.rs b/app/src/terminal/input.rs index 2400a0a2..98e8e731 100644 --- a/app/src/terminal/input.rs +++ b/app/src/terminal/input.rs @@ -141,7 +141,7 @@ use super::view::queued_prompts_panel::{QueuedPromptsPanelEvent, QueuedPromptsPa use super::view::{ 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 crate::ai::agent::conversation::AIConversationId; use crate::ai::agent::{ @@ -11829,13 +11829,13 @@ impl Input { // CLI agent rich input in shell mode (! prefix) should allow completions // 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). let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx) && CLIAgentSessionsModel::as_ref(ctx).is_input_open(self.terminal_view_id) && !self .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 (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) { diff --git a/app/src/terminal/input/classic.rs b/app/src/terminal/input/classic.rs index c295e0a4..c717b144 100644 --- a/app/src/terminal/input/classic.rs +++ b/app/src/terminal/input/classic.rs @@ -23,7 +23,7 @@ use crate::terminal::input::common::{ use crate::terminal::input::{get_input_box_top_border_width, InputDropTargetData}; use crate::terminal::settings::{SpacingMode, TerminalSettings}; 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 { /// Renders the classic input. This is used when the user has 'Honor PS1' enabled in settings, diff --git a/app/src/terminal/line_editor_status.rs b/app/src/terminal/line_editor_status.rs index 9334f131..3ea970e3 100644 --- a/app/src/terminal/line_editor_status.rs +++ b/app/src/terminal/line_editor_status.rs @@ -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 /// 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 - /// (for Warpified sessions) is active, we must check if there was a corresponding precmd + /// not its a Wormholed session, so to in order properly signal downstream that the line editor + /// (for Wormholed sessions) is active, we must check if there was a corresponding precmd /// emitted prior to the end prompt marker. /// /// Precmd is always emitted before prompt markers. diff --git a/app/src/terminal/local_tty/terminal_manager.rs b/app/src/terminal/local_tty/terminal_manager.rs index d2a0efc7..1ab89453 100644 --- a/app/src/terminal/local_tty/terminal_manager.rs +++ b/app/src/terminal/local_tty/terminal_manager.rs @@ -53,7 +53,7 @@ use crate::terminal::session_settings::{SessionSettings, ToolbarChipSelection}; use crate::terminal::shared_session::sharer::network::Network; use crate::terminal::shared_session::{IsSharedSessionCreator, SharedSessionStatus}; 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::terminal_manager_util::{ init_pty_controller_model, init_remote_server_controller, wire_up_pty_controller_with_surface, @@ -740,13 +740,11 @@ impl TerminalManager { .contains(&ContextChipKind::NodeVersion) }; - // `enable_ssh_warpification` is the single source of truth for whether the SSH - // wrapper is active. The bootstrap scripts check `WARP_USE_SSH_WRAPPER` (derived + // `enable_ssh_wormholing` is the single source of truth for whether the SSH + // wrapper is active. The bootstrap scripts check `GALAXY_USE_SSH_WRAPPER` (derived // from this value) before invoking `warp_ssh_helper`, which spawns the ControlMaster // and opens agent-protocol channels. - let enable_ssh_wrapper = *WarpifySettings::as_ref(ctx) - .enable_ssh_warpification - .value(); + let enable_ssh_wrapper = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value(); // Only meaningful when the legacy ControlMaster wrapper is active. let reuse_ssh_control_master = enable_ssh_wrapper diff --git a/app/src/terminal/local_tty/unix.rs b/app/src/terminal/local_tty/unix.rs index d9f30c80..43212ba4 100644 --- a/app/src/terminal/local_tty/unix.rs +++ b/app/src/terminal/local_tty/unix.rs @@ -306,7 +306,7 @@ fn build_host_shell_command( // Whether the SSH wrapper should attach to an existing ControlMaster // for the destination host instead of always creating its own. builder.env( - "WARP_SSH_REUSE_CONTROL_MASTER", + "GALAXY_SSH_REUSE_CONTROL_MASTER", 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 // pre-refactor host-shell `spawn` set when the starter happened to // be a Docker sandbox, so behaviour is unchanged from before the - // split. Many of these (e.g. `WARP_USE_SSH_WRAPPER`, - // `SSH_SOCKET_DIR`, `HISTFILESIZE`, `WARP_IS_LOCAL_SHELL_SESSION`) + // split. Many of these (e.g. `GALAXY_USE_SSH_WRAPPER`, + // `SSH_SOCKET_DIR`, `HISTFILESIZE`, `GALAXY_IS_LOCAL_SHELL_SESSION`) // are set on the *host* `sbx` process and may or may not propagate // into the container depending on `sbx`'s env passthrough rules. // 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" }, ); builder.env( - "WARP_SSH_REUSE_CONTROL_MASTER", + "GALAXY_SSH_REUSE_CONTROL_MASTER", if reuse_ssh_control_master { "1" } else { "0" }, ); builder.env("SSH_SOCKET_DIR", ssh_socket_dir()); diff --git a/app/src/terminal/local_tty/windows/environment.rs b/app/src/terminal/local_tty/windows/environment.rs index d4333141..36082441 100644 --- a/app/src/terminal/local_tty/windows/environment.rs +++ b/app/src/terminal/local_tty/windows/environment.rs @@ -18,15 +18,15 @@ use crate::terminal::local_tty::PtyOptions; const HONOR_PS1_NAME: &str = "WARP_HONOR_PS1"; const PROMPT_NODE_VERSION_ENABLED_NAME: &str = "WARP_PROMPT_NODE_VERSION_ENABLED"; const INITIAL_WORKING_DIR_NAME: &str = "WARP_INITIAL_WORKING_DIR"; -const USE_SSH_WRAPPER_NAME: &str = "WARP_USE_SSH_WRAPPER"; -const SSH_REUSE_CONTROL_MASTER_NAME: &str = "WARP_SSH_REUSE_CONTROL_MASTER"; +const USE_SSH_WRAPPER_NAME: &str = "GALAXY_USE_SSH_WRAPPER"; +const SSH_REUSE_CONTROL_MASTER_NAME: &str = "GALAXY_SSH_REUSE_CONTROL_MASTER"; const SHELL_DEBUG_MODE_NAME: &str = "WARP_SHELL_DEBUG_MODE"; 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 PATH_APPEND_NAME: &str = "WARP_PATH_APPEND"; -const CLIENT_VERSION_NAME: &str = "WARP_CLIENT_VERSION"; -const CLI_AGENT_PROTOCOL_VERSION_NAME: &str = "WARP_CLI_AGENT_PROTOCOL_VERSION"; +const CLIENT_VERSION_NAME: &str = "GALAXY_CLIENT_VERSION"; +const CLI_AGENT_PROTOCOL_VERSION_NAME: &str = "GALAXY_CLI_AGENT_PROTOCOL_VERSION"; const WSLENV: &str = "WSLENV"; const HISTIGNORE: &str = "HISTIGNORE"; diff --git a/app/src/terminal/mod.rs b/app/src/terminal/mod.rs index c7780d4b..a0744e14 100644 --- a/app/src/terminal/mod.rs +++ b/app/src/terminal/mod.rs @@ -79,8 +79,8 @@ pub mod ssh; pub mod terminal_manager; mod terminal_size_element; pub mod view; -pub mod warpify; mod waterfall_gap_element; +pub mod wormhole; mod writeable_pty; #[cfg(feature = "tui")] pub use writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface}; diff --git a/app/src/terminal/model/blocks/selection.rs b/app/src/terminal/model/blocks/selection.rs index d61c83ae..c021eb78 100644 --- a/app/src/terminal/model/blocks/selection.rs +++ b/app/src/terminal/model/blocks/selection.rs @@ -22,7 +22,7 @@ use crate::terminal::model::block::BlockSection; use crate::terminal::model::index::{Direction, Point, Side}; use crate::terminal::model::selection::{ExpandedSelectionRange, Selection, SelectionDirection}; 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; /// 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(ssh_block) = app - .view_with_id::(active_window_id, *view_id) - { - let warpify_success_block = app.view(&ssh_block); + if let Some(ssh_block) = app.view_with_id::( + active_window_id, + *view_id, + ) { + let wormhole_success_block = app.view(&ssh_block); if let Some(selected_text) = - warpify_success_block.selected_text() + wormhole_success_block.selected_text() { selected_texts.push(selected_text); } @@ -1123,10 +1124,10 @@ impl BlockList { } if let Some(ssh_block) = - app.view_with_id::(active_window_id, view_id) + app.view_with_id::(active_window_id, view_id) { - let warpify_success_block = app.view(&ssh_block); - if let Some(selected_text) = warpify_success_block.selected_text() { + let wormhole_success_block = app.view(&ssh_block); + if let Some(selected_text) = wormhole_success_block.selected_text() { selected_texts.push(selected_text); } } diff --git a/app/src/terminal/model/rich_content.rs b/app/src/terminal/model/rich_content.rs index 7748db18..f5c696eb 100644 --- a/app/src/terminal/model/rich_content.rs +++ b/app/src/terminal/model/rich_content.rs @@ -4,7 +4,7 @@ pub enum RichContentType { AIBlock, EnterAgentView, - WarpifySuccessBlock, + WormholeSuccessBlock, InlineAgentViewHeader, AgentViewZeroState, TerminalViewZeroState, diff --git a/app/src/terminal/model/session.rs b/app/src/terminal/model/session.rs index b17ee319..b380da72 100644 --- a/app/src/terminal/model/session.rs +++ b/app/src/terminal/model/session.rs @@ -40,7 +40,7 @@ use crate::remote_server::manager::{RemoteServerManager, RemoteServerManagerEven use crate::server::telemetry::{BootstrappingInfo, TelemetryEvent}; use crate::terminal::event::{ExecutedExecutorCommandEvent, RemoteServerSetupState}; use crate::terminal::shell::{Shell, ShellType}; -use crate::terminal::warpify::SubshellSource; +use crate::terminal::wormhole::SubshellSource; use crate::terminal::{History, ShellHost, ShellLaunchData}; #[derive(thiserror::Error, Debug)] @@ -361,7 +361,7 @@ impl Sessions { let session = Arc::new(session); 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 // waiting for the next SessionConnected event. The // RemoteServerCommandExecutor already has its client baked in, so @@ -370,7 +370,7 @@ impl Sessions { if FeatureFlag::SshRemoteServer.is_enabled() && matches!( session_info.session_type, - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::WormholedRemote ) { if let Some(host_id) = RemoteServerManager::as_ref(ctx).host_id_for_session(session_id) @@ -518,7 +518,7 @@ impl Sessions { impl From for command_corrections::SessionType { fn from(session_type: SessionType) -> Self { match session_type { - SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote, + SessionType::WormholedRemote { .. } => command_corrections::SessionType::Remote, SessionType::Local => command_corrections::SessionType::Local, } } @@ -527,20 +527,20 @@ impl From for command_corrections::SessionType { impl From<&SessionType> for command_corrections::SessionType { fn from(session_type: &SessionType) -> Self { match session_type { - SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote, + SessionType::WormholedRemote { .. } => command_corrections::SessionType::Remote, 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 -/// 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 /// proxy and for the `RemoteCommandExecutor` fallback). /// -/// `No` covers local sessions, subshells, and remote sessions warpified *without* the wrapper -/// (e.g. via the auto-warpify RC snippet inside an unwrapped `ssh` session), which carry no +/// `No` covers local sessions, subshells, and remote sessions wormholed *without* the wrapper +/// (e.g. via the auto-wormhole RC snippet inside an unwrapped `ssh` session), which carry no /// ControlMaster socket. #[derive(Debug, Clone, PartialEq, Eq)] pub enum IsSSHWrapperSession { @@ -550,7 +550,7 @@ pub enum IsSSHWrapperSession { socket_path: PathBuf, /// `true` when `socket_path` points at a ControlMaster the user /// 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. external_control_master: bool, }, @@ -651,7 +651,7 @@ impl SessionInfo { 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() { active_block_session_id @@ -699,7 +699,7 @@ impl SessionInfo { { BootstrapSessionType::Local } else { - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::WormholedRemote } } Err(e) => { @@ -715,7 +715,7 @@ impl SessionInfo { _is_ssh_session: bool, ) -> BootstrapSessionType { // 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 @@ -859,26 +859,26 @@ impl SessionInfo { /// which happens *after* the session is bootstrapped. #[derive(Clone, Debug, PartialEq, Eq)] 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, - /// The session host is a different host from where Warp is running. - WarpifiedRemote, + /// The session host is a different host from where Galaxy is running. + WormholedRemote, } #[derive(Clone, Debug, PartialEq, Eq)] 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, - /// The session host is a different host from where Warp is running. - /// Note that we only know this for sure when we Warpify a block. + /// The session host is a different host from where Galaxy is running. + /// 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 /// `RemoteServerManager` has completed the connection handshake. It is /// `None` when the feature flag is off or the connection hasn't been /// established yet. - WarpifiedRemote { + WormholedRemote { host_id: Option, }, } @@ -887,7 +887,7 @@ impl From for SessionType { fn from(bst: BootstrapSessionType) -> Self { match bst { 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() } - /// 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). pub fn set_remote_host_id(&self, host_id: Option) { 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; } } @@ -1012,9 +1012,9 @@ impl Session { self.info.host_info.clone() } - /// Returns whether this session was established by Warp's in-band SSH wrapper (see - /// [`IsSSHWrapperSession`]). Note this stays `false` for remote sessions warpified via - /// the auto-warpify RC snippet inside an unwrapped `ssh` session. + /// Returns whether this session was established by Galaxy's in-band SSH wrapper (see + /// [`IsSSHWrapperSession`]). Note this stays `false` for remote sessions wormholed via + /// the auto-wormhole RC snippet inside an unwrapped `ssh` session. pub fn is_ssh_wrapper_session(&self) -> bool { matches!( self.info.is_ssh_wrapper_session, @@ -1023,7 +1023,7 @@ impl Session { } 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.subshell_info().is_some() } @@ -1539,7 +1539,7 @@ impl Session { self.read_history_for_local_session(is_kaspersky_running) .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`]. pub fn convert_directory_to_typed_path_buf(&self, pwd: String) -> TypedPathBuf { // 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 // - powershell on linux ---> unix // - powershell on windows ---> windows // - 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 we supported warpifying a powershell-on-windows SSH session. + // if we supported wormholing a powershell-on-windows SSH session. if cfg!(unix) { return TypedPathBuf::from_unix(pwd); } // We assume that we're on Windows. match self.shell_family() { - // Cases: WSL, MSYS2, warpified bash + // Cases: WSL, MSYS2, wormholed bash ShellFamily::Posix => TypedPathBuf::from_unix(pwd), // Cases: powershell sessions 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 { cfg_if::cfg_if! { 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 { if let BootstrapSessionType::Local = self.session_type { - self.session_type = BootstrapSessionType::WarpifiedRemote; + self.session_type = BootstrapSessionType::WormholedRemote; } self.is_ssh_wrapper_session = IsSSHWrapperSession::Yes { socket_path, @@ -1856,7 +1856,7 @@ pub mod testing { pub fn test_remote() -> Self { 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. let session_type = SessionType::from(info.session_type.clone()); Self { diff --git a/app/src/terminal/model/session/active_session.rs b/app/src/terminal/model/session/active_session.rs index c695c7f0..e7688457 100644 --- a/app/src/terminal/model/session/active_session.rs +++ b/app/src/terminal/model/session/active_session.rs @@ -100,12 +100,12 @@ impl ActiveSession { /// the connected host ID. pub fn location_for_path(&self, path: &str, app: &AppContext) -> Option { match self.session_type(app) { - Some(SessionType::WarpifiedRemote { + Some(SessionType::WormholedRemote { host_id: Some(host_id), }) => StandardizedPath::try_new(path) .ok() .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 => { let path = dunce::canonicalize(Path::new(path)).unwrap_or_else(|_| PathBuf::from(path)); diff --git a/app/src/terminal/model/session/command_executor.rs b/app/src/terminal/model/session/command_executor.rs index 521d5fe0..5b4ce9d1 100644 --- a/app/src/terminal/model/session/command_executor.rs +++ b/app/src/terminal/model/session/command_executor.rs @@ -289,7 +289,7 @@ fn new_command_executor_for_local_tty_session( } } } - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::WormholedRemote if is_ssh_wrapper_session && !FeatureFlag::InBandGeneratorsForSSH.is_enabled() && !force_use_in_band_generators => diff --git a/app/src/terminal/model/session_tests.rs b/app/src/terminal/model/session_tests.rs index 84e44fff..19e8a071 100644 --- a/app/src/terminal/model/session_tests.rs +++ b/app/src/terminal/model/session_tests.rs @@ -114,11 +114,11 @@ fn test_malicious_histfile_path_does_not_execute_injected_commands() { let malicious_histfile = format!("/tmp/x'; touch {marker}; echo '"); let session_info = SessionInfo::new_for_test() - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::WormholedRemote) .with_histfile(Some(malicious_histfile)); 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 let _ = session.read_history(false).await; diff --git a/app/src/terminal/model/terminal_model.rs b/app/src/terminal/model/terminal_model.rs index 89ab3bea..1b8b828e 100644 --- a/app/src/terminal/model/terminal_model.rs +++ b/app/src/terminal/model/terminal_model.rs @@ -351,7 +351,7 @@ enum IsReceivingHook { 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)] pub struct SubshellSuccessBlockInfo { /// 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 /// 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 - /// 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) { let Some(mut ssh_login_state) = self.notify_on_end_of_ssh_login.clone() else { return; @@ -2279,7 +2279,7 @@ impl TerminalModel { SshLoginState::LastLogin | SshLoginState::PromptDetected => { self.event_proxy .send_terminal_event(Event::DetectedEndOfSshLogin( - SshLoginStatus::ReadyToWarpify, + SshLoginStatus::ReadyToWormhole, )); ssh_login_state.notification_state = SshLoginNotificationState::Completed; @@ -2290,7 +2290,7 @@ impl TerminalModel { if ssh_login_state.notification_state == SshLoginNotificationState::Monitoring { self.event_proxy .send_terminal_event(Event::DetectedEndOfSshLogin( - SshLoginStatus::RecheckBeforeWarpifying, + SshLoginStatus::RecheckBeforeWormholing, )); // We want to avoid emitting redundant events for the initial check. @@ -2300,7 +2300,7 @@ impl TerminalModel { } else { self.event_proxy .send_terminal_event(Event::DetectedEndOfSshLogin( - SshLoginStatus::ReadyToWarpify, + SshLoginStatus::ReadyToWormhole, )); ssh_login_state.notification_state = SshLoginNotificationState::Completed; diff --git a/app/src/terminal/model_events.rs b/app/src/terminal/model_events.rs index 769e9ebe..c8e531c3 100644 --- a/app/src/terminal/model_events.rs +++ b/app/src/terminal/model_events.rs @@ -381,7 +381,7 @@ pub enum ModelEvent { ExecutedInBandCommand(ExecutedExecutorCommandEvent), /// 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 - /// useful for detecting when an ssh session becomes ready for warpification. + /// useful for detecting when an ssh session becomes ready for wormholing. DetectedEndOfSshLogin(SshLoginStatus), InitSubshell(InitSubshellEvent), /// Emitted when the user's RC file has been executed in a subshell. diff --git a/app/src/terminal/prompt/mod.rs b/app/src/terminal/prompt/mod.rs index af1765d2..83302ebe 100644 --- a/app/src/terminal/prompt/mod.rs +++ b/app/src/terminal/prompt/mod.rs @@ -21,7 +21,7 @@ pub fn user_and_host_name_string( ) -> Option { match session_type { SessionType::Local => None, - SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:")), + SessionType::WormholedRemote { .. } => Some(format!("{user}@{hostname}:")), } } diff --git a/app/src/terminal/prompt_render_helper.rs b/app/src/terminal/prompt_render_helper.rs index 7865591b..8419267c 100644 --- a/app/src/terminal/prompt_render_helper.rs +++ b/app/src/terminal/prompt_render_helper.rs @@ -251,13 +251,11 @@ impl PromptRenderHelper { RemoteServerSetupState::Checking => "Starting shell...".to_string(), RemoteServerSetupState::Installing { progress_percent: Some(p), - } => format!("Installing Warp SSH Extension... ({p}%)"), + } => format!("Installing Wormhole helper... ({p}%)"), RemoteServerSetupState::Installing { progress_percent: None, - } => "Installing Warp SSH Extension...".to_string(), - RemoteServerSetupState::Updating => { - "Updating Warp SSH Extension...".to_string() - } + } => "Installing Wormhole helper...".to_string(), + RemoteServerSetupState::Updating => "Updating Wormhole helper...".to_string(), RemoteServerSetupState::Initializing => "Initializing...".to_string(), RemoteServerSetupState::Ready => "Starting shell...".to_string(), // Failed and Unsupported both fall back to the wrapper-only SSH diff --git a/app/src/terminal/ssh/error.rs b/app/src/terminal/ssh/error.rs index ad7cf8bf..6cc97077 100644 --- a/app/src/terminal/ssh/error.rs +++ b/app/src/terminal/ssh/error.rs @@ -1,9 +1,9 @@ use crate::appearance::Appearance; -use crate::terminal::model::ansi::WarpificationUnavailableReason; -use crate::terminal::warpify; -use crate::terminal::warpify::render::apply_spacing_styles; -use crate::terminal::warpify::render::build_description_row; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::model::ansi::WormholingUnavailableReason; +use crate::terminal::wormhole; +use crate::terminal::wormhole::render::apply_spacing_styles; +use crate::terminal::wormhole::render::build_description_row; +use crate::terminal::wormhole::settings::WormholeSettings; use crate::ui_components::icons::Icon as UiIcon; use galaxy_core::channel::ChannelState; 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."; const TMUX_FAILED_ERROR: &str = "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 = "Unsupported shell. Please set bash, zsh, or fish as your default shell and try again."; const TMUX_INSTALL_FAILED_ERROR: &str = @@ -55,28 +55,28 @@ fn get_ssh_github_issue_url(title: &str) -> String { format!("{url}&title={title}") } -impl WarpificationUnavailableReason { +impl WormholingUnavailableReason { fn error_message(&self) -> &'static str { match self { - WarpificationUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR, - WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => { + WormholingUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR, + WormholingUnavailableReason::UnsupportedTmuxVersion { .. } => { UNSUPPORTED_TMUX_VERSION_ERROR } - WarpificationUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR, - WarpificationUnavailableReason::Timeout { .. } => WARPIFY_TIMEOUT_ERROR, - WarpificationUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR, - WarpificationUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR, + WormholingUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR, + WormholingUnavailableReason::Timeout { .. } => WORMHOLE_TIMEOUT_ERROR, + WormholingUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR, + WormholingUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR, } } fn error_title(&self) -> &'static str { match self { - WarpificationUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed", - WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => { + WormholingUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed", + WormholingUnavailableReason::UnsupportedTmuxVersion { .. } => { "Unsupported Tmux Version" } - WarpificationUnavailableReason::TmuxFailed => "tmux Failed", - WarpificationUnavailableReason::Timeout { + WormholingUnavailableReason::TmuxFailed => "tmux Failed", + WormholingUnavailableReason::Timeout { is_tmux_install, .. } => { if *is_tmux_install { @@ -85,34 +85,34 @@ impl WarpificationUnavailableReason { "SSH Wormhole Timeout" } } - WarpificationUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell", - WarpificationUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed", + WormholingUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell", + WormholingUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed", } } } #[derive(Debug, Clone)] pub enum SshErrorBlockEvent { - ContinueWithoutWarpification, - WarpifyWithoutTmux, + ContinueWithoutWormholing, + WormholeWithoutTmux, } #[derive(Debug, Clone)] pub enum SshErrorBlockAction { - ContinueWithoutWarpification, - WarpifyWithoutTmux, + ContinueWithoutWormholing, + WormholeWithoutTmux, OpenUrl(String), AddSshHostToDenylist(String), Focus, } pub struct SshErrorBlock { - error_reason: WarpificationUnavailableReason, + error_reason: WormholingUnavailableReason, ssh_host: Option, - warpify_without_tmux_button_mouse_state: MouseStateHandle, + wormhole_without_tmux_button_mouse_state: MouseStateHandle, continue_button_mouse_state: MouseStateHandle, report_link_highlight_index: HighlightedHyperlink, - never_warpify_mouse_state_handle: MouseStateHandle, + never_wormhole_mouse_state_handle: MouseStateHandle, block_mouse_state: MouseStateHandle, is_focused: bool, } @@ -123,17 +123,17 @@ pub fn init(app: &mut AppContext) { app.register_fixed_bindings([ FixedBinding::new( "enter", - SshErrorBlockAction::WarpifyWithoutTmux, + SshErrorBlockAction::WormholeWithoutTmux, id!(SshErrorBlock::ui_name()), ), FixedBinding::new( "escape", - SshErrorBlockAction::ContinueWithoutWarpification, + SshErrorBlockAction::ContinueWithoutWormholing, id!(SshErrorBlock::ui_name()), ), FixedBinding::new( "ctrl-c", - SshErrorBlockAction::ContinueWithoutWarpification, + SshErrorBlockAction::ContinueWithoutWormholing, id!(SshErrorBlock::ui_name()), ), ]); @@ -141,14 +141,14 @@ pub fn init(app: &mut AppContext) { impl SshErrorBlock { #[allow(clippy::new_without_default)] - pub fn new(error_reason: WarpificationUnavailableReason, ssh_host: Option) -> Self { + pub fn new(error_reason: WormholingUnavailableReason, ssh_host: Option) -> Self { Self { error_reason, ssh_host, - warpify_without_tmux_button_mouse_state: Default::default(), + wormhole_without_tmux_button_mouse_state: Default::default(), continue_button_mouse_state: 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(), is_focused: false, } @@ -162,8 +162,8 @@ impl SshErrorBlock { fn should_show_report_to_warp_button(&self) -> bool { matches!( self.error_reason, - WarpificationUnavailableReason::Timeout { .. } - | WarpificationUnavailableReason::TmuxInstallFailed { .. } + WormholingUnavailableReason::Timeout { .. } + | WormholingUnavailableReason::TmuxInstallFailed { .. } ) } @@ -173,7 +173,7 @@ impl SshErrorBlock { theme: &GalaxyTheme, appearance: &Appearance, ) -> Box { - let header_contents = warpify::render::build_header_row( + let header_contents = wormhole::render::build_header_row( "Error Wormholing session", Icon::new(UiIcon::AlertTriangle.into(), theme.ui_error_color()), theme, @@ -182,11 +182,11 @@ impl SshErrorBlock { .with_margin_right(8.) .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, app, appearance, - self.never_warpify_mouse_state_handle.clone(), + self.never_wormhole_mouse_state_handle.clone(), move |ctx, ssh_host| { ctx.dispatch_typed_action(SshErrorBlockAction::AddSshHostToDenylist( ssh_host.to_owned(), @@ -204,7 +204,7 @@ impl SshErrorBlock { 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(warpify::render::description_row( + content.add_child(wormhole::render::description_row( self.error_reason.error_message(), theme, appearance, @@ -256,7 +256,7 @@ impl View for SshErrorBlock { ui_builder .button( 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_style(UiComponentStyles { @@ -266,7 +266,7 @@ impl View for SshErrorBlock { .build() .with_cursor(Cursor::PointingHand) .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(SshErrorBlockAction::WarpifyWithoutTmux) + ctx.dispatch_typed_action(SshErrorBlockAction::WormholeWithoutTmux) }) .finish(), ) @@ -287,7 +287,7 @@ impl View for SshErrorBlock { .build() .with_cursor(Cursor::PointingHand) .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutWarpification) + ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutWormholing) }) .finish(), ); @@ -331,21 +331,21 @@ impl TypedActionView for SshErrorBlock { fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - SshErrorBlockAction::WarpifyWithoutTmux => { - ctx.emit(SshErrorBlockEvent::WarpifyWithoutTmux) + SshErrorBlockAction::WormholeWithoutTmux => { + ctx.emit(SshErrorBlockEvent::WormholeWithoutTmux) } - SshErrorBlockAction::ContinueWithoutWarpification => { - ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification) + SshErrorBlockAction::ContinueWithoutWormholing => { + ctx.emit(SshErrorBlockEvent::ContinueWithoutWormholing) } SshErrorBlockAction::OpenUrl(url) => { ctx.open_url(url); } SshErrorBlockAction::AddSshHostToDenylist(ssh_host) => { - let settings = WarpifySettings::handle(ctx); - settings.update(ctx, |warpify, ctx| { - warpify.denylist_ssh_host(ssh_host, ctx); + let settings = WormholeSettings::handle(ctx); + settings.update(ctx, |wormhole, ctx| { + wormhole.denylist_ssh_host(ssh_host, ctx); }); - ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification); + ctx.emit(SshErrorBlockEvent::ContinueWithoutWormholing); ctx.notify() } SshErrorBlockAction::Focus => { diff --git a/app/src/terminal/ssh/install_tmux.rs b/app/src/terminal/ssh/install_tmux.rs index 7b45c1f2..63b7f858 100644 --- a/app/src/terminal/ssh/install_tmux.rs +++ b/app/src/terminal/ssh/install_tmux.rs @@ -6,14 +6,13 @@ use crate::ai::blocklist::inline_action::requested_script::{RequestedScriptStatu use crate::appearance::Appearance; use crate::terminal::model::ansi::SystemDetails; use crate::terminal::model::escape_sequences; -use crate::terminal::warpify::render; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::wormhole::render; +use crate::terminal::wormhole::settings::WormholeSettings; use crate::ui_components::blended_colors; use crate::ui_components::icons::Icon as UiIcon; use galaxy_core::ui::theme::GalaxyTheme; use galaxyui::elements::{ - FormattedTextElement, HighlightedHyperlink, Hoverable, Icon, MainAxisAlignment, MainAxisSize, - MouseStateHandle, + Hoverable, Icon, MainAxisAlignment, MainAxisSize, MouseStateHandle, Text, }; use galaxyui::keymap::FixedBinding; use galaxyui::ui_components::toggle_menu::ToggleMenuStateHandle; @@ -22,10 +21,6 @@ use galaxyui::{ AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, }; 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)] pub struct TmuxInstallMethod { @@ -35,7 +30,7 @@ pub struct TmuxInstallMethod { #[derive(Debug, Clone)] pub enum SshInstallTmuxBlockEvent { - InstallTmuxAndWarpify(TmuxInstallMethod), + InstallTmuxAndWormhole(TmuxInstallMethod), ToggleScriptVisibility, Cancel, Interrupt, @@ -88,15 +83,14 @@ impl SshKeyEvent { pub struct SshInstallTmuxBlock { requested_script_mouse_states: RequestedScriptMouseStates, - why_install_tmux_highlight_index: HighlightedHyperlink, - never_warpify_mouse_state_handle: MouseStateHandle, + never_wormhole_mouse_state_handle: MouseStateHandle, block_mouse_state: MouseStateHandle, is_focused: bool, is_collapsed: bool, show_tmux_install_block: bool, script_status: RequestedScriptStatus, 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, ssh_host: Option, ssh_command: String, @@ -166,8 +160,7 @@ impl SshInstallTmuxBlock { ) -> Self { Self { requested_script_mouse_states: Default::default(), - why_install_tmux_highlight_index: Default::default(), - never_warpify_mouse_state_handle: Default::default(), + never_wormhole_mouse_state_handle: Default::default(), block_mouse_state: Default::default(), is_focused: false, is_collapsed: true, @@ -220,7 +213,7 @@ impl SshInstallTmuxBlock { ctx: &mut ViewContext, ) { self.script_status = RequestedScriptStatus::Running; - ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndWarpify( + ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndWormhole( install_method, )); ctx.notify() @@ -261,7 +254,7 @@ impl SshInstallTmuxBlock { content: tmux_system_install_script.to_string(), }, TitledScript { - title: "Install to ~/.warp".to_string(), + title: "Install to ~/.galaxy".to_string(), content: self.tmux_local_install_script.clone(), }, *is_first_script_active, @@ -320,7 +313,7 @@ impl SshInstallTmuxBlock { ) -> Box { let header_contents = render::build_header_row( "Install tmux?", - Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()), + Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail()), theme, appearance, ) @@ -331,11 +324,11 @@ impl SshInstallTmuxBlock { let right_hand_size = is_awaiting_action .then(|| { - render::render_never_warpify_ssh_link( + render::render_never_wormhole_ssh_link( &self.ssh_host, app, appearance, - self.never_warpify_mouse_state_handle.clone(), + self.never_wormhole_mouse_state_handle.clone(), move |ctx, ssh_host| { ctx.dispatch_typed_action(SshInstallTmuxBlockAction::AddSshHostToDenylist( ssh_host.to_owned(), @@ -382,30 +375,20 @@ impl View for SshInstallTmuxBlock { "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 = blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()); - let warpify_description = FormattedTextElement::new( - FormattedText::new([FormattedTextLine::Line(warpify_description)]), + let wormhole_description = Text::new( + explanation.to_string(), + appearance.monospace_font_family(), 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()) - .register_default_click_handlers(|url, _, ctx| { - ctx.open_url(&url.url); - }) + .soft_wrap(true) + .with_color(text_color) .finish(); 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 { content.add_child(self.render_system_install_ui(root_install_state, app)); @@ -490,9 +473,9 @@ impl TypedActionView for SshInstallTmuxBlock { ctx.emit(SshInstallTmuxBlockEvent::Interrupt); } (SshInstallTmuxBlockAction::AddSshHostToDenylist(ssh_host), true) => { - let settings = WarpifySettings::handle(ctx); - settings.update(ctx, |warpify, ctx| { - warpify.denylist_ssh_host(ssh_host, ctx); + let settings = WormholeSettings::handle(ctx); + settings.update(ctx, |wormhole, ctx| { + wormhole.denylist_ssh_host(ssh_host, ctx); }); ctx.emit(SshInstallTmuxBlockEvent::Cancel); ctx.notify(); @@ -519,16 +502,16 @@ pub fn install_tmux_script(system: &SystemDetails, app: &AppContext) -> Option { - bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_linux.sh") + bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_linux.sh") } ("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") => { - 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") => { - bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh") + bundled_asset!("ssh/fish/install_tmux_and_wormhole_brew.sh") } _ => return None, }; @@ -555,19 +538,19 @@ pub fn install_root_tmux_script( system.shell.as_str(), ) { ("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") => { - 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") => { - 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") => { - 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") => { - 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, }; diff --git a/app/src/terminal/ssh/ssh_detection.rs b/app/src/terminal/ssh/ssh_detection.rs index 3025959a..92f233ef 100644 --- a/app/src/terminal/ssh/ssh_detection.rs +++ b/app/src/terminal/ssh/ssh_detection.rs @@ -2,7 +2,7 @@ use galaxy_core::{features::FeatureFlag, settings::Setting}; use galaxy_util::path::ShellFamily; 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. /// Also the payload for the [`crate::server::telemetry::TelemetryEvent::SshInteractiveSessionDetected`] event. @@ -12,8 +12,8 @@ pub enum SshInteractiveSessionDetected { FeatureDisabled, #[serde(rename = "host_denylisted")] HostDenylisted, - #[serde(rename = "warpify_prompt")] - ShouldPromptWarpification { + #[serde(rename = "wormhole_prompt")] + ShouldPromptWormholing { #[serde(skip)] command: String, #[serde(skip)] @@ -21,17 +21,17 @@ pub enum SshInteractiveSessionDetected { }, } -/// Determines whether a host could be warpified. -pub fn evaluate_warpify_ssh_host( +/// Determines whether a host could be wormholed. +pub fn evaluate_wormhole_ssh_host( command: &str, ssh_host: Option<&str>, shell_family: ShellFamily, - warpify_settings: &WarpifySettings, + wormhole_settings: &WormholeSettings, ) -> SshInteractiveSessionDetected { - let should_prompt_ssh_tmux_wrapper = *warpify_settings.enable_ssh_warpification.value() - && *warpify_settings.use_ssh_tmux_wrapper.value(); - let matches_subshell = warpify_settings.is_denylisted_subshell_command(command) - || warpify_settings.is_compatible_subshell_command(command, shell_family); + let should_prompt_ssh_tmux_wrapper = *wormhole_settings.enable_ssh_wormholing.value() + && *wormhole_settings.use_ssh_tmux_wrapper.value(); + let matches_subshell = wormhole_settings.is_denylisted_subshell_command(command) + || wormhole_settings.is_compatible_subshell_command(command, shell_family); if !should_prompt_ssh_tmux_wrapper || matches_subshell || !FeatureFlag::SSHTmuxWrapper.is_enabled() @@ -40,12 +40,12 @@ pub fn evaluate_warpify_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; } } - SshInteractiveSessionDetected::ShouldPromptWarpification { + SshInteractiveSessionDetected::ShouldPromptWormholing { host: ssh_host.map(|host| host.to_owned()), command: command.to_string(), } diff --git a/app/src/terminal/ssh/util.rs b/app/src/terminal/ssh/util.rs index de5aea05..91c13936 100644 --- a/app/src/terminal/ssh/util.rs +++ b/app/src/terminal/ssh/util.rs @@ -78,7 +78,7 @@ pub fn check_ssh_login_state(block_output: &str) -> SshLoginState { } /// 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 /// empty `InteractiveSshCommand` to indicate that we did /// 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 /// session. `Ssh` means a literal `ssh` command, where all other commands (e.g. `gcloud /// compute ssh`) are categorized as SSH-like commands. -pub enum SshWarpifyCommand { +pub enum SshWormholeCommand { Ssh, SshLike(SshLikeCommand), } -impl SshWarpifyCommand { +impl SshWormholeCommand { /// Not a literal `ssh` command, but another command that starts an interactive SSH /// session. pub fn is_ssh_like_command(&self) -> bool { - matches!(self, SshWarpifyCommand::SshLike(_)) + matches!(self, SshWormholeCommand::SshLike(_)) } } -impl SshWarpifyCommand { - pub fn matches(command: &str) -> Option { +impl SshWormholeCommand { + pub fn matches(command: &str) -> Option { let tokens = normalized_command_tokens(command)?; match tokens.as_slice() { [command, arguments @ ..] if command == "ssh" && !arguments.is_empty() => { - Some(SshWarpifyCommand::Ssh) + Some(SshWormholeCommand::Ssh) } [command, compute, ssh, arguments @ ..] if command == "gcloud" @@ -176,12 +176,14 @@ impl SshWarpifyCommand { && ssh == "ssh" && !arguments.is_empty() => { - Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud)) + Some(SshWormholeCommand::SshLike(SshLikeCommand::Gcloud)) } [command, ssh, arguments @ ..] if command == "eb" && ssh == "ssh" && !arguments.is_empty() => { - Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) + Some(SshWormholeCommand::SshLike( + SshLikeCommand::ElasticBeanstalk, + )) } [command, compute, ssh, arguments @ ..] if command == "doctl" @@ -189,7 +191,7 @@ impl SshWarpifyCommand { && ssh == "ssh" && !arguments.is_empty() => { - Some(SshWarpifyCommand::SshLike( + Some(SshWormholeCommand::SshLike( SshLikeCommand::DigitalOceanDroplet, )) } @@ -199,15 +201,15 @@ impl SshWarpifyCommand { } pub fn parse_interactive_ssh_command(command: &str) -> Option { - match SshWarpifyCommand::matches(command) { - Some(SshWarpifyCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command), - Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud)) => { + match SshWormholeCommand::matches(command) { + Some(SshWormholeCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command), + Some(SshWormholeCommand::SshLike(SshLikeCommand::Gcloud)) => { Some(InteractiveSshCommand::default()) } - Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => { + Some(SshWormholeCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => { Some(InteractiveSshCommand::default()) } - Some(SshWarpifyCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => { + Some(SshWormholeCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => { Some(InteractiveSshCommand::default()) } None => None, @@ -299,7 +301,7 @@ fn executable_name(executable: &str) -> String { .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( local_file_path: String, ssh_host: String, diff --git a/app/src/terminal/ssh/warpify.rs b/app/src/terminal/ssh/wormhole.rs similarity index 66% rename from app/src/terminal/ssh/warpify.rs rename to app/src/terminal/ssh/wormhole.rs index eebb7adb..9b500cce 100644 --- a/app/src/terminal/ssh/warpify.rs +++ b/app/src/terminal/ssh/wormhole.rs @@ -6,8 +6,7 @@ use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine}; use crate::ai::blocklist::inline_action::requested_action::RenderableAction; use crate::appearance::Appearance; use crate::terminal::shell::ShellType; -use crate::terminal::warpify; -use crate::terminal::warpify::render::SSH_DOCS_URL; +use crate::terminal::wormhole; use crate::ui_components::icons::Icon as UiIcon; use galaxyui::elements::{HighlightedHyperlink, Hoverable, Icon, MouseStateHandle}; use galaxyui::keymap::FixedBinding; @@ -18,19 +17,19 @@ use galaxyui::{ }; #[derive(Debug, Clone)] -pub enum SshWarpifyBlockEvent { - WarpifySession, +pub enum SshWormholeBlockEvent { + WormholeSession, Cancel, Interrupt, } #[derive(Debug, Clone, Eq, PartialEq)] -pub enum SshWarpifyBlockAction { +pub enum SshWormholeBlockAction { Interrupt, Focus, } -pub struct SshWarpifyBlock { +pub struct SshWormholeBlock { block_mouse_state: MouseStateHandle, ssh_command: String, } @@ -40,12 +39,12 @@ pub fn init(app: &mut AppContext) { app.register_fixed_bindings([FixedBinding::new( "ctrl-c", - SshWarpifyBlockAction::Interrupt, - id!(SshWarpifyBlock::ui_name()), + SshWormholeBlockAction::Interrupt, + id!(SshWormholeBlock::ui_name()), )]); } -impl SshWarpifyBlock { +impl SshWormholeBlock { #[allow(clippy::new_without_default)] pub fn new(ssh_command: String) -> Self { Self { @@ -60,18 +59,18 @@ impl SshWarpifyBlock { } } -impl Entity for SshWarpifyBlock { - type Event = SshWarpifyBlockEvent; +impl Entity for SshWormholeBlock { + type Event = SshWormholeBlockEvent; } -impl SshWarpifyBlock { +impl SshWormholeBlock { fn render_title_ui(&self, theme: &GalaxyTheme, appearance: &Appearance) -> Box { - let icon = Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()); - warpify::render::header_row("Wormholing SSH Session...", icon, theme, appearance) + let icon = Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail()); + wormhole::render::header_row("Wormholing SSH Session...", icon, theme, appearance) } } -pub fn warpify_description( +pub fn wormhole_description( app: &AppContext, hyperlink_index: &HighlightedHyperlink, ) -> Box { @@ -80,21 +79,16 @@ pub fn warpify_description( let description = FormattedText::new(vec![FormattedTextLine::Line(vec![ 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()) - .with_hyperlink_font_color(appearance.theme().accent().into_solid()) - .register_default_click_handlers(|url, _, ctx| { - ctx.open_url(&url.url); - }) + wormhole::render::build_description_row(description, theme, appearance, hyperlink_index.clone()) .finish() } -impl View for SshWarpifyBlock { +impl View for SshWormholeBlock { fn ui_name() -> &'static str { - "SshWarpifyBlock" + "SshWormholeBlock" } fn render(&self, app: &AppContext) -> Box { @@ -124,39 +118,39 @@ impl View for SshWarpifyBlock { .finish() }) .on_click(|ctx, _, _| { - ctx.dispatch_typed_action(SshWarpifyBlockAction::Focus); + ctx.dispatch_typed_action(SshWormholeBlockAction::Focus); }) .finish() } } -impl TypedActionView for SshWarpifyBlock { - type Action = SshWarpifyBlockAction; +impl TypedActionView for SshWormholeBlock { + type Action = SshWormholeBlockAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - SshWarpifyBlockAction::Interrupt => { - ctx.emit(SshWarpifyBlockEvent::Interrupt); + SshWormholeBlockAction::Interrupt => { + ctx.emit(SshWormholeBlockEvent::Interrupt); } - SshWarpifyBlockAction::Focus => { + SshWormholeBlockAction::Focus => { self.focus(ctx); } } } } -/// Convert the begin_warpify_ssh_session script into a string. -pub fn begin_warpify_ssh_session_command(app: &AppContext) -> String { +/// Convert the begin_wormhole_ssh_session script into a string. +pub fn begin_wormhole_ssh_session_command(app: &AppContext) -> String { let asset = bundled_asset!("bootstrap/unknown_init_subshell.sh"); match AssetCache::as_ref(app).load_asset::(asset) { 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. -pub fn warpify_ssh_session_command( +/// Convert the wormhole_ssh_session script into a string. +pub fn wormhole_ssh_session_command( uname: &str, shell_type: ShellType, app: &AppContext, @@ -164,14 +158,14 @@ pub fn warpify_ssh_session_command( let asset = match (uname, shell_type) { // Mac scripts must be less than 1020 characters due to macOS 15+ pty issue ("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 - ("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) => { - 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. (_, ShellType::PowerShell) => return None, }; @@ -179,9 +173,9 @@ pub fn warpify_ssh_session_command( // Todo(Jack): look into avoiding an allocation here. match AssetCache::as_ref(app).load_asset::(asset) { 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)] -#[path = "warpify_test.rs"] +#[path = "wormhole_test.rs"] mod tests; diff --git a/app/src/terminal/ssh/warpify_test.rs b/app/src/terminal/ssh/wormhole_test.rs similarity index 77% rename from app/src/terminal/ssh/warpify_test.rs rename to app/src/terminal/ssh/wormhole_test.rs index 20898fca..19af964b 100644 --- a/app/src/terminal/ssh/warpify_test.rs +++ b/app/src/terminal/ssh/wormhole_test.rs @@ -37,50 +37,50 @@ fn get_script(asset_source: AssetSource, ctx: &AppContext) -> String { #[cfg_attr(windows, ignore = "TODO(CORE-3626)")] #[test] /// 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 { initialize_app(&mut app); app.read(|ctx| { assert_script_is_short_enough_mac( - &begin_warpify_ssh_session_command(ctx), + &begin_wormhole_ssh_session_command(ctx), "unknown_init_subshell.sh", false, ); assert_script_is_short_enough_mac( &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, ), - "install_tmux_and_warpify_brew.sh", + "install_tmux_and_wormhole_brew.sh", false, ); assert_script_is_short_enough_mac( &get_script( - bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh"), + bundled_asset!("ssh/fish/install_tmux_and_wormhole_brew.sh"), ctx, ), - "fish/install_tmux_and_warpify_brew.sh", + "fish/install_tmux_and_wormhole_brew.sh", false, ); 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"), - "zsh warpify", + "zsh wormhole", true, ); 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"), - "bash warpify", + "bash wormhole", true, ); 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"), - "fish warpify", + "fish wormhole", true, ) }); diff --git a/app/src/terminal/universal_developer_input.rs b/app/src/terminal/universal_developer_input.rs index 793b5fdd..abd2e003 100644 --- a/app/src/terminal/universal_developer_input.rs +++ b/app/src/terminal/universal_developer_input.rs @@ -126,23 +126,23 @@ impl AtContextMenuDisabledReason { let session_type = session.session_type(); let has_connected_remote_server = matches!( session_type, - SessionType::WarpifiedRemote { host_id: Some(_) } + SessionType::WormholedRemote { host_id: Some(_) } ); // The @ menu requires repo metadata which is only available for: // - 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: // - 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 - // 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 // incorrectly blocking upgraded sessions. let is_ssh_without_remote_server = !has_connected_remote_server && (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(); (is_ssh_without_remote_server, is_subshell) }) diff --git a/app/src/terminal/view.rs b/app/src/terminal/view.rs index e63f39ba..b10c50a4 100644 --- a/app/src/terminal/view.rs +++ b/app/src/terminal/view.rs @@ -67,13 +67,13 @@ use std::sync::Arc; use std::thread::JoinHandle; use std::time::Duration; -use action::RememberForWarpification; +use action::RememberForWormholing; pub use action::{AgentOnboardingVersion, OnboardingIntention, OnboardingVersion, TerminalAction}; use ai::api_keys::{ApiKeyManager, AwsCredentialsState}; use ai::index::full_source_code_embedding::manager::{BuildSource, CodebaseIndexManager}; use async_channel::{Receiver, Sender}; 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}; use block_onboarding::onboarding_drive_sharing_block::OnboardingDriveSharingBlock; use bookmarks::render_floating_block_snapshot; @@ -192,10 +192,9 @@ use super::model::secrets::RichContentSecretTooltipInfo; use super::model::selection::ExpandedSelectionRange; use super::model::session::SessionBootstrappedEvent; use super::settings::AltScreenPaddingMode; -use super::ssh::util::{parse_interactive_ssh_command, InteractiveSshCommand, SshWarpifyCommand}; -use super::warpify::success_block::{WarpifySuccessBlock, WarpifySuccessBlockEvent}; -use super::warpify::trigger_state::{SshBlockState, WarpifyState}; -use super::warpify::WarpificationSource; +use super::ssh::util::{parse_interactive_ssh_command, InteractiveSshCommand, SshWormholeCommand}; +use super::wormhole::success_block::{WormholeSuccessBlock, WormholeSuccessBlockEvent}; +use super::wormhole::trigger_state::{SshBlockState, WormholeState}; use super::{cli_agent, CLIAgent, GridType}; use crate::ai::agent::api::ServerConversationToken; 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::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::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::{ block_list_element::BlockHoverAction, @@ -634,10 +633,6 @@ const KNOWN_ISSUES_URL: &str = const PROMPT_COMPATIBILITY_URL: &str = "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. const P10K_UPDATE_INSTRUCTIONS_URL: &str = "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. pub const ALIAS_EXPANSION_BANNER_SEEN_KEY: &str = "AliasExpansionBannerSeen"; -/// Delay between receiving preexec hook for a command we want to auto-warpify -/// and triggering the warpification (subshell bootstrapping). +/// Delay between receiving preexec hook for a command we want to auto-wormhole +/// and triggering the wormholing (subshell bootstrapping). /// 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 /// Emacs-style keybindings instead of IDE-style keybindings. @@ -2755,7 +2750,7 @@ pub struct TerminalView { onboarding_callout_view: Option>, - /// 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. pending_auto_bootstrap_shell_type: Option, env_vars: Vec, @@ -2817,7 +2812,7 @@ pub struct TerminalView { find_model: ModelHandle, - warpify_state: WarpifyState, + wormhole_state: WormholeState, /// The keystroke bound to canceling a command. /// @@ -3947,12 +3942,12 @@ impl TerminalView { let control_master_error_banner = ctx.add_typed_action_view(|_| { Banner::new_permanently_dismissible(BannerTextContent::formatted_text(vec![ - FormattedTextFragment::plain_text("Seems like your completions are not working ("), - FormattedTextFragment::hyperlink("more info", CONTROLMASTER_ISSUES_URL), - FormattedTextFragment::plain_text("). Enabling the SSH extension in "), + FormattedTextFragment::plain_text( + "Your completions may not be working. Enabling the Wormhole helper in ", + ), FormattedTextFragment::hyperlink_action( "settings", - TerminalAction::ShowWarpifySettings, + TerminalAction::ShowWormholeSettings, ), FormattedTextFragment::plain_text(" may resolve this issue."), ])) @@ -4436,7 +4431,7 @@ impl TerminalView { input_position_id, input_hoverable_handle: Default::default(), find_model, - warpify_state: Default::default(), + wormhole_state: Default::default(), cancel_command_keystroke: keybinding_name_to_keystroke(CANCEL_COMMAND_KEYBINDING, ctx), is_file_drop_target: false, is_ssh_file_uploader: false, @@ -4582,7 +4577,7 @@ impl TerminalView { me.show_ssh_remote_server_failed_banner( *session_id, remote_server::transport::UserFacingError { - body: "Failed to start SSH extension".into(), + body: "Failed to start Wormhole helper".into(), detail: if error.is_empty() { None } else { @@ -9116,7 +9111,7 @@ impl TerminalView { /// events, allow it to handle the event. /// /// 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) { if self.active_ai_block(ctx).is_some() { self.cancel_active_conversation_via_status_bar(ctx); @@ -9192,7 +9187,7 @@ impl TerminalView { /// the workspace to derive `PendingRemoteSession` without storing /// mutable state on the workspace itself. 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 @@ -9779,7 +9774,7 @@ impl TerminalView { .is_some_and(|session| { matches!( session.session_type(), - SessionType::WarpifiedRemote { + SessionType::WormholedRemote { host_id: Some(_), .. } @@ -9839,7 +9834,7 @@ impl TerminalView { triggered_by_rc_file_snippet: bool, ctx: &mut ViewContext, ) { - 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 // actually confirms subshell bootstrap is in progress. @@ -9852,7 +9847,7 @@ impl TerminalView { .is_active_and_long_running() { 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 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 .model .lock() @@ -9897,15 +9892,15 @@ impl TerminalView { } fn clear_ssh_blocks(&mut self, ctx: &mut ViewContext) { - self.dismiss_warpify_banner(&RememberForWarpification::DoNotRememberSSHHost, ctx); - if let Some(ssh_block) = self.warpify_state.ssh_block_state() { + self.dismiss_wormhole_banner(&RememberForWormholing::DoNotRememberSSHHost, ctx); + if let Some(ssh_block) = self.wormhole_state.ssh_block_state() { let view_id = ssh_block.get_block_view_id(); self.remove_ssh_block_by_id(view_id); 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, subshell_info, shell, - session_type, .. }: SessionBootstrappedEvent, ctx: &mut ViewContext, @@ -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| { - WarpifySuccessBlock::new( - warpification_source, - spawning_command, - subshell_info, - shell, - ctx, - ) + WormholeSuccessBlock::new(spawning_command, subshell_info, shell, ctx) }); ctx.subscribe_to_view(&ssh_success_block_handle, move |me, _, event, ctx| { me.handle_ssh_success_block_events(event, ctx); @@ -9948,9 +9932,9 @@ impl TerminalView { self.clear_ssh_blocks(ctx); self.insert_rich_content( - Some(RichContentType::WarpifySuccessBlock), + Some(RichContentType::WormholeSuccessBlock), ssh_success_block_handle.clone(), - Some(RichContentMetadata::WarpifySuccessBlock { + Some(RichContentMetadata::WormholeSuccessBlock { bootstrap_success_block_handle: ssh_success_block_handle.clone(), }), RichContentInsertionPosition::Append { @@ -9958,30 +9942,30 @@ impl TerminalView { }, ctx, ); - self.warpify_state - .set_ssh_block_state(SshBlockState::WarpifySuccess { + self.wormhole_state + .set_ssh_block_state(SshBlockState::WormholeSuccess { handle: ssh_success_block_handle, }); 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); } fn handle_ssh_success_block_events( &mut self, - event: &WarpifySuccessBlockEvent, + event: &WormholeSuccessBlockEvent, ctx: &mut ViewContext, ) { match event { - WarpifySuccessBlockEvent::OpenWarpifySettings => { - ctx.emit(Event::OpenSettings(SettingsSection::Warpify)); + WormholeSuccessBlockEvent::OpenWormholeSettings => { + ctx.emit(Event::OpenSettings(SettingsSection::Wormhole)); } } } - fn dismiss_warpify_banner( + fn dismiss_wormhole_banner( &mut self, - remember_command: &RememberForWarpification, + remember_command: &RememberForWormholing, ctx: &mut ViewContext, ) { { @@ -9989,54 +9973,54 @@ impl TerminalView { 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. - if FeatureFlag::WarpifyFooter.is_enabled() { + if FeatureFlag::WormholeFooter.is_enabled() { self.use_agent_footer.update(ctx, |footer, ctx| { - footer.clear_warpify(ctx); + footer.clear_wormhole(ctx); }); } match remember_command { - RememberForWarpification::RememberSubshellCommand(command) => { - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.denylist_subshell_command(command, ctx); + RememberForWormholing::RememberSubshellCommand(command) => { + WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| { + wormhole.denylist_subshell_command(command, ctx); }); } - RememberForWarpification::RememberSSHHost(host) => { - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.denylist_ssh_host(host, ctx); + RememberForWormholing::RememberSSHHost(host) => { + WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| { + wormhole.denylist_ssh_host(host, ctx); }); } - RememberForWarpification::DoNotRememberSubshellCommand - | RememberForWarpification::DoNotRememberSSHHost => {} + RememberForWormholing::DoNotRememberSubshellCommand + | RememberForWormholing::DoNotRememberSSHHost => {} } } - fn show_warpify_banner( + fn show_wormhole_banner( &mut self, command: String, title: &str, lowercase_title: &str, - warpify_keybinding: Option, + wormhole_keybinding: Option, telemetry_event: TelemetryEvent, ctx: &mut ViewContext, ) { - if FeatureFlag::WarpifyFooter.is_enabled() { + if FeatureFlag::WormholeFooter.is_enabled() { return; } let mut model = self.model.lock(); - // Shared session viewers can't initiate warpification currently. - // Don't show the warpify banner when an agent is monitoring the command either. + // Shared session viewers can't initiate wormholing currently. + // Don't show the wormhole banner when an agent is monitoring the command either. if model.shared_session_status().is_viewer() || model.block_list().active_block().is_agent_monitoring() { return; } - let a11y_message = match &warpify_keybinding { + let a11y_message = match &wormhole_keybinding { Some(keystroke) => format!( "You can press {} to Wormhole this {} for more Galaxy features.", keystroke.displayed(), @@ -10047,8 +10031,8 @@ impl TerminalView { model .block_list_mut() - .set_active_block_banner(Some(WithinBlockBanner::WarpifyBanner( - WarpifyBannerState::new(command, warpify_keybinding), + .set_active_block_banner(Some(WithinBlockBanner::WormholeBanner( + WormholeBannerState::new(command, wormhole_keybinding), ))); let a11y_content = AccessibilityContent::new( @@ -11310,7 +11294,7 @@ impl TerminalView { /// 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 - /// warpified remote blocks. + /// wormholed remote blocks. /// /// For some organizations, we accept a regex list that we run against commands to /// further make the determination. @@ -11320,7 +11304,7 @@ impl TerminalView { command: Option<&str>, app: &AppContext, ) -> bool { - let is_warpified_remote = session_id + let is_wormholed_remote = session_id .map(|id| { self.sessions .as_ref(app) @@ -11330,7 +11314,7 @@ impl TerminalView { }) .unwrap_or_default(); - if is_warpified_remote { + if is_wormholed_remote { return true; } @@ -11980,7 +11964,8 @@ impl TerminalView { // If this block ran a possible subshell command, and it exited before the 1s timer // 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(); } @@ -12022,9 +12007,9 @@ impl TerminalView { 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| { - footer.clear_warpify(ctx); + footer.clear_wormhole(ctx); }); self.hide_use_agent_footer_in_blocklist(ctx); if matches!(block_completed_event.block_type, BlockType::User(_)) { @@ -12109,7 +12094,7 @@ impl TerminalView { self.drop_hidden_passive_ai_blocks(ctx); // 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'`). let expanded_command = self .active_block_session_id() @@ -12119,19 +12104,19 @@ impl TerminalView { let alias_value = session.alias_value(first_word)?; 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 warpify_settings = WarpifySettings::as_ref(ctx); - let is_compatible_subshell_command = warpify_settings + let wormhole_settings = WormholeSettings::as_ref(ctx); + let is_compatible_subshell_command = wormhole_settings .is_compatible_subshell_command(command, shell_family) - || warpify_settings - .is_compatible_subshell_command(warpify_command, shell_family); - let command_is_denylisted = warpify_settings + || wormhole_settings + .is_compatible_subshell_command(wormhole_command, shell_family); + let command_is_denylisted = wormhole_settings .is_denylisted_subshell_command(command) - || warpify_settings.is_denylisted_subshell_command(warpify_command); - // Never warpify or surface warpification for agent-requested commands. + || wormhole_settings.is_denylisted_subshell_command(wormhole_command); + // Never wormhole or surface wormholing for agent-requested commands. let has_ai_metadata = self .model .lock() @@ -12142,30 +12127,30 @@ impl TerminalView { if is_compatible_subshell_command { 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() { // If there is a subshell we're waiting to bootstrap until we receive // the preexec hook, now we can bootstrap it. - let auto_warpify_abort_handle = ctx.spawn_abortable( - Timer::after(Duration::from_millis(AUTO_WARPIFY_DELAY)), + let auto_wormhole_abort_handle = ctx.spawn_abortable( + Timer::after(Duration::from_millis(AUTO_WORMHOLE_DELAY)), move |me, _, ctx| { me.trigger_subshell_bootstrap(Some(shell_type), false, ctx); }, |_, _| (), ); - self.warpify_state - .add_auto_warpify_abort_handle(auto_warpify_abort_handle); + self.wormhole_state + .add_auto_wormhole_abort_handle(auto_wormhole_abort_handle); } else { // Wait 1 second before showing the banner, just to make sure the // command stays running for a bit. If the command fails instantly, // we don't want to flicker the banner away so quickly. let command = command.clone(); - self.warpify_state + self.wormhole_state .add_subshell_banner_abort_handle(ctx.spawn_abortable( Timer::after(*SUBSHELL_BANNER_DELAY_DURATION), |view, _, ctx| { - if FeatureFlag::WarpifyFooter.is_enabled() { - view.show_warpify_footer(ctx); + if FeatureFlag::WormholeFooter.is_enabled() { + view.show_wormhole_footer(ctx); } else { view.handle_action( &TerminalAction::ShowSubshellBanner(command), @@ -12179,14 +12164,14 @@ impl TerminalView { } else { if !has_ai_metadata { 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 - .set_pending_ssh_host(warpify_command.to_string(), ssh_host); + self.wormhole_state + .set_pending_ssh_host(wormhole_command.to_string(), ssh_host); self.model.lock().start_notify_on_end_of_ssh_login(); ctx.emit(Event::TerminalViewStateChanged); } else { - self.warpify_state.clear_pending_ssh_host(); + self.wormhole_state.clear_pending_ssh_host(); ctx.spawn( Timer::after(Duration::from_millis( @@ -12284,14 +12269,14 @@ impl TerminalView { cloud_workflow_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 // 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 // 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 // because the abort is time sensitive. - self.warpify_state.abort_auto_warpify(); + self.wormhole_state.abort_auto_wormhole(); let active_session = self .active_block_session_id() @@ -12366,14 +12351,14 @@ impl TerminalView { } let active_session_id = self.active_block_session_id(); if let Some(block_id) = self - .warpify_state - .get_completed_warpify_session_id(active_session_id, ctx) + .wormhole_state + .get_completed_wormhole_session_id(active_session_id, ctx) { self.remove_ssh_block_by_id(block_id); } - self.dismiss_warpify_banner( - &RememberForWarpification::DoNotRememberSubshellCommand, + self.dismiss_wormhole_banner( + &RememberForWormholing::DoNotRememberSubshellCommand, ctx, ); @@ -12868,7 +12853,7 @@ impl TerminalView { .active_block() .agent_interaction_metadata() .is_some(); - // Never warpify for agent-requested commands. + // Never wormhole for agent-requested commands. if has_ai_metadata { return; } @@ -13063,8 +13048,8 @@ impl TerminalView { me.remove_ssh_remote_server_choice_block(session_id, ctx); ctx.emit(Event::RemoteServerSkipRequested { session_id }); } - SshRemoteServerChoiceViewEvent::OpenWarpifySettings => { - ctx.emit(Event::OpenSettings(SettingsSection::Warpify)); + SshRemoteServerChoiceViewEvent::OpenWormholeSettings => { + 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 // 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); }); @@ -13690,7 +13675,7 @@ impl TerminalView { self.update_incompatible_configuration_banner(session.shell().plugins(), ctx); if let Some(subshell_info) = session.subshell_info() { - self.warpify_state + self.wormhole_state .add_subshell_separator(subshell_info, self.model.clone(), ctx); } @@ -13772,22 +13757,23 @@ impl TerminalView { .spawn(async move { session_clone2.load_all_builtins().await }) .detach(); - // If we were waiting for a successful warpification, it's come. Stop the timeout. - self.warpify_state.abort_ssh_warpify_timeout(); + // If we were waiting for a successful wormholing, it's come. Stop the timeout. + self.wormhole_state.abort_ssh_wormhole_timeout(); - let is_warpified_remote = matches!( + let is_wormholed_remote = matches!( bootstrap_event.session_type, - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::WormholedRemote ); if bootstrap_event.subshell_info.is_some() { self.add_bootstrap_success_block(bootstrap_event, ctx); } // 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 - // fires for sessions that stay unwarpified, since warpification replaces the + // wormholes. The end-of-ssh-login path (`handle_detected_end_of_ssh_login`) only + // fires for sessions that stay unwormholed, since wormholing replaces the // 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); } @@ -15201,7 +15187,7 @@ impl TerminalView { // 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 // 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 { return vec![]; } @@ -19105,7 +19091,7 @@ impl TerminalView { .and_then(|id| self.sessions.as_ref(ctx).get(id)) { if let Some(info) = session.subshell_info() { - self.warpify_state + self.wormhole_state .add_subshell_separator(info, self.model.clone(), ctx); } } @@ -20152,9 +20138,9 @@ impl TerminalView { env_var_collection_block.clear_selection(ctx); }); } - Some(RichContentMetadata::WarpifySuccessBlock { .. }) => { - // TODO(Simon): We should be checking for WarpifySuccessBlocks here as well. - // The `WarpifySuccessBlock` implements a `SelectableArea`. + Some(RichContentMetadata::WormholeSuccessBlock { .. }) => { + // TODO(Simon): We should be checking for WormholeSuccessBlocks here as well. + // The `WormholeSuccessBlock` implements a `SelectableArea`. } _ => {} } @@ -23365,7 +23351,7 @@ impl TerminalView { } else { // Remote session: pair CWD with the session's host_id. let host_id = match session.session_type() { - SessionType::WarpifiedRemote { host_id } => host_id, + SessionType::WormholedRemote { host_id } => host_id, SessionType::Local => return None, }?; let std_path = StandardizedPath::try_new(cwd_str).ok()?; @@ -24110,7 +24096,7 @@ impl TerminalView { 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)); } @@ -24122,8 +24108,8 @@ impl TerminalView { .active_block() .block_banner() .map(|banner| match banner { - WithinBlockBanner::WarpifyBanner(state) => { - render_warpification_banner(state, appearance) + WithinBlockBanner::WormholeBanner(state) => { + 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. - /// 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. pub fn insert_subshell_command_and_bootstrap_if_supported( &mut self, @@ -25631,7 +25617,7 @@ impl TerminalView { shell_type: ShellType, ctx: &mut ViewContext, ) { - // 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.input.update(ctx, |input, ctx| { @@ -25853,7 +25839,7 @@ impl TerminalView { ctx: &mut ViewContext, ) { 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: "), // 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 @@ -25875,11 +25861,11 @@ impl TerminalView { }, ); } - SshLoginStatus::ReadyToWarpify => { - // The tmux-based SSH warpification flow has been removed in favor of the + SshLoginStatus::ReadyToWormhole => { + // 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 // 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() { self.show_ssh_tmux_deprecation_banner(session_id, ctx); } @@ -25922,22 +25908,22 @@ impl TerminalView { let alias_value = session.alias_value(first_word)?; 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 warpify_settings = WarpifySettings::as_ref(ctx); - let is_compatible_subshell_command = warpify_settings + let wormhole_settings = WormholeSettings::as_ref(ctx); + let is_compatible_subshell_command = wormhole_settings .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 } - /// Shows the warpify footer for a detected subshell command. - fn show_warpify_footer(&mut self, ctx: &mut ViewContext) { + /// Shows the wormhole footer for a detected subshell command. + fn show_wormhole_footer(&mut self, ctx: &mut ViewContext) { let model = self.model.lock(); - // Shared session viewers can't initiate warpification currently. - // Don't show the warpify footer when an agent is monitoring the command either. + // Shared session viewers can't initiate wormholing currently. + // Don't show the wormhole footer when an agent is monitoring the command either. if model.shared_session_status().is_viewer() || model.block_list().active_block().is_agent_monitoring() { @@ -25946,11 +25932,11 @@ impl TerminalView { drop(model); 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); - 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) { @@ -26330,7 +26316,7 @@ impl TypedActionView for TerminalView { "Showed initialization block", GalaxyA11yRole::TextareaRole, )), - ShowWarpifySettings => Custom(AccessibilityContent::new_without_help( + ShowWormholeSettings => Custom(AccessibilityContent::new_without_help( "Opened Wormhole Settings", GalaxyA11yRole::ButtonRole, )), @@ -26380,7 +26366,7 @@ impl TypedActionView for TerminalView { | ControlSequence(_) | TriggerSubshellBootstrap | ShowSubshellBanner(_) - | DismissWarpifyBanner(_) + | DismissWormholeBanner(_) | OpenBlockListContextMenu | AliasExpansionBanner(_) | VimModeBanner(_) @@ -26891,21 +26877,21 @@ impl TypedActionView for TerminalView { TriggerSubshellBootstrap => self.trigger_subshell_bootstrap(None, false, ctx), ShowSubshellBanner(command) => { // 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 = - keybinding_name_to_keystroke("terminal:warpify_subshell", ctx); - self.show_warpify_banner( + let wormhole_keybinding = + keybinding_name_to_keystroke("terminal:wormhole_subshell", ctx); + self.show_wormhole_banner( command.to_owned(), "Subshell", "subshell", - warpify_keybinding, + wormhole_keybinding, TelemetryEvent::ShowSubshellBanner, ctx, ); } - DismissWarpifyBanner(remember) => { - self.dismiss_warpify_banner(remember, ctx); + DismissWormholeBanner(remember) => { + self.dismiss_wormhole_banner(remember, ctx); if !remember.is_ssh() { send_telemetry_from_ctx!( TelemetryEvent::DeclineSubshellBootstrap { @@ -27142,7 +27128,7 @@ impl TypedActionView for TerminalView { LoadAgentModeConversation => { self.load_agent_mode_conversation(ctx); } - ShowWarpifySettings => ctx.emit(Event::OpenSettings(SettingsSection::Warpify)), + ShowWormholeSettings => ctx.emit(Event::OpenSettings(SettingsSection::Wormhole)), DeleteAttachment { index } => { self.ai_context_model.update(ctx, |context_model, 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); } - if let Some(WithinBlockBanner::WarpifyBanner(_)) = + if let Some(WithinBlockBanner::WormholeBanner(_)) = model_lock.block_list().active_block().block_banner() { 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. - 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"); } diff --git a/app/src/terminal/view/action.rs b/app/src/terminal/view/action.rs index 531b3e08..7ffb6b94 100644 --- a/app/src/terminal/view/action.rs +++ b/app/src/terminal/view/action.rs @@ -67,7 +67,7 @@ pub enum OnboardingVersion { /// This represents whether entering a subshell for a particular command should become automatic in /// the future, or to ask again. #[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 RememberSubshellCommand(String), RememberSSHHost(String), @@ -75,22 +75,22 @@ pub enum RememberForWarpification { DoNotRememberSSHHost, } -impl RememberForWarpification { +impl RememberForWormholing { pub fn as_bool(&self) -> bool { match self { - RememberForWarpification::RememberSubshellCommand(_) => true, - RememberForWarpification::RememberSSHHost(_) => true, - RememberForWarpification::DoNotRememberSubshellCommand => false, - RememberForWarpification::DoNotRememberSSHHost => false, + RememberForWormholing::RememberSubshellCommand(_) => true, + RememberForWormholing::RememberSSHHost(_) => true, + RememberForWormholing::DoNotRememberSubshellCommand => false, + RememberForWormholing::DoNotRememberSSHHost => false, } } pub fn is_ssh(&self) -> bool { match self { - RememberForWarpification::RememberSSHHost(_) => true, - RememberForWarpification::DoNotRememberSSHHost => true, - RememberForWarpification::RememberSubshellCommand(_) => false, - RememberForWarpification::DoNotRememberSubshellCommand => false, + RememberForWormholing::RememberSSHHost(_) => true, + RememberForWormholing::DoNotRememberSSHHost => true, + RememberForWormholing::RememberSubshellCommand(_) => false, + RememberForWormholing::DoNotRememberSubshellCommand => false, } } } @@ -284,8 +284,8 @@ pub enum TerminalAction { }, /// Starts a subshell in the active session. TriggerSubshellBootstrap, - /// If the user says "no" to Warpification, possibly requesting not to be asked again - DismissWarpifyBanner(RememberForWarpification), + /// If the user says "no" to Wormholing, possibly requesting not to be asked again + DismissWormholeBanner(RememberForWormholing), /// Triggers the banner asking to turn the running block into a subshell. The String is the /// command that the user entered. ShowSubshellBanner(String), @@ -342,7 +342,7 @@ pub enum TerminalAction { GenerateCodebaseIndex, /// This is for debugging, dev only for now LoadAgentModeConversation, - ShowWarpifySettings, + ShowWormholeSettings, /// Removes a pending attachment (image or file) by index in the unified list. DeleteAttachment { index: usize, @@ -622,7 +622,7 @@ impl fmt::Debug for TerminalAction { OpenBlockListContextMenu => f.write_str("OpenBlockListContextMenu"), AskAIAssistant { block_index } => write!(f, "AskAIAssistant({block_index:?})"), TriggerSubshellBootstrap => f.write_str("TriggerSubshellBootstrap"), - DismissWarpifyBanner(remember) => write!(f, "DismissWarpifyBanner({remember:?})"), + DismissWormholeBanner(remember) => write!(f, "DismissWormholeBanner({remember:?})"), ShowSubshellBanner(_) => f.write_str("ShowSubshellBanner"), InsertMostRecentCommandCorrection => f.write_str("InsertMostRecentCommandCorrection"), AliasExpansionBanner(action) => write!(f, "AliasExpansionBanner({action:?}"), @@ -682,7 +682,7 @@ impl fmt::Debug for TerminalAction { ShowInitializationBlock => write!(f, "ShowInitializationBlock"), GenerateCodebaseIndex => write!(f, "GenerateIndexForRepo"), LoadAgentModeConversation => write!(f, "LoadAgentModeConversation"), - ShowWarpifySettings => write!(f, "ShowWarpifySettings"), + ShowWormholeSettings => write!(f, "ShowWormholeSettings"), DeleteAttachment { index } => write!(f, "DeleteAttachment({index:?})"), OpenAttachmentLightbox { index } => { write!(f, "OpenAttachmentLightbox({index:?})") diff --git a/app/src/terminal/view/block_banner/mod.rs b/app/src/terminal/view/block_banner/mod.rs index 2c0d0813..368afafb 100644 --- a/app/src/terminal/view/block_banner/mod.rs +++ b/app/src/terminal/view/block_banner/mod.rs @@ -6,14 +6,14 @@ //! without a LayoutContext. Use the exported BLOCK_BANNER_HEIGHT const when the banner height //! needs to be taken into account. -mod warpify; +mod wormhole; use galaxyui::elements::{ ConstrainedBox, Container, CornerRadius, Hoverable, MouseState, MouseStateHandle, ParentElement, Radius, Stack, }; use galaxyui::Element; -pub use warpify::*; +pub use wormhole::*; 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 enum WithinBlockBanner { - WarpifyBanner(WarpifyBannerState), + WormholeBanner(WormholeBannerState), } impl WithinBlockBanner { pub fn banner_height(&self) -> f32 { match self { - WithinBlockBanner::WarpifyBanner(_) => BLOCK_BANNER_HEIGHT, + WithinBlockBanner::WormholeBanner(_) => BLOCK_BANNER_HEIGHT, } } } diff --git a/app/src/terminal/view/block_banner/warpify.rs b/app/src/terminal/view/block_banner/wormhole.rs similarity index 82% rename from app/src/terminal/view/block_banner/warpify.rs rename to app/src/terminal/view/block_banner/wormhole.rs index c1f93741..63fbfab2 100644 --- a/app/src/terminal/view/block_banner/warpify.rs +++ b/app/src/terminal/view/block_banner/wormhole.rs @@ -10,14 +10,14 @@ use pathfinder_color::ColorU; use super::render_block_banner; use crate::appearance::Appearance; -use crate::terminal::view::{RememberForWarpification, TerminalAction}; +use crate::terminal::view::{RememberForWormholing, TerminalAction}; use crate::themes::theme::Fill; use crate::ui_components::blended_colors; const CLOSE_BUTTON_DIAMETER: f32 = 20.0; const STANDARD_PADDING: f32 = 8.0; -pub struct WarpifyBannerState { +pub struct WormholeBannerState { /// The subshell command that triggered the banner. pub command: String, pub height: f32, @@ -25,19 +25,19 @@ pub struct WarpifyBannerState { pub dont_ask_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 /// looked up during action handling and cached here. - pub initialize_warpify_keybinding: Option, + pub initialize_wormhole_keybinding: Option, pub hover_state: MouseStateHandle, } -impl WarpifyBannerState { - pub fn new(command: String, initialize_warpify_keybinding: Option) -> Self { +impl WormholeBannerState { + pub fn new(command: String, initialize_wormhole_keybinding: Option) -> Self { Self { command, height: 0.0, - initialize_warpify_keybinding, + initialize_wormhole_keybinding, accept_button_mouse_state: Default::default(), dont_ask_button_mouse_state: Default::default(), dismiss_button_mouse_state: Default::default(), @@ -46,18 +46,18 @@ impl WarpifyBannerState { } pub fn title(&self) -> &str { - "Warpify subshell" + "Wormhole subshell" } pub fn action(&self) -> TerminalAction { TerminalAction::TriggerSubshellBootstrap } - fn remember_for_warpification(&self, should_remember: bool) -> RememberForWarpification { + fn remember_for_wormholing(&self, should_remember: bool) -> RememberForWormholing { if should_remember { - RememberForWarpification::RememberSubshellCommand(self.command.to_owned()) + RememberForWormholing::RememberSubshellCommand(self.command.to_owned()) } 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 /// 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. -pub fn render_warpification_banner( - state: &WarpifyBannerState, +pub fn render_wormholing_banner( + state: &WormholeBannerState, appearance: &Appearance, ) -> Box { let yes_button = render_yes_button( state, - &state.initialize_warpify_keybinding, + &state.initialize_wormhole_keybinding, &state.accept_button_mouse_state, appearance, ); - let remember = state.remember_for_warpification(true); + let remember = state.remember_for_wormholing(true); let dont_ask_button = Container::new( appearance .ui_builder() @@ -87,7 +87,7 @@ pub fn render_warpification_banner( .with_text_label("Do not show again".to_owned()) .build() .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner( + ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner( remember.to_owned(), )); }) @@ -96,7 +96,7 @@ pub fn render_warpification_banner( .with_margin_right(16.) .finish(); - let do_not_remember = state.remember_for_warpification(false); + let do_not_remember = state.remember_for_wormholing(false); let close_button = appearance .ui_builder() .close_button( @@ -105,7 +105,7 @@ pub fn render_warpification_banner( ) .build() .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner( + ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner( do_not_remember.to_owned(), )); }) @@ -132,12 +132,12 @@ pub fn render_warpification_banner( } fn render_yes_button( - state: &WarpifyBannerState, - initialize_warpification_keybinding: &Option, + state: &WormholeBannerState, + initialize_wormholing_keybinding: &Option, mouse_state: &MouseStateHandle, appearance: &Appearance, ) -> Box { - let yes_button = match initialize_warpification_keybinding { + let yes_button = match initialize_wormholing_keybinding { Some(keystroke) => appearance .ui_builder() .keyboard_shortcut_button(state.title().to_owned(), keystroke, mouse_state.clone()) diff --git a/app/src/terminal/view/init.rs b/app/src/terminal/view/init.rs index 35945248..8b595751 100644 --- a/app/src/terminal/view/init.rs +++ b/app/src/terminal/view/init.rs @@ -81,8 +81,8 @@ pub fn init(app: &mut AppContext) { app.register_binding_validator::(is_binding_pty_compliant); init_overlapping_keybindings(app); - // Register input mode bindings before warpify bindings so ctrl-i warpifies - // instead of opening inline agent when a warpify banner is visible. + // Register input mode bindings before wormhole bindings so ctrl-i wormholes + // instead of opening inline agent when a wormhole banner is visible. register_input_mode_bindings(app); 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)), ), EditableBinding::new( - "terminal:warpify_subshell", + "terminal:wormhole_subshell", "Wormhole subshell", TerminalAction::TriggerSubshellBootstrap, ) diff --git a/app/src/terminal/view/rich_content.rs b/app/src/terminal/view/rich_content.rs index ac494d83..07dbf50a 100644 --- a/app/src/terminal/view/rich_content.rs +++ b/app/src/terminal/view/rich_content.rs @@ -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_failed_banner::SshRemoteServerFailedBanner; 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; /// Specifies where to insert rich content in the blocklist. @@ -249,8 +249,8 @@ pub enum RichContentMetadata { SshTmuxDeprecationBanner { handle: ViewHandle, }, - WarpifySuccessBlock { - bootstrap_success_block_handle: ViewHandle, + WormholeSuccessBlock { + bootstrap_success_block_handle: ViewHandle, }, TelemetryBanner { telemetry_banner_handle: ViewHandle, diff --git a/app/src/terminal/view/ssh_file_upload.rs b/app/src/terminal/view/ssh_file_upload.rs index 0ba43953..31f4773d 100644 --- a/app/src/terminal/view/ssh_file_upload.rs +++ b/app/src/terminal/view/ssh_file_upload.rs @@ -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 { // "sftp " let mut command = String::from("sftp "); diff --git a/app/src/terminal/view/ssh_remote_server_choice_view.rs b/app/src/terminal/view/ssh_remote_server_choice_view.rs index 39659d38..411ec3af 100644 --- a/app/src/terminal/view/ssh_remote_server_choice_view.rs +++ b/app/src/terminal/view/ssh_remote_server_choice_view.rs @@ -1,14 +1,14 @@ //! 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 -//! ControlMaster warpification path). +//! ControlMaster wormholing path). //! //! Designed from frame 6050:2448 of the Figma file //! [Remote session initialization](https://www.figma.com/design/r0BO9cTZCK6pDE6qerg2K0/Remote-session-initialization). //! //! The view owns: //! - 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 current "Don't ask me this again" checked state (purely local to //! 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::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::{send_telemetry_from_ctx, Appearance}; @@ -48,14 +48,14 @@ pub enum SshRemoteServerChoiceViewAction { Install, Skip, ToggleDoNotAskAgain, - OpenWarpifySettings, + OpenWormholeSettings, } #[derive(Clone, Debug)] pub enum SshRemoteServerChoiceViewEvent { Install, Skip, - OpenWarpifySettings, + OpenWormholeSettings, } /// 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(|_| { KeyboardNavigableButtons::new(vec![ rich_navigation_button( - "Install Galaxy's SSH extension".to_string(), + "Install Wormhole helper".to_string(), Some( "Install Galaxy's extension to enable agent features like file browsing, \ 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()) .finish(); - // Right: "Manage Warpify settings" link. + // Right: "Manage Wormhole settings" link. let manage_settings_link = appearance .ui_builder() .link( "Manage Wormhole settings".into(), None, Some(Box::new(|ctx| { - ctx.dispatch_typed_action(SshRemoteServerChoiceViewAction::OpenWarpifySettings); + ctx.dispatch_typed_action( + SshRemoteServerChoiceViewAction::OpenWormholeSettings, + ); })), self.manage_settings_mouse_state.clone(), ) @@ -264,7 +266,7 @@ impl TypedActionView for SshRemoteServerChoiceView { SshRemoteServerChoiceViewAction::Install => { if self.do_not_ask_again { 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) { log::error!("Failed to persist ssh_extension_install_mode: {e}"); } @@ -281,7 +283,7 @@ impl TypedActionView for SshRemoteServerChoiceView { SshRemoteServerChoiceViewAction::Skip => { if self.do_not_ask_again { 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) { log::error!("Failed to persist ssh_extension_install_mode: {e}"); } @@ -305,8 +307,8 @@ impl TypedActionView for SshRemoteServerChoiceView { ); ctx.notify(); } - SshRemoteServerChoiceViewAction::OpenWarpifySettings => { - ctx.emit(SshRemoteServerChoiceViewEvent::OpenWarpifySettings); + SshRemoteServerChoiceViewAction::OpenWormholeSettings => { + ctx.emit(SshRemoteServerChoiceViewEvent::OpenWormholeSettings); } } } diff --git a/app/src/terminal/view/ssh_remote_server_failed_banner.rs b/app/src/terminal/view/ssh_remote_server_failed_banner.rs index f9b3500c..d708bac0 100644 --- a/app/src/terminal/view/ssh_remote_server_failed_banner.rs +++ b/app/src/terminal/view/ssh_remote_server_failed_banner.rs @@ -1,5 +1,5 @@ //! 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::AnsiColorIdentifier; @@ -15,11 +15,11 @@ use crate::terminal::model::session::SessionId; use crate::ui_components::icons::Icon; 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 = "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)] pub enum SshRemoteServerFailedBannerAction { diff --git a/app/src/terminal/view/ssh_tmux_deprecation_banner.rs b/app/src/terminal/view/ssh_tmux_deprecation_banner.rs index 524ed7bd..10ee6a13 100644 --- a/app/src/terminal/view/ssh_tmux_deprecation_banner.rs +++ b/app/src/terminal/view/ssh_tmux_deprecation_banner.rs @@ -1,6 +1,6 @@ //! 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 -//! off in favor of Warp's SSH extension (remote server) and links to the docs. +//! tmux-based SSH wormholing flow. It explains that tmux SSH wormholing has been turned +//! 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 //! `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 warpui::elements::{ - Align, ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment, + ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentElement, Shrinkable, Text, }; use warpui::platform::Cursor; -use warpui::ui_components::components::{UiComponent, UiComponentStyles}; use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext}; use crate::terminal::model::session::SessionId; -use crate::terminal::warpify::render::SSH_DOCS_URL; use crate::ui_components::icons::Icon; 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."; -const LEARN_MORE_LABEL: &str = "Learn more"; - #[derive(Clone, Debug)] pub enum SshTmuxDeprecationBannerAction { Dismiss, - LearnMore, } #[derive(Clone, Debug)] @@ -40,7 +36,6 @@ pub enum SshTmuxDeprecationBannerEvent { pub struct SshTmuxDeprecationBanner { session_id: SessionId, - learn_more_mouse_state: MouseStateHandle, close_mouse_state: MouseStateHandle, } @@ -48,7 +43,6 @@ impl SshTmuxDeprecationBanner { pub fn new(session_id: SessionId) -> Self { Self { session_id, - learn_more_mouse_state: MouseStateHandle::default(), close_mouse_state: MouseStateHandle::default(), } } @@ -72,13 +66,12 @@ impl View for SshTmuxDeprecationBanner { let theme = appearance.theme(); let fg_color = theme.foreground().into_solid(); let muted_color = internal_colors::neutral_5(theme); - let accent_color = theme.accent().into_solid(); let font_size = appearance.monospace_font_size(); 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( - 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_height(16.) .finish(), @@ -103,26 +96,6 @@ impl View for SshTmuxDeprecationBanner { .with_color(muted_color) .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 let close_icon_color = muted_color; let close = Hoverable::new(self.close_mouse_state.clone(), move |_| { @@ -158,26 +131,17 @@ impl View for SshTmuxDeprecationBanner { .with_child(close_container) .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) .with_margin_top(2.) .with_margin_left(24.) .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() .with_main_axis_size(MainAxisSize::Min) .with_cross_axis_alignment(CrossAxisAlignment::Stretch) .with_child(header_row) .with_child(body_container) - .with_child(learn_more_container) .finish(); Container::new(content) @@ -195,10 +159,6 @@ impl TypedActionView for SshTmuxDeprecationBanner { SshTmuxDeprecationBannerAction::Dismiss => { ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed); } - SshTmuxDeprecationBannerAction::LearnMore => { - ctx.open_url(SSH_DOCS_URL); - ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed); - } } } } diff --git a/app/src/terminal/view/use_agent_footer/mod.rs b/app/src/terminal/view/use_agent_footer/mod.rs index 9ef35425..7a01b56b 100644 --- a/app/src/terminal/view/use_agent_footer/mod.rs +++ b/app/src/terminal/view/use_agent_footer/mod.rs @@ -16,7 +16,7 @@ use crate::terminal::shared_session::{ SharedSessionActionSource, SharedSessionScrollbackType, SharedSessionSource, }; 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::sync::{Arc, LazyLock}; @@ -44,7 +44,7 @@ use galaxyui::{ }; use parking_lot::FairMutex; use pathfinder_color::ColorU; -use warpify_footer::{WarpifyFooterView, WarpifyFooterViewEvent}; +use wormhole_footer::{WormholeFooterView, WormholeFooterViewEvent}; use super::{RichContentInsertionPosition, TerminalAction, TerminalView}; use crate::ai::blocklist::agent_view::agent_view_bg_fill; @@ -267,11 +267,11 @@ impl TerminalView { UseAgentToolbarEvent::HideRichInput => { self.close_cli_agent_rich_input_and_disable_auto_toggle(ctx); } - UseAgentToolbarEvent::Warpify => { + UseAgentToolbarEvent::Wormhole => { self.hide_use_agent_footer_in_blocklist(ctx); self.handle_action(&TerminalAction::TriggerSubshellBootstrap, ctx); send_telemetry_from_ctx!( - TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh: false }, + TelemetryEvent::WormholeFooterAcceptedWormhole { is_ssh: false }, ctx ); } @@ -295,8 +295,8 @@ impl TerminalView { ) -> bool { let ai_settings = AISettings::as_ref(app); - // If the warpify 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 the wormhole footer is active, a subshell was detected and we should show the footer. + if self.use_agent_footer.as_ref(app).is_wormhole_active(app) { return true; } @@ -421,7 +421,7 @@ impl TerminalView { if !self.model.lock().is_alt_screen_active() { self.use_agent_footer.update(ctx, |footer, ctx| { - footer.clear_warpify(ctx); + footer.clear_wormhole(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). agent_input_footer: ViewHandle, - // Warpify footer UI (shown when a subshell/SSH command is detected). - warpify_footer_view: ViewHandle, + // Wormhole footer UI (shown when a subshell/SSH command is detected). + wormhole_footer_view: ViewHandle, // `true` if the user has dismissed the footer. // @@ -1120,11 +1120,11 @@ impl UseAgentToolbar { me.handle_agent_input_footer_event(event, ctx); }); - let warpify_footer_view = - ctx.add_typed_action_view(|ctx| WarpifyFooterView::new(terminal_model.clone(), ctx)); + let wormhole_footer_view = + ctx.add_typed_action_view(|ctx| WormholeFooterView::new(terminal_model.clone(), ctx)); - ctx.subscribe_to_view(&warpify_footer_view, |me, _, event, ctx| { - me.handle_warpify_footer_event(event, ctx); + ctx.subscribe_to_view(&wormhole_footer_view, |me, _, event, ctx| { + me.handle_wormhole_footer_event(event, ctx); }); ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| { @@ -1150,7 +1150,7 @@ impl UseAgentToolbar { dismiss_button, dont_show_again_button, agent_input_footer, - warpify_footer_view, + wormhole_footer_view, terminal_model, did_user_dismiss: false, } @@ -1186,19 +1186,19 @@ impl UseAgentToolbar { } } - fn handle_warpify_footer_event( + fn handle_wormhole_footer_event( &mut self, - event: &WarpifyFooterViewEvent, + event: &WormholeFooterViewEvent, ctx: &mut ViewContext, ) { match event { - WarpifyFooterViewEvent::Warpify => { - ctx.emit(UseAgentToolbarEvent::Warpify); + WormholeFooterViewEvent::Wormhole => { + ctx.emit(UseAgentToolbarEvent::Wormhole); } - WarpifyFooterViewEvent::UseAgent => { + WormholeFooterViewEvent::UseAgent => { ctx.emit(UseAgentToolbarEvent::UseAgent); } - WarpifyFooterViewEvent::Dismiss => { + WormholeFooterViewEvent::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) { 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.give_control_back_button .update(ctx, |_, ctx| ctx.notify()); @@ -1227,26 +1227,26 @@ impl UseAgentToolbar { .map(|session| session.agent) } - /// Activates the warpify footer. When active, the footer shows the - /// warpify view instead of the CLI agent or regular "Use agent" views. - pub(in crate::terminal) fn show_warpify(&mut self, ctx: &mut ViewContext) { - self.warpify_footer_view.update(ctx, |view, ctx| { + /// Activates the wormhole footer. When active, the footer shows the + /// wormhole view instead of the CLI agent or regular "Use agent" views. + pub(in crate::terminal) fn show_wormhole(&mut self, ctx: &mut ViewContext) { + self.wormhole_footer_view.update(ctx, |view, ctx| { view.show(ctx); }); ctx.notify(); } - /// Deactivates the warpify footer so it reverts to its default behavior. - pub(in crate::terminal) fn clear_warpify(&mut self, ctx: &mut ViewContext) { - self.warpify_footer_view.update(ctx, |view, ctx| { + /// Deactivates the wormhole footer so it reverts to its default behavior. + pub(in crate::terminal) fn clear_wormhole(&mut self, ctx: &mut ViewContext) { + self.wormhole_footer_view.update(ctx, |view, ctx| { view.clear(ctx); }); ctx.notify(); } - /// Returns whether the warpify footer is currently active. - pub(in crate::terminal) fn is_warpify_active(&self, app: &AppContext) -> bool { - self.warpify_footer_view.as_ref(app).is_active() + /// Returns whether the wormhole footer is currently active. + pub(in crate::terminal) fn is_wormhole_active(&self, app: &AppContext) -> bool { + self.wormhole_footer_view.as_ref(app).is_active() } /// Returns whether there's a current CLI agent (like Claude Code). @@ -1272,8 +1272,8 @@ pub enum UseAgentToolbarEvent { OpenRichInput, /// Hide the rich input editor (same as Escape). HideRichInput, - /// User chose to warpify the subshell. - Warpify, + /// User chose to wormhole the subshell. + Wormhole, /// User chose to use the agent. UseAgent, StartRemoteControl { @@ -1292,9 +1292,9 @@ impl View for UseAgentToolbar { } fn render(&self, app: &AppContext) -> Box { - // If the warpify footer is active, delegate rendering to the warpify footer view. - if self.warpify_footer_view.as_ref(app).is_active() { - return ChildView::new(&self.warpify_footer_view).finish(); + // If the wormhole footer is active, delegate rendering to the wormhole footer view. + if self.wormhole_footer_view.as_ref(app).is_active() { + return ChildView::new(&self.wormhole_footer_view).finish(); } // Hide the toolbar entirely when CLI rich input is open, diff --git a/app/src/terminal/view/use_agent_footer/warpify_footer.rs b/app/src/terminal/view/use_agent_footer/wormhole_footer.rs similarity index 71% rename from app/src/terminal/view/use_agent_footer/warpify_footer.rs rename to app/src/terminal/view/use_agent_footer/wormhole_footer.rs index a98684c7..97876147 100644 --- a/app/src/terminal/view/use_agent_footer/warpify_footer.rs +++ b/app/src/terminal/view/use_agent_footer/wormhole_footer.rs @@ -15,28 +15,28 @@ use crate::view_components::action_button::{ }; /// Footer view rendered for detected subshell commands, offering both -/// "Warpify" and "Use agent" buttons in a horizontal row. -pub(super) struct WarpifyFooterView { +/// "Wormhole" and "Use agent" buttons in a horizontal row. +pub(super) struct WormholeFooterView { terminal_model: Arc>, - warpify_button: ViewHandle, + wormhole_button: ViewHandle, use_agent_button: ViewHandle, dismiss_button: ViewHandle, - /// Whether the footer is currently offering subshell warpification. + /// Whether the footer is currently offering subshell wormholing. is_active: bool, } -impl WarpifyFooterView { +impl WormholeFooterView { pub fn new(terminal_model: Arc>, ctx: &mut ViewContext) -> Self { 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)) - .with_icon(Icon::Warp) + .with_icon(Icon::GalaxyLogo) .with_size(button_size) .with_tooltip("Enable Galaxy shell integration in this session") .with_tooltip_alignment(TooltipAlignment::Left) .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_alignment(TooltipAlignment::Left) .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)) .with_size(button_size) .on_click(|ctx| { - ctx.dispatch_typed_action(WarpifyFooterViewAction::Dismiss); + ctx.dispatch_typed_action(WormholeFooterViewAction::Dismiss); }) }); Self { terminal_model, - warpify_button, + wormhole_button, use_agent_button, dismiss_button, 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.warpify_button.update(ctx, |button, ctx| { + self.wormhole_button.update(ctx, |button, ctx| { button.set_keybinding( - Some(KeystrokeSource::Binding("terminal:warpify_subshell")), + Some(KeystrokeSource::Binding("terminal:wormhole_subshell")), ctx, ); }); @@ -81,7 +81,7 @@ impl WarpifyFooterView { 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 { self.is_active } @@ -89,7 +89,7 @@ impl WarpifyFooterView { /// Deactivates the footer. pub fn clear(&mut self, ctx: &mut ViewContext) { self.is_active = false; - self.warpify_button.update(ctx, |button, ctx| { + self.wormhole_button.update(ctx, |button, ctx| { button.set_keybinding(None, ctx); }); ctx.notify(); @@ -97,25 +97,25 @@ impl WarpifyFooterView { } #[derive(Debug, Clone)] -pub enum WarpifyFooterViewAction { - Warpify, +pub enum WormholeFooterViewAction { + Wormhole, UseAgent, Dismiss, } -pub enum WarpifyFooterViewEvent { - Warpify, +pub enum WormholeFooterViewEvent { + Wormhole, UseAgent, Dismiss, } -impl Entity for WarpifyFooterView { - type Event = WarpifyFooterViewEvent; +impl Entity for WormholeFooterView { + type Event = WormholeFooterViewEvent; } -impl View for WarpifyFooterView { +impl View for WormholeFooterView { fn ui_name() -> &'static str { - "WarpifyFooterView" + "WormholeFooterView" } fn render(&self, _app: &AppContext) -> Box { @@ -125,7 +125,7 @@ impl View for WarpifyFooterView { .with_spacing(4.) .with_main_axis_size(MainAxisSize::Max) .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(Expanded::new(1., Empty::new().finish()).finish()) .with_child(ChildView::new(&self.dismiss_button).finish()); @@ -144,24 +144,24 @@ impl View for WarpifyFooterView { } } -impl TypedActionView for WarpifyFooterView { - type Action = WarpifyFooterViewAction; +impl TypedActionView for WormholeFooterView { + type Action = WormholeFooterViewAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - WarpifyFooterViewAction::Warpify => { + WormholeFooterViewAction::Wormhole => { if self.is_active { self.clear(ctx); - ctx.emit(WarpifyFooterViewEvent::Warpify); + ctx.emit(WormholeFooterViewEvent::Wormhole); } } - WarpifyFooterViewAction::UseAgent => { + WormholeFooterViewAction::UseAgent => { self.clear(ctx); - ctx.emit(WarpifyFooterViewEvent::UseAgent); + ctx.emit(WormholeFooterViewEvent::UseAgent); } - WarpifyFooterViewAction::Dismiss => { + WormholeFooterViewAction::Dismiss => { self.clear(ctx); - ctx.emit(WarpifyFooterViewEvent::Dismiss); + ctx.emit(WormholeFooterViewEvent::Dismiss); } } } diff --git a/app/src/terminal/warpify/mod.rs b/app/src/terminal/wormhole/mod.rs similarity index 95% rename from app/src/terminal/warpify/mod.rs rename to app/src/terminal/wormhole/mod.rs index 795a1e9b..bc5239c3 100644 --- a/app/src/terminal/warpify/mod.rs +++ b/app/src/terminal/wormhole/mod.rs @@ -10,12 +10,6 @@ use crate::terminal::model::terminal_model::SubshellInitializationInfo; use crate::terminal::shell::ShellType; use crate::ASSETS; -#[derive(Debug)] -pub enum WarpificationSource { - Ssh, - Subshell, -} - #[derive(Clone, PartialEq, Eq, Debug)] pub enum SubshellSource { 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. /// /// The exact block contents varies based on whether or not the session is local or remote, in diff --git a/app/src/terminal/warpify/render.rs b/app/src/terminal/wormhole/render.rs similarity index 94% rename from app/src/terminal/warpify/render.rs rename to app/src/terminal/wormhole/render.rs index 4df57c3a..fd65e7d2 100644 --- a/app/src/terminal/warpify/render.rs +++ b/app/src/terminal/wormhole/render.rs @@ -13,7 +13,7 @@ use pathfinder_color::ColorU; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::vector::Vector2F; -use super::settings::WarpifySettings; +use super::settings::WormholeSettings; use super::SubshellSource; use crate::ai::blocklist::inline_action::inline_action_icons; 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 TERMINAL_ICON: &str = "bundled/svg/terminal.svg"; 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. pub const LEFT_STRIPE_WIDTH: f32 = 5.; @@ -92,7 +90,7 @@ fn green_check_icon(appearance: &Appearance, size: f32) -> Box { .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( command: String, theme: &GalaxyTheme, @@ -164,21 +162,21 @@ pub fn description_row( .finish() } -/// Renders a "Never Warpify this host" link or nothing. -pub fn render_never_warpify_ssh_link( +/// Renders a "Never Wormhole this host" link or nothing. +pub fn render_never_wormhole_ssh_link( ssh_host: &Option, app: &AppContext, appearance: &Appearance, mouse_state_handle: MouseStateHandle, - on_never_warpify: fn(&mut EventContext<'_>, ssh_host: String), + on_never_wormhole: fn(&mut EventContext<'_>, ssh_host: String), ) -> Option> { let Some(ssh_host) = ssh_host else { return None; }; - let settings = WarpifySettings::handle(app); + let settings = WormholeSettings::handle(app); 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; } @@ -189,7 +187,7 @@ pub fn render_never_warpify_ssh_link( None, Some(Box::new({ 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, ) diff --git a/app/src/terminal/warpify/settings.rs b/app/src/terminal/wormhole/settings.rs similarity index 84% rename from app/src/terminal/warpify/settings.rs rename to app/src/terminal/wormhole/settings.rs index b2d22536..55a6937e 100644 --- a/app/src/terminal/warpify/settings.rs +++ b/app/src/terminal/wormhole/settings.rs @@ -9,65 +9,65 @@ use settings::{ }; 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 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, default: Vec::new(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, 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.", }); -maybe_define_setting!(SubshellCommandsDenylist, group: WarpifySettings, { +maybe_define_setting!(SubshellCommandsDenylist, group: WormholeSettings, { type: Vec, default: Vec::new(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, private: false, - toml_path: "warpify.subshells.subshell_commands_denylist", - description: "Commands that should not trigger the subshell warpification prompt.", + toml_path: "wormhole.subshells.subshell_commands_denylist", + description: "Commands that should not trigger the subshell wormholing prompt.", }); -maybe_define_setting!(SshHostsDenylist, group: WarpifySettings, { +maybe_define_setting!(SshHostsDenylist, group: WormholeSettings, { type: Vec, default: Vec::new(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, private: false, - toml_path: "warpify.ssh.ssh_hosts_denylist", - description: "SSH hosts that should not trigger the warpification prompt.", + toml_path: "wormhole.ssh.ssh_hosts_denylist", + description: "SSH hosts that should not trigger the wormholing prompt.", }); -maybe_define_setting!(EnableSshWarpification, group: WarpifySettings, { +maybe_define_setting!(EnableSshWormholing, group: WormholeSettings, { type: bool, default: true, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, 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.", }); -// 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 // 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. // The storage key and TOML path are intentionally kept identical to the old // `SshSettings::enable_ssh_wrapper` field for backward compatibility. -maybe_define_setting!(EnableSshWrapper, group: WarpifySettings, { +maybe_define_setting!(EnableSshWrapper, group: WormholeSettings, { type: bool, default: true, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, storage_key: "EnableSSHWrapper", - toml_path: "warpify.ssh.enable_legacy_ssh_wrapper", - description: "Deprecated: unified into enable_ssh_warpification. Retained only for one-time migration.", + toml_path: "wormhole.ssh.enable_legacy_ssh_wrapper", + 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 @@ -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 // user's previous opt-in and reset it. It can be deleted in a future release once the // migration has shipped to all users. -maybe_define_setting!(UseSshTmuxWrapper, group: WarpifySettings, { +maybe_define_setting!(UseSshTmuxWrapper, group: WormholeSettings, { type: bool, default: false, supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()), sync_to_cloud: SyncToCloud::Never, private: false, - toml_path: "warpify.ssh.use_ssh_tmux_wrapper", - description: "Deprecated: whether to use a tmux-based wrapper for SSH warpification.", + toml_path: "wormhole.ssh.use_ssh_tmux_wrapper", + 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 // 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 // banner has been shown. -maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WarpifySettings, { +maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WormholeSettings, { type: bool, default: false, supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()), sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), 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.", }); -/// 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. #[derive( Default, @@ -115,7 +115,7 @@ maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WarpifySettings, { )] #[serde(rename_all = "snake_case")] #[schemars( - description = "Controls SSH extension installation behavior.", + description = "Controls Wormhole helper installation behavior.", rename_all = "snake_case" )] pub enum SshExtensionInstallMode { @@ -124,18 +124,18 @@ pub enum SshExtensionInstallMode { AlwaysAsk, /// Automatically install and connect without prompting. AlwaysInstall, - /// Never install; fall back to wrapper-only SSH warpification. + /// Never install; fall back to wrapper-only SSH wormholing. NeverInstall, } -maybe_define_setting!(SshExtensionInstallModeSetting, group: WarpifySettings, { +maybe_define_setting!(SshExtensionInstallModeSetting, group: WormholeSettings, { type: SshExtensionInstallMode, default: SshExtensionInstallMode::default(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, - toml_path: "warpify.ssh.ssh_extension_install_mode", - description: "Controls SSH extension installation behavior.", + toml_path: "wormhole.ssh.ssh_extension_install_mode", + description: "Controls Wormhole helper installation behavior.", }); impl SshExtensionInstallMode { @@ -151,7 +151,7 @@ impl SshExtensionInstallMode { /// 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 /// 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 /// represents the raw, serialized value. Therefore, it is Vec. 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 /// method for how this is done. pub parsed_added_subshell_commands: Vec>, - /// 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 - /// 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, /// 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 @@ -172,11 +172,11 @@ pub struct WarpifySettings { /// method for how this is done. pub parsed_subshell_command_denylist: Vec>, - /// 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, - /// 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 - /// 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, /// 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 @@ -185,10 +185,10 @@ pub struct WarpifySettings { /// method for how this is done. pub parsed_ssh_hosts_denylist: Vec>, - /// This setting controls whether we should ever warpify ssh sessions. - pub enable_ssh_warpification: EnableSshWarpification, + /// This setting controls whether we should ever wormhole ssh sessions. + 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 /// gate any behavior. pub enable_ssh_wrapper: EnableSshWrapper, @@ -238,7 +238,7 @@ lazy_static! { // Matches commands that spawn a pipenv subshell. 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"), // 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. /// I have separated this stuff from the other impl block, which contains the subshell-specific /// logic, because this is basically boilerplate. -impl WarpifySettings { +impl WormholeSettings { fn new_from_storage(ctx: &mut ModelContext) -> Self { let added_subshell_commands = AddedSubshellCommands::new_from_storage(ctx); let subshell_command_denylist = SubshellCommandsDenylist::new_from_storage(ctx); @@ -267,7 +267,7 @@ impl WarpifySettings { subshell_command_denylist, parsed_ssh_hosts_denylist: Self::parse_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), use_ssh_tmux_wrapper: UseSshTmuxWrapper::new_from_storage(ctx), ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new_from_storage( @@ -294,7 +294,7 @@ impl WarpifySettings { subshell_command_denylist, parsed_ssh_hosts_denylist: Self::parse_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), use_ssh_tmux_wrapper: UseSshTmuxWrapper::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); ctx.subscribe_to_model(&handle, |settings, event, ctx| { settings.update(ctx, |me, _| match event { - WarpifySettingsChangedEvent::AddedSubshellCommands { .. } => { + WormholeSettingsChangedEvent::AddedSubshellCommands { .. } => { me.parsed_added_subshell_commands = Self::parse_added_subshell_commands(&me.added_subshell_commands) } - WarpifySettingsChangedEvent::SubshellCommandsDenylist { .. } => { + WormholeSettingsChangedEvent::SubshellCommandsDenylist { .. } => { me.parsed_subshell_command_denylist = Self::parse_subshell_command_denylist(&me.subshell_command_denylist) } - WarpifySettingsChangedEvent::SshHostsDenylist { .. } => { + WormholeSettingsChangedEvent::SshHostsDenylist { .. } => { me.parsed_ssh_hosts_denylist = Self::parse_ssh_hosts_denylist(&me.ssh_hosts_denylist) } - WarpifySettingsChangedEvent::EnableSshWarpification { .. } => {} - WarpifySettingsChangedEvent::EnableSshWrapper { .. } => {} - WarpifySettingsChangedEvent::UseSshTmuxWrapper { .. } => {} - WarpifySettingsChangedEvent::SshTmuxDeprecationNoticePending { .. } => {} - WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {} + WormholeSettingsChangedEvent::EnableSshWormholing { .. } => {} + WormholeSettingsChangedEvent::EnableSshWrapper { .. } => {} + WormholeSettingsChangedEvent::UseSshTmuxWrapper { .. } => {} + WormholeSettingsChangedEvent::SshTmuxDeprecationNoticePending { .. } => {} + WormholeSettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {} }); }); // 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 - // 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 // the migration does not run again on subsequent launches. handle.update(ctx, |me, ctx| { 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!( - "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) { @@ -366,7 +366,7 @@ impl WarpifySettings { }); register_settings_events!( - WarpifySettings, + WormholeSettings, added_subshell_commands, AddedSubshellCommands, handle.clone(), @@ -374,7 +374,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + WormholeSettings, subshell_command_denylist, SubshellCommandsDenylist, handle.clone(), @@ -382,15 +382,15 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, - enable_ssh_warpification, - EnableSshWarpification, + WormholeSettings, + enable_ssh_wormholing, + EnableSshWormholing, handle.clone(), ctx ); register_settings_events!( - WarpifySettings, + WormholeSettings, enable_ssh_wrapper, EnableSshWrapper, handle.clone(), @@ -398,7 +398,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + WormholeSettings, use_ssh_tmux_wrapper, UseSshTmuxWrapper, handle.clone(), @@ -406,7 +406,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + WormholeSettings, ssh_tmux_deprecation_notice_pending, SshTmuxDeprecationNoticePending, handle.clone(), @@ -414,7 +414,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + WormholeSettings, ssh_extension_install_mode, SshExtensionInstallModeSetting, handle.clone(), @@ -422,7 +422,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + WormholeSettings, ssh_hosts_denylist, SshHostsDenylist, handle, @@ -432,9 +432,9 @@ impl WarpifySettings { } /// 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. -pub enum WarpifySettingsChangedEvent { +pub enum WormholeSettingsChangedEvent { AddedSubshellCommands { change_event_reason: ChangeEventReason, }, @@ -444,7 +444,7 @@ pub enum WarpifySettingsChangedEvent { SshHostsDenylist { change_event_reason: ChangeEventReason, }, - EnableSshWarpification { + EnableSshWormholing { change_event_reason: ChangeEventReason, }, EnableSshWrapper { @@ -461,15 +461,15 @@ pub enum WarpifySettingsChangedEvent { }, } -impl Entity for WarpifySettings { - type Event = WarpifySettingsChangedEvent; +impl Entity for WormholeSettings { + 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 /// logic. -impl WarpifySettings { +impl WormholeSettings { fn is_built_in_subshell_match(command: &str) -> bool { for command_regex in SUBSHELL_COMMAND_REGEXES.iter() { if command_regex.is_match(command) { @@ -494,7 +494,7 @@ impl WarpifySettings { 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; } @@ -505,8 +505,8 @@ impl WarpifySettings { } } - // While in-band generators are our best option for warpifying ssh sessions from powershell, hard-code - // the warpify subshell banner to show up. + // While in-band generators are our best option for wormholing ssh sessions from powershell, hard-code + // the wormhole subshell banner to show up. if matches!(shell_family, ShellFamily::PowerShell) && parse_interactive_ssh_command(command).is_some() { @@ -602,7 +602,7 @@ impl WarpifySettings { new_added_commands_list.push(command_to_add.trim().to_owned()); // 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. self.added_subshell_commands .set_value(new_added_commands_list, ctx) @@ -611,7 +611,7 @@ impl WarpifySettings { 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 { let command = command.trim(); self.parsed_subshell_command_denylist diff --git a/app/src/terminal/warpify/settings_tests.rs b/app/src/terminal/wormhole/settings_tests.rs similarity index 77% rename from app/src/terminal/warpify/settings_tests.rs rename to app/src/terminal/wormhole/settings_tests.rs index 73ec5aac..06f14ea1 100644 --- a/app/src/terminal/warpify/settings_tests.rs +++ b/app/src/terminal/wormhole/settings_tests.rs @@ -1,7 +1,7 @@ use settings::Setting; use warpui::{App, SingletonEntity}; -use super::WarpifySettings; +use super::WormholeSettings; use crate::test_util::settings::initialize_settings_for_tests; #[test] @@ -10,12 +10,12 @@ fn test_parsed_subshell_commands_updated_via_self_subscription() { initialize_settings_for_tests(&mut app); app.read(|ctx| { - assert!(WarpifySettings::as_ref(ctx) + assert!(WormholeSettings::as_ref(ctx) .parsed_added_subshell_commands .is_empty()); }); - WarpifySettings::handle(&app).update(&mut app, |settings, ctx| { + WormholeSettings::handle(&app).update(&mut app, |settings, ctx| { settings .added_subshell_commands .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. 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!( parsed.len(), 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` /// (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] -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 { initialize_settings_for_tests(&mut app); // 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 .enable_ssh_wrapper .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 // by applying it explicitly here. 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() && !*me.enable_ssh_wrapper.value() { - me.enable_ssh_warpification + me.enable_ssh_wormholing .set_value(false, ctx) - .expect("migration set enable_ssh_warpification"); + .expect("migration set enable_ssh_wormholing"); me.enable_ssh_wrapper .set_value(true, ctx) .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| { - let settings = WarpifySettings::as_ref(ctx); + let settings = WormholeSettings::as_ref(ctx); assert!( - !*settings.enable_ssh_warpification.value(), - "enable_ssh_warpification should be false after migration" + !*settings.enable_ssh_wormholing.value(), + "enable_ssh_wormholing should be false after migration" ); // The wrapper is reset to true so the migration condition // (`!*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 -/// spuriously disable `enable_ssh_warpification`. +/// spuriously disable `enable_ssh_wormholing`. #[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 { initialize_settings_for_tests(&mut app); 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. assert!( !settings.enable_ssh_wrapper.is_value_explicitly_set(), "enable_ssh_wrapper should not be explicitly set in a fresh install" ); assert!( - *settings.enable_ssh_warpification.value(), - "enable_ssh_warpification should remain true when no migration is needed" + *settings.enable_ssh_wormholing.value(), + "enable_ssh_wormholing should remain true when no migration is needed" ); }); }); @@ -133,7 +133,7 @@ fn test_wsl_subshell_detection_success() { .iter() .for_each(|cmd| { assert!( - WarpifySettings::is_built_in_subshell_match(cmd), + WormholeSettings::is_built_in_subshell_match(cmd), "{} failed to match", *cmd ) @@ -164,7 +164,7 @@ fn test_wsl_subshell_detection_fail() { .iter() .for_each(|cmd| { assert!( - !WarpifySettings::is_built_in_subshell_match(cmd), + !WormholeSettings::is_built_in_subshell_match(cmd), "{} accidentally matched", *cmd ) diff --git a/app/src/terminal/warpify/success_block.rs b/app/src/terminal/wormhole/success_block.rs similarity index 65% rename from app/src/terminal/warpify/success_block.rs rename to app/src/terminal/wormhole/success_block.rs index 07dbdb53..4a585c5d 100644 --- a/app/src/terminal/warpify/success_block.rs +++ b/app/src/terminal/wormhole/success_block.rs @@ -7,14 +7,13 @@ use galaxy_core::ui::theme::GalaxyTheme; use parking_lot::RwLock; use warpui::elements::{ 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 super::render::{HORIZONTAL_TEXT_MARGIN, SSH_DOCS_URL, SUBSHELL_DOCS_URL}; -use super::settings::WarpifySettings; -use super::{render, subshell_bootstrap_success_block_bytes, WarpificationSource}; +use super::render::HORIZONTAL_TEXT_MARGIN; +use super::settings::WormholeSettings; +use super::{render, subshell_bootstrap_success_block_bytes}; use crate::ai::agent::ProgrammingLanguage; use crate::ai::blocklist::code_block::{render_runnable_code_snippet, CodeSnippetButtonHandles}; use crate::appearance::Appearance; @@ -27,20 +26,19 @@ use crate::workspace::WorkspaceAction; const VERTICAL_TEXT_MARGIN: f32 = 16.; #[derive(Debug, Clone)] -pub enum WarpifySuccessBlockEvent { - OpenWarpifySettings, +pub enum WormholeSuccessBlockEvent { + OpenWormholeSettings, } #[derive(Debug, Clone, Eq, PartialEq)] -pub enum WarpifySuccessBlockAction { - ClearAutoWarpifySnippet, - OpenWarpifySettings, - OpenUrl(String), +pub enum WormholeSuccessBlockAction { + ClearAutoWormholeSnippet, + OpenWormholeSettings, } -struct AutoWarpifySnippet { +struct AutoWormholeSnippet { /// 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>, /// The output grid needs to be selectable to allow users to copy the command to their clipboard. selection_handle: SelectionHandle, @@ -52,23 +50,20 @@ struct AutoWarpifySnippet { can_write_to_rc: bool, } -pub struct WarpifySuccessBlock { - source: WarpificationSource, +pub struct WormholeSuccessBlock { spawning_command: String, - learn_more_link_mouse_states: MouseStateHandle, - auto_warpify_snippet: Option, + auto_wormhole_snippet: Option, } -impl WarpifySuccessBlock { +impl WormholeSuccessBlock { #[allow(clippy::new_without_default)] pub fn new( - source: WarpificationSource, spawning_command: String, subshell_info: Option, shell: Shell, ctx: &mut ViewContext, ) -> Self { - ctx.subscribe_to_model(&WarpifySettings::handle(ctx), move |_, _, _, ctx| { + ctx.subscribe_to_model(&WormholeSettings::handle(ctx), move |_, _, _, ctx| { ctx.notify(); }); @@ -76,17 +71,17 @@ impl WarpifySuccessBlock { // getting the OS to write to the correct RC file. let remote_os = TargetOS::Linux; - let is_auto_warpify_configured = subshell_info + let is_auto_wormhole_configured = subshell_info .as_ref() .map(|info| info.was_triggered_by_rc_file_snippet) .unwrap_or_default(); - let auto_warpify_snippet = if is_auto_warpify_configured { + let auto_wormhole_snippet = if is_auto_wormhole_configured { None } else { subshell_info.and_then(|subshell_info| { - // If warpification wasn't triggered automatically, show a snippet about - // how to automatically warpify. + // If wormholing wasn't triggered automatically, show a snippet about + // how to automatically wormhole. (!subshell_info.was_triggered_by_rc_file_snippet).then(|| { let (command, is_executable) = subshell_bootstrap_success_block_bytes( &subshell_info, @@ -108,8 +103,8 @@ impl WarpifySuccessBlock { }) }) }; - let auto_warpify_snippet = auto_warpify_snippet.map(|(output_grid, can_write_to_rc)| { - AutoWarpifySnippet { + let auto_wormhole_snippet = auto_wormhole_snippet.map(|(output_grid, can_write_to_rc)| { + AutoWormholeSnippet { description: (if !output_grid.is_empty() { "Run the following to automatically Wormhole in the future:" } else { @@ -125,15 +120,13 @@ impl WarpifySuccessBlock { }); Self { - source, - learn_more_link_mouse_states: Default::default(), spawning_command, - auto_warpify_snippet, + auto_wormhole_snippet, } } pub fn selected_text(&self) -> Option { - self.auto_warpify_snippet + self.auto_wormhole_snippet .as_ref() .and_then(|snippet| snippet.selected_text.read().clone()) } @@ -156,18 +149,12 @@ impl WarpifySuccessBlock { ) -> Box { let header_contents = render::build_header_row( "Session Wormholed", - Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()), + Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail()), theme, appearance, ) .with_margin_right(8.) .finish(); - let header_contents = Container::new( - Flex::row() - .with_children([header_contents, self.render_learn_more_link(appearance)]) - .finish(), - ) - .finish(); Container::new( Flex::row() @@ -182,45 +169,13 @@ impl WarpifySuccessBlock { .finish() } - fn render_learn_more_link(&self, appearance: &Appearance) -> Box { - let url = match self.source { - WarpificationSource::Ssh => SSH_DOCS_URL, - 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 Wormholed session. + pub fn on_wormholed_session_complete(&mut self, ctx: &mut ViewContext) { + self.clear_auto_wormhole_snippet(ctx); } - /// Fired when a block ends and we are not in a Warpified session. - pub fn on_warpified_session_complete(&mut self, ctx: &mut ViewContext) { - self.clear_auto_warpify_snippet(ctx); - } - - pub fn clear_auto_warpify_snippet(&mut self, ctx: &mut ViewContext) { - self.auto_warpify_snippet = None; + pub fn clear_auto_wormhole_snippet(&mut self, ctx: &mut ViewContext) { + self.auto_wormhole_snippet = None; ctx.notify(); } @@ -231,16 +186,16 @@ impl WarpifySuccessBlock { appearance: &Appearance, ) -> Option> { 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; } - 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( - &auto_warpify_snippet.output_grid, - if auto_warpify_snippet.can_write_to_rc { + &auto_wormhole_snippet.output_grid, + if auto_wormhole_snippet.can_write_to_rc { Some(&shell_language) } else { None @@ -251,7 +206,7 @@ impl WarpifySuccessBlock { code_snippet.to_string(), )); - ctx.dispatch_typed_action(WarpifySuccessBlockAction::ClearAutoWarpifySnippet); + ctx.dispatch_typed_action(WormholeSuccessBlockAction::ClearAutoWormholeSnippet); } })), Some(Box::new({ @@ -259,19 +214,19 @@ impl WarpifySuccessBlock { 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, ); 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 // 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! let output_grid = SelectableArea::new( - auto_warpify_snippet.selection_handle.clone(), + auto_wormhole_snippet.selection_handle.clone(), move |selection_args, _, _| { *selected_text.write() = selection_args.selection; }, @@ -285,7 +240,7 @@ impl WarpifySuccessBlock { .with_child( Container::new( Text::new( - auto_warpify_snippet.description.clone(), + auto_wormhole_snippet.description.clone(), appearance.monospace_font_family(), appearance.monospace_font_size(), ) @@ -307,15 +262,15 @@ impl WarpifySuccessBlock { } } -impl Entity for WarpifySuccessBlock { - type Event = WarpifySuccessBlockEvent; +impl Entity for WormholeSuccessBlock { + 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 { - "WarpifySuccessBlock" + "WormholeSuccessBlock" } fn render(&self, app: &AppContext) -> Box { @@ -340,19 +295,16 @@ impl View for WarpifySuccessBlock { } } -impl TypedActionView for WarpifySuccessBlock { - type Action = WarpifySuccessBlockAction; +impl TypedActionView for WormholeSuccessBlock { + type Action = WormholeSuccessBlockAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - WarpifySuccessBlockAction::OpenWarpifySettings => { - ctx.emit(WarpifySuccessBlockEvent::OpenWarpifySettings); + WormholeSuccessBlockAction::OpenWormholeSettings => { + ctx.emit(WormholeSuccessBlockEvent::OpenWormholeSettings); } - WarpifySuccessBlockAction::OpenUrl(url) => { - ctx.open_url(url); - } - WarpifySuccessBlockAction::ClearAutoWarpifySnippet => { - self.clear_auto_warpify_snippet(ctx); + WormholeSuccessBlockAction::ClearAutoWormholeSnippet => { + self.clear_auto_wormhole_snippet(ctx); } } } diff --git a/app/src/terminal/warpify/trigger_state.rs b/app/src/terminal/wormhole/trigger_state.rs similarity index 80% rename from app/src/terminal/warpify/trigger_state.rs rename to app/src/terminal/wormhole/trigger_state.rs index 150be7fa..d659dacd 100644 --- a/app/src/terminal/warpify/trigger_state.rs +++ b/app/src/terminal/wormhole/trigger_state.rs @@ -6,7 +6,7 @@ use galaxyui::r#async::SpawnedFutureHandle; use galaxyui::{EntityId, SingletonEntity as _, ViewContext, ViewHandle}; use parking_lot::FairMutex; -use super::success_block::WarpifySuccessBlock; +use super::success_block::WormholeSuccessBlock; use crate::terminal::model::block::BlockId; use crate::terminal::model::session::SessionId; use crate::terminal::model::terminal_model::SubshellInitializationInfo; @@ -40,8 +40,8 @@ impl SubshellSeparatorState { #[derive(Debug)] pub enum SshBlockState { - WarpifySuccess { - handle: ViewHandle, + WormholeSuccess { + handle: ViewHandle, }, } @@ -52,18 +52,18 @@ impl SshBlockState { pub fn get_block_view_id(&self) -> EntityId { 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, ctx: &mut ViewContext, ) -> Option { match self { - SshBlockState::WarpifySuccess { handle } => { + SshBlockState::WormholeSuccess { handle } => { 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)] -struct WarpifyTriggerState { +struct WormholeTriggerState { block_id: Option, - /// 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. - auto_warpify_abort_handle: Option, + auto_wormhole_abort_handle: Option, /// 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 /// TerminalEvent::BlockCompleted event can abort the banner. subshell_banner_abort_handle: Option, - /// The command which may trigger ssh Warpification + /// The command which may trigger ssh Wormholing pending_command: Option, - /// The Host which may trigger ssh Warpification - pending_warpify_ssh_host: Option, + /// The Host which may trigger ssh Wormholing + pending_wormhole_ssh_host: Option, /// Which, if any, SSH block is currently added to the blocklist. ssh_block_state: Option, - ssh_warpify_timeout_handle: Option, + ssh_wormhole_timeout_handle: Option, shell_type: Option, @@ -101,17 +101,17 @@ struct WarpifyTriggerState { } #[derive(Default)] -pub struct WarpifyState { +pub struct WormholeState { session_id: Option, - pending_state: Option, + pending_state: Option, /// Stores the metadata needed to render any separators above the first block of a subshell. subshell_separator_state: SubshellSeparatorState, /// A unique-enough ID that is used to validate that a timeout is still valid. timeout_id: u8, } -impl WarpifyState { +impl WormholeState { pub fn delete_state(&mut self) { self.pending_state.take(); } @@ -180,32 +180,32 @@ impl WarpifyState { .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); - 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 .pending_state .as_mut() - .and_then(|state| state.auto_warpify_abort_handle.take()) + .and_then(|state| state.auto_wormhole_abort_handle.take()) { 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); - 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(); if let Some(handle) = self .pending_state .as_mut() - .and_then(|state| state.ssh_warpify_timeout_handle.take()) + .and_then(|state| state.ssh_wormhole_timeout_handle.take()) { handle.abort(); }; @@ -231,31 +231,31 @@ impl WarpifyState { pub fn get_pending_ssh_host(&self) -> Option { self.pending_state .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 { self.pending_state .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 { self.pending_state .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) { 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) { let pending_state = self.pending_state.get_or_insert_with(Default::default); 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) { @@ -290,10 +290,10 @@ impl WarpifyState { } /// Called once whenever we get a local block completed, as opposed to a remote ssh block - /// and we have a Warpify Success block. - fn on_warpified_session_complete( + /// and we have a Wormhole Success block. + fn on_wormholed_session_complete( &mut self, - state: WarpifyTriggerState, + state: WormholeTriggerState, ctx: &mut ViewContext, ) -> Option { self.clear_ssh_block_state(); @@ -301,16 +301,16 @@ impl WarpifyState { let Some(block) = &state.ssh_block_state else { 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) { + pub fn on_wormhole_start(&mut self, active_session_id: Option) { 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. - pub fn get_completed_warpify_session_id( + pub fn get_completed_wormhole_session_id( &mut self, active_session_id: Option, ctx: &mut ViewContext, @@ -319,7 +319,7 @@ impl WarpifyState { return None; } 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 } diff --git a/app/src/terminal/writeable_pty/remote_server_controller.rs b/app/src/terminal/writeable_pty/remote_server_controller.rs index 008e167d..01cd619c 100644 --- a/app/src/terminal/writeable_pty/remote_server_controller.rs +++ b/app/src/terminal/writeable_pty/remote_server_controller.rs @@ -20,7 +20,7 @@ use crate::server::server_api::ServerApiProvider; use crate::settings::PrivacySettings; use crate::terminal::model::session::{IsSSHWrapperSession, SessionInfo}; 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}; /// Per-SSH-init state machine. Encoding the state as an enum makes invalid @@ -310,7 +310,7 @@ impl RemoteServerController { }); } Ok(false) => { - let install_mode = *WarpifySettings::as_ref(ctx) + let install_mode = *WormholeSettings::as_ref(ctx) .ssh_extension_install_mode .value(); match install_mode { diff --git a/app/src/test_util/settings.rs b/app/src/test_util/settings.rs index 9547ce8e..b786633a 100644 --- a/app/src/test_util/settings.rs +++ b/app/src/test_util/settings.rs @@ -46,7 +46,7 @@ pub fn initialize_settings_for_tests_with_mode( use crate::terminal::session_settings::SessionSettings; use crate::terminal::settings::TerminalSettings; 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::undo_close::UndoCloseSettings; use crate::user_config::WarpConfig; @@ -104,7 +104,7 @@ pub fn initialize_settings_for_tests_with_mode( ScrollSettings::register(app); SelectionSettings::register(app); app.update(|ctx| { - WarpifySettings::register(ctx); + WormholeSettings::register(ctx); }); SessionSettings::register(app); SshSettings::register(app); diff --git a/app/src/uri/docker.rs b/app/src/uri/docker.rs index 9e2281a6..1f743b83 100644 --- a/app/src/uri/docker.rs +++ b/app/src/uri/docker.rs @@ -28,7 +28,7 @@ impl TryFrom for DockerContainerId { )) } else if input.chars().any(|c| !c.is_ascii_hexdigit()) { Err(anyhow!( - "Could not find valid docker container id to open warpified shell" + "Could not find valid docker container id to open wormholed shell" )) } else { Ok(DockerContainerId(input)) @@ -44,7 +44,7 @@ impl Display for DockerContainerId { /// Given a Url with query parameters in the correct format, dispatch an action to create a new tab /// (or open a new window if there is no window), then run a command to open a subshell into the -/// specified Docker container, and then warpify that new subshell. +/// specified Docker container, and then wormhole that new subshell. pub fn open_docker_container(url: &Url, ctx: &mut AppContext) -> Result<()> { let query_params: HashMap = url .query_pairs() @@ -109,7 +109,7 @@ pub fn open_docker_container(url: &Url, ctx: &mut AppContext) -> Result<()> { ); send_telemetry_from_app_ctx!( - TelemetryEvent::OpenAndWarpifyDockerSubshell { shell_type }, + TelemetryEvent::OpenAndWormholeDockerSubshell { shell_type }, ctx ); diff --git a/app/src/util/bindings.rs b/app/src/util/bindings.rs index f81a17aa..69a424cc 100644 --- a/app/src/util/bindings.rs +++ b/app/src/util/bindings.rs @@ -198,7 +198,7 @@ lazy_static! { /// compliant. We weren't always diligent about avoiding bindings that could conflict with /// character codes, unfortunately some bindings on Mac currently conflict with the PTY. We have /// this allowlist to special case these legacy actions for the purposes of binding validation. - pub static ref MAC_PTY_NON_COMPLIANT_ACTIONS: HashSet<&'static str> = HashSet::from_iter(["terminal:warpify_subshell", "terminal:open_block_list_context_menu_via_keybinding"]); + pub static ref MAC_PTY_NON_COMPLIANT_ACTIONS: HashSet<&'static str> = HashSet::from_iter(["terminal:wormhole_subshell", "terminal:open_block_list_context_menu_via_keybinding"]); /// Set of actions on Windows that should be considered valid bindings even though they aren't /// PTY compliant. Windows users expect pasting to work using both `ctrl-v` and `ctrl-shift-v`, diff --git a/app/src/util/file/external_editor/linux.rs b/app/src/util/file/external_editor/linux.rs index aaf30cac..d1880d1b 100644 --- a/app/src/util/file/external_editor/linux.rs +++ b/app/src/util/file/external_editor/linux.rs @@ -9,6 +9,7 @@ use galaxy_util::path::LineAndColumnArg; use galaxyui::AppContext; use super::Editor; +use super::Editor::*; static INSTALLED_EDITOR_METADATA: OnceLock> = OnceLock::new(); diff --git a/app/src/workspace/mod.rs b/app/src/workspace/mod.rs index 29523c0b..60e3b0f7 100644 --- a/app/src/workspace/mod.rs +++ b/app/src/workspace/mod.rs @@ -1309,7 +1309,9 @@ pub fn init(app: &mut AppContext) { }, ) .with_enabled(|| FeatureFlag::AgentMode.is_enabled()) - .with_context_predicate(id!("Workspace") & id!(flags::IS_ANY_AI_ENABLED)) + .with_context_predicate( + id!("Workspace") & id!(flags::IS_ANY_AI_ENABLED) & !id!("CodeEditorView"), + ) .with_group(bindings::BindingGroup::WarpAi.as_str()) .with_custom_action(CustomAction::NewAgentModePane), EditableBinding::new( @@ -1318,7 +1320,9 @@ pub fn init(app: &mut AppContext) { WorkspaceAction::ToggleAIAssistant, ) .with_enabled(|| !FeatureFlag::AgentMode.is_enabled()) - .with_context_predicate(id!("Workspace") & id!(flags::IS_ANY_AI_ENABLED)) + .with_context_predicate( + id!("Workspace") & id!(flags::IS_ANY_AI_ENABLED) & !id!("CodeEditorView"), + ) .with_group(bindings::BindingGroup::WarpAi.as_str()) // We use the same custom action as AM so that we don't have // two mac menu items for AM vs Warp AI since they are mutually exclusive. @@ -1583,10 +1587,10 @@ fn add_open_setting_pages_as_editable_binding(app: &mut AppContext) { .with_group(bindings::BindingGroup::Settings.as_str()) .with_context_predicate(id!("Workspace")), EditableBinding::new( - "workspace:show_settings_warpify_page", + "workspace:show_settings_wormhole_page", BindingDescription::new("Open Settings: Wormhole") .with_custom_description(bindings::MAC_MENUS_CONTEXT, "Configure Wormhole..."), - WorkspaceAction::ShowSettingsPage(SettingsSection::Warpify), + WorkspaceAction::ShowSettingsPage(SettingsSection::Wormhole), ) .with_group(bindings::BindingGroup::Settings.as_str()) .with_context_predicate(id!("Workspace")), diff --git a/app/src/workspace/view.rs b/app/src/workspace/view.rs index a4a6772a..f67ec8f1 100644 --- a/app/src/workspace/view.rs +++ b/app/src/workspace/view.rs @@ -418,7 +418,7 @@ use crate::terminal::view::{ OnboardingIntention, OnboardingVersion, SyncEvent, SyncInputType, TerminalAction, NOTIFICATIONS_TROUBLESHOOT_URL, }; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::wormhole::settings::WormholeSettings; use crate::terminal::{self, BlockListSettings, SizeInfo, TerminalModel, TerminalView}; use crate::themes::theme::{AnsiColorIdentifier, RespectSystemTheme, ThemeKind}; use crate::themes::theme_chooser::{ThemeChooser, ThemeChooserEvent, ThemeChooserMode}; @@ -17039,7 +17039,7 @@ impl Workspace { } /// Insert the given command that should open a subshell. And set a flag that we should - /// automatically bootstrap AKA "warpify" that subshell if we support it. No-op if there is + /// automatically bootstrap AKA "wormhole" that subshell if we support it. No-op if there is /// no active terminal session. pub fn insert_subshell_command_and_bootstrap_if_supported( &mut self, @@ -17140,7 +17140,7 @@ impl Workspace { // Check whether this remote session has an active remote server // connection (or is in the process of connecting). This is only - // true for Auto SSH Warpification (mode 1) sessions where + // true for Auto SSH Wormholing (mode 1) sessions where // `connect_session` was called at `InitShell` time. let has_remote_server = is_remote && FeatureFlag::SshRemoteServer.is_enabled() @@ -22566,7 +22566,7 @@ impl Workspace { let reporting_setings = AltScreenReporting::as_ref(app); let general_settings = GeneralSettings::as_ref(app); let theme_settings = ThemeSettings::as_ref(app); - let warpify_settings = WarpifySettings::as_ref(app); + let wormhole_settings = WormholeSettings::as_ref(app); let terminal_settings = TerminalSettings::as_ref(app); let window_settings = WindowSettings::as_ref(app); let pane_settings = PaneSettings::as_ref(app); @@ -22618,8 +22618,8 @@ impl Workspace { .set .insert(flags::SSH_REUSE_CONTROL_MASTER_CONTEXT_FLAG); } - if *warpify_settings.enable_ssh_warpification.value() { - context.set.insert(flags::SSH_WARPIFICATION_CONTEXT_FLAG); + if *wormhole_settings.enable_ssh_wormholing.value() { + context.set.insert(flags::SSH_WORMHOLING_CONTEXT_FLAG); } if keys_settings.extra_meta_keys.left_alt { diff --git a/crates/computer_use/src/linux/x11/mod.rs b/crates/computer_use/src/linux/x11/mod.rs index fec45fb6..9d636c94 100644 --- a/crates/computer_use/src/linux/x11/mod.rs +++ b/crates/computer_use/src/linux/x11/mod.rs @@ -5,7 +5,6 @@ mod mouse; mod screenshot; use async_trait::async_trait; -use galaxyui::r#async::Timer; use galaxyui_core::r#async::Timer; use pathfinder_geometry::vector::Vector2I; use x11rb::connection::Connection; diff --git a/crates/galaxy_features/src/lib.rs b/crates/galaxy_features/src/lib.rs index 25d8a997..8e6b928e 100644 --- a/crates/galaxy_features/src/lib.rs +++ b/crates/galaxy_features/src/lib.rs @@ -814,8 +814,8 @@ pub enum FeatureFlag { /// Enables Custom Inference endpoints for enterprise users. CustomInferenceEndpointsEnterprise, - /// Replaces the in-block warpification banner with a warpify footer. - WarpifyFooter, + /// Replaces the in-block wormholing banner with a wormhole footer. + WormholeFooter, /// Enables conversation retrieval via the CLI (oz run conversation get, oz run get --conversation). ConversationApi, diff --git a/crates/galaxy_graphql_schema/api/schema.graphql b/crates/galaxy_graphql_schema/api/schema.graphql index 076192ab..c059457f 100644 --- a/crates/galaxy_graphql_schema/api/schema.graphql +++ b/crates/galaxy_graphql_schema/api/schema.graphql @@ -1332,8 +1332,8 @@ enum Experiment { SSH_REMOTE_SERVER_EXPERIMENT SUGGESTED_CODE_DIFFS_CONTROL SUGGESTED_CODE_DIFFS_EXPERIMENT - TMUX_SSH_WARPIFICATION_CONTROL - TMUX_SSH_WARPIFICATION_EXPERIMENT + TMUX_SSH_WORMHOLING_CONTROL + TMUX_SSH_WORMHOLING_EXPERIMENT UPGRADE_TO_PRO_MODAL_CONTROL UPGRADE_TO_PRO_MODAL_EXPERIMENT_NO_PROMO UPGRADE_TO_PRO_MODAL_EXPERIMENT_WITH_PROMO diff --git a/crates/galaxy_terminal/src/shell/mod.rs b/crates/galaxy_terminal/src/shell/mod.rs index 8eed344a..813c7584 100644 --- a/crates/galaxy_terminal/src/shell/mod.rs +++ b/crates/galaxy_terminal/src/shell/mod.rs @@ -335,7 +335,7 @@ impl ShellType { /// /// The returned [`TypedPathBuf`]s are encoded for the target OS rather than the /// host OS, because the resulting path is rendered into a shell command executed - /// on the target (e.g. via SSH or Auto-Warpify). A plain `PathBuf` would pick the + /// on the target (e.g. via SSH or Auto-Wormhole). A plain `PathBuf` would pick the /// host's separator and produce strings like `~\.zshrc` on a Windows host when /// targeting a Unix shell, which the remote shell cannot resolve. Encoding for /// the target OS lets `TypedPathBuf` enforce the correct separator. diff --git a/crates/galaxy_terminal/src/shell/mod_tests.rs b/crates/galaxy_terminal/src/shell/mod_tests.rs index da3710fe..33cb9f6a 100644 --- a/crates/galaxy_terminal/src/shell/mod_tests.rs +++ b/crates/galaxy_terminal/src/shell/mod_tests.rs @@ -274,7 +274,7 @@ fn test_should_add_command_to_history() { /// Regression test for https://github.com/warpdotdev/warp/issues/10474. /// /// `rc_file_paths` is rendered into a shell command that runs on the *target* -/// (e.g. an SSH remote, or a subshell during Auto-Warpify). The path separator +/// (e.g. an SSH remote, or a subshell during Auto-Wormhole). The path separator /// must therefore depend on the target OS, not the host that built Warp. /// Previously this used `PathBuf::join`, which uses the host's separator and /// produced strings like `~\.zshrc` on a Windows host targeting a Unix shell — @@ -308,7 +308,7 @@ fn test_rc_file_paths_use_target_os_separator() { ); } - // On Windows the only Auto-Warpify-supported shell is PowerShell; Unix + // On Windows the only Auto-Wormhole-supported shell is PowerShell; Unix // shells deliberately return no rc paths. // The leading separator follows target OS; the literal interior slashes // are left as-is (PowerShell accepts forward slashes on Windows). diff --git a/crates/graphql/src/api/experiment.rs b/crates/graphql/src/api/experiment.rs index 382206f8..7cce225b 100644 --- a/crates/graphql/src/api/experiment.rs +++ b/crates/graphql/src/api/experiment.rs @@ -51,8 +51,8 @@ pub enum Experiment { SplitCreditCostExperiment, WindowsLaunchControl, WindowsLaunchExperiment, - TmuxSshWarpificationControl, - TmuxSshWarpificationExperiment, + TmuxSshWormholingControl, + TmuxSshWormholingExperiment, UsageBasedPricingControl, UsageBasedPricingExperiment, CodebaseContextExperiment, diff --git a/crates/integration/src/test/remote_server.rs b/crates/integration/src/test/remote_server.rs index e4ba30f5..00f9f450 100644 --- a/crates/integration/src/test/remote_server.rs +++ b/crates/integration/src/test/remote_server.rs @@ -24,7 +24,7 @@ use warp::integration_testing::terminal::{ wait_until_bootstrapped_single_pane_for_tab, }; use warp::terminal::shell::ShellType; -use warp::terminal::warpify::settings::{SshExtensionInstallMode, SshExtensionInstallModeSetting}; +use warp::terminal::wormhole::settings::{SshExtensionInstallMode, SshExtensionInstallModeSetting}; use super::{new_builder, Builder}; diff --git a/crates/integration/src/test/subshell.rs b/crates/integration/src/test/subshell.rs index 80b23070..63d224ce 100644 --- a/crates/integration/src/test/subshell.rs +++ b/crates/integration/src/test/subshell.rs @@ -17,7 +17,7 @@ use warp::integration_testing::terminal::wait_until_bootstrapped_single_pane_for use warp::integration_testing::view_getters::single_input_view_for_tab; use warp::root_view::SubshellCommandArg; use warp::terminal::shell::ShellType; -use warp::terminal::warpify::settings::AddedSubshellCommands; +use warp::terminal::wormhole::settings::AddedSubshellCommands; use super::{new_builder, Builder}; use crate::util::skip_if_powershell_core_2303; @@ -83,7 +83,7 @@ generate_can_bootstrap_remote_subshell_for_shell!(test_can_bootstrap_remote_bash // generate_can_bootstrap_remote_subshell_for_shell!(test_can_bootstrap_remote_fish_subshell, "fish"); // Test the flow of creating a new window and running a command that should create a subshell and -// automaticall bootstrapping AKA "warpifying" that subshell. +// automaticall bootstrapping AKA "wormholing" that subshell. pub fn test_can_auto_bootstrap() -> Builder { const SUBSHELL_COMMAND: &str = "zsh"; diff --git a/crates/jsonrpc/src/service.rs b/crates/jsonrpc/src/service.rs index 73e33ce4..ea36936c 100644 --- a/crates/jsonrpc/src/service.rs +++ b/crates/jsonrpc/src/service.rs @@ -182,21 +182,41 @@ impl JsonRpcService { request_error_code: i64, ) -> Result<()> { if let Ok(request) = serde_json::from_str::(message) { - let should_ack = matches!( - request.method, + let params = if let Some(params) = request.params { + match serde_json::from_str(params.get()) { + Ok(value) => value, + Err(e) => { + log::warn!("Failed to parse params for {} request: {e}", request.method); + Value::Null + } + } + } else { + Value::Null + }; + + let acknowledged_result = match request.method { + "workspace/configuration" => { + let item_count = params + .get("items") + .and_then(Value::as_array) + .map_or(0, Vec::len); + Some(Value::Array(vec![serde_json::json!({}); item_count])) + } "window/workDoneProgress/create" - | "client/registerCapability" - | "client/unregisterCapability" - ); + | "client/registerCapability" + | "client/unregisterCapability" => Some(Value::Null), + _ => None, + }; + let should_ack = acknowledged_result.is_some(); // Handle specific server -> client requests that we can safely acknowledge. // Some LSP servers crash if we return an error. - let response = if should_ack { + let response = if let Some(result) = acknowledged_result { log::debug!("Acknowledging {} request", request.method); serde_json::json!({ "jsonrpc": JSON_RPC_VERSION, "id": request.id, - "result": null + "result": result }) } else { // For other requests, return an error @@ -223,21 +243,6 @@ impl JsonRpcService { .clone(); if let Some(handler) = handler { - let params = if let Some(params) = request.params { - match serde_json::from_str(params.get()) { - Ok(value) => value, - Err(e) => { - log::warn!( - "Failed to parse params for {} request: {e}", - request.method - ); - Value::Null - } - } - } else { - Value::Null - }; - if let Err(e) = handler(request.method.to_string(), params, request.id) { log::warn!("Server request handler error for {}: {e}", request.method); } @@ -357,6 +362,20 @@ impl JsonRpcService { Ok(()) } + /// Sends a notification and waits until it has been written to the transport. + /// + /// Use this when a later request depends on the server having observed the + /// notification first, such as document synchronization before completion. + pub async fn send_notification_and_wait(&self, method: String, params: Value) -> Result<()> { + let notification = Notification { + jsonrpc: JSON_RPC_VERSION, + method, + params, + }; + let content = serde_json::to_string(¬ification)?; + self.transport.write(&content).await + } + pub async fn shutdown(&self, timeout: std::time::Duration) -> Result<()> { self.transport.shutdown(timeout).await } diff --git a/crates/lsp/src/config.rs b/crates/lsp/src/config.rs index 960abd1f..369b016b 100644 --- a/crates/lsp/src/config.rs +++ b/crates/lsp/src/config.rs @@ -35,6 +35,7 @@ pub enum LanguageId { JavaScriptReact, C, Cpp, + Toml, } impl LanguageId { @@ -55,6 +56,7 @@ impl LanguageId { // compile_commands.json is present, clangd will use the correct language // regardless of the languageId we send. "h" | "H" | "hh" | "hpp" | "hxx" => Some(Self::Cpp), + "toml" => Some(Self::Toml), _ => None, } } @@ -72,6 +74,7 @@ impl LanguageId { LanguageId::JavaScriptReact => "javascriptreact", LanguageId::C => "c", LanguageId::Cpp => "cpp", + LanguageId::Toml => "toml", } } @@ -86,6 +89,7 @@ impl LanguageId { | LanguageId::JavaScript | LanguageId::JavaScriptReact => LSPServerType::TypeScriptLanguageServer, LanguageId::C | LanguageId::Cpp => LSPServerType::Clangd, + LanguageId::Toml => LSPServerType::Tombi, } } } @@ -105,6 +109,8 @@ pub struct LspServerConfig { client: Arc, /// Optional path relative to the LSP log namespace for server stderr output. log_relative_path: Option, + /// Notifications sent immediately after the server completes LSP initialization. + post_initialize_notifications: Vec<(String, serde_json::Value)>, } impl fmt::Debug for LspServerConfig { @@ -115,6 +121,10 @@ impl fmt::Debug for LspServerConfig { .field("path_env_var", &self.path_env_var) .field("client_name", &self.client_name) .field("log_relative_path", &self.log_relative_path) + .field( + "post_initialize_notifications", + &self.post_initialize_notifications, + ) .finish() } } @@ -134,6 +144,7 @@ impl LspServerConfig { client_name, client, log_relative_path: None, + post_initialize_notifications: Vec::new(), } } @@ -147,6 +158,21 @@ impl LspServerConfig { self.log_relative_path.as_ref() } + /// Adds a custom notification to send after the language server initializes. + pub fn with_post_initialize_notification( + mut self, + method: impl Into, + params: serde_json::Value, + ) -> Self { + self.post_initialize_notifications + .push((method.into(), params)); + self + } + + pub(crate) fn post_initialize_notifications(&self) -> &[(String, serde_json::Value)] { + &self.post_initialize_notifications + } + /// Returns the initial workspace path. pub fn initial_workspace(&self) -> &Path { &self.initial_workspace diff --git a/crates/lsp/src/install.rs b/crates/lsp/src/install.rs index 841a3bd0..fe4d620d 100644 --- a/crates/lsp/src/install.rs +++ b/crates/lsp/src/install.rs @@ -145,6 +145,8 @@ where pub enum AssetKind { /// A gzip-compressed file (e.g., `rust-analyzer-aarch64-apple-darwin.gz`) Gz, + /// A gzip-compressed tar archive (e.g., `tombi-cli-1.4.1-aarch64-apple-darwin.tar.gz`) + TarGz, /// A zip archive (e.g., `rust-analyzer-x86_64-pc-windows-msvc.zip`) Zip, } @@ -153,7 +155,9 @@ pub enum AssetKind { impl AssetKind { /// Determines the asset kind from a file name based on its extension. pub fn from_filename(filename: &str) -> Option { - if filename.ends_with(".gz") && !filename.ends_with(".tar.gz") { + if filename.ends_with(".tar.gz") { + Some(AssetKind::TarGz) + } else if filename.ends_with(".gz") { Some(AssetKind::Gz) } else if filename.ends_with(".zip") { Some(AssetKind::Zip) @@ -263,6 +267,9 @@ pub async fn install_from_github( let binary_path = install_dir.join(&binary_name); node_runtime::extract_gz(&bytes, &binary_path).await?; } + AssetKind::TarGz => { + node_runtime::extract_tar_gz(&bytes, &install_dir)?; + } AssetKind::Zip => { if binary_finder.is_some() { // Extract the full archive when using a custom binary finder diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 9b47e04a..f50f963a 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -78,6 +78,7 @@ pub async fn spawn_lsp_service( logger: Option, ) -> Result { let workspace_root = config.initial_workspace().to_path_buf(); + let post_initialize_notifications = config.post_initialize_notifications().to_vec(); let resolved = match config.command_and_params().await { Ok(resolved) => resolved, @@ -119,6 +120,12 @@ pub async fn spawn_lsp_service( return Err(e); } + for (method, params) in post_initialize_notifications { + service + .send_custom_notification_and_wait(method, params) + .await?; + } + Ok(LspServiceInitializationResult { service, channel: notify_rx, diff --git a/crates/lsp/src/model.rs b/crates/lsp/src/model.rs index 93ae06a0..aa4860c7 100644 --- a/crates/lsp/src/model.rs +++ b/crates/lsp/src/model.rs @@ -16,7 +16,8 @@ use jsonrpc::ServerNotificationEvent; use lsp_types::notification::{self, Notification}; use lsp_types::{ CompletionItem, CompletionTriggerKind, FormattingOptions, NumberOrString, ProgressParams, - ProgressParamsValue, PublishDiagnosticsParams, Range as LspRange, WorkDoneProgress, + ProgressParamsValue, PublishDiagnosticsParams, Range as LspRange, TextDocumentSyncCapability, + TextDocumentSyncKind, WorkDoneProgress, }; #[cfg(not(target_arch = "wasm32"))] use simple_logger::manager::LogManager; @@ -531,6 +532,41 @@ impl LspServerModel { }) } + /// Whether the server requires each document change to contain the full document. + pub fn requires_full_document_sync(&self) -> bool { + let Ok(service) = self.service() else { + return false; + }; + let Some(sync) = service + .server_capabilities() + .as_ref() + .and_then(|capabilities| capabilities.text_document_sync.as_ref()) + else { + return false; + }; + + match sync { + TextDocumentSyncCapability::Kind(kind) => *kind == TextDocumentSyncKind::FULL, + TextDocumentSyncCapability::Options(options) => { + options.change == Some(TextDocumentSyncKind::FULL) + } + } + } + + /// Whether the server can resolve additional details for completion items. + pub fn supports_completion_resolve(&self) -> bool { + let Ok(service) = self.service() else { + return false; + }; + + service + .server_capabilities() + .as_ref() + .and_then(|capabilities| capabilities.completion_provider.as_ref()) + .and_then(|options| options.resolve_provider) + .unwrap_or(false) + } + pub fn did_change_watched_files(&self, events: Vec) -> Result<()> { let service = self.service()?; service.workspace_watched_files_changed(events) diff --git a/crates/lsp/src/servers/mod.rs b/crates/lsp/src/servers/mod.rs index 32d83676..5843321d 100644 --- a/crates/lsp/src/servers/mod.rs +++ b/crates/lsp/src/servers/mod.rs @@ -2,4 +2,5 @@ pub mod clangd; pub mod go; pub mod pyright; pub mod rust; +pub mod tombi; pub mod typescript_language_server; diff --git a/crates/lsp/src/servers/tombi.rs b/crates/lsp/src/servers/tombi.rs new file mode 100644 index 00000000..c9c91362 --- /dev/null +++ b/crates/lsp/src/servers/tombi.rs @@ -0,0 +1,212 @@ +use std::path::Path; +#[cfg(feature = "local_fs")] +use std::path::PathBuf; +use std::sync::Arc; + +use async_trait::async_trait; + +#[cfg(feature = "local_fs")] +use crate::install::{ + fetch_latest_metadata_from_github_dynamic_asset, install_from_github, AssetKind, +}; +use crate::language_server_candidate::{LanguageServerCandidate, LanguageServerMetadata}; +use crate::CommandBuilder; + +#[cfg(feature = "local_fs")] +const SERVER_NAME: &str = "tombi"; + +#[cfg_attr(not(feature = "local_fs"), allow(dead_code))] +pub struct TombiCandidate { + client: Arc, +} + +impl TombiCandidate { + pub fn new(client: Arc) -> Self { + Self { client } + } + + #[cfg(feature = "local_fs")] + pub async fn find_installed_binary_in_data_dir() -> Option { + let install_root = galaxy_core::paths::data_dir().join(SERVER_NAME); + let entries = std::fs::read_dir(install_root).ok()?; + + for entry in entries.flatten() { + let version_dir = entry.path(); + if !version_dir.is_dir() { + continue; + } + + let Some(binary_path) = find_binary(&version_dir) else { + continue; + }; + if binary_is_working(&binary_path).await { + return Some(binary_path); + } + } + + None + } +} + +#[cfg(feature = "local_fs")] +fn asset_target() -> anyhow::Result<&'static str> { + match (std::env::consts::OS, std::env::consts::ARCH) { + ("macos", "aarch64") => Ok("aarch64-apple-darwin"), + ("macos", "x86_64") => Ok("x86_64-apple-darwin"), + ("linux", "aarch64") => Ok("aarch64-unknown-linux-musl"), + ("linux", "x86_64") => Ok("x86_64-unknown-linux-musl"), + ("windows", "aarch64") => Ok("aarch64-pc-windows-msvc"), + ("windows", "x86_64") => Ok("x86_64-pc-windows-msvc"), + (os, arch) => anyhow::bail!("Unsupported platform for Tombi: {os}/{arch}"), + } +} + +#[cfg(feature = "local_fs")] +fn asset_name(tag: &str) -> anyhow::Result { + let version = tag.strip_prefix('v').unwrap_or(tag); + let extension = if cfg!(windows) { "zip" } else { "tar.gz" }; + Ok(format!( + "tombi-cli-{version}-{}.{extension}", + asset_target()? + )) +} + +#[cfg(feature = "local_fs")] +fn find_binary(install_dir: &Path) -> Option { + let binary_name = if cfg!(windows) { + "tombi.exe" + } else { + SERVER_NAME + }; + let direct_path = install_dir.join(binary_name); + if direct_path.is_file() { + return Some(direct_path); + } + + std::fs::read_dir(install_dir) + .ok()? + .flatten() + .filter_map(|entry| { + let path = entry.path(); + path.is_dir().then(|| path.join(binary_name)) + }) + .find(|path| path.is_file()) +} + +#[cfg(feature = "local_fs")] +async fn binary_is_working(binary_path: &Path) -> bool { + if !binary_path.is_file() { + return false; + } + + let mut command = command::r#async::Command::new(binary_path); + command.arg("--version"); + command + .output() + .await + .map(|output| output.status.success()) + .unwrap_or(false) +} + +#[async_trait] +#[cfg(feature = "local_fs")] +impl LanguageServerCandidate for TombiCandidate { + async fn should_suggest_for_repo(&self, path: &Path, _executor: &CommandBuilder) -> bool { + let Ok(entries) = std::fs::read_dir(path) else { + return false; + }; + + entries.flatten().any(|entry| { + let file_path = entry.path(); + file_path.is_file() + && file_path + .extension() + .and_then(|extension| extension.to_str()) + == Some("toml") + }) + } + + async fn is_installed_in_data_dir(&self, _executor: &CommandBuilder) -> bool { + Self::find_installed_binary_in_data_dir().await.is_some() + } + + async fn is_installed_on_path(&self, executor: &CommandBuilder) -> bool { + executor + .command(SERVER_NAME) + .arg("--version") + .output() + .await + .map(|output| output.status.success()) + .unwrap_or(false) + } + + async fn install( + &self, + metadata: LanguageServerMetadata, + _executor: &CommandBuilder, + ) -> anyhow::Result<()> { + let asset_name = asset_name(&metadata.version)?; + let asset_kind = AssetKind::from_filename(&asset_name) + .ok_or_else(|| anyhow::anyhow!("Unsupported archive format for asset: {asset_name}"))?; + let binary_path = install_from_github( + &self.client, + &metadata, + SERVER_NAME, + asset_kind, + Some(find_binary), + ) + .await?; + + if !binary_is_working(&binary_path).await { + anyhow::bail!( + "Installed Tombi binary at {} failed its version check", + binary_path.display() + ); + } + + Ok(()) + } + + async fn fetch_latest_server_metadata(&self) -> anyhow::Result { + let target = asset_target()?; + let extension = if cfg!(windows) { "zip" } else { "tar.gz" }; + fetch_latest_metadata_from_github_dynamic_asset( + &self.client, + "tombi-toml", + "tombi", + |tag| { + let version = tag.strip_prefix('v').unwrap_or(tag); + format!("tombi-cli-{version}-{target}.{extension}") + }, + ) + .await + } +} + +#[async_trait] +#[cfg(not(feature = "local_fs"))] +impl LanguageServerCandidate for TombiCandidate { + async fn should_suggest_for_repo(&self, _path: &Path, _executor: &CommandBuilder) -> bool { + false + } + + async fn is_installed_in_data_dir(&self, _executor: &CommandBuilder) -> bool { + false + } + + async fn is_installed_on_path(&self, _executor: &CommandBuilder) -> bool { + false + } + + async fn install( + &self, + _metadata: LanguageServerMetadata, + _executor: &CommandBuilder, + ) -> anyhow::Result<()> { + anyhow::bail!("Tombi installation is unavailable without local filesystem support") + } + + async fn fetch_latest_server_metadata(&self) -> anyhow::Result { + anyhow::bail!("Tombi metadata is unavailable without local filesystem support") + } +} diff --git a/crates/lsp/src/service.rs b/crates/lsp/src/service.rs index 2b91b26c..864aeb58 100644 --- a/crates/lsp/src/service.rs +++ b/crates/lsp/src/service.rs @@ -170,7 +170,8 @@ impl LspService { let response = self.send_request::(params).await?; self.server_capabilities = Some(response.capabilities); - self.send_notification::(InitializedParams {})?; + self.send_notification_and_wait::(InitializedParams {}) + .await?; self.subscribe::().await; self.subscribe::().await; @@ -297,6 +298,23 @@ impl LspService { .send_notification(N::METHOD.to_string(), params) } + async fn send_notification_and_wait(&self, params: N::Params) -> Result<()> { + let params = serde_json::to_value(params)?; + self.jsonrpc_service + .send_notification_and_wait(N::METHOD.to_string(), params) + .await + } + + pub(crate) async fn send_custom_notification_and_wait( + &self, + method: impl Into, + params: Value, + ) -> Result<()> { + self.jsonrpc_service + .send_notification_and_wait(method.into(), params) + .await + } + async fn send_request(&self, params: R::Params) -> Result { let params = serde_json::to_value(params)?; let request = self.send_request_internal(R::METHOD.to_string(), params); @@ -538,7 +556,8 @@ impl<'a> TextDocumentService<'a> { }; self.service - .send_notification::(did_open_params) + .send_notification_and_wait::(did_open_params) + .await } pub async fn did_close(&self, path: &Path) -> Result<()> { @@ -560,7 +579,8 @@ impl<'a> TextDocumentService<'a> { }; self.service - .send_notification::(did_close_params) + .send_notification_and_wait::(did_close_params) + .await } pub async fn did_change( @@ -592,7 +612,8 @@ impl<'a> TextDocumentService<'a> { }; self.service - .send_notification::(did_change_params) + .send_notification_and_wait::(did_change_params) + .await } pub async fn definition( diff --git a/crates/lsp/src/supported_servers.rs b/crates/lsp/src/supported_servers.rs index a93a301c..70e3963b 100644 --- a/crates/lsp/src/supported_servers.rs +++ b/crates/lsp/src/supported_servers.rs @@ -15,6 +15,7 @@ use crate::servers::clangd::ClangdCandidate; use crate::servers::go::GoPlsCandidate; use crate::servers::pyright::PyrightCandidate; use crate::servers::rust::RustAnalyzerCandidate; +use crate::servers::tombi::TombiCandidate; use crate::servers::typescript_language_server::TypeScriptLanguageServerCandidate; #[cfg(not(target_arch = "wasm32"))] use crate::CommandBuilder; @@ -45,6 +46,7 @@ pub enum LSPServerType { Pyright, TypeScriptLanguageServer, Clangd, + Tombi, } /// Provides server-specific configuration for each LSP server type. @@ -112,6 +114,12 @@ impl LSPServerType { binary_path: path, prepend_args: vec![], }), + LSPServerType::Tombi => TombiCandidate::find_installed_binary_in_data_dir() + .await + .map(|path| CustomBinaryConfig { + binary_path: path, + prepend_args: vec![], + }), } } @@ -135,6 +143,7 @@ impl LSPServerType { LSPServerType::Pyright => "pyright-langserver", LSPServerType::TypeScriptLanguageServer => "typescript-language-server", LSPServerType::Clangd => "clangd", + LSPServerType::Tombi => "tombi", } } @@ -144,6 +153,7 @@ impl LSPServerType { match self { LSPServerType::RustAnalyzer | LSPServerType::GoPls | LSPServerType::Clangd => vec![], LSPServerType::Pyright | LSPServerType::TypeScriptLanguageServer => vec!["--stdio"], + LSPServerType::Tombi => vec!["lsp", "--offline"], } } @@ -157,6 +167,7 @@ impl LSPServerType { LSPServerType::Pyright => vec!["--stdio"], LSPServerType::TypeScriptLanguageServer => vec!["--stdio"], LSPServerType::Clangd => vec![], + LSPServerType::Tombi => vec!["lsp", "--offline"], } } @@ -175,6 +186,7 @@ impl LSPServerType { ] } LSPServerType::Clangd => vec![LanguageId::C, LanguageId::Cpp], + LSPServerType::Tombi => vec![LanguageId::Toml], } } @@ -219,7 +231,11 @@ impl LSPServerType { ) .await } - _ => None, + LSPServerType::Tombi => None, + LSPServerType::RustAnalyzer + | LSPServerType::GoPls + | LSPServerType::Pyright + | LSPServerType::Clangd => None, } } @@ -232,6 +248,7 @@ impl LSPServerType { Box::new(TypeScriptLanguageServerCandidate::new(client)) } LSPServerType::Clangd => Box::new(ClangdCandidate::new(client)), + LSPServerType::Tombi => Box::new(TombiCandidate::new(client)), } } diff --git a/crates/remote_server/src/install_remote_server.sh b/crates/remote_server/src/install_remote_server.sh index 625552cd..bd9a40c9 100644 --- a/crates/remote_server/src/install_remote_server.sh +++ b/crates/remote_server/src/install_remote_server.sh @@ -93,10 +93,17 @@ tar -xzf "$tmpdir/oz.tar.gz" -C "$tmpdir" # The executable and its resources are siblings in the artifact. Exclude the # resources tree from the search: bundled skills may ship companion files # whose names also start with `oz`. -bin=$(find "$tmpdir" -type f -name 'oz*' ! -name '*.tar.gz' ! -path '*/resources/*' | head -n1) +bin=$(find "$tmpdir" -type f -name '{binary_name}*' ! -name '*.tar.gz' ! -path '*/resources/*' | head -n1) +# Continue accepting legacy archives while existing release infrastructure is +# migrated to Galaxy-named helper artifacts. +if [ -z "$bin" ]; then + bin=$(find "$tmpdir" -type f -name 'oz*' ! -name '*.tar.gz' ! -path '*/resources/*' | head -n1) +fi if [ -z "$bin" ]; then echo "no binary found in tarball" >&2; exit 1; fi chmod +x "$bin" +version_marker="$(dirname "$bin")/wormhole-version" + # Install the resources tree at the global, version-independent location # the daemon reads. `$tmpdir` lives inside `$install_dir`, so the `mv` is a # same-filesystem rename. Installed before the binary so an interrupted @@ -110,3 +117,8 @@ if [ -d "$resources" ]; then fi mv "$bin" "$install_dir/{binary_name}{version_suffix}" +if [ -f "$version_marker" ]; then + mv "$version_marker" "$install_dir/{binary_name}{version_suffix}.wormhole-version" +else + rm -f "$install_dir/{binary_name}{version_suffix}.wormhole-version" +fi diff --git a/crates/remote_server/src/manager.rs b/crates/remote_server/src/manager.rs index 51849412..fbb4c45c 100644 --- a/crates/remote_server/src/manager.rs +++ b/crates/remote_server/src/manager.rs @@ -1860,7 +1860,10 @@ impl RemoteServerManager { // surfaced as `None`, which the controller treats as // inconclusive (fail open). let preinstall = match &platform { - Some(p) if matches!(p.os, RemoteOs::Linux) => { + Some(p) + if matches!(p.os, RemoteOs::Linux) + && !crate::setup::uses_static_linux_helper() => + { match transport.run_preinstall_check().await { Ok(r) => Some(r), Err(e) => { diff --git a/crates/remote_server/src/setup.rs b/crates/remote_server/src/setup.rs index 92b07224..674250fb 100644 --- a/crates/remote_server/src/setup.rs +++ b/crates/remote_server/src/setup.rs @@ -507,7 +507,18 @@ pub fn binary_check_command() -> String { /// the next install overwrites it, and an older daemon that is still /// running parsed its skills at startup. pub fn remote_server_removal_command() -> String { - format!("rm -f {}", remote_server_binary()) + let binary = remote_server_binary(); + format!("rm -f {binary} {binary}.wormhole-version") +} + +/// Returns whether this channel ships a statically linked Linux Wormhole +/// helper with the client instead of downloading a glibc-linked CLI artifact. +/// +/// Static helpers do not depend on the remote host's libc implementation, so +/// the glibc compatibility gate used by the hosted release channels does not +/// apply to them. +pub fn uses_static_linux_helper() -> bool { + matches!(ChannelState::channel(), Channel::Local | Channel::Oss) } /// Returns the version string used to pin remote-server installs on diff --git a/crates/remote_server/src/transport.rs b/crates/remote_server/src/transport.rs index ef17dfd3..f1ade079 100644 --- a/crates/remote_server/src/transport.rs +++ b/crates/remote_server/src/transport.rs @@ -53,7 +53,7 @@ pub struct InstallOutcome { #[derive(Clone, Debug)] pub struct UserFacingError { /// Always-visible explanation of what went wrong, - /// e.g. "Failed to install SSH extension". + /// e.g. "Failed to install Wormhole helper". pub body: String, /// Optional technical detail shown to the user (stderr, /// timeout duration, unsupported OS/arch). `None` when the @@ -77,9 +77,9 @@ impl SetupStage { match self { Self::DetectPlatform => "detect remote platform", Self::PreinstallCheck => "run preinstall check", - Self::CheckBinary => "verify SSH extension", - Self::InstallBinary => "install SSH extension", - Self::Launch => "start SSH extension", + Self::CheckBinary => "verify Wormhole helper", + Self::InstallBinary => "install Wormhole helper", + Self::Launch => "start Wormhole helper", } } } diff --git a/plans/ui-flow-inventory.md b/plans/ui-flow-inventory.md index 7b391734..ad050ad4 100644 --- a/plans/ui-flow-inventory.md +++ b/plans/ui-flow-inventory.md @@ -53,7 +53,7 @@ Status values: | WS-04 | Open settings, resource center, logs, and diagnostic panes | Mixed local and server/account actions | Preserve local pages; remove hosted links/actions | Rebuild | | TERM-01 | Run shell commands and view structured blocks | Terminal/UI core | Preserve | Keep/local | | TERM-02 | Search command history, blocks, commands, files, and palettes | SQLite/local index plus cloud object sources | Preserve local sources; remove hosted sources | Rebuild | -| TERM-03 | Use SSH, remote shells, and Wormhole/warpification | Remote host and remote-server components | Keep only explicit remote-host behavior; audit branding and hidden service calls | Keep/explicit | +| TERM-03 | Use SSH, remote shells, and Wormhole/wormholing | Remote host and remote-server components | Keep only explicit remote-host behavior; audit branding and hidden service calls | Keep/explicit | | TERM-04 | Share a terminal/session by URL or QR code | Warp session-sharing service | No hosted replacement in local-first scope | Remove | | TERM-05 | Sync terminal input across panes/tabs | Local workspace state | Preserve | Keep/local | | AGENT-01 | Start an agent conversation in a tab/pane | Blocklist controller, Warp proto request, provider/ACP branch | Route through `AgentRuntime` | Rebuild | diff --git a/resources/wormhole-helpers/linux-aarch64/galaxy-wormhole.tar.gz b/resources/wormhole-helpers/linux-aarch64/galaxy-wormhole.tar.gz new file mode 100644 index 00000000..4dee489b Binary files /dev/null and b/resources/wormhole-helpers/linux-aarch64/galaxy-wormhole.tar.gz differ diff --git a/resources/wormhole-helpers/linux-aarch64/galaxy-wormhole.version b/resources/wormhole-helpers/linux-aarch64/galaxy-wormhole.version new file mode 100644 index 00000000..97d3716f --- /dev/null +++ b/resources/wormhole-helpers/linux-aarch64/galaxy-wormhole.version @@ -0,0 +1 @@ +1cae6bc96e9b8f3274740cc3e46987e504cf3d3b47caec9fb640affb7f4f026b diff --git a/resources/wormhole-helpers/linux-x86_64/galaxy-wormhole.tar.gz b/resources/wormhole-helpers/linux-x86_64/galaxy-wormhole.tar.gz new file mode 100644 index 00000000..4a666060 Binary files /dev/null and b/resources/wormhole-helpers/linux-x86_64/galaxy-wormhole.tar.gz differ diff --git a/resources/wormhole-helpers/linux-x86_64/galaxy-wormhole.version b/resources/wormhole-helpers/linux-x86_64/galaxy-wormhole.version new file mode 100644 index 00000000..3ca6b0cb --- /dev/null +++ b/resources/wormhole-helpers/linux-x86_64/galaxy-wormhole.version @@ -0,0 +1 @@ +9d0e0d89eccc6a742e0973bebedede45247b790dc0480976cdc3ab4d285ae4ed diff --git a/script/build_wormhole_helpers b/script/build_wormhole_helpers new file mode 100755 index 00000000..8c818921 --- /dev/null +++ b/script/build_wormhole_helpers @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# +# Builds the statically linked Linux helpers that Galaxy copies to Wormhole +# hosts over SCP. The output layout is consumed directly by development builds +# and copied into application bundles by prepare_bundled_resources: +# +# resources/wormhole-helpers/ +# ├── linux-x86_64/ +# │ ├── galaxy-wormhole.tar.gz +# │ └── galaxy-wormhole.version +# └── linux-aarch64/ +# ├── galaxy-wormhole.tar.gz +# └── galaxy-wormhole.version + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +# These artifacts are committed and shipped with every desktop bundle, so use +# the repository's size-optimized CLI profile. Ordinary `cargo run` builds do +# not invoke this script. +PROFILE="release-cli" +REQUESTED_ARCH="all" + +usage() { + echo "Usage: $(basename "$0") [--arch x86_64|aarch64|all] [--profile PROFILE]" >&2 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --arch) + REQUESTED_ARCH="$2" + shift 2 + ;; + --profile) + PROFILE="$2" + shift 2 + ;; + --help) + usage + exit 0 + ;; + *) + usage + exit 1 + ;; + esac +done + +case "$REQUESTED_ARCH" in + x86_64) TARGETS=("x86_64-unknown-linux-musl") ;; + aarch64) TARGETS=("aarch64-unknown-linux-musl") ;; + all) TARGETS=("x86_64-unknown-linux-musl" "aarch64-unknown-linux-musl") ;; + *) + echo "Unsupported Wormhole helper architecture: $REQUESTED_ARCH" >&2 + usage + exit 1 + ;; +esac + +case "$PROFILE" in + dev) PROFILE_DIR="debug" ;; + *) PROFILE_DIR="$PROFILE" ;; +esac + +CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT/target}" +HELPERS_ROOT="$WORKSPACE_ROOT/resources/wormhole-helpers" +FEATURES="release_bundle,standalone,agent_mode_debug,remote_codebase_indexing" + +hash_file() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} + +for target in "${TARGETS[@]}"; do + source "$SCRIPT_DIR/linux/configure_musl_toolchain" "$target" + if ! rustup target list --installed | grep -qx "$target"; then + rustup target add "$target" + fi + + case "$target" in + x86_64-unknown-linux-musl) arch="x86_64" ;; + aarch64-unknown-linux-musl) arch="aarch64" ;; + esac + + echo "Building Wormhole helper for linux-$arch" + cargo build \ + --manifest-path "$WORKSPACE_ROOT/Cargo.toml" \ + -p galaxy \ + --bin galaxy-oss \ + --target "$target" \ + --profile "$PROFILE" \ + --features "$FEATURES" + + built_binary="$CARGO_TARGET_DIR/$target/$PROFILE_DIR/galaxy-oss" + if [[ ! -x "$built_binary" ]]; then + echo "Expected Wormhole helper was not produced at $built_binary" >&2 + exit 1 + fi + + artifact_dir="$HELPERS_ROOT/linux-$arch" + staging_dir="$(mktemp -d "${TMPDIR:-/tmp}/galaxy-wormhole.XXXXXX")" + trap 'rm -rf "$staging_dir"' EXIT + mkdir -p "$artifact_dir" + + cp "$built_binary" "$staging_dir/galaxy-ai-oss" + # Release profiles retain line-table symbols for desktop crash reports. The + # remote helper is shipped as a standalone artifact, so strip its staged + # copy to keep the committed archive and application bundle small without + # modifying Cargo's build output. + "$WARP_MUSL_STRIP" --strip-unneeded "$staging_dir/galaxy-ai-oss" + NO_LICENSES=1 SKIP_SETTINGS_SCHEMA=1 \ + GALAXY_WORMHOLE_HELPERS_DIR="$staging_dir/no-wormhole-helpers" \ + "$SCRIPT_DIR/prepare_bundled_resources" "$staging_dir/resources" oss "$PROFILE" + + helper_version="$(hash_file "$staging_dir/galaxy-ai-oss")" + printf '%s\n' "$helper_version" > "$staging_dir/wormhole-version" + tar -czf "$artifact_dir/galaxy-wormhole.tar.gz" -C "$staging_dir" \ + galaxy-ai-oss resources wormhole-version + printf '%s\n' "$helper_version" > "$artifact_dir/galaxy-wormhole.version" + + rm -rf "$staging_dir" + trap - EXIT + echo "Wrote $artifact_dir/galaxy-wormhole.tar.gz" +done diff --git a/script/prepare_bundled_resources b/script/prepare_bundled_resources index deb9d697..1bba8730 100755 --- a/script/prepare_bundled_resources +++ b/script/prepare_bundled_resources @@ -62,6 +62,21 @@ else echo "Warning: No bundled directory found at $RESOURCES_SRC/bundled" >&2 fi +# Include the two statically linked Linux Wormhole helpers when they have been +# produced by `script/build_wormhole_helpers`. They are checked into the +# repository so ordinary Cargo builds never need a Linux cross-compiler; +# packaged applications receive this copy in their filesystem resources. +WORMHOLE_HELPERS_SRC="${GALAXY_WORMHOLE_HELPERS_DIR:-$REPO_ROOT/resources/wormhole-helpers}" +if [ -d "$WORMHOLE_HELPERS_SRC" ]; then + echo "Copying Wormhole helpers to $DEST_DIR/wormhole-helpers" + rm -rf "$DEST_DIR/wormhole-helpers" + cp -R "$WORMHOLE_HELPERS_SRC" "$DEST_DIR/wormhole-helpers" +elif [ "${REQUIRE_WORMHOLE_HELPERS:-0}" = "1" ]; then + echo "Error: Wormhole helpers were not found at $WORMHOLE_HELPERS_SRC" >&2 + echo "Run script/build_wormhole_helpers before packaging Galaxy." >&2 + exit 1 +fi + if [ -n "$GIT_RELEASE_TAG" ]; then VERSION_METADATA_DIR="$DEST_DIR/bundled/metadata" VERSION_METADATA_PATH="$VERSION_METADATA_DIR/version.json" diff --git a/specs/APP-3619/PRODUCT.md b/specs/APP-3619/PRODUCT.md index 1e5ad3d7..8b024cce 100644 --- a/specs/APP-3619/PRODUCT.md +++ b/specs/APP-3619/PRODUCT.md @@ -13,7 +13,7 @@ When the Warp notification plugin can't be auto-installed (SSH session, or a pre ## Chip Visibility Fix (Remote Sessions) -`should_show_install_plugin_button` hides the chip when `manager.is_installed()` returns true. But `is_installed()` reads the **local** filesystem (`~/.claude/plugins/installed_plugins.json`), not the remote machine's. In any remote session (warpified SSH, legacy SSH, Docker via SSH) where Claude Code runs on the remote, this check is wrong: +`should_show_install_plugin_button` hides the chip when `manager.is_installed()` returns true. But `is_installed()` reads the **local** filesystem (`~/.claude/plugins/installed_plugins.json`), not the remote machine's. In any remote session (wormholed SSH, legacy SSH, Docker via SSH) where Claude Code runs on the remote, this check is wrong: - Plugin installed locally but not on remote → chip hidden, user stuck with no instructions @@ -114,4 +114,4 @@ Failure state is not persisted. A new terminal session starts fresh in Mode 1 (a - **User installs plugin manually mid-session (without using the chip):** The listener will connect on next `SessionStart` event, chip disappears automatically. - **User clicks chip in Mode 2 then installs manually:** Modal stays open until dismissed. Chip disappears on next render once listener is present. - **Multiple terminal tabs with same agent:** Each tab has its own `AgentInputFooter` with independent failure tracking. This is correct — one tab's failure shouldn't affect another. -- **Warpified SSH (tmux wrapper):** Even though the local filesystem is accessible via tmux, the agent runs on the remote machine. The `is_remote` flag is set for all SSH sessions (warpified or legacy), so Mode 2 applies to all remote sessions. +- **Wormholed SSH (tmux wrapper):** Even though the local filesystem is accessible via tmux, the agent runs on the remote machine. The `is_remote` flag is set for all SSH sessions (wormholed or legacy), so Mode 2 applies to all remote sessions. diff --git a/specs/APP-3619/TECH.md b/specs/APP-3619/TECH.md index b6d3f8b2..e838610e 100644 --- a/specs/APP-3619/TECH.md +++ b/specs/APP-3619/TECH.md @@ -76,9 +76,9 @@ Follows the existing `OpenAutoReloadModal` pattern (`view.rs:18411`, `workspace/ ## 5. Remote Session Detection -`CLIAgentSession` has an `is_remote: bool` field set at session creation from `TerminalView::active_session_is_local()`. This uses `SessionType::WarpifiedRemote` and `IsLegacySSHSession` — the same logic as the SSH host chip (`context_chips/builtins.rs:76`). +`CLIAgentSession` has an `is_remote: bool` field set at session creation from `TerminalView::active_session_is_local()`. This uses `SessionType::WormholedRemote` and `IsLegacySSHSession` — the same logic as the SSH host chip (`context_chips/builtins.rs:76`). -This avoids relying on `terminal_model.is_ssh_block()` (which only tracks the pre-warpification login phase) or `is_warpified_ssh()` (which misses legacy SSH). The `is_remote` flag is threaded through `set_session` and `register_listener` at all call sites in `terminal/view.rs`. +This avoids relying on `terminal_model.is_ssh_block()` (which only tracks the pre-wormholing login phase) or `is_wormholed_ssh()` (which misses legacy SSH). The `is_remote` flag is threaded through `set_session` and `register_listener` at all call sites in `terminal/view.rs`. ## 6. Two-Mode Chip diff --git a/specs/APP-3787/TECH.md b/specs/APP-3787/TECH.md index 6eab1463..5e2733ae 100644 --- a/specs/APP-3787/TECH.md +++ b/specs/APP-3787/TECH.md @@ -31,7 +31,7 @@ This spec covers three pieces: - `app/src/terminal/view.rs (11022-11199)` — `TerminalView::handle_session_bootstrapped()` reacts to the event ### Session and SSH types -- `app/src/terminal/model/session.rs (691-699)` — `SessionType::Local` / `SessionType::WarpifiedRemote` +- `app/src/terminal/model/session.rs (691-699)` — `SessionType::Local` / `SessionType::WormholedRemote` - `app/src/terminal/model/session.rs (426-451)` — `SessionInfo` struct with `hostname`, `user`, `session_type`, `spawning_session_id` - `app/src/terminal/model/terminal_model.rs (632-647)` — `SubshellInitializationInfo` with `ssh_connection_info: Option` - `app/src/terminal/ssh/util.rs (86-89)` — `InteractiveSshCommand { host, port }` diff --git a/specs/APP-3790/TECH-remote-apply-diff.md b/specs/APP-3790/TECH-remote-apply-diff.md index b7bdef76..a10882ac 100644 --- a/specs/APP-3790/TECH-remote-apply-diff.md +++ b/specs/APP-3790/TECH-remote-apply-diff.md @@ -29,7 +29,7 @@ When an AI agent runs in an SSH session, the `ApplyFileDiffs` tool is disabled b **CodeDiffView save/delete/create**: `DiffSessionType` already exists with `Local` and `Remote(HostId)` variants. `set_candidate_diffs` routes to `register_file` (local) or `register_remote_file` (remote). `FileModel` has `FileBackend::Remote` that dispatches save/delete through `RemoteServerClient`. However, `RequestFileEditsExecutor` never sets `diff_session_type` — it defaults to `Local`. -**Agent tool gating**: `get_supported_tools` excludes `ApplyFileDiffs`, `ReadFiles`, and `SearchCodebase` when `session_type` is `WarpifiedRemote`. There is no field on `SessionContext` to indicate whether a `RemoteServerClient` is available. +**Agent tool gating**: `get_supported_tools` excludes `ApplyFileDiffs`, `ReadFiles`, and `SearchCodebase` when `session_type` is `WormholedRemote`. There is no field on `SessionContext` to indicate whether a `RemoteServerClient` is available. **Post-accept context**: After diffs are accepted, `execute` re-reads files from disk via `read_local_file_context` and sends updated content to the LLM. This would require a network round-trip for remote sessions. @@ -146,7 +146,7 @@ Session type is modeled as two distinct enums to separate immutable bootstrap-ti ```rust pub enum BootstrapSessionType { Local, - WarpifiedRemote, + WormholedRemote, } ``` @@ -155,7 +155,7 @@ pub enum BootstrapSessionType { ```rust pub enum SessionType { Local, - WarpifiedRemote { host_id: Option }, + WormholedRemote { host_id: Option }, } ``` @@ -176,10 +176,10 @@ match session_context.session_type() { api::ToolType::SearchCodebase, ]); } - Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => { + Some(SessionType::WormholedRemote { host_id: Some(_) }) => { supported_tools.push(api::ToolType::ApplyFileDiffs); } - Some(SessionType::WarpifiedRemote { host_id: None }) => { + Some(SessionType::WormholedRemote { host_id: None }) => { // Feature flag off or not yet connected — no remote tools. } } diff --git a/specs/APP-3790/TECH-remote-read-files.md b/specs/APP-3790/TECH-remote-read-files.md index 78c21654..fd9ea7ac 100644 --- a/specs/APP-3790/TECH-remote-read-files.md +++ b/specs/APP-3790/TECH-remote-read-files.md @@ -2,7 +2,7 @@ ## Problem -The `ReadFiles` agent tool is disabled for remote SSH sessions. `get_supported_tools` skips `ToolType::ReadFiles` when `SessionType::WarpifiedRemote`, because the underlying `read_local_file_context` reads files via `async_fs`, `FileModel::read_text_file`, and local image processing — all local-only APIs. +The `ReadFiles` agent tool is disabled for remote SSH sessions. `get_supported_tools` skips `ToolType::ReadFiles` when `SessionType::WormholedRemote`, because the underlying `read_local_file_context` reads files via `async_fs`, `FileModel::read_text_file`, and local image processing — all local-only APIs. The remote server already runs on the host machine with full filesystem access and has access to the same dependencies (`warp_files`, `warp_util`, `mime_guess`). Rather than building a degraded client-side approximation, we push the file-reading logic to the server so the ReadFiles tool has full feature parity with local: line-range extraction, binary/image support, metadata, and size limits. @@ -36,7 +36,7 @@ The remote server already runs on the host machine with full filesystem access a **Current `ReadFile` proto** (`remote_server.proto`): `ReadFile { path }` → `ReadFileSuccess { content, exists }`. The server handler just calls `tokio::fs::read_to_string` — no metadata, no line ranges, no size limits, no binary support. -**Tool gating**: `get_supported_tools` excludes `ReadFiles` for `WarpifiedRemote` sessions. `get_supported_cli_agent_tools` also excludes it. +**Tool gating**: `get_supported_tools` excludes `ReadFiles` for `WormholedRemote` sessions. `get_supported_cli_agent_tools` also excludes it. ## Proposed Changes @@ -135,8 +135,8 @@ Follows the same pattern as `write_file` / `delete_file` — sends request, awai In the `execute` method, after resolving cwd/shell, check `active_session.session_type(ctx)`: - **Local / None**: call `read_local_file_context` as today (unchanged). -- **WarpifiedRemote with host_id**: resolve `RemoteServerClient` via `RemoteServerManager::client_for_host`, call `client.read_file_context(...)`, convert `ReadFileContextResponse` → `ReadFileContextResult` (mapping proto `FileContextProto` → `FileContext`, `FailedFileRead` → `missing_files`). -- **WarpifiedRemote without host_id**: fall through to the local `read_local_file_context` path. +- **WormholedRemote with host_id**: resolve `RemoteServerClient` via `RemoteServerManager::client_for_host`, call `client.read_file_context(...)`, convert `ReadFileContextResponse` → `ReadFileContextResult` (mapping proto `FileContextProto` → `FileContext`, `FailedFileRead` → `missing_files`). +- **WormholedRemote without host_id**: fall through to the local `read_local_file_context` path. The remote client lookup uses a unified code path with no `cfg` gating — `RemoteServerManager` and `RemoteServerClient` compile on all targets including WASM. On WASM, `client_for_host` returns `None` (since `connect_session` is a no-op), so the local path is used automatically. @@ -146,7 +146,7 @@ The `read_remote_file` adapter currently uses the old `ReadFile`/`ReadFileSucces ### 7. Enable `ReadFiles` in `get_supported_tools` for remote sessions -In `get_supported_tools` (impl.rs:179), add `api::ToolType::ReadFiles` alongside `ApplyFileDiffs` for the `WarpifiedRemote { host_id: Some(_) }` arm. +In `get_supported_tools` (impl.rs:179), add `api::ToolType::ReadFiles` alongside `ApplyFileDiffs` for the `WormholedRemote { host_id: Some(_) }` arm. Also in `get_supported_cli_agent_tools` (impl.rs:234), enable `ReadFiles` for remote sessions with a connected host. @@ -161,7 +161,7 @@ sequenceDiagram participant Shared as read_single_file_context LLM->>Executor: ReadFiles action (file locations) - Note over Executor: session_type is WarpifiedRemote with host_id + Note over Executor: session_type is WormholedRemote with host_id Executor->>Client: read_file_context(files, max_bytes) Client->>Server: ReadFileContextRequest { files, max_file_bytes, max_batch_bytes } diff --git a/specs/APP-3791/TECH.md b/specs/APP-3791/TECH.md index a25c0ea4..0e0db465 100644 --- a/specs/APP-3791/TECH.md +++ b/specs/APP-3791/TECH.md @@ -175,7 +175,7 @@ The `SshRemoteServer` branch is added as the **first** check in `new_command_exe 1. SshRemoteServer + IsLegacySSHSession::Yes → RemoteServerCommandExecutor [NEW] 2. SSHTmuxWrapper + tmux_control_mode → TmuxCommandExecutor 3. SessionType::Local (various) → LocalCommandExecutor / MSYS2 / WSL -4. WarpifiedRemote + legacy SSH + !InBandForSSH → RemoteCommandExecutor +4. WormholedRemote + legacy SSH + !InBandForSSH → RemoteCommandExecutor 5. default → InBandCommandExecutor / NoOp ``` diff --git a/specs/APP-3792/TECH.md b/specs/APP-3792/TECH.md index cc7238c9..e670a87e 100644 --- a/specs/APP-3792/TECH.md +++ b/specs/APP-3792/TECH.md @@ -14,7 +14,7 @@ Behavior is specified in `specs/APP-3792/PRODUCT.md`. This document updates the - `app/src/server/server_api/ai.rs` implements that trait for the client-side `ServerApi`; current master includes the codebase calls around `generate_code_embeddings`, `sync_merkle_tree`, `populate_merkle_tree_cache`, `get_relevant_fragments`, `rerank_fragments`, and `codebase_context_config`. - `crates/ai/src/index/full_source_code_embedding/snapshot.rs` owns serialized snapshot persistence. The daemon path should reuse the format while changing the base directory. - `app/src/ai/blocklist/action_model/execute/search_codebase.rs:28` defines `SearchCodebaseExecutor`; the current hydration path uses local file reads after `GetRelevantFilesController`. -- `app/src/ai/agent/api/impl.rs:189-194` explicitly disables `SearchCodebase` for `WarpifiedRemote { host_id: Some(_) }`. +- `app/src/ai/agent/api/impl.rs:189-194` explicitly disables `SearchCodebase` for `WormholedRemote { host_id: Some(_) }`. - The existing local UI strings and flows live in `app/src/ai/blocklist/codebase_index_speedbump_banner.rs:20-30` and `app/src/settings_view/code_page.rs:84-98`. ### Current remote-server architecture on master @@ -257,7 +257,7 @@ Use this model from: Settings should distinguish local auto-indexing from remote auto-indexing. If implementation chooses to reuse one preference, the product spec must be updated before shipping; the current product expectation is independent control. ### 3.8 Remote retrieval path -When `SearchCodebaseExecutor` runs in `SessionType::WarpifiedRemote { host_id: Some(_) }`: +When `SearchCodebaseExecutor` runs in `SessionType::WormholedRemote { host_id: Some(_) }`: 1. Resolve the active remote repo path. 2. Read `RemoteCodebaseIndexModel` for `(remote_identity_key, host_id, repo_path)`. 3. If the state is not ready/stale with a root hash, return a typed `SearchCodebaseResult::Failed` reason for indexing-in-progress, failed, disabled, unavailable, or not indexed. diff --git a/specs/APP-4069/TECH.md b/specs/APP-4069/TECH.md index 292ad553..6da5bc42 100644 --- a/specs/APP-4069/TECH.md +++ b/specs/APP-4069/TECH.md @@ -1,12 +1,12 @@ # APP-4069 — SSH Initialization UX Linear: [APP-4069 — Initialization UX](https://linear.app/warpdotdev/issue/APP-4069/initialization-ux) ## 1. Problem -When a user SSHes into a remote host, we want to introduce a choice block for users to choose between (1) installing and connecting to the remote server (2) falling back to the existing warpify behaviour. +When a user SSHes into a remote host, we want to introduce a choice block for users to choose between (1) installing and connecting to the remote server (2) falling back to the existing wormhole behaviour. -To do so, we'll need to block the current bootstrap and connect server flow. Today, the client has a race where: `PtyController` writes the legacy bootstrap script to the PTY synchronously on `InitShell`, while `TerminalView` in parallel kicks off an async background task in `RemoteServerManager` that checks for the remote-server binary, installs it if missing, and initializes the server. Because the bootstrap is written before the check completes, we cannot defer or cancel warpification based on the check result — by the time we know whether the remote server is available, the legacy warpification has already taken effect. +To do so, we'll need to block the current bootstrap and connect server flow. Today, the client has a race where: `PtyController` writes the legacy bootstrap script to the PTY synchronously on `InitShell`, while `TerminalView` in parallel kicks off an async background task in `RemoteServerManager` that checks for the remote-server binary, installs it if missing, and initializes the server. Because the bootstrap is written before the check completes, we cannot defer or cancel wormholing based on the check result — by the time we know whether the remote server is available, the legacy wormholing has already taken effect. This spec resolves the race by deferring the bootstrap write under the control of the remote-server setup outcome, and introduces a **two-option choice block** that appears only when the binary is missing: -- **Yes, install** — flush the bootstrap, install the binary on the remote, launch + handshake the remote server. Session is fully warpified via the remote-server path. -- **No, skip** — flush the stashed bootstrap (so the shell is properly initialized) but do not call `connect_session`. The session falls back to ControlMaster warpification without engaging the remote-server path. +- **Yes, install** — flush the bootstrap, install the binary on the remote, launch + handshake the remote server. Session is fully wormholed via the remote-server path. +- **No, skip** — flush the stashed bootstrap (so the shell is properly initialized) but do not call `connect_session`. The session falls back to ControlMaster wormholing without engaging the remote-server path. This spec covers the following two sections: - **Part 1 — Blocking and wiring.** A new per-pane `RemoteServerController` owns the state machine that defers the bootstrap, checks the binary via `RemoteServerManager`, and flushes at the right moment. @@ -32,7 +32,7 @@ Two subscribers react synchronously to `ModelEvent::Handler(AnsiHandlerEvent::In - `TerminalView` (in `view.rs:10813–10836`) spawns `RemoteServerManager::connect_session`, which runs check + install + launch + handshake in a single background task. Because both subscribers fire on the same tick but the view's work is async, the bootstrap is already written by the time the check result is known. This is the core race. `RemoteServerManager::connect_session` today is monolithic: it emits `SetupStateChanged(Checking)` → runs the check → on "not installed" emits `SetupStateChanged(Installing)` and runs the install → on success emits `SetupReady` and proceeds to launch + handshake. There is no way to observe the binary-presence result without also triggering the install. -`ModelEventDispatcher` already has a stash-and-wait gate that waits for both `Bootstrapped` (from the remote shell sourcing the bootstrap script) and `RemoteServerReady` (forwarded today from `RemoteServerManager::SetupReady`) before calling `complete_bootstrapped_session`. The gate logic itself is unchanged, but its success-signal source moves: today `SetupReady` fires after the install decision but before `start_remote_server` and `client.initialize()` have run, so it is optimistic — launch or handshake can still fail after the gate has already resolved with `ready=true` (because `Bootstrapped` typically arrives while the handshake is still in flight), at which point the session has been committed to the warpified path against a manager that has no connected client. §4.2.1 sources the gate's success signal from `SessionConnected` (emitted only after handshake succeeds at `manager.rs:535`) to fix this. +`ModelEventDispatcher` already has a stash-and-wait gate that waits for both `Bootstrapped` (from the remote shell sourcing the bootstrap script) and `RemoteServerReady` (forwarded today from `RemoteServerManager::SetupReady`) before calling `complete_bootstrapped_session`. The gate logic itself is unchanged, but its success-signal source moves: today `SetupReady` fires after the install decision but before `start_remote_server` and `client.initialize()` have run, so it is optimistic — launch or handshake can still fail after the gate has already resolved with `ready=true` (because `Bootstrapped` typically arrives while the handshake is still in flight), at which point the session has been committed to the wormholed path against a manager that has no connected client. §4.2.1 sources the gate's success signal from `SessionConnected` (emitted only after handshake succeeds at `manager.rs:535`) to fix this. ## 4. Proposed changes ### Part 1: Blocking and wiring #### 4.1 `RemoteServerController` — per-pane orchestrator diff --git a/specs/APP-4234/TECH.md b/specs/APP-4234/TECH.md index 523f5a73..2f979207 100644 --- a/specs/APP-4234/TECH.md +++ b/specs/APP-4234/TECH.md @@ -6,7 +6,7 @@ The `SshRemoteServer` feature flag gates a new SSH session flow where a persiste ### Current integration test infra -The existing SSH integration tests (`crates/integration/src/test/ssh.rs`) cover the legacy warpification flow: +The existing SSH integration tests (`crates/integration/src/test/ssh.rs`) cover the legacy wormholing flow: - Connect to a GCP-hosted Ubuntu VM (`ubuntu-14-04`) via IAP tunnel with password auth - Helper steps in `app/src/integration_testing/subshell/` — `setup_gcloud_sdk()`, `enter_ssh_command()`, `enter_ssh_password()`, `wait_for_password_prompt()` - Builder pattern: `new_builder().with_step(TestStep)` with assertion callbacks @@ -22,7 +22,7 @@ When `SshRemoteServer` is enabled for a legacy SSH session (`app/src/terminal/wr 4. Bootstrap is flushed; `RemoteServerCommandExecutor` (`app/src/terminal/model/session/command_executor/remote_server_executor.rs`) is wired as the session's `CommandExecutor` 5. On CWD change, `navigate_to_directory` fires → returns `is_git` flag + triggers `RepoMetadataSnapshot` push -Key config: `SshExtensionInstallMode::AlwaysInstall` (setting in `app/src/terminal/warpify/settings.rs:85`) bypasses the choice block UI, needed for deterministic test flow. +Key config: `SshExtensionInstallMode::AlwaysInstall` (setting in `app/src/terminal/wormhole/settings.rs:85`) bypasses the choice block UI, needed for deterministic test flow. ### Binary deployment problem diff --git a/specs/APP-4386/TECH.md b/specs/APP-4386/TECH.md index f99e9c13..2d3d24b5 100644 --- a/specs/APP-4386/TECH.md +++ b/specs/APP-4386/TECH.md @@ -225,7 +225,7 @@ The SCP fallback uses a separate, longer timeout: pub const SCP_INSTALL_TIMEOUT: Duration = Duration::from_secs(120); ``` The standard `INSTALL_TIMEOUT` (60s) is sufficient for the curl/wget path because the remote host downloads directly from the CDN. The SCP path adds a local download step (~5s) plus an SCP upload that depends entirely on the SSH link bandwidth — embedded devices, VPNs, and high-latency connections can easily exceed 60s for a ~30-50 MB transfer. Each sub-step (`scp_upload`, `run_ssh_script` for extraction) uses the full `SCP_INSTALL_TIMEOUT` individually to avoid splitting a single budget across steps, which would require coordination logic for diminishing remaining time. -- If the SCP upload or extraction fails, the error surfaces the same way as a normal install failure — `BinaryInstallComplete { result: Err(_) }` — and the session falls back to ControlMaster warpification. +- If the SCP upload or extraction fails, the error surfaces the same way as a normal install failure — `BinaryInstallComplete { result: Err(_) }` — and the session falls back to ControlMaster wormholing. ## Testing and validation diff --git a/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md b/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md index 711840d0..2d8e995a 100644 --- a/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md +++ b/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md @@ -126,7 +126,7 @@ let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx) if (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) { ``` -Note this should NOT be allowed if we're in a Warpified remote host, where we cannot run in-band generators. +Note this should NOT be allowed if we're in a Wormholed remote host, where we cannot run in-band generators. ### 8. Placeholder text diff --git a/specs/GH478/tech.md b/specs/GH478/tech.md index 704c2965..a7033a93 100644 --- a/specs/GH478/tech.md +++ b/specs/GH478/tech.md @@ -607,7 +607,7 @@ global `theme()` form. Concretely: | --- | --- | --- | | `app/src/terminal/view/**` | per-tab | terminal grid, blocks, in-tab modals over the grid | | `app/src/terminal/input/**` | per-tab | input bar, inline menus, slash commands inside the tab | -| `app/src/terminal/{view.rs, block_filter.rs, rich_history.rs, terminal_manager.rs, universal_developer_input.rs, ssh/**, warpify/**, shared_session/**}` | per-tab | terminal-pane content | +| `app/src/terminal/{view.rs, block_filter.rs, rich_history.rs, terminal_manager.rs, universal_developer_input.rs, ssh/**, wormhole/**, shared_session/**}` | per-tab | terminal-pane content | | `app/src/terminal/{share_block_modal.rs, profile_model_selector.rs}` | per-tab | scoped to a specific tab's content | | `app/src/{tab.rs, root_view.rs, voltron.rs, modal.rs, menu.rs, search_bar.rs, input_suggestions.rs}` | global | window chrome, command palette, top-level modals | | `app/src/settings/**` | global | settings UI | diff --git a/specs/REMOTE-1254/TECH.md b/specs/REMOTE-1254/TECH.md index 3483a726..e5548ab5 100644 --- a/specs/REMOTE-1254/TECH.md +++ b/specs/REMOTE-1254/TECH.md @@ -150,7 +150,7 @@ sequenceDiagram ## 6. Risks and mitigations -**OSC vs protocol session conflict on viewer**: If a viewer somehow receives OSC `SessionStart` events (e.g. warpified SSH where plugin events leak), the local OSC path could clobber the protocol-managed session. Mitigated by an early return in `handle_cli_agent_notification` when `is_shared_session_viewer()` is true — the protocol is the sole source of truth for viewer session lifecycle. +**OSC vs protocol session conflict on viewer**: If a viewer somehow receives OSC `SessionStart` events (e.g. wormholed SSH where plugin events leak), the local OSC path could clobber the protocol-managed session. Mitigated by an early return in `handle_cli_agent_notification` when `is_shared_session_viewer()` is true — the protocol is the sole source of truth for viewer session lifecycle. **Echo loops**: Handled by the existing `RemoteUpdateGuard` pattern — all new broadcast subscribers check `guard.should_broadcast()`, and all incoming `apply_*` calls run inside an `ActiveRemoteUpdate` scope. diff --git a/specs/andy/CODE-1779/PRODUCT.md b/specs/andy/CODE-1779/PRODUCT.md index 7a250bec..20e0b833 100644 --- a/specs/andy/CODE-1779/PRODUCT.md +++ b/specs/andy/CODE-1779/PRODUCT.md @@ -33,7 +33,7 @@ When a Warp tab is attached to a Unix-like shell on Windows — WSL, or MSYS2 / 4. Shell-specific escaping (quoting spaces, special characters) applies on top of the transformed path using the active session's shell family — identical to the non-WSL/non-MSYS2 behavior today. -5. When the active session is neither WSL nor MSYS2/Git Bash (local PowerShell, cmd, SSH into a remote host, Warpified remote, etc.), dropped paths are inserted exactly as they are today. No transformation happens. +5. When the active session is neither WSL nor MSYS2/Git Bash (local PowerShell, cmd, SSH into a remote host, Wormholed remote, etc.), dropped paths are inserted exactly as they are today. No transformation happens. 6. Image auto-attachment (dragging an image file into Agent Mode / an empty buffer, which attaches it as AI image context) continues to use the original Windows-native path for filesystem reads, regardless of WSL / MSYS2 state. Transformed paths would not be readable from the Windows host. @@ -47,7 +47,7 @@ When a Warp tab is attached to a Unix-like shell on Windows — WSL, or MSYS2 / 10. Non-regressions: - Dropping into any non-terminal editor (notebooks, settings, themes, etc.) is unchanged — no path transformation is applied. - - Dropping into a non-WSL, non-MSYS2 terminal session (PowerShell, cmd, SSH, remote Warpified) is unchanged. + - Dropping into a non-WSL, non-MSYS2 terminal session (PowerShell, cmd, SSH, remote Wormholed) is unchanged. - The terminal-grid long-running-command code paths for both WSL and MSYS2 are unchanged. - Dropping image-only content into the input in Agent Mode still attaches the images; nothing about attachment behavior changes. - Pasting paths via clipboard is out of scope for this ticket and remains unchanged.