188 lines
6.1 KiB
Rust
188 lines
6.1 KiB
Rust
use asset_macro::bundled_asset;
|
|
use galaxy_core::ui::theme::WarpTheme;
|
|
use galaxyui::assets::asset_cache::{AssetCache, AssetState};
|
|
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::ui_components::icons::Icon as UiIcon;
|
|
use galaxyui::elements::{HighlightedHyperlink, Hoverable, Icon, MouseStateHandle};
|
|
use galaxyui::keymap::FixedBinding;
|
|
use galaxyui::AppContext;
|
|
use galaxyui::{
|
|
elements::{Border, Container, CrossAxisAlignment, Flex, ParentElement},
|
|
Element, Entity, SingletonEntity, TypedActionView, View, ViewContext,
|
|
};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum SshWarpifyBlockEvent {
|
|
WarpifySession,
|
|
Cancel,
|
|
Interrupt,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub enum SshWarpifyBlockAction {
|
|
Interrupt,
|
|
Focus,
|
|
}
|
|
|
|
pub struct SshWarpifyBlock {
|
|
block_mouse_state: MouseStateHandle,
|
|
ssh_command: String,
|
|
}
|
|
|
|
pub fn init(app: &mut AppContext) {
|
|
use galaxyui::keymap::macros::*;
|
|
|
|
app.register_fixed_bindings([FixedBinding::new(
|
|
"ctrl-c",
|
|
SshWarpifyBlockAction::Interrupt,
|
|
id!(SshWarpifyBlock::ui_name()),
|
|
)]);
|
|
}
|
|
|
|
impl SshWarpifyBlock {
|
|
#[allow(clippy::new_without_default)]
|
|
pub fn new(ssh_command: String) -> Self {
|
|
Self {
|
|
block_mouse_state: Default::default(),
|
|
ssh_command,
|
|
}
|
|
}
|
|
|
|
pub fn focus(&self, ctx: &mut ViewContext<Self>) {
|
|
ctx.focus_self();
|
|
ctx.notify();
|
|
}
|
|
}
|
|
|
|
impl Entity for SshWarpifyBlock {
|
|
type Event = SshWarpifyBlockEvent;
|
|
}
|
|
|
|
impl SshWarpifyBlock {
|
|
fn render_title_ui(&self, theme: &WarpTheme, appearance: &Appearance) -> Box<dyn Element> {
|
|
let icon = Icon::new(UiIcon::Warp.into(), theme.active_ui_detail());
|
|
warpify::render::header_row("Galaxyizeing SSH Session...", icon, theme, appearance)
|
|
}
|
|
}
|
|
|
|
pub fn warpify_description(
|
|
app: &AppContext,
|
|
hyperlink_index: &HighlightedHyperlink,
|
|
) -> Box<dyn Element> {
|
|
let appearance = Appearance::as_ref(app);
|
|
let theme = appearance.theme();
|
|
|
|
let description = FormattedText::new(vec![FormattedTextLine::Line(vec![
|
|
FormattedTextFragment::plain_text(
|
|
"Bring Warp's features to your remote session. Blocks, full text editing, auto-complete, 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);
|
|
})
|
|
.finish()
|
|
}
|
|
|
|
impl View for SshWarpifyBlock {
|
|
fn ui_name() -> &'static str {
|
|
"SshWarpifyBlock"
|
|
}
|
|
|
|
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
|
let appearance = Appearance::as_ref(app);
|
|
let theme = appearance.theme();
|
|
|
|
let mut content = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch);
|
|
|
|
content.add_child(self.render_title_ui(theme, appearance));
|
|
|
|
content.add_child(
|
|
Container::new(
|
|
RenderableAction::new(&self.ssh_command, app)
|
|
.with_background_color(theme.background().into_solid())
|
|
.render(app)
|
|
.finish(),
|
|
)
|
|
.with_margin_top(16.)
|
|
.finish(),
|
|
);
|
|
|
|
Hoverable::new(self.block_mouse_state.clone(), |_| {
|
|
Container::new(content.finish())
|
|
.with_padding_top(10.)
|
|
.with_background(theme.foreground().with_opacity(10))
|
|
.with_border(Border::top(1.).with_border_fill(theme.outline()))
|
|
.finish()
|
|
})
|
|
.on_click(|ctx, _, _| {
|
|
ctx.dispatch_typed_action(SshWarpifyBlockAction::Focus);
|
|
})
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl TypedActionView for SshWarpifyBlock {
|
|
type Action = SshWarpifyBlockAction;
|
|
|
|
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
|
match action {
|
|
SshWarpifyBlockAction::Interrupt => {
|
|
ctx.emit(SshWarpifyBlockEvent::Interrupt);
|
|
}
|
|
SshWarpifyBlockAction::Focus => {
|
|
self.focus(ctx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert the begin_warpify_ssh_session script into a string.
|
|
pub fn begin_warpify_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"),
|
|
}
|
|
}
|
|
|
|
/// Convert the warpify_ssh_session script into a string.
|
|
pub fn warpify_ssh_session_command(
|
|
uname: &str,
|
|
shell_type: ShellType,
|
|
app: &AppContext,
|
|
) -> Option<String> {
|
|
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")
|
|
}
|
|
// 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"),
|
|
(_, ShellType::Zsh | ShellType::Bash) => {
|
|
bundled_asset!("ssh/bash_zsh/warpify_ssh_session.sh")
|
|
}
|
|
(_, ShellType::Fish) => bundled_asset!("ssh/fish/warpify_ssh_session.sh"),
|
|
// PowerShell is not supported yet.
|
|
(_, ShellType::PowerShell) => return None,
|
|
};
|
|
|
|
// 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"),
|
|
}
|
|
}
|
|
#[cfg(test)]
|
|
#[path = "warpify_test.rs"]
|
|
mod tests;
|