par_term/app/tab_ops/
pane_ops.rs1use std::sync::Arc;
4
5use super::super::window_state::WindowState;
6
7impl WindowState {
8 pub(crate) fn split_pane_direction(
17 &mut self,
18 direction: crate::pane::SplitDirection,
19 focus_new: bool,
20 initial_command: Option<(String, Vec<String>)>,
21 split_percent: u8,
22 ) -> Option<crate::pane::PaneId> {
23 let is_tmux_connected = self.is_tmux_connected();
25 let status_bar_height = crate::tmux_status_bar_ui::TmuxStatusBarUI::height(
26 &self.config.load(),
27 is_tmux_connected,
28 );
29 let custom_status_bar_height = self
30 .status_bar_ui
31 .height(&self.config.load(), self.is_fullscreen);
32
33 let bounds_info = self.renderer.as_ref().map(|r| {
35 let size = r.size();
36 let padding = r.window_padding();
37 let content_offset_y = r.content_offset_y();
38 let cell_width = r.cell_width();
39 let cell_height = r.cell_height();
40 let scale = r.scale_factor();
41 (
42 size,
43 padding,
44 content_offset_y,
45 cell_width,
46 cell_height,
47 scale,
48 )
49 });
50
51 let dpi_scale = bounds_info.map(|b| b.5).unwrap_or(1.0);
52
53 let tab = self.tab_manager.active_tab_mut()?;
54
55 if let Some((size, padding, content_offset_y, cell_width, cell_height, scale)) = bounds_info
57 {
58 let effective_padding = if self.config.load().window.hide_window_padding_on_split {
60 0.0
61 } else {
62 padding
63 };
64 let physical_status_bar_height = (status_bar_height + custom_status_bar_height) * scale;
66 let content_width = size.width as f32 - effective_padding * 2.0;
67 let content_height = size.height as f32
68 - content_offset_y
69 - effective_padding
70 - physical_status_bar_height;
71 let bounds = crate::pane::PaneBounds::new(
72 effective_padding,
73 content_offset_y,
74 content_width,
75 content_height,
76 );
77 tab.set_pane_bounds(bounds, cell_width, cell_height);
78 }
79
80 let result = match direction {
81 crate::pane::SplitDirection::Horizontal => tab.split_horizontal(
82 focus_new,
83 &self.config.load(),
84 Arc::clone(&self.runtime),
85 dpi_scale,
86 initial_command,
87 split_percent,
88 ),
89 crate::pane::SplitDirection::Vertical => tab.split_vertical(
90 focus_new,
91 &self.config.load(),
92 Arc::clone(&self.runtime),
93 dpi_scale,
94 initial_command,
95 split_percent,
96 ),
97 };
98
99 match result {
100 Ok(Some(pane_id)) => {
101 log::info!("Split pane {:?}, new pane {}", direction, pane_id);
102 if let Some(renderer) = &mut self.renderer {
104 renderer.clear_all_cells();
105 }
106 if let Some(tab) = self.tab_manager.active_tab_mut() {
108 tab.active_cache_mut().cells = None;
109 }
110 if let Some(window) = &self.window
112 && let Some(tab) = self.tab_manager.active_tab_mut()
113 {
114 tab.start_pane_refresh_tasks(
115 Arc::clone(&self.runtime),
116 Arc::clone(window),
117 self.config.load().rendering.max_fps,
118 self.config.load().power.inactive_tab_fps,
119 );
120 }
121 self.focus_state.needs_redraw = true;
122 self.request_redraw();
123 Some(pane_id)
124 }
125 Ok(None) => {
126 log::info!(
127 "{:?} split not yet functional (renderer integration pending)",
128 direction
129 );
130 None
131 }
132 Err(e) => {
133 log::error!("Failed to split pane {:?}: {}", direction, e);
134 None
135 }
136 }
137 }
138
139 pub fn split_pane_horizontal(&mut self) {
141 #[cfg(feature = "mux")]
145 if self.split_pane_via_mux(false) {
146 return;
147 }
148 if self.is_tmux_connected() && self.split_pane_via_tmux(false) {
150 crate::debug_info!("TMUX", "Sent horizontal split command to tmux");
151 return;
152 }
153 self.split_pane_direction(crate::pane::SplitDirection::Horizontal, true, None, 50);
155 }
156
157 pub fn split_pane_vertical(&mut self) {
159 #[cfg(feature = "mux")]
163 if self.split_pane_via_mux(true) {
164 return;
165 }
166 if self.is_tmux_connected() && self.split_pane_via_tmux(true) {
168 crate::debug_info!("TMUX", "Sent vertical split command to tmux");
169 return;
170 }
171 self.split_pane_direction(crate::pane::SplitDirection::Vertical, true, None, 50);
173 }
174
175 pub fn close_focused_pane(&mut self) -> bool {
180 #[cfg(feature = "mux")]
184 {
185 if self.tmux_state.transport.is_some()
194 && self.focused_mux_pane_from_native().is_some()
195 && !self
196 .tab_manager
197 .active_tab()
198 .is_some_and(|tab| tab.has_multiple_panes())
199 {
200 crate::debug_info!("MUX", "close of a mux tab's last pane — closing the tab");
201 self.show_toast("par-mux: closed tab — window survives in the daemon");
202 return self.close_current_tab_keeping_mux_window();
203 }
204 if self.close_pane_via_mux() {
205 return false;
208 }
209 }
210 if self.is_tmux_connected() {
211 let active_tab_id = self.tab_manager.active_tab_id();
216 let is_tmux_display_tab = active_tab_id
217 .map(|id| {
218 self.tmux_state.tmux_gateway_tab_id.is_some()
219 && self.tmux_state.tmux_gateway_tab_id != Some(id)
220 })
221 .unwrap_or(false);
222
223 if is_tmux_display_tab {
224 crate::debug_info!(
225 "TMUX",
226 "close_focused_pane on display tab — closing display tab instead of kill-pane"
227 );
228 return self.close_current_tab_immediately();
229 }
230
231 if self.close_pane_via_tmux() {
233 crate::debug_info!("TMUX", "Sent kill-pane command to tmux");
234 return false;
236 }
237 }
238 if self.config.load().shell.confirm_close_running_jobs
242 && let Some(command_name) = self.check_current_pane_running_job()
243 && let Some(tab) = self.tab_manager.active_tab()
244 && let Some(pane_id) = tab.focused_pane_id()
245 {
246 let tab_id = tab.id;
247 let tab_title = if tab.title.is_empty() {
248 "Terminal".to_string()
249 } else {
250 tab.title.clone()
251 };
252 self.overlay_ui.close_confirmation_ui.show_for_pane(
253 tab_id,
254 pane_id,
255 &tab_title,
256 &command_name,
257 );
258 self.focus_state.needs_redraw = true;
259 self.request_redraw();
260 return false; }
262
263 self.close_focused_pane_immediately()
264 }
265
266 pub(crate) fn close_focused_pane_immediately(&mut self) -> bool {
269 if let Some(tab) = self.tab_manager.active_tab_mut()
270 && tab.has_multiple_panes()
271 {
272 let is_last_pane = tab.close_focused_pane();
273 if is_last_pane {
274 return self.close_current_tab_immediately();
276 }
277 self.focus_state.needs_redraw = true;
278 self.request_redraw();
279 return false;
280 }
281 self.close_current_tab_immediately()
283 }
284
285 pub(super) fn check_current_pane_running_job(&self) -> Option<String> {
289 let tab = self.tab_manager.active_tab()?;
290
291 if tab.has_multiple_panes() {
293 let pane_manager = tab.pane_manager()?;
294 let focused_id = pane_manager.focused_pane_id()?;
295 let pane = pane_manager.get_pane(focused_id)?;
296 let term = pane.terminal.blocking_read();
299 log::info!(
300 "[CLOSE_CONFIRM] check_current_pane_running_job (split pane): marker={:?} command={:?}",
301 term.shell_integration_marker(),
302 term.shell_integration_command()
303 );
304 return term.should_confirm_close(&self.config.load().shell.jobs_to_ignore);
305 }
306
307 let term = tab.terminal.blocking_read();
310 log::info!(
311 "[CLOSE_CONFIRM] check_current_pane_running_job (single pane): marker={:?} command={:?} is_running={}",
312 term.shell_integration_marker(),
313 term.shell_integration_command(),
314 term.is_command_running()
315 );
316 term.should_confirm_close(&self.config.load().shell.jobs_to_ignore)
317 }
318
319 pub fn has_multiple_panes(&self) -> bool {
321 self.tab_manager
322 .active_tab()
323 .is_some_and(|tab| tab.has_multiple_panes())
324 }
325
326 pub fn navigate_pane(&mut self, direction: crate::pane::NavigationDirection) {
328 if let Some(tab) = self.tab_manager.active_tab_mut()
329 && tab.has_multiple_panes()
330 {
331 tab.navigate_pane(direction);
332 self.focus_state.needs_redraw = true;
333 self.request_redraw();
334 }
335 }
336
337 pub fn resize_pane(&mut self, direction: crate::pane::NavigationDirection) {
341 use crate::pane::NavigationDirection;
342
343 const RESIZE_DELTA: f32 = 0.05;
345
346 let delta = match direction {
350 NavigationDirection::Right | NavigationDirection::Down => RESIZE_DELTA,
351 NavigationDirection::Left | NavigationDirection::Up => -RESIZE_DELTA,
352 };
353
354 if let Some(tab) = self.tab_manager.active_tab_mut()
355 && let Some(pm) = tab.pane_manager_mut()
356 && let Some(focused_id) = pm.focused_pane_id()
357 {
358 pm.resize_split(focused_id, delta);
359 self.focus_state.needs_redraw = true;
360 self.request_redraw();
361 }
362 }
363}