par_term/
remote_shell_install_ui.rs1const INSTALL_URL: &str = "https://paulrobello.github.io/par-term/install-shell-integration.sh";
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum RemoteShellInstallAction {
13 Install,
15 Cancel,
17 None,
19}
20
21pub struct RemoteShellInstallUI {
23 visible: bool,
25 copy_feedback: Option<std::time::Instant>,
27}
28
29impl Default for RemoteShellInstallUI {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl RemoteShellInstallUI {
36 pub fn new() -> Self {
38 Self {
39 visible: false,
40 copy_feedback: None,
41 }
42 }
43
44 pub fn is_visible(&self) -> bool {
46 self.visible
47 }
48
49 pub fn show_dialog(&mut self) {
51 self.visible = true;
52 self.copy_feedback = None;
53 }
54
55 fn hide(&mut self) {
57 self.visible = false;
58 self.copy_feedback = None;
59 }
60
61 pub fn install_command() -> String {
63 format!("curl -sSL {} | sh", INSTALL_URL)
64 }
65
66 pub fn show(&mut self, ctx: &egui::Context) -> RemoteShellInstallAction {
68 if !self.visible {
69 return RemoteShellInstallAction::None;
70 }
71
72 let mut action = RemoteShellInstallAction::None;
73 let command = Self::install_command();
74
75 ctx.request_repaint();
77
78 egui::Window::new("Install Shell Integration on Remote Host")
79 .collapsible(false)
80 .resizable(false)
81 .order(egui::Order::Foreground)
82 .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
83 .show(ctx, |ui| {
84 ui.vertical_centered(|ui| {
85 ui.add_space(10.0);
86
87 ui.label(
88 egui::RichText::new("Send Install Command to Terminal")
89 .size(16.0)
90 .strong(),
91 );
92 ui.add_space(8.0);
93
94 ui.label("This will send the following command to the active terminal:");
95 ui.add_space(8.0);
96
97 egui::Frame::new()
99 .fill(egui::Color32::from_rgba_unmultiplied(40, 40, 40, 220))
100 .inner_margin(egui::Margin::symmetric(12, 8))
101 .corner_radius(4.0)
102 .show(ui, |ui| {
103 ui.label(
104 egui::RichText::new(&command)
105 .color(egui::Color32::LIGHT_GREEN)
106 .monospace()
107 .size(13.0),
108 );
109 });
110
111 ui.add_space(4.0);
112
113 let copy_label = if self
115 .copy_feedback
116 .is_some_and(|t| t.elapsed().as_millis() < 1500)
117 {
118 "Copied!"
119 } else {
120 "Copy Command"
121 };
122 if ui
123 .button(egui::RichText::new(copy_label).size(12.0))
124 .clicked()
125 {
126 ctx.copy_text(command.clone());
127 self.copy_feedback = Some(std::time::Instant::now());
128 }
129
130 ui.add_space(10.0);
131
132 ui.label(
134 egui::RichText::new(
135 "Only use this when SSH'd into a remote host that needs shell integration.",
136 )
137 .color(egui::Color32::YELLOW)
138 .size(12.0),
139 );
140
141 ui.add_space(15.0);
142
143 ui.horizontal(|ui| {
145 if ui.button("Install").clicked() {
146 action = RemoteShellInstallAction::Install;
147 }
148
149 ui.add_space(10.0);
150
151 if ui.button("Cancel").clicked() {
152 action = RemoteShellInstallAction::Cancel;
153 }
154 });
155 ui.add_space(10.0);
156 });
157 });
158
159 if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
161 action = RemoteShellInstallAction::Cancel;
162 }
163
164 if ctx.input(|i| i.key_pressed(egui::Key::Enter)) {
166 action = RemoteShellInstallAction::Install;
167 }
168
169 if !matches!(action, RemoteShellInstallAction::None) {
171 self.hide();
172 }
173
174 action
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_install_command_format() {
184 let cmd = RemoteShellInstallUI::install_command();
185 assert!(cmd.starts_with("curl"));
186 assert!(cmd.contains("paulrobello.github.io/par-term"));
187 assert!(cmd.contains("install-shell-integration.sh"));
188 assert!(cmd.ends_with("| sh"));
189 }
190
191 #[test]
192 fn test_dialog_initial_state() {
193 let ui = RemoteShellInstallUI::new();
194 assert!(!ui.is_visible());
195 }
196
197 #[test]
198 fn test_dialog_show_hide() {
199 let mut ui = RemoteShellInstallUI::new();
200 assert!(!ui.is_visible());
201
202 ui.show_dialog();
203 assert!(ui.is_visible());
204
205 ui.hide();
206 assert!(!ui.is_visible());
207 }
208
209 #[test]
210 fn test_default_impl() {
211 let ui = RemoteShellInstallUI::default();
212 assert!(!ui.is_visible());
213 }
214}