Skip to main content

par_term/app/tab_ops/
tab_helpers.rs

1//! Tab query and duplication helpers for WindowState.
2//!
3//! Contains:
4//! - `duplicate_tab`, `duplicate_tab_by_id` — duplicate an existing tab
5//! - `has_multiple_tabs` — query predicate
6//! - `active_terminal` — accessor for the active tab's terminal
7//! - `check_current_tab_running_job` — running-job confirmation gate
8
9//! - Debug logging for close confirmation flow
10
11use std::sync::Arc;
12
13use super::super::window_state::WindowState;
14
15impl WindowState {
16    /// Duplicate current tab
17    pub fn duplicate_tab(&mut self) {
18        // Get current grid size from renderer
19        let grid_size = self.renderer.as_ref().map(|r| r.grid_size());
20
21        match self.tab_manager.duplicate_active_tab(
22            &self.config.load(),
23            Arc::clone(&self.runtime),
24            grid_size,
25        ) {
26            Ok(Some(tab_id)) => {
27                // Start refresh task for the new tab
28                if let Some(window) = &self.window
29                    && let Some(tab) = self.tab_manager.get_tab_mut(tab_id)
30                {
31                    tab.start_refresh_task(
32                        Arc::clone(&self.runtime),
33                        Arc::clone(window),
34                        self.config.load().rendering.max_fps,
35                        self.config.load().power.inactive_tab_fps,
36                    );
37                }
38                self.focus_state.needs_redraw = true;
39                self.request_redraw();
40            }
41            Ok(None) => {
42                log::debug!("No active tab to duplicate");
43            }
44            Err(e) => {
45                log::error!("Failed to duplicate tab: {}", e);
46            }
47        }
48    }
49
50    /// Duplicate a specific tab by ID
51    pub fn duplicate_tab_by_id(&mut self, source_tab_id: crate::tab::TabId) {
52        let grid_size = self.renderer.as_ref().map(|r| r.grid_size());
53
54        match self.tab_manager.duplicate_tab_by_id(
55            source_tab_id,
56            &self.config.load(),
57            Arc::clone(&self.runtime),
58            grid_size,
59        ) {
60            Ok(Some(tab_id)) => {
61                if let Some(window) = &self.window
62                    && let Some(tab) = self.tab_manager.get_tab_mut(tab_id)
63                {
64                    tab.start_refresh_task(
65                        Arc::clone(&self.runtime),
66                        Arc::clone(window),
67                        self.config.load().rendering.max_fps,
68                        self.config.load().power.inactive_tab_fps,
69                    );
70                }
71                self.focus_state.needs_redraw = true;
72                self.request_redraw();
73            }
74            Ok(None) => {
75                log::debug!("Tab {} not found for duplication", source_tab_id);
76            }
77            Err(e) => {
78                log::error!("Failed to duplicate tab {}: {}", source_tab_id, e);
79            }
80        }
81    }
82
83    /// Check if there are multiple tabs
84    pub fn has_multiple_tabs(&self) -> bool {
85        self.tab_manager.has_multiple_tabs()
86    }
87
88    /// Get the active tab's terminal
89    pub fn active_terminal(
90        &self,
91    ) -> Option<&Arc<tokio::sync::RwLock<par_term_terminal::TerminalManager>>> {
92        self.tab_manager.active_tab().map(|tab| &tab.terminal)
93    }
94
95    /// Check if the current tab's terminal has a running job that should trigger confirmation
96    ///
97    /// Returns Some(command_name) if confirmation should be shown, None otherwise.
98    pub(super) fn check_current_tab_running_job(&self) -> Option<String> {
99        let tab = self.tab_manager.active_tab()?;
100        // blocking_read: user-initiated close — we must not silently skip confirmation.
101        // should_confirm_close() only needs &self so a shared read lock is correct.
102        // blocking_read() waits for any active writer (e.g. async key/mouse task) to
103        // finish rather than returning None and letting the tab close without prompting.
104        let term = tab.terminal.blocking_read();
105        log::info!(
106            "[CLOSE_CONFIRM] checking: confirm_close_running_jobs={} jobs_to_ignore_len={}",
107            self.config.load().shell.confirm_close_running_jobs,
108            self.config.load().shell.jobs_to_ignore.len()
109        );
110        let marker = term.shell_integration_marker();
111        let command_name = term.shell_integration_command();
112        let is_command_running = term.is_command_running();
113        log::info!(
114            "[CLOSE_CONFIRM] shell_integration: marker={:?} command_name={:?} is_command_running={}",
115            marker,
116            command_name,
117            is_command_running
118        );
119        let result = term.should_confirm_close(&self.config.load().shell.jobs_to_ignore);
120        log::info!("[CLOSE_CONFIRM] should_confirm_close result={:?}", result);
121        result
122    }
123}