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:
@@ -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,
|
||||
)
|
||||
});
|
||||
Reference in New Issue
Block a user