1use crate::config::Config;
8use crate::pane::{NavigationDirection, PaneManager, SplitDirection};
9use crate::tab::Tab;
10use std::sync::{Arc, atomic::Ordering};
11use tokio::runtime::Runtime;
12
13struct SplitRequest {
14 focus_new: bool,
15 dpi_scale: f32,
16 initial_command: Option<(String, Vec<String>)>,
17 split_percent: u8,
18}
19
20impl Tab {
21 pub fn has_multiple_panes(&self) -> bool {
23 self.pane_manager
24 .as_ref()
25 .is_some_and(|pm| pm.has_multiple_panes())
26 }
27
28 pub fn pane_count(&self) -> usize {
30 self.pane_manager
31 .as_ref()
32 .map(|pm| pm.pane_count())
33 .unwrap_or(1)
34 }
35
36 pub fn split_horizontal(
43 &mut self,
44 focus_new: bool,
45 config: &Config,
46 runtime: Arc<Runtime>,
47 dpi_scale: f32,
48 initial_command: Option<(String, Vec<String>)>,
49 split_percent: u8,
50 ) -> anyhow::Result<Option<crate::pane::PaneId>> {
51 self.split(
52 SplitDirection::Horizontal,
53 config,
54 runtime,
55 SplitRequest {
56 focus_new,
57 dpi_scale,
58 initial_command,
59 split_percent,
60 },
61 )
62 }
63
64 pub fn split_vertical(
71 &mut self,
72 focus_new: bool,
73 config: &Config,
74 runtime: Arc<Runtime>,
75 dpi_scale: f32,
76 initial_command: Option<(String, Vec<String>)>,
77 split_percent: u8,
78 ) -> anyhow::Result<Option<crate::pane::PaneId>> {
79 self.split(
80 SplitDirection::Vertical,
81 config,
82 runtime,
83 SplitRequest {
84 focus_new,
85 dpi_scale,
86 initial_command,
87 split_percent,
88 },
89 )
90 }
91
92 fn split(
97 &mut self,
98 direction: SplitDirection,
99 config: &Config,
100 runtime: Arc<Runtime>,
101 request: SplitRequest,
102 ) -> anyhow::Result<Option<crate::pane::PaneId>> {
103 if config.panes.max_panes > 0 && self.pane_count() >= config.panes.max_panes {
105 log::warn!(
106 "Cannot split: max panes limit ({}) reached",
107 config.panes.max_panes
108 );
109 return Ok(None);
110 }
111
112 let needs_initial_pane = self
114 .pane_manager
115 .as_ref()
116 .map(|pm| pm.pane_count() == 0)
117 .unwrap_or(true);
118
119 if needs_initial_pane {
120 if self.pane_manager.is_none() {
122 let mut pm = PaneManager::new();
123 pm.set_divider_width(
125 config.panes.pane_divider_width.unwrap_or(2.0) * request.dpi_scale,
126 );
127 pm.set_divider_hit_width(config.panes.pane_divider_hit_width * request.dpi_scale);
128 self.pane_manager = Some(pm);
129 }
130
131 if let Some(ref mut pm) = self.pane_manager {
134 pm.create_initial_pane_for_split(
135 direction,
136 config,
137 Arc::clone(&runtime),
138 self.working_directory.clone(),
139 )?;
140 log::info!(
141 "Created PaneManager for tab {} with initial pane on first split",
142 self.id
143 );
144 }
145 }
146
147 if let Some(ref mut pm) = self.pane_manager {
149 let ratio = (request.split_percent.clamp(10, 90) as f32) / 100.0;
150 let new_pane_id = pm.split(
151 direction,
152 request.focus_new,
153 config,
154 Arc::clone(&runtime),
155 request.initial_command,
156 ratio,
157 )?;
158 if let Some(id) = new_pane_id {
159 log::info!("Split tab {} {:?}, new pane {}", self.id, direction, id);
160 }
161 Ok(new_pane_id)
162 } else {
163 Ok(None)
164 }
165 }
166
167 pub fn close_focused_pane(&mut self) -> bool {
171 if let Some(ref mut pm) = self.pane_manager
172 && let Some(focused_id) = pm.focused_pane_id()
173 {
174 let is_last = pm.close_pane(focused_id);
175 if is_last {
176 self.pane_manager = None;
178 }
179 return is_last;
180 }
181 true
183 }
184
185 pub fn close_exited_panes(&mut self) -> (Vec<crate::pane::PaneId>, bool) {
191 let mut closed_panes = Vec::new();
192
193 let exited_pane_ids: Vec<crate::pane::PaneId> = if let Some(ref pm) = self.pane_manager {
195 let focused_id = pm.focused_pane_id();
196 pm.all_panes()
197 .iter()
198 .filter_map(|pane| {
199 let is_running = pane.is_running();
200 crate::debug_info!(
201 "PANE_CHECK",
202 "Pane {} running={} focused={} bounds=({:.0},{:.0} {:.0}x{:.0})",
203 pane.id,
204 is_running,
205 focused_id == Some(pane.id),
206 pane.bounds.x,
207 pane.bounds.y,
208 pane.bounds.width,
209 pane.bounds.height
210 );
211 if !is_running { Some(pane.id) } else { None }
212 })
213 .collect()
214 } else {
215 Vec::new()
216 };
217
218 if let Some(ref mut pm) = self.pane_manager {
220 for pane_id in exited_pane_ids {
221 crate::debug_info!("PANE_CLOSE", "Closing pane {} - shell exited", pane_id);
222 let is_last = pm.close_pane(pane_id);
223 closed_panes.push(pane_id);
224
225 if is_last {
226 self.pane_manager = None;
228 return (closed_panes, true);
229 }
230 }
231 }
232
233 (closed_panes, false)
234 }
235
236 pub fn pane_manager(&self) -> Option<&PaneManager> {
238 self.pane_manager.as_ref()
239 }
240
241 pub fn pane_manager_mut(&mut self) -> Option<&mut PaneManager> {
243 self.pane_manager.as_mut()
244 }
245
246 pub fn init_pane_manager(&mut self) {
251 if self.pane_manager.is_none() {
252 self.pane_manager = Some(PaneManager::new());
253 }
254 }
255
256 pub fn set_pane_bounds(
261 &mut self,
262 bounds: crate::pane::PaneBounds,
263 cell_width: f32,
264 cell_height: f32,
265 ) {
266 self.set_pane_bounds_with_padding(bounds, cell_width, cell_height, 0.0);
267 }
268
269 pub fn set_pane_bounds_with_padding(
274 &mut self,
275 bounds: crate::pane::PaneBounds,
276 cell_width: f32,
277 cell_height: f32,
278 padding: f32,
279 ) {
280 if self.pane_manager.is_none() {
281 let mut pm = PaneManager::new();
282 pm.set_bounds(bounds);
283 self.pane_manager = Some(pm);
284 } else if let Some(ref mut pm) = self.pane_manager {
285 pm.set_bounds(bounds);
286 pm.resize_all_terminals_with_padding(cell_width, cell_height, padding, 0.0);
287 }
288 }
289
290 pub fn focus_pane_at(&mut self, x: f32, y: f32) -> Option<crate::pane::PaneId> {
294 if let Some(ref mut pm) = self.pane_manager {
295 pm.focus_pane_at(x, y)
296 } else {
297 None
298 }
299 }
300
301 pub fn focused_pane_id(&self) -> Option<crate::pane::PaneId> {
303 self.pane_manager
304 .as_ref()
305 .and_then(|pm| pm.focused_pane_id())
306 }
307
308 pub fn is_pane_focused(&self, pane_id: crate::pane::PaneId) -> bool {
310 self.focused_pane_id() == Some(pane_id)
311 }
312
313 pub fn navigate_pane(&mut self, direction: NavigationDirection) {
315 if let Some(ref mut pm) = self.pane_manager {
316 pm.navigate(direction);
317 }
318 }
319
320 pub fn is_on_divider(&self, x: f32, y: f32) -> bool {
322 self.pane_manager
323 .as_ref()
324 .is_some_and(|pm| pm.is_on_divider(x, y))
325 }
326
327 pub fn find_divider_at(&self, x: f32, y: f32) -> Option<usize> {
331 self.pane_manager
332 .as_ref()
333 .and_then(|pm| pm.find_divider_at(x, y, pm.divider_hit_padding()))
334 }
335
336 pub fn get_divider(&self, index: usize) -> Option<crate::pane::DividerRect> {
338 self.pane_manager
339 .as_ref()
340 .and_then(|pm| pm.get_divider(index))
341 }
342
343 pub fn drag_divider(&mut self, divider_index: usize, x: f32, y: f32) {
345 if let Some(ref mut pm) = self.pane_manager {
346 pm.drag_divider(divider_index, x, y);
347 }
348 }
349
350 pub fn restore_pane_layout(
356 &mut self,
357 layout: &crate::session::SessionPaneNode,
358 config: &Config,
359 runtime: Arc<Runtime>,
360 ) {
361 let mut pm = PaneManager::new();
362 pm.set_divider_width(config.panes.pane_divider_width.unwrap_or(1.0));
363 pm.set_divider_hit_width(config.panes.pane_divider_hit_width);
364
365 match pm.build_from_layout(layout, config, runtime) {
366 Ok(()) => {
367 log::info!(
368 "Restored pane layout for tab {} ({} panes)",
369 self.id,
370 pm.pane_count()
371 );
372 self.pane_manager = Some(pm);
373 }
374 Err(e) => {
375 log::warn!(
376 "Failed to restore pane layout for tab {}: {}, keeping single pane",
377 self.id,
378 e
379 );
380 }
381 }
382 }
383
384 pub fn start_pane_refresh_tasks(
390 &mut self,
391 runtime: Arc<Runtime>,
392 window: Arc<winit::window::Window>,
393 active_fps: u32,
394 inactive_fps: u32,
395 ) {
396 if let Some(ref mut pm) = self.pane_manager {
397 let is_tab_active = self.is_active.load(Ordering::Relaxed);
398 for pane in pm.all_panes_mut() {
399 pane.is_active.store(is_tab_active, Ordering::Relaxed);
400 if pane.refresh_task.is_none() {
401 pane.start_refresh_task(
402 Arc::clone(&runtime),
403 Arc::clone(&window),
404 active_fps,
405 inactive_fps,
406 );
407 }
408 }
409 }
410 }
411}