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 if self.close_pane_via_mux() {
185 return false;
188 }
189 if self.is_tmux_connected() {
190 let active_tab_id = self.tab_manager.active_tab_id();
195 let is_tmux_display_tab = active_tab_id
196 .map(|id| {
197 self.tmux_state.tmux_gateway_tab_id.is_some()
198 && self.tmux_state.tmux_gateway_tab_id != Some(id)
199 })
200 .unwrap_or(false);
201
202 if is_tmux_display_tab {
203 crate::debug_info!(
204 "TMUX",
205 "close_focused_pane on display tab — closing display tab instead of kill-pane"
206 );
207 return self.close_current_tab_immediately();
208 }
209
210 if self.close_pane_via_tmux() {
212 crate::debug_info!("TMUX", "Sent kill-pane command to tmux");
213 return false;
215 }
216 }
217 if self.config.load().shell.confirm_close_running_jobs
221 && let Some(command_name) = self.check_current_pane_running_job()
222 && let Some(tab) = self.tab_manager.active_tab()
223 && let Some(pane_id) = tab.focused_pane_id()
224 {
225 let tab_id = tab.id;
226 let tab_title = if tab.title.is_empty() {
227 "Terminal".to_string()
228 } else {
229 tab.title.clone()
230 };
231 self.overlay_ui.close_confirmation_ui.show_for_pane(
232 tab_id,
233 pane_id,
234 &tab_title,
235 &command_name,
236 );
237 self.focus_state.needs_redraw = true;
238 self.request_redraw();
239 return false; }
241
242 self.close_focused_pane_immediately()
243 }
244
245 pub(crate) fn close_focused_pane_immediately(&mut self) -> bool {
248 if let Some(tab) = self.tab_manager.active_tab_mut()
249 && tab.has_multiple_panes()
250 {
251 let is_last_pane = tab.close_focused_pane();
252 if is_last_pane {
253 return self.close_current_tab_immediately();
255 }
256 self.focus_state.needs_redraw = true;
257 self.request_redraw();
258 return false;
259 }
260 self.close_current_tab_immediately()
262 }
263
264 pub(super) fn check_current_pane_running_job(&self) -> Option<String> {
268 let tab = self.tab_manager.active_tab()?;
269
270 if tab.has_multiple_panes() {
272 let pane_manager = tab.pane_manager()?;
273 let focused_id = pane_manager.focused_pane_id()?;
274 let pane = pane_manager.get_pane(focused_id)?;
275 let term = pane.terminal.blocking_read();
278 log::info!(
279 "[CLOSE_CONFIRM] check_current_pane_running_job (split pane): marker={:?} command={:?}",
280 term.shell_integration_marker(),
281 term.shell_integration_command()
282 );
283 return term.should_confirm_close(&self.config.load().shell.jobs_to_ignore);
284 }
285
286 let term = tab.terminal.blocking_read();
289 log::info!(
290 "[CLOSE_CONFIRM] check_current_pane_running_job (single pane): marker={:?} command={:?} is_running={}",
291 term.shell_integration_marker(),
292 term.shell_integration_command(),
293 term.is_command_running()
294 );
295 term.should_confirm_close(&self.config.load().shell.jobs_to_ignore)
296 }
297
298 pub fn has_multiple_panes(&self) -> bool {
300 self.tab_manager
301 .active_tab()
302 .is_some_and(|tab| tab.has_multiple_panes())
303 }
304
305 pub fn navigate_pane(&mut self, direction: crate::pane::NavigationDirection) {
307 if let Some(tab) = self.tab_manager.active_tab_mut()
308 && tab.has_multiple_panes()
309 {
310 tab.navigate_pane(direction);
311 self.focus_state.needs_redraw = true;
312 self.request_redraw();
313 }
314 }
315
316 pub fn resize_pane(&mut self, direction: crate::pane::NavigationDirection) {
320 use crate::pane::NavigationDirection;
321
322 const RESIZE_DELTA: f32 = 0.05;
324
325 let delta = match direction {
329 NavigationDirection::Right | NavigationDirection::Down => RESIZE_DELTA,
330 NavigationDirection::Left | NavigationDirection::Up => -RESIZE_DELTA,
331 };
332
333 if let Some(tab) = self.tab_manager.active_tab_mut()
334 && let Some(pm) = tab.pane_manager_mut()
335 && let Some(focused_id) = pm.focused_pane_id()
336 {
337 pm.resize_split(focused_id, delta);
338 self.focus_state.needs_redraw = true;
339 self.request_redraw();
340 }
341 }
342}