feat: expand Galaxy agent and remote tooling
Add Wormhole remote helpers, provider and agent improvements, filesystem diagnostics, model metadata support, and schema-aware settings IntelliSense.
This commit is contained in:
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -130,7 +130,7 @@ pub struct CLIAgentSession {
|
||||
/// `None` if the plugin predates version reporting or Codex is using OSC9 fallback.
|
||||
pub plugin_version: Option<String>,
|
||||
/// `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<String>,
|
||||
/// Draft text saved from the rich input composer when it was closed.
|
||||
|
||||
@@ -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<SerializedBlock>),
|
||||
|
||||
/// This is a block containing static/hardcoded content (e.g. the subshell Warpification
|
||||
/// This is a block containing static/hardcoded content (e.g. the subshell Wormholing
|
||||
/// welcome block).
|
||||
Static,
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<S> TerminalManager<S> {
|
||||
.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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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::<WarpifySuccessBlock>(active_window_id, *view_id)
|
||||
{
|
||||
let warpify_success_block = app.view(&ssh_block);
|
||||
if let Some(ssh_block) = app.view_with_id::<WormholeSuccessBlock>(
|
||||
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::<WarpifySuccessBlock>(active_window_id, view_id)
|
||||
app.view_with_id::<WormholeSuccessBlock>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
pub enum RichContentType {
|
||||
AIBlock,
|
||||
EnterAgentView,
|
||||
WarpifySuccessBlock,
|
||||
WormholeSuccessBlock,
|
||||
InlineAgentViewHeader,
|
||||
AgentViewZeroState,
|
||||
TerminalViewZeroState,
|
||||
|
||||
@@ -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<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,
|
||||
}
|
||||
}
|
||||
@@ -527,20 +527,20 @@ impl From<SessionType> 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<galaxy_core::HostId>,
|
||||
},
|
||||
}
|
||||
@@ -887,7 +887,7 @@ impl From<BootstrapSessionType> 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<galaxy_core::HostId>) {
|
||||
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<String> {
|
||||
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 {
|
||||
|
||||
@@ -100,12 +100,12 @@ impl ActiveSession {
|
||||
/// the connected host ID.
|
||||
pub fn location_for_path(&self, path: &str, app: &AppContext) -> Option<LocalOrRemotePath> {
|
||||
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));
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn user_and_host_name_string(
|
||||
) -> Option<String> {
|
||||
match session_type {
|
||||
SessionType::Local => None,
|
||||
SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:")),
|
||||
SessionType::WormholedRemote { .. } => Some(format!("{user}@{hostname}:")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String>,
|
||||
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<String>) -> Self {
|
||||
pub fn new(error_reason: WormholingUnavailableReason, ssh_host: Option<String>) -> 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<dyn Element> {
|
||||
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<Self>) {
|
||||
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 => {
|
||||
|
||||
@@ -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<String>,
|
||||
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>,
|
||||
) {
|
||||
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<dyn Element> {
|
||||
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<S
|
||||
system.shell.as_str(),
|
||||
) {
|
||||
("Linux", _, "bash" | "zsh") => {
|
||||
bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_linux.sh")
|
||||
bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_linux.sh")
|
||||
}
|
||||
("Linux", _, "fish") => {
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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<SshWarpifyCommand> {
|
||||
impl SshWormholeCommand {
|
||||
pub fn matches(command: &str) -> Option<SshWormholeCommand> {
|
||||
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<InteractiveSshCommand> {
|
||||
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,
|
||||
|
||||
@@ -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<dyn Element> {
|
||||
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<dyn Element> {
|
||||
@@ -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<dyn Element> {
|
||||
@@ -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<Self>) {
|
||||
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::<String>(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::<String>(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;
|
||||
@@ -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,
|
||||
)
|
||||
});
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
+144
-158
@@ -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<ViewHandle<onboarding::OnboardingCalloutView>>,
|
||||
|
||||
/// The type of the subshell that we will bootstrap/"warpify"" on the next [`AfterBlockStarted`]
|
||||
/// The type of the subshell that we will bootstrap/"wormhole"" on the next [`AfterBlockStarted`]
|
||||
/// terminal model event. Will only be `Some` with a [`ShellType`] we can bootstrap.
|
||||
pending_auto_bootstrap_shell_type: Option<ShellType>,
|
||||
env_vars: Vec<EnvVar>,
|
||||
@@ -2817,7 +2812,7 @@ pub struct TerminalView {
|
||||
|
||||
find_model: ModelHandle<TerminalFindModel>,
|
||||
|
||||
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<Self>) {
|
||||
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>,
|
||||
) {
|
||||
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>) {
|
||||
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<Self>,
|
||||
@@ -9929,18 +9923,8 @@ impl TerminalView {
|
||||
});
|
||||
}
|
||||
|
||||
let warpification_source = match session_type {
|
||||
BootstrapSessionType::WarpifiedRemote => WarpificationSource::Ssh,
|
||||
BootstrapSessionType::Local => WarpificationSource::Subshell,
|
||||
};
|
||||
let ssh_success_block_handle = ctx.add_typed_action_view(|ctx| {
|
||||
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<Self>,
|
||||
) {
|
||||
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<Self>,
|
||||
) {
|
||||
{
|
||||
@@ -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<Keystroke>,
|
||||
wormhole_keybinding: Option<Keystroke>,
|
||||
telemetry_event: TelemetryEvent,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
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<Self>,
|
||||
) {
|
||||
// Attempt to auto warpify the subshell when bootstrapped
|
||||
// Attempt to auto wormhole the subshell when bootstrapped
|
||||
self.pending_auto_bootstrap_shell_type = Some(shell_type);
|
||||
|
||||
self.input.update(ctx, |input, ctx| {
|
||||
@@ -25853,7 +25839,7 @@ impl TerminalView {
|
||||
ctx: &mut ViewContext<TerminalView>,
|
||||
) {
|
||||
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<Self>) {
|
||||
/// Shows the wormhole footer for a detected subshell command.
|
||||
fn show_wormhole_footer(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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:?})")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-21
@@ -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<Keystroke>,
|
||||
pub initialize_wormhole_keybinding: Option<Keystroke>,
|
||||
pub hover_state: MouseStateHandle,
|
||||
}
|
||||
|
||||
impl WarpifyBannerState {
|
||||
pub fn new(command: String, initialize_warpify_keybinding: Option<Keystroke>) -> Self {
|
||||
impl WormholeBannerState {
|
||||
pub fn new(command: String, initialize_wormhole_keybinding: Option<Keystroke>) -> 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<dyn Element> {
|
||||
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<Keystroke>,
|
||||
state: &WormholeBannerState,
|
||||
initialize_wormholing_keybinding: &Option<Keystroke>,
|
||||
mouse_state: &MouseStateHandle,
|
||||
appearance: &Appearance,
|
||||
) -> Box<dyn Element> {
|
||||
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())
|
||||
@@ -81,8 +81,8 @@ pub fn init(app: &mut AppContext) {
|
||||
app.register_binding_validator::<TerminalView>(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,
|
||||
)
|
||||
|
||||
@@ -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<SshTmuxDeprecationBanner>,
|
||||
},
|
||||
WarpifySuccessBlock {
|
||||
bootstrap_success_block_handle: ViewHandle<WarpifySuccessBlock>,
|
||||
WormholeSuccessBlock {
|
||||
bootstrap_success_block_handle: ViewHandle<WormholeSuccessBlock>,
|
||||
},
|
||||
TelemetryBanner {
|
||||
telemetry_banner_handle: ViewHandle<TelemetryBanner>,
|
||||
|
||||
@@ -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 ");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AgentInputFooter>,
|
||||
|
||||
// Warpify footer UI (shown when a subshell/SSH command is detected).
|
||||
warpify_footer_view: ViewHandle<WarpifyFooterView>,
|
||||
// Wormhole footer UI (shown when a subshell/SSH command is detected).
|
||||
wormhole_footer_view: ViewHandle<WormholeFooterView>,
|
||||
|
||||
// `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<Self>,
|
||||
) {
|
||||
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<Self>) {
|
||||
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>) {
|
||||
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>) {
|
||||
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>) {
|
||||
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>) {
|
||||
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<dyn Element> {
|
||||
// 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,
|
||||
|
||||
+33
-33
@@ -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<FairMutex<TerminalModel>>,
|
||||
warpify_button: ViewHandle<ActionButton>,
|
||||
wormhole_button: ViewHandle<ActionButton>,
|
||||
use_agent_button: ViewHandle<ActionButton>,
|
||||
dismiss_button: ViewHandle<ActionButton>,
|
||||
/// Whether the footer is currently offering subshell warpification.
|
||||
/// Whether the footer is currently offering subshell wormholing.
|
||||
is_active: bool,
|
||||
}
|
||||
|
||||
impl WarpifyFooterView {
|
||||
impl WormholeFooterView {
|
||||
pub fn new(terminal_model: Arc<FairMutex<TerminalModel>>, ctx: &mut ViewContext<Self>) -> 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>) {
|
||||
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>) {
|
||||
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<dyn Element> {
|
||||
@@ -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<Self>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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<dyn Element> {
|
||||
.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<String>,
|
||||
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<Box<dyn Element>> {
|
||||
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,
|
||||
)
|
||||
@@ -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<Regex> here b/c Regex doesn't impl Eq, Serialize, and Deserialize.
|
||||
maybe_define_setting!(AddedSubshellCommands, group: WarpifySettings, {
|
||||
maybe_define_setting!(AddedSubshellCommands, group: WormholeSettings, {
|
||||
type: Vec<String>,
|
||||
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<String>,
|
||||
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<String>,
|
||||
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<String>.
|
||||
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<Result<Regex, regex::Error>>,
|
||||
/// A list of commands that we shouldn't attempt to warpify. These can be added either b/c the
|
||||
/// A list of commands that we shouldn't attempt to wormhole. These can be added either b/c the
|
||||
/// "don't ask again" button was clicked in the trigger banner, or it was added explicitly on
|
||||
/// 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<Result<Regex, regex::Error>>,
|
||||
|
||||
/// A list of hosts that we shouldn't attempt to warpify. This supports regex.
|
||||
/// A list of hosts that we shouldn't attempt to wormhole. This supports regex.
|
||||
/// These can be added either b/c the "don't ask again" button was clicked in the trigger banner,
|
||||
/// 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<Result<Regex, regex::Error>>,
|
||||
|
||||
/// 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>) -> 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
|
||||
+20
-20
@@ -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
|
||||
)
|
||||
+51
-99
@@ -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<AutoWarpifySnippet>,
|
||||
auto_wormhole_snippet: Option<AutoWormholeSnippet>,
|
||||
}
|
||||
|
||||
impl WarpifySuccessBlock {
|
||||
impl WormholeSuccessBlock {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new(
|
||||
source: WarpificationSource,
|
||||
spawning_command: String,
|
||||
subshell_info: Option<SubshellInitializationInfo>,
|
||||
shell: Shell,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) -> 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<String> {
|
||||
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<dyn Element> {
|
||||
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<dyn Element> {
|
||||
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>) {
|
||||
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>) {
|
||||
self.clear_auto_warpify_snippet(ctx);
|
||||
}
|
||||
|
||||
pub fn clear_auto_warpify_snippet(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.auto_warpify_snippet = None;
|
||||
pub fn clear_auto_wormhole_snippet(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.auto_wormhole_snippet = None;
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
@@ -231,16 +186,16 @@ impl WarpifySuccessBlock {
|
||||
appearance: &Appearance,
|
||||
) -> Option<Box<dyn Element>> {
|
||||
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<dyn Element> {
|
||||
@@ -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<Self>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
-39
@@ -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<WarpifySuccessBlock>,
|
||||
WormholeSuccess {
|
||||
handle: ViewHandle<WormholeSuccessBlock>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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<TerminalView>,
|
||||
) -> Option<EntityId> {
|
||||
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<BlockId>,
|
||||
|
||||
/// Lets us abort an attempt to auto warpify if the subshell command
|
||||
/// Lets us abort an attempt to auto wormhole if the subshell command
|
||||
/// hasn't completed.
|
||||
auto_warpify_abort_handle: Option<SpawnedFutureHandle>,
|
||||
auto_wormhole_abort_handle: Option<SpawnedFutureHandle>,
|
||||
|
||||
/// The subshell banner waits 1s before showing. This is to see that the command stays running
|
||||
/// 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<SpawnedFutureHandle>,
|
||||
|
||||
/// The command which may trigger ssh Warpification
|
||||
/// The command which may trigger ssh Wormholing
|
||||
pending_command: Option<String>,
|
||||
/// The Host which may trigger ssh Warpification
|
||||
pending_warpify_ssh_host: Option<String>,
|
||||
/// The Host which may trigger ssh Wormholing
|
||||
pending_wormhole_ssh_host: Option<String>,
|
||||
|
||||
/// Which, if any, SSH block is currently added to the blocklist.
|
||||
ssh_block_state: Option<SshBlockState>,
|
||||
|
||||
ssh_warpify_timeout_handle: Option<SpawnedFutureHandle>,
|
||||
ssh_wormhole_timeout_handle: Option<SpawnedFutureHandle>,
|
||||
|
||||
shell_type: Option<ShellType>,
|
||||
|
||||
@@ -101,17 +101,17 @@ struct WarpifyTriggerState {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WarpifyState {
|
||||
pub struct WormholeState {
|
||||
session_id: Option<SessionId>,
|
||||
|
||||
pending_state: Option<WarpifyTriggerState>,
|
||||
pending_state: Option<WormholeTriggerState>,
|
||||
/// Stores the metadata needed to render any separators above the first block of a subshell.
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<String>) {
|
||||
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<TerminalView>,
|
||||
) -> Option<EntityId> {
|
||||
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<SessionId>) {
|
||||
pub fn on_wormhole_start(&mut self, active_session_id: Option<SessionId>) {
|
||||
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<SessionId>,
|
||||
ctx: &mut ViewContext<TerminalView>,
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<T: EventLoopSender> RemoteServerController<T> {
|
||||
});
|
||||
}
|
||||
Ok(false) => {
|
||||
let install_mode = *WarpifySettings::as_ref(ctx)
|
||||
let install_mode = *WormholeSettings::as_ref(ctx)
|
||||
.ssh_extension_install_mode
|
||||
.value();
|
||||
match install_mode {
|
||||
|
||||
Reference in New Issue
Block a user