Skip to main content

par_term/
close_confirmation_ui.rs

1//! Close confirmation dialog for tabs with running jobs.
2//!
3//! Shows a confirmation dialog when the user attempts to close a terminal tab
4//! that has a running command (detected via shell integration). Allows the user
5//! to either force close the tab or cancel the close operation.
6
7use crate::tab::TabId;
8
9/// Action returned by the close confirmation dialog
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum CloseConfirmAction {
12    /// User confirmed - close the tab/pane with the given IDs
13    Close {
14        tab_id: TabId,
15        pane_id: Option<crate::pane::PaneId>,
16    },
17    /// User cancelled - keep the tab open
18    Cancel,
19    /// No action yet (dialog still showing)
20    None,
21}
22
23/// State for the close confirmation dialog
24pub struct CloseConfirmationUI {
25    /// Whether the dialog is visible
26    visible: bool,
27    /// The tab ID pending close
28    pending_tab_id: Option<TabId>,
29    /// The pane ID pending close (None means close entire tab)
30    pending_pane_id: Option<crate::pane::PaneId>,
31    /// The name of the running command
32    command_name: String,
33    /// The tab title for display
34    tab_title: String,
35}
36
37impl Default for CloseConfirmationUI {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl CloseConfirmationUI {
44    /// Create a new close confirmation UI
45    pub fn new() -> Self {
46        Self {
47            visible: false,
48            pending_tab_id: None,
49            pending_pane_id: None,
50            command_name: String::new(),
51            tab_title: String::new(),
52        }
53    }
54
55    /// Check if the dialog is currently visible
56    pub fn is_visible(&self) -> bool {
57        self.visible
58    }
59
60    /// Show the confirmation dialog for a tab with a running command
61    pub fn show_for_tab(&mut self, tab_id: TabId, tab_title: &str, command_name: &str) {
62        self.visible = true;
63        self.pending_tab_id = Some(tab_id);
64        self.pending_pane_id = None;
65        self.command_name = command_name.to_string();
66        self.tab_title = tab_title.to_string();
67    }
68
69    /// Show the confirmation dialog for a pane with a running command
70    pub fn show_for_pane(
71        &mut self,
72        tab_id: TabId,
73        pane_id: crate::pane::PaneId,
74        tab_title: &str,
75        command_name: &str,
76    ) {
77        self.visible = true;
78        self.pending_tab_id = Some(tab_id);
79        self.pending_pane_id = Some(pane_id);
80        self.command_name = command_name.to_string();
81        self.tab_title = tab_title.to_string();
82    }
83
84    /// Hide the dialog and clear state
85    fn hide(&mut self) {
86        self.visible = false;
87        self.pending_tab_id = None;
88        self.pending_pane_id = None;
89        self.command_name.clear();
90        self.tab_title.clear();
91    }
92
93    /// Render the dialog and return any action
94    pub fn show(&mut self, ctx: &egui::Context) -> CloseConfirmAction {
95        if !self.visible {
96            return CloseConfirmAction::None;
97        }
98
99        let mut action = CloseConfirmAction::None;
100
101        egui::Window::new("Close Tab?")
102            .collapsible(false)
103            .resizable(false)
104            .order(egui::Order::Foreground)
105            .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
106            .show(ctx, |ui| {
107                ui.vertical_centered(|ui| {
108                    ui.add_space(10.0);
109
110                    // Warning icon and title
111                    ui.label(
112                        egui::RichText::new("⚠ Running Job Detected")
113                            .color(egui::Color32::YELLOW)
114                            .size(18.0)
115                            .strong(),
116                    );
117                    ui.add_space(10.0);
118
119                    // Tab/pane info
120                    let target = if self.pending_pane_id.is_some() {
121                        "pane"
122                    } else {
123                        "tab"
124                    };
125                    ui.label(format!(
126                        "The {} \"{}\" has a running command:",
127                        target, self.tab_title
128                    ));
129                    ui.add_space(5.0);
130
131                    // Command name in a highlighted box
132                    ui.horizontal(|ui| {
133                        ui.add_space(20.0);
134                        egui::Frame::new()
135                            .fill(egui::Color32::from_rgba_unmultiplied(60, 60, 60, 200))
136                            .inner_margin(egui::Margin::symmetric(12, 6))
137                            .corner_radius(4.0)
138                            .show(ui, |ui| {
139                                ui.label(
140                                    egui::RichText::new(&self.command_name)
141                                        .color(egui::Color32::LIGHT_GREEN)
142                                        .monospace()
143                                        .size(14.0),
144                                );
145                            });
146                    });
147
148                    ui.add_space(10.0);
149                    ui.label(
150                        egui::RichText::new("Closing will terminate this process.")
151                            .color(egui::Color32::GRAY),
152                    );
153                    ui.add_space(15.0);
154
155                    // Buttons
156                    ui.horizontal(|ui| {
157                        // Close button with danger styling
158                        let close_button = egui::Button::new(
159                            egui::RichText::new("Close Anyway").color(egui::Color32::WHITE),
160                        )
161                        .fill(egui::Color32::from_rgb(180, 50, 50));
162
163                        if ui.add(close_button).clicked() {
164                            // Capture IDs before we hide
165                            if let Some(tab_id) = self.pending_tab_id {
166                                action = CloseConfirmAction::Close {
167                                    tab_id,
168                                    pane_id: self.pending_pane_id,
169                                };
170                            }
171                        }
172
173                        ui.add_space(10.0);
174
175                        if ui.button("Cancel").clicked() {
176                            action = CloseConfirmAction::Cancel;
177                        }
178                    });
179                    ui.add_space(10.0);
180                });
181            });
182
183        // Handle escape key to cancel
184        if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
185            action = CloseConfirmAction::Cancel;
186        }
187
188        // Hide dialog on any action
189        if !matches!(action, CloseConfirmAction::None) {
190            self.hide();
191        }
192
193        action
194    }
195}