1use super::PaneManager;
7use crate::config::Config;
8use crate::pane::tmux_helpers::RemoveResult;
9use crate::pane::types::{Pane, PaneBounds, PaneId, PaneNode, SplitDirection};
10use anyhow::Result;
11use std::sync::Arc;
12use tokio::runtime::Runtime;
13
14impl PaneManager {
15 pub fn create_initial_pane(
21 &mut self,
22 config: &Config,
23 runtime: Arc<Runtime>,
24 working_directory: Option<String>,
25 ) -> Result<PaneId> {
26 self.create_initial_pane_internal(None, config, runtime, working_directory)
27 }
28
29 pub fn create_initial_pane_for_split(
34 &mut self,
35 direction: SplitDirection,
36 config: &Config,
37 runtime: Arc<Runtime>,
38 working_directory: Option<String>,
39 ) -> Result<PaneId> {
40 self.create_initial_pane_internal(Some(direction), config, runtime, working_directory)
41 }
42
43 pub(super) fn create_initial_pane_internal(
45 &mut self,
46 split_direction: Option<SplitDirection>,
47 config: &Config,
48 runtime: Arc<Runtime>,
49 working_directory: Option<String>,
50 ) -> Result<PaneId> {
51 let id = self.next_pane_id;
52 self.next_pane_id += 1;
53
54 let pane_config = if self.total_bounds.width > 0.0 && self.total_bounds.height > 0.0 {
56 let cell_width = config.font_size * 0.6; let cell_height = config.font_size * 1.2; let effective_bounds = match split_direction {
62 Some(SplitDirection::Vertical) => {
63 PaneBounds::new(
65 self.total_bounds.x,
66 self.total_bounds.y,
67 (self.total_bounds.width - self.divider_width) / 2.0,
68 self.total_bounds.height,
69 )
70 }
71 Some(SplitDirection::Horizontal) => {
72 PaneBounds::new(
74 self.total_bounds.x,
75 self.total_bounds.y,
76 self.total_bounds.width,
77 (self.total_bounds.height - self.divider_width) / 2.0,
78 )
79 }
80 None => self.total_bounds,
81 };
82
83 let (cols, rows) = effective_bounds.grid_size(cell_width, cell_height);
84
85 let mut cfg = config.clone();
86 cfg.cols = cols.max(10);
87 cfg.rows = rows.max(5);
88 log::info!(
89 "Initial pane {} using bounds-based dimensions: {}x{} (split={:?})",
90 id,
91 cfg.cols,
92 cfg.rows,
93 split_direction
94 );
95 cfg
96 } else {
97 log::info!(
98 "Initial pane {} using config dimensions: {}x{}",
99 id,
100 config.cols,
101 config.rows
102 );
103 config.clone()
104 };
105
106 let mut pane = Pane::new(id, &pane_config, runtime, working_directory)?;
107
108 if let Some((image_path, mode, opacity, darken)) = config.get_pane_background(0) {
110 pane.set_background(crate::pane::PaneBackground {
111 image_path: Some(image_path),
112 mode,
113 opacity,
114 darken,
115 });
116 }
117
118 self.root = Some(PaneNode::leaf(pane));
119 self.focused_pane_id = Some(id);
120
121 Ok(id)
122 }
123
124 pub fn split(
132 &mut self,
133 direction: SplitDirection,
134 focus_new: bool,
135 config: &Config,
136 runtime: Arc<Runtime>,
137 initial_command: Option<(String, Vec<String>)>,
138 ratio: f32,
139 ) -> Result<Option<PaneId>> {
140 let focused_id = match self.focused_pane_id {
141 Some(id) => id,
142 None => return Ok(None),
143 };
144
145 let (working_dir, focused_bounds) = if let Some(pane) = self.focused_pane() {
147 (pane.get_cwd(), pane.bounds)
148 } else {
149 (None, self.total_bounds)
150 };
151
152 let (new_cols, new_rows) = match direction {
154 SplitDirection::Vertical => {
155 let half_width = (focused_bounds.width - self.divider_width) / 2.0;
157 let cols = (half_width / config.font_size * 1.8).floor() as usize; (cols.max(10), config.rows)
159 }
160 SplitDirection::Horizontal => {
161 let half_height = (focused_bounds.height - self.divider_width) / 2.0;
163 let rows = (half_height / (config.font_size * 1.2)).floor() as usize; (config.cols, rows.max(5))
165 }
166 };
167
168 let mut pane_config = config.clone();
170 pane_config.cols = new_cols;
171 pane_config.rows = new_rows;
172
173 let new_id = self.next_pane_id;
175 self.next_pane_id += 1;
176
177 let mut new_pane = if let Some((cmd, args)) = initial_command {
178 Pane::new_with_command(new_id, &pane_config, runtime, working_dir, cmd, args)?
179 } else {
180 Pane::new(new_id, &pane_config, runtime, working_dir)?
181 };
182
183 let new_pane_index = self.pane_count(); if let Some((image_path, mode, opacity, darken)) =
187 config.get_pane_background(new_pane_index)
188 {
189 new_pane.set_background(crate::pane::PaneBackground {
190 image_path: Some(image_path),
191 mode,
192 opacity,
193 darken,
194 });
195 }
196
197 if let Some(root) = self.root.take() {
199 let (new_root, _) =
200 Self::split_node(root, focused_id, direction, Some(new_pane), ratio);
201 self.root = Some(new_root);
202 }
203
204 self.recalculate_bounds();
206
207 if focus_new {
209 self.focused_pane_id = Some(new_id);
210 }
211
212 crate::debug_info!(
213 "PANE_SPLIT",
214 "Split pane {} {:?}, created new pane {}. First(left/top)={} Second(right/bottom)={} (focused)",
215 focused_id,
216 direction,
217 new_id,
218 focused_id,
219 new_id
220 );
221
222 Ok(Some(new_id))
223 }
224
225 pub(super) fn split_node(
230 node: PaneNode,
231 target_id: PaneId,
232 direction: SplitDirection,
233 new_pane: Option<Pane>,
234 ratio: f32,
235 ) -> (PaneNode, Option<Pane>) {
236 match node {
237 PaneNode::Leaf(pane) => {
238 if pane.id == target_id {
239 if let Some(new) = new_pane {
240 (
242 PaneNode::split(
243 direction,
244 ratio,
245 PaneNode::leaf(*pane),
246 PaneNode::leaf(new),
247 ),
248 None,
249 )
250 } else {
251 (PaneNode::Leaf(pane), None)
253 }
254 } else {
255 (PaneNode::Leaf(pane), new_pane)
257 }
258 }
259 PaneNode::Split {
260 direction: split_dir,
261 ratio: existing_ratio,
262 first,
263 second,
264 } => {
265 let (new_first, remaining) =
267 Self::split_node(*first, target_id, direction, new_pane, ratio);
268
269 if remaining.is_none() {
270 (
272 PaneNode::Split {
273 direction: split_dir,
274 ratio: existing_ratio,
275 first: Box::new(new_first),
276 second,
277 },
278 None,
279 )
280 } else {
281 let (new_second, remaining) =
283 Self::split_node(*second, target_id, direction, remaining, ratio);
284 (
285 PaneNode::Split {
286 direction: split_dir,
287 ratio: existing_ratio,
288 first: Box::new(new_first),
289 second: Box::new(new_second),
290 },
291 remaining,
292 )
293 }
294 }
295 }
296 }
297
298 pub(super) fn remove_pane(node: PaneNode, target_id: PaneId) -> RemoveResult {
300 match node {
301 PaneNode::Leaf(pane) => {
302 if pane.id == target_id {
303 RemoveResult::Removed(None)
305 } else {
306 RemoveResult::NotFound(PaneNode::Leaf(pane))
307 }
308 }
309 PaneNode::Split {
310 direction,
311 ratio,
312 first,
313 second,
314 } => {
315 match Self::remove_pane(*first, target_id) {
317 RemoveResult::Removed(None) => {
318 RemoveResult::Removed(Some(*second))
321 }
322 RemoveResult::Removed(Some(new_first)) => {
323 RemoveResult::Removed(Some(PaneNode::Split {
325 direction,
326 ratio,
327 first: Box::new(new_first),
328 second,
329 }))
330 }
331 RemoveResult::NotFound(first_node) => {
332 match Self::remove_pane(*second, target_id) {
334 RemoveResult::Removed(None) => {
335 RemoveResult::Removed(Some(first_node))
338 }
339 RemoveResult::Removed(Some(new_second)) => {
340 RemoveResult::Removed(Some(PaneNode::Split {
342 direction,
343 ratio,
344 first: Box::new(first_node),
345 second: Box::new(new_second),
346 }))
347 }
348 RemoveResult::NotFound(second_node) => {
349 RemoveResult::NotFound(PaneNode::Split {
351 direction,
352 ratio,
353 first: Box::new(first_node),
354 second: Box::new(second_node),
355 })
356 }
357 }
358 }
359 }
360 }
361 }
362 }
363
364 pub(super) fn insert_subtree_at_node(
370 node: PaneNode,
371 target_id: PaneId,
372 subtree: PaneNode,
373 direction: crate::pane::types::SplitDirection,
374 ratio: f32,
375 ) -> Result<PaneNode, (PaneNode, PaneNode)> {
376 match node {
377 PaneNode::Leaf(pane) => {
378 if pane.id == target_id {
379 Ok(PaneNode::Split {
380 direction,
381 ratio,
382 first: Box::new(PaneNode::Leaf(pane)),
383 second: Box::new(subtree),
384 })
385 } else {
386 Err((PaneNode::Leaf(pane), subtree))
387 }
388 }
389 PaneNode::Split {
390 direction: split_dir,
391 ratio: existing_ratio,
392 first,
393 second,
394 } => match Self::insert_subtree_at_node(*first, target_id, subtree, direction, ratio) {
395 Ok(new_first) => Ok(PaneNode::Split {
396 direction: split_dir,
397 ratio: existing_ratio,
398 first: Box::new(new_first),
399 second,
400 }),
401 Err((first_node, subtree)) => {
402 match Self::insert_subtree_at_node(
403 *second, target_id, subtree, direction, ratio,
404 ) {
405 Ok(new_second) => Ok(PaneNode::Split {
406 direction: split_dir,
407 ratio: existing_ratio,
408 first: Box::new(first_node),
409 second: Box::new(new_second),
410 }),
411 Err((second_node, subtree)) => Err((
412 PaneNode::Split {
413 direction: split_dir,
414 ratio: existing_ratio,
415 first: Box::new(first_node),
416 second: Box::new(second_node),
417 },
418 subtree,
419 )),
420 }
421 }
422 },
423 }
424 }
425
426 pub(super) fn extract_pane_from_node(
428 node: PaneNode,
429 target_id: PaneId,
430 ) -> super::ExtractInternal {
431 match node {
432 PaneNode::Leaf(pane) => {
433 if pane.id == target_id {
434 super::ExtractInternal::OnlyPane(*pane)
435 } else {
436 super::ExtractInternal::NotFound(PaneNode::Leaf(pane))
437 }
438 }
439 PaneNode::Split {
440 direction,
441 ratio,
442 first,
443 second,
444 } => match Self::extract_pane_from_node(*first, target_id) {
445 super::ExtractInternal::OnlyPane(pane) => super::ExtractInternal::Extracted {
446 pane,
447 remaining: *second,
448 },
449 super::ExtractInternal::Extracted { pane, remaining } => {
450 super::ExtractInternal::Extracted {
451 pane,
452 remaining: PaneNode::Split {
453 direction,
454 ratio,
455 first: Box::new(remaining),
456 second,
457 },
458 }
459 }
460 super::ExtractInternal::NotFound(first_node) => {
461 match Self::extract_pane_from_node(*second, target_id) {
462 super::ExtractInternal::OnlyPane(pane) => {
463 super::ExtractInternal::Extracted {
464 pane,
465 remaining: first_node,
466 }
467 }
468 super::ExtractInternal::Extracted { pane, remaining } => {
469 super::ExtractInternal::Extracted {
470 pane,
471 remaining: PaneNode::Split {
472 direction,
473 ratio,
474 first: Box::new(first_node),
475 second: Box::new(remaining),
476 },
477 }
478 }
479 super::ExtractInternal::NotFound(second_node) => {
480 super::ExtractInternal::NotFound(PaneNode::Split {
481 direction,
482 ratio,
483 first: Box::new(first_node),
484 second: Box::new(second_node),
485 })
486 }
487 }
488 }
489 },
490 }
491 }
492}