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 if self.is_tmux_connected() && self.split_pane_via_tmux(false) {
143 crate::debug_info!("TMUX", "Sent horizontal split command to tmux");
144 return;
145 }
146 self.split_pane_direction(crate::pane::SplitDirection::Horizontal, true, None, 50);
148 }
149
150 pub fn split_pane_vertical(&mut self) {
152 if self.is_tmux_connected() && self.split_pane_via_tmux(true) {
154 crate::debug_info!("TMUX", "Sent vertical split command to tmux");
155 return;
156 }
157 self.split_pane_direction(crate::pane::SplitDirection::Vertical, true, None, 50);
159 }
160
161 pub fn close_focused_pane(&mut self) -> bool {
166 if self.is_tmux_connected() {
167 let active_tab_id = self.tab_manager.active_tab_id();
172 let is_tmux_display_tab = active_tab_id
173 .map(|id| {
174 self.tmux_state.tmux_gateway_tab_id.is_some()
175 && self.tmux_state.tmux_gateway_tab_id != Some(id)
176 })
177 .unwrap_or(false);
178
179 if is_tmux_display_tab {
180 crate::debug_info!(
181 "TMUX",
182 "close_focused_pane on display tab — closing display tab instead of kill-pane"
183 );
184 return self.close_current_tab_immediately();
185 }
186
187 if self.close_pane_via_tmux() {
189 crate::debug_info!("TMUX", "Sent kill-pane command to tmux");
190 return false;
192 }
193 }
194 if self.config.load().shell.confirm_close_running_jobs
198 && let Some(command_name) = self.check_current_pane_running_job()
199 && let Some(tab) = self.tab_manager.active_tab()
200 && let Some(pane_id) = tab.focused_pane_id()
201 {
202 let tab_id = tab.id;
203 let tab_title = if tab.title.is_empty() {
204 "Terminal".to_string()
205 } else {
206 tab.title.clone()
207 };
208 self.overlay_ui.close_confirmation_ui.show_for_pane(
209 tab_id,
210 pane_id,
211 &tab_title,
212 &command_name,
213 );
214 self.focus_state.needs_redraw = true;
215 self.request_redraw();
216 return false; }
218
219 self.close_focused_pane_immediately()
220 }
221
222 pub(crate) fn close_focused_pane_immediately(&mut self) -> bool {
225 if let Some(tab) = self.tab_manager.active_tab_mut()
226 && tab.has_multiple_panes()
227 {
228 let is_last_pane = tab.close_focused_pane();
229 if is_last_pane {
230 return self.close_current_tab_immediately();
232 }
233 self.focus_state.needs_redraw = true;
234 self.request_redraw();
235 return false;
236 }
237 self.close_current_tab_immediately()
239 }
240
241 pub(super) fn check_current_pane_running_job(&self) -> Option<String> {
245 let tab = self.tab_manager.active_tab()?;
246
247 if tab.has_multiple_panes() {
249 let pane_manager = tab.pane_manager()?;
250 let focused_id = pane_manager.focused_pane_id()?;
251 let pane = pane_manager.get_pane(focused_id)?;
252 let term = pane.terminal.blocking_read();
255 log::info!(
256 "[CLOSE_CONFIRM] check_current_pane_running_job (split pane): marker={:?} command={:?}",
257 term.shell_integration_marker(),
258 term.shell_integration_command()
259 );
260 return term.should_confirm_close(&self.config.load().shell.jobs_to_ignore);
261 }
262
263 let term = tab.terminal.blocking_read();
266 log::info!(
267 "[CLOSE_CONFIRM] check_current_pane_running_job (single pane): marker={:?} command={:?} is_running={}",
268 term.shell_integration_marker(),
269 term.shell_integration_command(),
270 term.is_command_running()
271 );
272 term.should_confirm_close(&self.config.load().shell.jobs_to_ignore)
273 }
274
275 pub fn has_multiple_panes(&self) -> bool {
277 self.tab_manager
278 .active_tab()
279 .is_some_and(|tab| tab.has_multiple_panes())
280 }
281
282 pub fn navigate_pane(&mut self, direction: crate::pane::NavigationDirection) {
284 if let Some(tab) = self.tab_manager.active_tab_mut()
285 && tab.has_multiple_panes()
286 {
287 tab.navigate_pane(direction);
288 self.focus_state.needs_redraw = true;
289 self.request_redraw();
290 }
291 }
292
293 pub fn resize_pane(&mut self, direction: crate::pane::NavigationDirection) {
297 use crate::pane::NavigationDirection;
298
299 const RESIZE_DELTA: f32 = 0.05;
301
302 let delta = match direction {
306 NavigationDirection::Right | NavigationDirection::Down => RESIZE_DELTA,
307 NavigationDirection::Left | NavigationDirection::Up => -RESIZE_DELTA,
308 };
309
310 if let Some(tab) = self.tab_manager.active_tab_mut()
311 && let Some(pm) = tab.pane_manager_mut()
312 && let Some(focused_id) = pm.focused_pane_id()
313 {
314 pm.resize_split(focused_id, delta);
315 self.focus_state.needs_redraw = true;
316 self.request_redraw();
317 }
318 }
319}