par-term 0.29.0

Cross-platform GPU-accelerated terminal emulator with inline graphics support (Sixel, iTerm2, Kitty)
//! Remote shell integration install confirmation dialog.
//!
//! Shows a confirmation dialog when the user selects "Install Shell Integration
//! on Remote Host" from the Shell menu. Displays the exact curl command that will
//! be sent to the active terminal and lets the user confirm or cancel.

/// The install command URL
const INSTALL_URL: &str = "https://paulrobello.github.io/par-term/install-shell-integration.sh";

/// Action returned by the remote shell install dialog
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RemoteShellInstallAction {
    /// User confirmed - send the install command to the active terminal
    Install,
    /// User cancelled
    Cancel,
    /// No action yet (dialog still showing or not visible)
    None,
}

/// State for the remote shell integration install dialog
pub struct RemoteShellInstallUI {
    /// Whether the dialog is visible
    visible: bool,
    /// Brief flash message after copy
    copy_feedback: Option<std::time::Instant>,
}

impl Default for RemoteShellInstallUI {
    fn default() -> Self {
        Self::new()
    }
}

impl RemoteShellInstallUI {
    /// Create a new remote shell install UI
    pub fn new() -> Self {
        Self {
            visible: false,
            copy_feedback: None,
        }
    }

    /// Check if the dialog is currently visible
    pub fn is_visible(&self) -> bool {
        self.visible
    }

    /// Show the confirmation dialog
    pub fn show_dialog(&mut self) {
        self.visible = true;
        self.copy_feedback = None;
    }

    /// Hide the dialog
    fn hide(&mut self) {
        self.visible = false;
        self.copy_feedback = None;
    }

    /// Get the install command string
    pub fn install_command() -> String {
        format!("curl -sSL {} | sh", INSTALL_URL)
    }

    /// Render the dialog and return any action
    pub fn show(&mut self, ctx: &egui::Context) -> RemoteShellInstallAction {
        if !self.visible {
            return RemoteShellInstallAction::None;
        }

        let mut action = RemoteShellInstallAction::None;
        let command = Self::install_command();

        // Request continuous repaints while dialog is visible to ensure clicks are processed
        ctx.request_repaint();

        egui::Window::new("Install Shell Integration on Remote Host")
            .collapsible(false)
            .resizable(false)
            .order(egui::Order::Foreground)
            .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
            .show(ctx, |ui| {
                ui.vertical_centered(|ui| {
                    ui.add_space(10.0);

                    ui.label(
                        egui::RichText::new("Send Install Command to Terminal")
                            .size(16.0)
                            .strong(),
                    );
                    ui.add_space(8.0);

                    ui.label("This will send the following command to the active terminal:");
                    ui.add_space(8.0);

                    // Command preview in a highlighted code block
                    egui::Frame::new()
                        .fill(egui::Color32::from_rgba_unmultiplied(40, 40, 40, 220))
                        .inner_margin(egui::Margin::symmetric(12, 8))
                        .corner_radius(4.0)
                        .show(ui, |ui| {
                            ui.label(
                                egui::RichText::new(&command)
                                    .color(egui::Color32::LIGHT_GREEN)
                                    .monospace()
                                    .size(13.0),
                            );
                        });

                    ui.add_space(4.0);

                    // Copy button on its own line
                    let copy_label = if self
                        .copy_feedback
                        .is_some_and(|t| t.elapsed().as_millis() < 1500)
                    {
                        "Copied!"
                    } else {
                        "Copy Command"
                    };
                    if ui
                        .button(egui::RichText::new(copy_label).size(12.0))
                        .clicked()
                    {
                        ctx.copy_text(command.clone());
                        self.copy_feedback = Some(std::time::Instant::now());
                    }

                    ui.add_space(10.0);

                    // Warning
                    ui.label(
                        egui::RichText::new(
                            "Only use this when SSH'd into a remote host that needs shell integration.",
                        )
                        .color(egui::Color32::YELLOW)
                        .size(12.0),
                    );

                    ui.add_space(15.0);

                    // Buttons
                    ui.horizontal(|ui| {
                        if ui.button("Install").clicked() {
                            action = RemoteShellInstallAction::Install;
                        }

                        ui.add_space(10.0);

                        if ui.button("Cancel").clicked() {
                            action = RemoteShellInstallAction::Cancel;
                        }
                    });
                    ui.add_space(10.0);
                });
            });

        // Handle escape key to cancel
        if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
            action = RemoteShellInstallAction::Cancel;
        }

        // Handle enter key to confirm install
        if ctx.input(|i| i.key_pressed(egui::Key::Enter)) {
            action = RemoteShellInstallAction::Install;
        }

        // Hide dialog on any action (except None)
        if !matches!(action, RemoteShellInstallAction::None) {
            self.hide();
        }

        action
    }
}

impl crate::traits::OverlayComponent for RemoteShellInstallUI {
    type Action = RemoteShellInstallAction;

    fn show(&mut self, ctx: &egui::Context) -> Self::Action {
        RemoteShellInstallUI::show(self, ctx)
    }

    fn is_visible(&self) -> bool {
        self.is_visible()
    }

    fn set_visible(&mut self, visible: bool) {
        if !visible {
            self.hide();
        } else {
            self.show_dialog();
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_install_command_format() {
        let cmd = RemoteShellInstallUI::install_command();
        assert!(cmd.starts_with("curl"));
        assert!(cmd.contains("paulrobello.github.io/par-term"));
        assert!(cmd.contains("install-shell-integration.sh"));
        assert!(cmd.ends_with("| sh"));
    }

    #[test]
    fn test_dialog_initial_state() {
        let ui = RemoteShellInstallUI::new();
        assert!(!ui.is_visible());
    }

    #[test]
    fn test_dialog_show_hide() {
        let mut ui = RemoteShellInstallUI::new();
        assert!(!ui.is_visible());

        ui.show_dialog();
        assert!(ui.is_visible());

        ui.hide();
        assert!(!ui.is_visible());
    }

    #[test]
    fn test_default_impl() {
        let ui = RemoteShellInstallUI::default();
        assert!(!ui.is_visible());
    }
}