1use std::collections::HashMap;
2
3use gpui::Pixels;
4
5use crate::Placement;
6
7use super::node::{NodeId, NodeKind, PaneNode, PanelId};
8use super::tree::PaneTree;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum InsertTarget {
13 Tabs {
15 node: NodeId,
16 ix: Option<usize>,
17 activate: bool,
18 },
19 Split {
21 node: NodeId,
22 placement: Placement,
23 size: Option<Pixels>,
24 },
25}
26
27#[derive(Clone, Debug, Default, PartialEq)]
35pub struct EditResult {
36 changed: bool,
37}
38
39impl EditResult {
40 pub fn changed(&self) -> bool {
41 self.changed
42 }
43}
44
45impl PaneTree {
46 pub fn insert_panel(&mut self, panel: PanelId, target: InsertTarget) -> EditResult {
47 self.edit(|tree| tree.apply_insert(panel, target))
48 }
49
50 pub fn remove_panel(&mut self, panel: PanelId) -> EditResult {
51 self.edit(|tree| tree.detach_panel(panel))
52 }
53
54 pub fn move_panel(&mut self, panel: PanelId, target: InsertTarget) -> EditResult {
57 self.edit(|tree| {
58 let detached = tree.detach_panel(panel);
59 let inserted = tree.apply_insert(panel, target);
60 detached || inserted
61 })
62 }
63
64 pub fn split(
65 &mut self,
66 at: NodeId,
67 panel: PanelId,
68 placement: Placement,
69 size: Option<Pixels>,
70 ) -> EditResult {
71 self.insert_panel(
72 panel,
73 InsertTarget::Split {
74 node: at,
75 placement,
76 size,
77 },
78 )
79 }
80
81 pub fn set_active(&mut self, node: NodeId, ix: usize) -> EditResult {
82 self.edit(|tree| {
83 let Some(path) = tree.path_of_node(node) else {
84 return false;
85 };
86 let NodeKind::Tabs { active_ix, .. } = tree.node_at_mut(&path).kind_mut() else {
87 return false;
88 };
89 if *active_ix == ix {
90 return false;
91 }
92 *active_ix = ix;
93 true
94 })
95 }
96
97 pub fn set_sizes(&mut self, node: NodeId, new_sizes: Vec<Option<Pixels>>) -> EditResult {
105 self.edit(|tree| {
106 let Some(path) = tree.path_of_node(node) else {
107 return false;
108 };
109 let NodeKind::Split {
110 children, sizes, ..
111 } = tree.node_at_mut(&path).kind_mut()
112 else {
113 return false;
114 };
115 if new_sizes.len() != children.len() || *sizes == new_sizes {
116 return false;
117 }
118 *sizes = new_sizes;
119 true
120 })
121 }
122}
123
124impl PaneTree {
125 pub(crate) fn adopt_measured_sizes(&mut self, measured: &HashMap<NodeId, Vec<Pixels>>) {
136 fn walk(node: &mut PaneNode, measured: &HashMap<NodeId, Vec<Pixels>>) {
137 let id = node.id();
138 let NodeKind::Split {
139 children, sizes, ..
140 } = node.kind_mut()
141 else {
142 return;
143 };
144
145 if let Some(actual) = measured.get(&id) {
146 if actual.len() == sizes.len() {
147 for (slot, size) in sizes.iter_mut().zip(actual) {
148 if *size > Pixels::ZERO {
151 *slot = Some(*size);
152 }
153 }
154 }
155 }
156
157 for child in children.iter_mut() {
158 walk(child, measured);
159 }
160 }
161
162 walk(self.root_mut(), measured);
163 }
164}
165
166impl PaneTree {
167 fn edit(&mut self, apply: impl FnOnce(&mut Self) -> bool) -> EditResult {
174 let mutated = apply(self);
175 let collapsed = self.normalize_reporting();
176
177 EditResult {
178 changed: mutated || collapsed,
179 }
180 }
181
182 pub fn contains_panel(&self, panel: PanelId) -> bool {
184 self.panels().any(|candidate| candidate == panel)
185 }
186}
187
188impl PaneTree {
189 fn apply_insert(&mut self, panel: PanelId, target: InsertTarget) -> bool {
193 match target {
194 InsertTarget::Tabs { node, ix, activate } => {
195 let Some(path) = self.path_of_node(node) else {
196 return false;
197 };
198 let NodeKind::Tabs { panels, active_ix } = self.node_at_mut(&path).kind_mut()
199 else {
200 return false;
201 };
202 let ix = ix.unwrap_or(panels.len()).min(panels.len());
203 panels.insert(ix, panel);
204 if activate {
205 *active_ix = ix;
206 } else if ix <= *active_ix && panels.len() > 1 {
207 *active_ix += 1;
209 }
210 true
211 }
212 InsertTarget::Split {
213 node,
214 placement,
215 size,
216 } => self.insert_beside(node, panel, placement, size),
217 }
218 }
219
220 fn insert_beside(
228 &mut self,
229 node: NodeId,
230 panel: PanelId,
231 placement: Placement,
232 size: Option<Pixels>,
233 ) -> bool {
234 let Some(path) = self.path_of_node(node) else {
235 return false;
236 };
237 let group_id = self.allocate_node_id();
238 let group = PaneNode::new(
239 group_id,
240 NodeKind::Tabs {
241 panels: vec![panel],
242 active_ix: 0,
243 },
244 );
245 let before = matches!(placement, Placement::Left | Placement::Top);
246
247 if let Some((parent_path, ix)) = split_parent_of(&path) {
248 let parent_axis = match self.node_at(&parent_path).kind_ref() {
249 NodeKind::Split { axis, .. } => Some(*axis),
250 _ => None,
251 };
252
253 if parent_axis == Some(placement.axis()) {
254 let NodeKind::Split {
255 children, sizes, ..
256 } = self.node_at_mut(&parent_path).kind_mut()
257 else {
258 return false;
259 };
260 let share = match size {
271 Some(size) => Some(size),
272 None => {
273 let half = sizes[ix].map(|slot| slot / 2.);
274 sizes[ix] = half;
275 half
276 }
277 };
278 let at = if before { ix } else { ix + 1 };
279 children.insert(at, group);
280 sizes.insert(at, share);
281 return true;
282 }
283 }
284
285 let wrapper_id = self.allocate_node_id();
289 let target = std::mem::replace(
290 self.node_at_mut(&path),
291 PaneNode::new(
292 wrapper_id,
293 NodeKind::Tabs {
294 panels: Vec::new(),
295 active_ix: 0,
296 },
297 ),
298 );
299 let (children, sizes) = if before {
300 (vec![group, target], vec![size, None])
301 } else {
302 (vec![target, group], vec![None, size])
303 };
304 let wrapper = PaneNode::new(
305 wrapper_id,
306 NodeKind::Split {
307 axis: placement.axis(),
308 children,
309 sizes,
310 },
311 );
312 *self.node_at_mut(&path) = wrapper;
313 true
314 }
315
316 fn detach_panel(&mut self, panel: PanelId) -> bool {
318 let Some(node) = self.find_panel_node(panel) else {
319 return false;
320 };
321 let Some(path) = self.path_of_node(node) else {
322 return false;
323 };
324
325 match self.node_at_mut(&path).kind_mut() {
326 NodeKind::Tabs { panels, active_ix } => {
327 let Some(ix) = panels.iter().position(|p| *p == panel) else {
328 return false;
329 };
330 panels.remove(ix);
331 if ix < *active_ix {
332 *active_ix -= 1;
333 }
334 true
335 }
336 NodeKind::Split { .. } => false,
337 }
338 }
339}
340
341fn split_parent_of(path: &super::tree::NodePath) -> Option<(super::tree::NodePath, usize)> {
343 let (&ix, parent) = path.split_last()?;
344 Some((parent.iter().copied().collect(), ix))
345}
346
347#[cfg(test)]
348mod tests {
349 use super::super::*;
350 use crate::Placement;
351 use gpui::{Axis, px};
352
353 fn panel(n: u64) -> PanelId {
354 PanelId::from_u64(n)
355 }
356
357 fn tree_with_one_group() -> (PaneTree, NodeId) {
358 let mut tree = PaneTree::new(RootKind::Split);
359 let tabs = tree.push_tabs_for_test(tree.root().id(), vec![panel(1)]);
360 tree.normalize();
361 (tree, tabs)
362 }
363
364 #[test]
365 fn inserting_into_a_tab_group_appends_and_can_activate() {
366 let (mut tree, tabs) = tree_with_one_group();
367 let result = tree.insert_panel(
368 panel(2),
369 InsertTarget::Tabs {
370 node: tabs,
371 ix: None,
372 activate: true,
373 },
374 );
375
376 assert!(result.changed());
377 let PaneRef::Tabs { panels, active_ix } = tree.find_node(tabs).unwrap().kind() else {
378 panic!()
379 };
380 assert_eq!(panels, [panel(1), panel(2)]);
381 assert_eq!(active_ix, 1, "the inserted panel becomes the displayed one");
382 }
383
384 #[test]
385 fn a_background_insert_leaves_the_active_panel_alone() {
386 let (mut tree, tabs) = tree_with_one_group();
387 let result = tree.insert_panel(
388 panel(2),
389 InsertTarget::Tabs {
390 node: tabs,
391 ix: None,
392 activate: false,
393 },
394 );
395
396 assert!(result.changed());
397 let PaneRef::Tabs { panels, active_ix } = tree.find_node(tabs).unwrap().kind() else {
398 panic!()
399 };
400 assert_eq!(panels, [panel(1), panel(2)]);
401 assert_eq!(active_ix, 0, "the displayed panel is left alone");
402 }
403
404 #[test]
405 fn removing_the_last_panel_collapses_the_group_and_reports_it() {
406 let (mut tree, tabs) = tree_with_one_group();
407 let result = tree.remove_panel(panel(1));
408
409 assert!(result.changed());
410 assert!(!tree.contains_panel(panel(1)), "the panel left the tree");
411 assert!(tree.find_node(tabs).is_none(), "its empty group collapsed");
412 assert!(
413 matches!(tree.root().kind(), PaneRef::Split { children, .. } if children.is_empty())
414 );
415 }
416
417 #[test]
418 fn splitting_creates_a_sibling_group_on_the_requested_side() {
419 let (mut tree, tabs) = tree_with_one_group();
420 let result = tree.split(tabs, panel(2), Placement::Right, Some(px(240.)));
421
422 assert!(result.changed());
423 let PaneRef::Split {
424 axis,
425 children,
426 sizes,
427 } = tree.root().kind()
428 else {
429 panic!()
430 };
431 assert_eq!(axis, Axis::Horizontal);
432 assert_eq!(children.len(), 2);
433 assert_eq!(sizes[1], Some(px(240.)));
434
435 let PaneRef::Tabs { panels, .. } = children[0].kind() else {
436 panic!()
437 };
438 assert_eq!(panels, [panel(1)]);
439 let PaneRef::Tabs { panels, .. } = children[1].kind() else {
440 panic!()
441 };
442 assert_eq!(panels, [panel(2)]);
443 }
444
445 #[test]
446 fn splitting_left_puts_the_new_group_first() {
447 let (mut tree, tabs) = tree_with_one_group();
448 tree.split(tabs, panel(2), Placement::Left, None);
449
450 let PaneRef::Split { children, .. } = tree.root().kind() else {
451 panic!()
452 };
453 let PaneRef::Tabs { panels, .. } = children[0].kind() else {
454 panic!()
455 };
456 assert_eq!(panels, [panel(2)]);
457 }
458
459 #[test]
460 fn splitting_across_the_parent_axis_nests_a_new_split() {
461 let (mut tree, tabs) = tree_with_one_group();
462 tree.push_tabs_for_test(tree.root().id(), vec![panel(9)]);
463 tree.normalize();
464
465 tree.split(tabs, panel(2), Placement::Bottom, None);
466
467 let PaneRef::Split { axis, children, .. } = tree.root().kind() else {
468 panic!()
469 };
470 assert_eq!(axis, Axis::Horizontal);
471 let PaneRef::Split {
472 axis: inner,
473 children: inner_children,
474 ..
475 } = children[0].kind()
476 else {
477 panic!("the split target is wrapped in a vertical split")
478 };
479 assert_eq!(inner, Axis::Vertical);
480 assert_eq!(inner_children.len(), 2);
481
482 let PaneRef::Tabs { panels, .. } = inner_children[0].kind() else {
485 panic!()
486 };
487 assert_eq!(panels, [panel(1)], "the original target stays first");
488 let PaneRef::Tabs { panels, .. } = inner_children[1].kind() else {
489 panic!()
490 };
491 assert_eq!(panels, [panel(2)], "the new group lands second, below");
492 }
493
494 #[test]
495 fn splitting_top_across_the_parent_axis_puts_the_new_group_first() {
496 let (mut tree, tabs) = tree_with_one_group();
497 tree.push_tabs_for_test(tree.root().id(), vec![panel(9)]);
498 tree.normalize();
499
500 tree.split(tabs, panel(2), Placement::Top, None);
501
502 let PaneRef::Split { children, .. } = tree.root().kind() else {
503 panic!()
504 };
505 let PaneRef::Split {
506 axis: inner,
507 children: inner_children,
508 ..
509 } = children[0].kind()
510 else {
511 panic!("the split target is wrapped in a vertical split")
512 };
513 assert_eq!(inner, Axis::Vertical);
514 assert_eq!(inner_children.len(), 2);
515
516 let PaneRef::Tabs { panels, .. } = inner_children[0].kind() else {
519 panic!()
520 };
521 assert_eq!(panels, [panel(2)], "the new group lands first, above");
522 let PaneRef::Tabs { panels, .. } = inner_children[1].kind() else {
523 panic!()
524 };
525 assert_eq!(panels, [panel(1)], "the original target moves second");
526 }
527
528 #[test]
529 fn the_split_target_keeps_its_node_id_so_its_entity_survives() {
530 let (mut tree, tabs) = tree_with_one_group();
531 tree.split(tabs, panel(2), Placement::Right, None);
532
533 assert!(
534 tree.find_node(tabs).is_some(),
535 "the target group is reused, not rebuilt"
536 );
537 }
538
539 #[test]
540 fn moving_a_panel_between_groups_preserves_its_identity() {
541 let (mut tree, tabs) = tree_with_one_group();
542 assert!(tree.split(tabs, panel(2), Placement::Right, None).changed());
543 let other = tree
544 .find_panel_node(panel(2))
545 .expect("the split put panel 2 in a group of its own");
546
547 let result = tree.move_panel(
548 panel(1),
549 InsertTarget::Tabs {
550 node: other,
551 ix: None,
552 activate: true,
553 },
554 );
555
556 assert!(result.changed());
557 assert_eq!(
558 tree.panels().collect::<Vec<_>>(),
559 vec![panel(2), panel(1)],
560 "a move is not a removal; both panels are still in the tree"
561 );
562 assert!(
563 tree.find_node(tabs).is_none(),
564 "the emptied group collapses"
565 );
566 }
567
568 #[test]
569 fn a_no_op_edit_reports_no_change() {
570 let (mut tree, tabs) = tree_with_one_group();
571 let result = tree.set_active(tabs, 0);
572 assert!(!result.changed());
573 }
574
575 #[test]
576 fn set_sizes_replaces_a_matching_length_vector() {
577 let (mut tree, tabs) = tree_with_one_group();
578 tree.split(tabs, panel(2), Placement::Right, None);
579 let root = tree.root().id();
580
581 let result = tree.set_sizes(root, vec![Some(px(100.)), Some(px(200.))]);
582
583 assert!(result.changed());
584 let PaneRef::Split { sizes, .. } = tree.root().kind() else {
585 panic!()
586 };
587 assert_eq!(sizes, &[Some(px(100.)), Some(px(200.))]);
588 }
589
590 #[test]
591 fn set_sizes_ignores_a_mismatched_length_vector() {
592 let (mut tree, tabs) = tree_with_one_group();
593 tree.split(tabs, panel(2), Placement::Right, None);
594 let root = tree.root().id();
595 let PaneRef::Split {
596 sizes: before_sizes,
597 ..
598 } = tree.root().kind()
599 else {
600 panic!()
601 };
602 let before_sizes = before_sizes.to_vec();
603
604 let result = tree.set_sizes(root, vec![Some(px(10.)), Some(px(20.)), Some(px(30.))]);
606
607 assert!(
608 !result.changed(),
609 "a mismatched vector must not report a change"
610 );
611 let PaneRef::Split { sizes, .. } = tree.root().kind() else {
612 panic!()
613 };
614 assert_eq!(
615 sizes,
616 before_sizes.as_slice(),
617 "the mismatched vector is rejected"
618 );
619 }
620
621 #[test]
622 fn every_edit_leaves_the_tree_normalized() {
623 let (mut tree, tabs) = tree_with_one_group();
624 tree.split(tabs, panel(2), Placement::Bottom, None);
625 tree.insert_panel(
626 panel(3),
627 InsertTarget::Tabs {
628 node: tabs,
629 ix: None,
630 activate: false,
631 },
632 );
633 tree.remove_panel(panel(2));
634 tree.remove_panel(panel(3));
635
636 assert!(tree.is_normalized());
637 }
638}