feat: expand Galaxy agent and remote tooling

Add Wormhole remote helpers, provider and agent improvements, filesystem diagnostics, model metadata support, and schema-aware settings IntelliSense.
This commit is contained in:
2026-08-23 13:55:47 -05:00
parent f17642fc62
commit 7c106eecd5
147 changed files with 2208 additions and 1514 deletions
+50 -50
View File
@@ -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 => {
+30 -47
View File
@@ -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,
};
+12 -12
View File
@@ -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(),
}
+18 -16
View File
@@ -78,7 +78,7 @@ pub fn check_ssh_login_state(block_output: &str) -> SshLoginState {
}
/// Represents the parsed components of an interactive SSH command.
/// 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,
)
});