1use crate::model::buffer::Buffer;
27use crate::model::cursor::Cursors;
28use crate::model::event::{BufferId, ContainerId, LeafId, SplitDirection, SplitId};
29use crate::model::marker::MarkerList;
30use crate::view::folding::FoldManager;
31use crate::view::ui::view_pipeline::Layout;
32use crate::view::viewport::Viewport;
33use crate::{services::plugins::api::ViewTransformPayload, state::ViewMode};
34use ratatui::layout::Rect;
35use serde::{Deserialize, Serialize};
36use std::collections::HashMap;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
43pub enum TabTarget {
44 Buffer(BufferId),
46 Group(LeafId),
48}
49
50impl TabTarget {
51 pub fn as_buffer(self) -> Option<BufferId> {
52 match self {
53 Self::Buffer(id) => Some(id),
54 Self::Group(_) => None,
55 }
56 }
57
58 pub fn as_group(self) -> Option<LeafId> {
59 match self {
60 Self::Buffer(_) => None,
61 Self::Group(id) => Some(id),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
72pub enum SplitRole {
73 UtilityDock,
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub enum SplitNode {
82 Leaf {
84 buffer_id: BufferId,
86 split_id: LeafId,
88 #[serde(default)]
92 role: Option<SplitRole>,
93 },
94 Split {
96 direction: SplitDirection,
98 first: Box<Self>,
100 second: Box<Self>,
102 ratio: f32,
105 split_id: ContainerId,
107 #[serde(default)]
109 fixed_first: Option<u16>,
110 #[serde(default)]
112 fixed_second: Option<u16>,
113 },
114 Grouped {
119 split_id: LeafId,
122 name: String,
124 layout: Box<Self>,
126 active_inner_leaf: LeafId,
128 },
129}
130
131#[derive(Debug)]
138pub struct BufferViewState {
139 pub cursors: Cursors,
141
142 pub viewport: Viewport,
144
145 pub view_mode: ViewMode,
147
148 pub compose_width: Option<u16>,
150
151 pub compose_column_guides: Option<Vec<u16>>,
153
154 pub rulers: Vec<usize>,
156
157 pub show_line_numbers: bool,
162
163 pub highlight_current_line: bool,
167
168 pub view_transform: Option<ViewTransformPayload>,
170
171 pub view_transform_stale: bool,
175
176 pub plugin_state: std::collections::HashMap<String, serde_json::Value>,
180
181 pub folds: FoldManager,
183}
184
185impl BufferViewState {
186 pub fn ensure_cursor_visible(&mut self, buffer: &mut Buffer, marker_list: &MarkerList) {
192 let hidden: Vec<(usize, usize)> = self
193 .folds
194 .resolved_ranges(buffer, marker_list)
195 .into_iter()
196 .map(|r| (r.start_byte, r.end_byte))
197 .collect();
198 let cursor = *self.cursors.primary();
199 self.viewport.ensure_visible(buffer, &cursor, &hidden);
200 }
201
202 pub fn new(width: u16, height: u16) -> Self {
204 Self {
205 cursors: Cursors::new(),
206 viewport: Viewport::new(width, height),
207 view_mode: ViewMode::Source,
208 compose_width: None,
209 compose_column_guides: None,
210 rulers: Vec::new(),
211 show_line_numbers: true,
212 highlight_current_line: true,
213 view_transform: None,
214 view_transform_stale: false,
215 plugin_state: std::collections::HashMap::new(),
216 folds: FoldManager::new(),
217 }
218 }
219
220 pub fn apply_config_defaults(
227 &mut self,
228 line_numbers: bool,
229 highlight_current_line: bool,
230 line_wrap: bool,
231 wrap_indent: bool,
232 wrap_column: Option<usize>,
233 rulers: Vec<usize>,
234 ) {
235 self.show_line_numbers = line_numbers;
236 self.highlight_current_line = highlight_current_line;
237 self.viewport.line_wrap_enabled = line_wrap;
238 self.viewport.wrap_indent = wrap_indent;
239 self.viewport.wrap_column = wrap_column;
240 self.rulers = rulers;
241 }
242
243 pub fn activate_page_view(&mut self, page_width: Option<usize>) {
249 self.view_mode = ViewMode::PageView;
250 self.show_line_numbers = false;
251 self.viewport.line_wrap_enabled = false;
252 if let Some(width) = page_width {
253 self.compose_width = Some(width as u16);
254 }
255 }
256}
257
258impl Clone for BufferViewState {
259 fn clone(&self) -> Self {
260 Self {
261 cursors: self.cursors.clone(),
262 viewport: self.viewport.clone(),
263 view_mode: self.view_mode.clone(),
264 compose_width: self.compose_width,
265 compose_column_guides: self.compose_column_guides.clone(),
266 rulers: self.rulers.clone(),
267 show_line_numbers: self.show_line_numbers,
268 highlight_current_line: self.highlight_current_line,
269 view_transform: self.view_transform.clone(),
270 view_transform_stale: self.view_transform_stale,
271 plugin_state: self.plugin_state.clone(),
272 folds: FoldManager::new(),
274 }
275 }
276}
277
278#[derive(Debug, Clone)]
290pub struct SplitViewState {
291 pub active_buffer: BufferId,
293
294 pub keyed_states: HashMap<BufferId, BufferViewState>,
296
297 pub open_buffers: Vec<TabTarget>,
303
304 pub tab_scroll_offset: usize,
306
307 pub layout: Option<Layout>,
310
311 pub layout_dirty: bool,
313
314 pub focus_history: Vec<TabTarget>,
318
319 pub sync_group: Option<u32>,
322
323 pub composite_view: Option<BufferId>,
328
329 pub suppress_chrome: bool,
332
333 pub hide_tilde: bool,
336
337 pub active_group_tab: Option<LeafId>,
341
342 pub focused_group_leaf: Option<LeafId>,
345}
346
347impl std::ops::Deref for SplitViewState {
348 type Target = BufferViewState;
349
350 fn deref(&self) -> &BufferViewState {
351 self.active_state()
352 }
353}
354
355impl std::ops::DerefMut for SplitViewState {
356 fn deref_mut(&mut self) -> &mut BufferViewState {
357 self.active_state_mut()
358 }
359}
360
361impl SplitViewState {
362 pub fn with_buffer(width: u16, height: u16, buffer_id: BufferId) -> Self {
364 let buf_state = BufferViewState::new(width, height);
365 let mut keyed_states = HashMap::new();
366 keyed_states.insert(buffer_id, buf_state);
367 Self {
368 active_buffer: buffer_id,
369 keyed_states,
370 open_buffers: vec![TabTarget::Buffer(buffer_id)],
371 tab_scroll_offset: 0,
372 layout: None,
373 layout_dirty: true,
374 focus_history: Vec::new(),
375 sync_group: None,
376 composite_view: None,
377 suppress_chrome: false,
378 hide_tilde: false,
379 active_group_tab: None,
380 focused_group_leaf: None,
381 }
382 }
383
384 pub fn active_state(&self) -> &BufferViewState {
386 self.keyed_states
387 .get(&self.active_buffer)
388 .expect("active_buffer must always have an entry in keyed_states")
389 }
390
391 pub fn active_state_mut(&mut self) -> &mut BufferViewState {
393 self.keyed_states
394 .get_mut(&self.active_buffer)
395 .expect("active_buffer must always have an entry in keyed_states")
396 }
397
398 pub fn switch_buffer(&mut self, new_buffer_id: BufferId) {
404 if new_buffer_id == self.active_buffer {
405 return;
406 }
407 if !self.keyed_states.contains_key(&new_buffer_id) {
409 let active = self.active_state();
410 let width = active.viewport.width;
411 let height = active.viewport.height;
412 self.keyed_states
413 .insert(new_buffer_id, BufferViewState::new(width, height));
414 }
415 self.active_buffer = new_buffer_id;
416 self.layout_dirty = true;
418 }
419
420 pub fn buffer_state(&self, buffer_id: BufferId) -> Option<&BufferViewState> {
422 self.keyed_states.get(&buffer_id)
423 }
424
425 pub fn buffer_state_mut(&mut self, buffer_id: BufferId) -> Option<&mut BufferViewState> {
427 self.keyed_states.get_mut(&buffer_id)
428 }
429
430 pub fn ensure_buffer_state(&mut self, buffer_id: BufferId) -> &mut BufferViewState {
433 let (width, height) = {
434 let active = self.active_state();
435 (active.viewport.width, active.viewport.height)
436 };
437 self.keyed_states
438 .entry(buffer_id)
439 .or_insert_with(|| BufferViewState::new(width, height))
440 }
441
442 pub fn remove_buffer_state(&mut self, buffer_id: BufferId) {
444 if buffer_id != self.active_buffer {
445 self.keyed_states.remove(&buffer_id);
446 }
447 }
448
449 pub fn invalidate_layout(&mut self) {
451 self.layout_dirty = true;
452 }
453
454 pub fn ensure_layout(
462 &mut self,
463 tokens: &[fresh_core::api::ViewTokenWire],
464 source_range: std::ops::Range<usize>,
465 tab_size: usize,
466 ) -> &Layout {
467 if self.layout.is_none() || self.layout_dirty {
468 self.layout = Some(Layout::from_tokens(tokens, source_range, tab_size));
469 self.layout_dirty = false;
470 }
471 self.layout.as_ref().unwrap()
472 }
473
474 pub fn get_layout(&self) -> Option<&Layout> {
476 if self.layout_dirty {
477 None
478 } else {
479 self.layout.as_ref()
480 }
481 }
482
483 pub fn add_buffer(&mut self, buffer_id: BufferId) {
485 if !self.has_buffer(buffer_id) {
486 self.open_buffers.push(TabTarget::Buffer(buffer_id));
487 }
488 }
489
490 pub fn remove_buffer(&mut self, buffer_id: BufferId) {
492 self.open_buffers
493 .retain(|t| *t != TabTarget::Buffer(buffer_id));
494 if buffer_id != self.active_buffer {
496 self.keyed_states.remove(&buffer_id);
497 }
498 }
499
500 pub fn has_buffer(&self, buffer_id: BufferId) -> bool {
502 self.open_buffers.contains(&TabTarget::Buffer(buffer_id))
503 }
504
505 pub fn add_group(&mut self, leaf_id: LeafId) {
507 if !self.has_group(leaf_id) {
508 self.open_buffers.push(TabTarget::Group(leaf_id));
509 }
510 }
511
512 pub fn remove_group(&mut self, leaf_id: LeafId) {
514 self.open_buffers
515 .retain(|t| *t != TabTarget::Group(leaf_id));
516 }
517
518 pub fn has_group(&self, leaf_id: LeafId) -> bool {
520 self.open_buffers.contains(&TabTarget::Group(leaf_id))
521 }
522
523 pub fn buffer_tab_ids(&self) -> impl Iterator<Item = BufferId> + '_ {
525 self.open_buffers.iter().filter_map(|t| t.as_buffer())
526 }
527
528 pub fn buffer_tab_ids_vec(&self) -> Vec<BufferId> {
531 self.buffer_tab_ids().collect()
532 }
533
534 pub fn buffer_tab_count(&self) -> usize {
536 self.open_buffers
537 .iter()
538 .filter(|t| matches!(t, TabTarget::Buffer(_)))
539 .count()
540 }
541
542 pub fn active_target(&self) -> TabTarget {
546 match self.active_group_tab {
547 Some(leaf_id) => TabTarget::Group(leaf_id),
548 None => TabTarget::Buffer(self.active_buffer),
549 }
550 }
551
552 pub fn set_active_buffer_tab(&mut self, buffer_id: BufferId) {
555 self.active_group_tab = None;
556 self.focused_group_leaf = None;
557 self.switch_buffer(buffer_id);
558 }
559
560 pub fn set_active_group_tab(&mut self, leaf_id: LeafId) {
562 self.active_group_tab = Some(leaf_id);
563 }
564
565 pub fn push_focus(&mut self, target: TabTarget) {
568 self.focus_history.retain(|t| *t != target);
569 self.focus_history.push(target);
570 if self.focus_history.len() > 50 {
571 self.focus_history.remove(0);
572 }
573 }
574
575 pub fn previous_tab(&self) -> Option<TabTarget> {
577 self.focus_history.last().copied()
578 }
579
580 pub fn remove_from_history(&mut self, buffer_id: BufferId) {
582 self.focus_history
583 .retain(|t| *t != TabTarget::Buffer(buffer_id));
584 }
585
586 pub fn remove_group_from_history(&mut self, leaf_id: LeafId) {
588 self.focus_history
589 .retain(|t| *t != TabTarget::Group(leaf_id));
590 }
591}
592
593impl SplitNode {
594 pub fn leaf(buffer_id: BufferId, split_id: SplitId) -> Self {
596 Self::Leaf {
597 buffer_id,
598 split_id: LeafId(split_id),
599 role: None,
600 }
601 }
602
603 pub fn leaf_with_role(buffer_id: BufferId, split_id: SplitId, role: SplitRole) -> Self {
605 Self::Leaf {
606 buffer_id,
607 split_id: LeafId(split_id),
608 role: Some(role),
609 }
610 }
611
612 pub fn role(&self) -> Option<SplitRole> {
614 match self {
615 Self::Leaf { role, .. } => *role,
616 _ => None,
617 }
618 }
619
620 pub fn set_role(&mut self, new_role: Option<SplitRole>) {
622 if let Self::Leaf { role, .. } = self {
623 *role = new_role;
624 }
625 }
626
627 pub fn split(
629 direction: SplitDirection,
630 first: SplitNode,
631 second: SplitNode,
632 ratio: f32,
633 split_id: SplitId,
634 ) -> Self {
635 SplitNode::Split {
636 direction,
637 first: Box::new(first),
638 second: Box::new(second),
639 ratio: ratio.clamp(0.1, 0.9), split_id: ContainerId(split_id),
641 fixed_first: None,
642 fixed_second: None,
643 }
644 }
645
646 pub fn id(&self) -> SplitId {
648 match self {
649 Self::Leaf { split_id, .. } => split_id.0,
650 Self::Split { split_id, .. } => split_id.0,
651 Self::Grouped { split_id, .. } => split_id.0,
652 }
653 }
654
655 pub fn buffer_id(&self) -> Option<BufferId> {
657 match self {
658 Self::Leaf { buffer_id, .. } => Some(*buffer_id),
659 Self::Split { .. } | Self::Grouped { .. } => None,
660 }
661 }
662
663 pub fn find_mut(&mut self, target_id: SplitId) -> Option<&mut Self> {
667 if self.id() == target_id {
668 return Some(self);
669 }
670
671 match self {
672 Self::Leaf { .. } => None,
673 Self::Split { first, second, .. } => first
674 .find_mut(target_id)
675 .or_else(|| second.find_mut(target_id)),
676 Self::Grouped { layout, .. } => layout.find_mut(target_id),
677 }
678 }
679
680 pub fn find(&self, target_id: SplitId) -> Option<&Self> {
684 if self.id() == target_id {
685 return Some(self);
686 }
687
688 match self {
689 Self::Leaf { .. } => None,
690 Self::Split { first, second, .. } => {
691 first.find(target_id).or_else(|| second.find(target_id))
692 }
693 Self::Grouped { layout, .. } => layout.find(target_id),
694 }
695 }
696
697 pub fn parent_container_of(&self, target_id: SplitId) -> Option<ContainerId> {
701 match self {
702 Self::Leaf { .. } => None,
703 Self::Split {
704 split_id,
705 first,
706 second,
707 ..
708 } => {
709 if first.id() == target_id || second.id() == target_id {
710 Some(*split_id)
711 } else {
712 first
713 .parent_container_of(target_id)
714 .or_else(|| second.parent_container_of(target_id))
715 }
716 }
717 Self::Grouped { layout, .. } => layout.parent_container_of(target_id),
718 }
719 }
720
721 pub fn grouped_ancestor_of(&self, target_id: SplitId) -> Option<LeafId> {
724 match self {
725 Self::Leaf { .. } => None,
726 Self::Split { first, second, .. } => first
727 .grouped_ancestor_of(target_id)
728 .or_else(|| second.grouped_ancestor_of(target_id)),
729 Self::Grouped {
730 split_id, layout, ..
731 } => {
732 if layout.find(target_id).is_some() {
733 Some(*split_id)
734 } else {
735 layout.grouped_ancestor_of(target_id)
736 }
737 }
738 }
739 }
740
741 pub fn find_grouped(&self, target: LeafId) -> Option<&Self> {
744 match self {
745 Self::Leaf { .. } => None,
746 Self::Split { first, second, .. } => first
747 .find_grouped(target)
748 .or_else(|| second.find_grouped(target)),
749 Self::Grouped {
750 split_id, layout, ..
751 } => {
752 if *split_id == target {
753 Some(self)
754 } else {
755 layout.find_grouped(target)
756 }
757 }
758 }
759 }
760
761 pub fn get_leaves_with_rects(&self, rect: Rect) -> Vec<(LeafId, BufferId, Rect)> {
767 match self {
768 Self::Leaf {
769 buffer_id,
770 split_id,
771 ..
772 } => {
773 vec![(*split_id, *buffer_id, rect)]
774 }
775 Self::Split {
776 direction,
777 first,
778 second,
779 ratio,
780 fixed_first,
781 fixed_second,
782 ..
783 } => {
784 let (first_rect, second_rect) =
785 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
786 let mut leaves = first.get_leaves_with_rects(first_rect);
787 leaves.extend(second.get_leaves_with_rects(second_rect));
788 leaves
789 }
790 Self::Grouped { layout, .. } => layout.get_leaves_with_rects(rect),
791 }
792 }
793
794 pub fn get_visible_leaves_with_rects<F>(
800 &self,
801 rect: Rect,
802 is_group_active: &F,
803 ) -> Vec<(LeafId, BufferId, Rect)>
804 where
805 F: Fn(LeafId) -> bool,
806 {
807 match self {
808 Self::Leaf {
809 buffer_id,
810 split_id,
811 ..
812 } => {
813 vec![(*split_id, *buffer_id, rect)]
814 }
815 Self::Split {
816 direction,
817 first,
818 second,
819 ratio,
820 fixed_first,
821 fixed_second,
822 ..
823 } => {
824 let (first_rect, second_rect) =
825 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
826 let mut leaves = first.get_visible_leaves_with_rects(first_rect, is_group_active);
827 leaves.extend(second.get_visible_leaves_with_rects(second_rect, is_group_active));
828 leaves
829 }
830 Self::Grouped {
831 split_id, layout, ..
832 } => {
833 if is_group_active(*split_id) {
834 layout.get_visible_leaves_with_rects(rect, is_group_active)
835 } else {
836 Vec::new()
837 }
838 }
839 }
840 }
841
842 pub fn get_separators(&self, rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
845 self.get_separators_with_ids(rect)
846 .into_iter()
847 .map(|(_, dir, x, y, len)| (dir, x, y, len))
848 .collect()
849 }
850
851 pub fn get_separators_with_ids(
854 &self,
855 rect: Rect,
856 ) -> Vec<(ContainerId, SplitDirection, u16, u16, u16)> {
857 match self {
858 Self::Leaf { .. } => vec![],
859 Self::Grouped { layout, .. } => layout.get_separators_with_ids(rect),
860 Self::Split {
861 direction,
862 first,
863 second,
864 ratio,
865 split_id,
866 fixed_first,
867 fixed_second,
868 } => {
869 let (first_rect, second_rect) =
870 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
871 let mut separators = Vec::new();
872
873 match direction {
875 SplitDirection::Horizontal => {
876 separators.push((
879 *split_id,
880 SplitDirection::Horizontal,
881 rect.x,
882 first_rect.y + first_rect.height,
883 rect.width,
884 ));
885 }
886 SplitDirection::Vertical => {
887 separators.push((
890 *split_id,
891 SplitDirection::Vertical,
892 first_rect.x + first_rect.width,
893 rect.y,
894 rect.height,
895 ));
896 }
897 }
898
899 separators.extend(first.get_separators_with_ids(first_rect));
901 separators.extend(second.get_separators_with_ids(second_rect));
902 separators
903 }
904 }
905 }
906
907 pub fn all_split_ids(&self) -> Vec<SplitId> {
909 let mut ids = vec![self.id()];
910 match self {
911 Self::Leaf { .. } => ids,
912 Self::Split { first, second, .. } => {
913 ids.extend(first.all_split_ids());
914 ids.extend(second.all_split_ids());
915 ids
916 }
917 Self::Grouped { layout, .. } => {
918 ids.extend(layout.all_split_ids());
919 ids
920 }
921 }
922 }
923
924 pub fn leaf_split_ids(&self) -> Vec<LeafId> {
927 match self {
928 Self::Leaf { split_id, .. } => vec![*split_id],
929 Self::Split { first, second, .. } => {
930 let mut ids = first.leaf_split_ids();
931 ids.extend(second.leaf_split_ids());
932 ids
933 }
934 Self::Grouped { layout, .. } => layout.leaf_split_ids(),
935 }
936 }
937
938 pub fn count_leaves(&self) -> usize {
941 match self {
942 Self::Leaf { .. } => 1,
943 Self::Split { first, second, .. } => first.count_leaves() + second.count_leaves(),
944 Self::Grouped { layout, .. } => layout.count_leaves(),
945 }
946 }
947
948 pub fn collect_group_names(&self) -> HashMap<LeafId, String> {
951 let mut map = HashMap::new();
952 self.collect_group_names_into(&mut map);
953 map
954 }
955
956 fn collect_group_names_into(&self, map: &mut HashMap<LeafId, String>) {
957 match self {
958 Self::Leaf { .. } => {}
959 Self::Split { first, second, .. } => {
960 first.collect_group_names_into(map);
961 second.collect_group_names_into(map);
962 }
963 Self::Grouped {
964 split_id,
965 name,
966 layout,
967 ..
968 } => {
969 map.insert(*split_id, name.clone());
970 layout.collect_group_names_into(map);
971 }
972 }
973 }
974}
975
976#[cfg(test)]
979fn split_rect(rect: Rect, direction: SplitDirection, ratio: f32) -> (Rect, Rect) {
980 split_rect_ext(rect, direction, ratio, None, None)
981}
982
983fn split_rect_ext(
984 rect: Rect,
985 direction: SplitDirection,
986 ratio: f32,
987 fixed_first: Option<u16>,
988 fixed_second: Option<u16>,
989) -> (Rect, Rect) {
990 match direction {
991 SplitDirection::Horizontal => {
992 let total_height = rect.height.saturating_sub(1); let first_height = if let Some(f) = fixed_first {
995 f.min(total_height)
996 } else if let Some(s) = fixed_second {
997 total_height.saturating_sub(s.min(total_height))
998 } else {
999 (total_height as f32 * ratio).round() as u16
1000 };
1001 let second_height = total_height.saturating_sub(first_height);
1002
1003 let first = Rect {
1004 x: rect.x,
1005 y: rect.y,
1006 width: rect.width,
1007 height: first_height,
1008 };
1009
1010 let second = Rect {
1011 x: rect.x,
1012 y: rect.y + first_height + 1, width: rect.width,
1014 height: second_height,
1015 };
1016
1017 (first, second)
1018 }
1019 SplitDirection::Vertical => {
1020 let total_width = rect.width.saturating_sub(1); let first_width = if let Some(f) = fixed_first {
1023 f.min(total_width)
1024 } else if let Some(s) = fixed_second {
1025 total_width.saturating_sub(s.min(total_width))
1026 } else {
1027 (total_width as f32 * ratio).round() as u16
1028 };
1029 let second_width = total_width.saturating_sub(first_width);
1030
1031 let first = Rect {
1032 x: rect.x,
1033 y: rect.y,
1034 width: first_width,
1035 height: rect.height,
1036 };
1037
1038 let second = Rect {
1039 x: rect.x + first_width + 1, y: rect.y,
1041 width: second_width,
1042 height: rect.height,
1043 };
1044
1045 (first, second)
1046 }
1047 }
1048}
1049
1050#[derive(Debug)]
1052pub struct SplitManager {
1053 root: SplitNode,
1055
1056 active_split: LeafId,
1058
1059 next_split_id: usize,
1061
1062 maximized_split: Option<SplitId>,
1064
1065 labels: HashMap<SplitId, String>,
1067}
1068
1069impl SplitManager {
1070 pub fn new(buffer_id: BufferId) -> Self {
1072 let split_id = SplitId(0);
1073 Self {
1074 root: SplitNode::leaf(buffer_id, split_id),
1075 active_split: LeafId(split_id),
1076 next_split_id: 1,
1077 maximized_split: None,
1078 labels: HashMap::new(),
1079 }
1080 }
1081
1082 pub fn root(&self) -> &SplitNode {
1084 &self.root
1085 }
1086
1087 pub fn allocate_split_id(&mut self) -> SplitId {
1089 let id = SplitId(self.next_split_id);
1090 self.next_split_id += 1;
1091 id
1092 }
1093
1094 pub fn replace_root(&mut self, new_root: SplitNode, new_active: LeafId) {
1098 self.root = new_root;
1099 self.active_split = new_active;
1100 }
1101
1102 pub fn active_split(&self) -> LeafId {
1104 self.active_split
1105 }
1106
1107 pub fn set_active_split(&mut self, split_id: LeafId) -> bool {
1109 if self.root.find(split_id.into()).is_some() {
1111 self.active_split = split_id;
1112 true
1113 } else {
1114 false
1115 }
1116 }
1117
1118 pub fn active_buffer_id(&self) -> Option<BufferId> {
1120 self.root
1121 .find(self.active_split.into())
1122 .and_then(|node| node.buffer_id())
1123 }
1124
1125 pub fn get_buffer_id(&self, split_id: SplitId) -> Option<BufferId> {
1127 self.root.find(split_id).and_then(|node| node.buffer_id())
1128 }
1129
1130 pub fn set_active_buffer_id(&mut self, new_buffer_id: BufferId) -> bool {
1132 if let Some(SplitNode::Leaf { buffer_id, .. }) =
1133 self.root.find_mut(self.active_split.into())
1134 {
1135 *buffer_id = new_buffer_id;
1136 return true;
1137 }
1138 false
1139 }
1140
1141 pub fn set_split_buffer(&mut self, leaf_id: LeafId, new_buffer_id: BufferId) {
1143 match self.root.find_mut(leaf_id.into()) {
1144 Some(SplitNode::Leaf { buffer_id, .. }) => {
1145 *buffer_id = new_buffer_id;
1146 }
1147 Some(SplitNode::Split { .. }) => {
1148 unreachable!("LeafId {:?} points to a container", leaf_id)
1149 }
1150 Some(SplitNode::Grouped { .. }) => {
1151 unreachable!("LeafId {:?} points to a Grouped node", leaf_id)
1152 }
1153 None => {
1154 unreachable!("LeafId {:?} not found in split tree", leaf_id)
1155 }
1156 }
1157 }
1158
1159 pub fn split_active(
1163 &mut self,
1164 direction: SplitDirection,
1165 new_buffer_id: BufferId,
1166 ratio: f32,
1167 ) -> Result<LeafId, String> {
1168 self.split_active_positioned(direction, new_buffer_id, ratio, false)
1169 }
1170
1171 pub fn split_active_before(
1174 &mut self,
1175 direction: SplitDirection,
1176 new_buffer_id: BufferId,
1177 ratio: f32,
1178 ) -> Result<LeafId, String> {
1179 self.split_active_positioned(direction, new_buffer_id, ratio, true)
1180 }
1181
1182 pub fn split_active_positioned(
1183 &mut self,
1184 direction: SplitDirection,
1185 new_buffer_id: BufferId,
1186 ratio: f32,
1187 before: bool,
1188 ) -> Result<LeafId, String> {
1189 let active_id: SplitId = self.active_split.into();
1190
1191 let result =
1193 self.replace_split_with_split(active_id, direction, new_buffer_id, ratio, before);
1194
1195 if let Ok(new_split_id) = &result {
1196 self.active_split = *new_split_id;
1198 }
1199 result
1200 }
1201
1202 pub fn split_root_positioned(
1211 &mut self,
1212 direction: SplitDirection,
1213 new_buffer_id: BufferId,
1214 ratio: f32,
1215 before: bool,
1216 ) -> Result<LeafId, String> {
1217 let root_id = self.root.id();
1218 let result =
1219 self.replace_split_with_split(root_id, direction, new_buffer_id, ratio, before);
1220 if let Ok(new_split_id) = &result {
1221 self.active_split = *new_split_id;
1222 }
1223 result
1224 }
1225
1226 fn replace_split_with_split(
1229 &mut self,
1230 target_id: SplitId,
1231 direction: SplitDirection,
1232 new_buffer_id: BufferId,
1233 ratio: f32,
1234 before: bool,
1235 ) -> Result<LeafId, String> {
1236 let temp_id = self.allocate_split_id();
1238 let new_split_id = self.allocate_split_id();
1239 let new_leaf_id = self.allocate_split_id();
1240
1241 if self.root.id() == target_id {
1243 let old_root =
1244 std::mem::replace(&mut self.root, SplitNode::leaf(new_buffer_id, temp_id));
1245 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1246
1247 let (first, second) = if before {
1248 (new_leaf, old_root)
1249 } else {
1250 (old_root, new_leaf)
1251 };
1252
1253 self.root = SplitNode::split(direction, first, second, ratio, new_split_id);
1254
1255 return Ok(LeafId(new_leaf_id));
1256 }
1257
1258 if let Some(node) = self.root.find_mut(target_id) {
1260 let old_node = std::mem::replace(node, SplitNode::leaf(new_buffer_id, temp_id));
1261 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1262
1263 let (first, second) = if before {
1264 (new_leaf, old_node)
1265 } else {
1266 (old_node, new_leaf)
1267 };
1268
1269 *node = SplitNode::split(direction, first, second, ratio, new_split_id);
1270
1271 Ok(LeafId(new_leaf_id))
1272 } else {
1273 Err(format!("Split {:?} not found", target_id))
1274 }
1275 }
1276
1277 pub fn close_split(&mut self, split_id: LeafId) -> Result<(), String> {
1279 if self.root.count_leaves() <= 1 {
1281 return Err("Cannot close the last split".to_string());
1282 }
1283
1284 if self.root.id() == split_id.into() && self.root.buffer_id().is_some() {
1286 return Err("Cannot close the only split".to_string());
1287 }
1288
1289 if self.maximized_split == Some(split_id.into()) {
1291 self.maximized_split = None;
1292 }
1293
1294 let removed_ids: Vec<SplitId> = self
1296 .root
1297 .find(split_id.into())
1298 .map(|node| node.all_split_ids())
1299 .unwrap_or_default();
1300
1301 let result = self.remove_split_node(split_id.into());
1304
1305 if result.is_ok() {
1306 for id in &removed_ids {
1308 self.labels.remove(id);
1309 }
1310
1311 if self.active_split == split_id {
1313 let leaf_ids = self.root.leaf_split_ids();
1314 if let Some(&first_leaf) = leaf_ids.first() {
1315 self.active_split = first_leaf;
1316 }
1317 }
1318 }
1319
1320 result
1321 }
1322
1323 fn remove_split_node(&mut self, target_id: SplitId) -> Result<(), String> {
1325 if self.root.id() == target_id {
1327 if let SplitNode::Split { first, .. } = &self.root {
1328 self.root = (**first).clone();
1331 return Ok(());
1332 }
1333 }
1334
1335 Self::remove_child_static(&mut self.root, target_id)
1337 }
1338
1339 fn remove_child_static(node: &mut SplitNode, target_id: SplitId) -> Result<(), String> {
1341 match node {
1342 SplitNode::Leaf { .. } => Err("Target not found".to_string()),
1343 SplitNode::Grouped { layout, .. } => Self::remove_child_static(layout, target_id),
1344 SplitNode::Split { first, second, .. } => {
1345 if first.id() == target_id {
1347 *node = (**second).clone();
1349 Ok(())
1350 } else if second.id() == target_id {
1351 *node = (**first).clone();
1353 Ok(())
1354 } else {
1355 Self::remove_child_static(first, target_id)
1357 .or_else(|_| Self::remove_child_static(second, target_id))
1358 }
1359 }
1360 }
1361 }
1362
1363 pub fn remove_grouped(&mut self, target: LeafId) -> Result<(), String> {
1371 let target_id: SplitId = target.into();
1372 if self.root.id() == target_id {
1373 return Err("Cannot remove root Grouped node".to_string());
1374 }
1375 Self::remove_child_static(&mut self.root, target_id)
1376 }
1377
1378 pub fn adjust_ratio(&mut self, container_id: ContainerId, delta: f32) {
1380 match self.root.find_mut(container_id.into()) {
1381 Some(SplitNode::Split { ratio, .. }) => {
1382 *ratio = (*ratio + delta).clamp(0.1, 0.9);
1383 }
1384 Some(SplitNode::Leaf { .. }) => {
1385 unreachable!("ContainerId {:?} points to a leaf", container_id)
1386 }
1387 Some(SplitNode::Grouped { .. }) => {
1388 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1389 }
1390 None => {
1391 unreachable!("ContainerId {:?} not found in split tree", container_id)
1392 }
1393 }
1394 }
1395
1396 pub fn parent_container_of(&self, leaf_id: LeafId) -> Option<ContainerId> {
1398 self.root.parent_container_of(leaf_id.into())
1399 }
1400
1401 pub fn get_visible_buffers(&self, viewport_rect: Rect) -> Vec<(LeafId, BufferId, Rect)> {
1403 if let Some(maximized_id) = self.maximized_split {
1405 if let Some(SplitNode::Leaf {
1406 buffer_id,
1407 split_id,
1408 ..
1409 }) = self.root.find(maximized_id)
1410 {
1411 return vec![(*split_id, *buffer_id, viewport_rect)];
1412 }
1413 }
1415 self.root.get_leaves_with_rects(viewport_rect)
1416 }
1417
1418 pub fn get_separators(&self, viewport_rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
1421 if self.maximized_split.is_some() {
1423 return vec![];
1424 }
1425 self.root.get_separators(viewport_rect)
1426 }
1427
1428 pub fn get_separators_with_ids(
1431 &self,
1432 viewport_rect: Rect,
1433 ) -> Vec<(ContainerId, SplitDirection, u16, u16, u16)> {
1434 if self.maximized_split.is_some() {
1436 return vec![];
1437 }
1438 self.root.get_separators_with_ids(viewport_rect)
1439 }
1440
1441 pub fn get_ratio(&self, split_id: SplitId) -> Option<f32> {
1443 if let Some(SplitNode::Split { ratio, .. }) = self.root.find(split_id) {
1444 Some(*ratio)
1445 } else {
1446 None
1447 }
1448 }
1449
1450 pub fn set_ratio(&mut self, container_id: ContainerId, new_ratio: f32) {
1452 match self.root.find_mut(container_id.into()) {
1453 Some(SplitNode::Split { ratio, .. }) => {
1454 *ratio = new_ratio.clamp(0.1, 0.9);
1455 }
1456 Some(SplitNode::Leaf { .. }) => {
1457 unreachable!("ContainerId {:?} points to a leaf", container_id)
1458 }
1459 Some(SplitNode::Grouped { .. }) => {
1460 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1461 }
1462 None => {
1463 unreachable!("ContainerId {:?} not found in split tree", container_id)
1464 }
1465 }
1466 }
1467
1468 pub fn set_fixed_size(
1471 &mut self,
1472 container_id: ContainerId,
1473 first: Option<u16>,
1474 second: Option<u16>,
1475 ) {
1476 if let Some(SplitNode::Split {
1477 fixed_first,
1478 fixed_second,
1479 ..
1480 }) = self.root.find_mut(container_id.into())
1481 {
1482 *fixed_first = first;
1483 *fixed_second = second;
1484 }
1485 }
1486
1487 pub fn distribute_splits_evenly(&mut self) {
1490 Self::distribute_node_evenly(&mut self.root);
1491 }
1492
1493 fn distribute_node_evenly(node: &mut SplitNode) -> usize {
1496 match node {
1497 SplitNode::Leaf { .. } => 1,
1498 SplitNode::Grouped { layout, .. } => Self::distribute_node_evenly(layout),
1499 SplitNode::Split {
1500 first,
1501 second,
1502 ratio,
1503 ..
1504 } => {
1505 let first_leaves = Self::distribute_node_evenly(first);
1506 let second_leaves = Self::distribute_node_evenly(second);
1507 let total_leaves = first_leaves + second_leaves;
1508
1509 *ratio = (first_leaves as f32 / total_leaves as f32).clamp(0.1, 0.9);
1512
1513 total_leaves
1514 }
1515 }
1516 }
1517
1518 pub fn next_split(&mut self) {
1520 let leaf_ids = self.root.leaf_split_ids();
1521 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1522 let next_pos = (pos + 1) % leaf_ids.len();
1523 self.active_split = leaf_ids[next_pos];
1524 }
1525 }
1526
1527 pub fn prev_split(&mut self) {
1529 let leaf_ids = self.root.leaf_split_ids();
1530 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1531 let prev_pos = if pos == 0 { leaf_ids.len() } else { pos } - 1;
1532 self.active_split = leaf_ids[prev_pos];
1533 }
1534 }
1535
1536 pub fn splits_for_buffer(&self, target_buffer_id: BufferId) -> Vec<LeafId> {
1538 self.root
1539 .get_leaves_with_rects(Rect {
1540 x: 0,
1541 y: 0,
1542 width: 1,
1543 height: 1,
1544 })
1545 .into_iter()
1546 .filter(|(_, buffer_id, _)| *buffer_id == target_buffer_id)
1547 .map(|(split_id, _, _)| split_id)
1548 .collect()
1549 }
1550
1551 pub fn buffer_for_split(&self, target_split_id: LeafId) -> Option<BufferId> {
1553 self.root
1554 .get_leaves_with_rects(Rect {
1555 x: 0,
1556 y: 0,
1557 width: 1,
1558 height: 1,
1559 })
1560 .into_iter()
1561 .find(|(split_id, _, _)| *split_id == target_split_id)
1562 .map(|(_, buffer_id, _)| buffer_id)
1563 }
1564
1565 pub fn maximize_split(&mut self) -> Result<(), String> {
1568 if self.root.count_leaves() <= 1 {
1570 return Err("Cannot maximize: only one split exists".to_string());
1571 }
1572
1573 if self.maximized_split.is_some() {
1575 return Err("A split is already maximized".to_string());
1576 }
1577
1578 self.maximized_split = Some(self.active_split.into());
1580 Ok(())
1581 }
1582
1583 pub fn unmaximize_split(&mut self) -> Result<(), String> {
1586 if self.maximized_split.is_none() {
1587 return Err("No split is maximized".to_string());
1588 }
1589
1590 self.maximized_split = None;
1591 Ok(())
1592 }
1593
1594 pub fn is_maximized(&self) -> bool {
1596 self.maximized_split.is_some()
1597 }
1598
1599 pub fn maximized_split(&self) -> Option<SplitId> {
1601 self.maximized_split
1602 }
1603
1604 pub fn toggle_maximize(&mut self) -> Result<bool, String> {
1608 if self.is_maximized() {
1609 self.unmaximize_split()?;
1610 Ok(false)
1611 } else {
1612 self.maximize_split()?;
1613 Ok(true)
1614 }
1615 }
1616
1617 pub fn get_splits_in_group(
1619 &self,
1620 group_id: u32,
1621 view_states: &std::collections::HashMap<LeafId, SplitViewState>,
1622 ) -> Vec<LeafId> {
1623 self.root
1624 .leaf_split_ids()
1625 .into_iter()
1626 .filter(|id| {
1627 view_states
1628 .get(id)
1629 .and_then(|vs| vs.sync_group)
1630 .is_some_and(|g| g == group_id)
1631 })
1632 .collect()
1633 }
1634
1635 pub fn set_label(&mut self, split_id: LeafId, label: String) {
1639 self.labels.insert(split_id.into(), label);
1640 }
1641
1642 pub fn clear_label(&mut self, split_id: SplitId) {
1644 self.labels.remove(&split_id);
1645 }
1646
1647 pub fn get_label(&self, split_id: SplitId) -> Option<&str> {
1649 self.labels.get(&split_id).map(|s| s.as_str())
1650 }
1651
1652 pub fn labels(&self) -> &HashMap<SplitId, String> {
1654 &self.labels
1655 }
1656
1657 pub fn set_leaf_role(&mut self, split_id: LeafId, new_role: Option<SplitRole>) {
1661 if let Some(node) = self.root.find_mut(split_id.into()) {
1662 node.set_role(new_role);
1663 }
1664 }
1665
1666 pub fn find_leaf_by_role(&self, target: SplitRole) -> Option<LeafId> {
1668 fn walk(node: &SplitNode, target: SplitRole) -> Option<LeafId> {
1669 match node {
1670 SplitNode::Leaf {
1671 role: Some(r),
1672 split_id,
1673 ..
1674 } if *r == target => Some(*split_id),
1675 SplitNode::Leaf { .. } => None,
1676 SplitNode::Split { first, second, .. } => {
1677 walk(first, target).or_else(|| walk(second, target))
1678 }
1679 SplitNode::Grouped { layout, .. } => walk(layout, target),
1680 }
1681 }
1682 walk(&self.root, target)
1683 }
1684
1685 pub fn clear_role(&mut self, target: SplitRole) -> Option<LeafId> {
1689 let leaf = self.find_leaf_by_role(target)?;
1690 self.set_leaf_role(leaf, None);
1691 Some(leaf)
1692 }
1693
1694 pub fn find_split_by_label(&self, label: &str) -> Option<LeafId> {
1696 self.root
1697 .leaf_split_ids()
1698 .into_iter()
1699 .find(|id| self.labels.get(&(*id).into()).is_some_and(|l| l == label))
1700 }
1701
1702 pub fn find_unlabeled_leaf(&self) -> Option<LeafId> {
1704 self.root
1705 .leaf_split_ids()
1706 .into_iter()
1707 .find(|id| !self.labels.contains_key(&(*id).into()))
1708 }
1709}
1710
1711#[cfg(test)]
1712mod tests {
1713 use super::*;
1714
1715 #[test]
1716 fn test_create_split_manager() {
1717 let buffer_id = BufferId(0);
1718 let manager = SplitManager::new(buffer_id);
1719
1720 assert_eq!(manager.active_buffer_id(), Some(buffer_id));
1721 assert_eq!(manager.root().count_leaves(), 1);
1722 }
1723
1724 #[test]
1725 fn test_horizontal_split() {
1726 let buffer_a = BufferId(0);
1727 let buffer_b = BufferId(1);
1728
1729 let mut manager = SplitManager::new(buffer_a);
1730 let result = manager.split_active(SplitDirection::Horizontal, buffer_b, 0.5);
1731
1732 assert!(result.is_ok());
1733 assert_eq!(manager.root().count_leaves(), 2);
1734 }
1735
1736 #[test]
1737 fn test_vertical_split() {
1738 let buffer_a = BufferId(0);
1739 let buffer_b = BufferId(1);
1740
1741 let mut manager = SplitManager::new(buffer_a);
1742 let result = manager.split_active(SplitDirection::Vertical, buffer_b, 0.5);
1743
1744 assert!(result.is_ok());
1745 assert_eq!(manager.root().count_leaves(), 2);
1746 }
1747
1748 #[test]
1749 fn test_nested_splits() {
1750 let buffer_a = BufferId(0);
1751 let buffer_b = BufferId(1);
1752 let buffer_c = BufferId(2);
1753
1754 let mut manager = SplitManager::new(buffer_a);
1755
1756 manager
1758 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1759 .unwrap();
1760
1761 manager
1763 .split_active(SplitDirection::Vertical, buffer_c, 0.5)
1764 .unwrap();
1765
1766 assert_eq!(manager.root().count_leaves(), 3);
1767 }
1768
1769 #[test]
1770 fn test_close_split() {
1771 let buffer_a = BufferId(0);
1772 let buffer_b = BufferId(1);
1773
1774 let mut manager = SplitManager::new(buffer_a);
1775 let new_split = manager
1776 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1777 .unwrap();
1778
1779 assert_eq!(manager.root().count_leaves(), 2);
1780
1781 let result = manager.close_split(new_split);
1783 assert!(result.is_ok());
1784 assert_eq!(manager.root().count_leaves(), 1);
1785 }
1786
1787 #[test]
1788 fn test_cannot_close_last_split() {
1789 let buffer_a = BufferId(0);
1790 let mut manager = SplitManager::new(buffer_a);
1791
1792 let result = manager.close_split(manager.active_split());
1793 assert!(result.is_err());
1794 }
1795
1796 #[test]
1797 fn test_split_rect_horizontal() {
1798 let rect = Rect {
1799 x: 0,
1800 y: 0,
1801 width: 100,
1802 height: 100,
1803 };
1804
1805 let (first, second) = split_rect(rect, SplitDirection::Horizontal, 0.5);
1806
1807 assert_eq!(first.height, 50);
1809 assert_eq!(second.height, 49);
1810 assert_eq!(first.width, 100);
1811 assert_eq!(second.width, 100);
1812 assert_eq!(first.y, 0);
1813 assert_eq!(second.y, 51); }
1815
1816 #[test]
1817 fn test_split_rect_vertical() {
1818 let rect = Rect {
1819 x: 0,
1820 y: 0,
1821 width: 100,
1822 height: 100,
1823 };
1824
1825 let (first, second) = split_rect(rect, SplitDirection::Vertical, 0.5);
1826
1827 assert_eq!(first.width, 50);
1829 assert_eq!(second.width, 49);
1830 assert_eq!(first.height, 100);
1831 assert_eq!(second.height, 100);
1832 assert_eq!(first.x, 0);
1833 assert_eq!(second.x, 51); }
1835
1836 #[test]
1839 fn test_set_and_get_label() {
1840 let mut manager = SplitManager::new(BufferId(0));
1841 let split = manager.active_split();
1842
1843 assert_eq!(manager.get_label(split.into()), None);
1844
1845 manager.set_label(split, "sidebar".to_string());
1846 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
1847 }
1848
1849 #[test]
1850 fn test_clear_label() {
1851 let mut manager = SplitManager::new(BufferId(0));
1852 let split = manager.active_split();
1853
1854 manager.set_label(split, "sidebar".to_string());
1855 assert!(manager.get_label(split.into()).is_some());
1856
1857 manager.clear_label(split.into());
1858 assert_eq!(manager.get_label(split.into()), None);
1859 }
1860
1861 #[test]
1862 fn test_find_split_by_label() {
1863 let mut manager = SplitManager::new(BufferId(0));
1864 let first_split = manager.active_split();
1865
1866 let second_split = manager
1867 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1868 .unwrap();
1869
1870 manager.set_label(first_split, "sidebar".to_string());
1871
1872 assert_eq!(manager.find_split_by_label("sidebar"), Some(first_split));
1873 assert_eq!(manager.find_split_by_label("terminal"), None);
1874
1875 assert_ne!(manager.find_split_by_label("sidebar"), Some(second_split));
1877 }
1878
1879 #[test]
1880 fn test_find_unlabeled_leaf() {
1881 let mut manager = SplitManager::new(BufferId(0));
1882 let first_split = manager.active_split();
1883
1884 let second_split = manager
1885 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1886 .unwrap();
1887
1888 assert!(manager.find_unlabeled_leaf().is_some());
1890
1891 manager.set_label(first_split, "sidebar".to_string());
1893 assert_eq!(manager.find_unlabeled_leaf(), Some(second_split));
1894
1895 manager.set_label(second_split, "terminal".to_string());
1897 assert_eq!(manager.find_unlabeled_leaf(), None);
1898 }
1899
1900 #[test]
1901 fn test_close_split_cleans_up_label() {
1902 let mut manager = SplitManager::new(BufferId(0));
1903 let _first_split = manager.active_split();
1904
1905 let second_split = manager
1906 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1907 .unwrap();
1908
1909 manager.set_label(second_split, "sidebar".to_string());
1910 assert_eq!(manager.find_split_by_label("sidebar"), Some(second_split));
1911
1912 manager.close_split(second_split).unwrap();
1913
1914 assert_eq!(manager.find_split_by_label("sidebar"), None);
1916 assert_eq!(manager.get_label(second_split.into()), None);
1917 }
1918
1919 #[test]
1920 fn test_label_overwrite() {
1921 let mut manager = SplitManager::new(BufferId(0));
1922 let split = manager.active_split();
1923
1924 manager.set_label(split, "sidebar".to_string());
1925 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
1926
1927 manager.set_label(split, "terminal".to_string());
1928 assert_eq!(manager.get_label(split.into()), Some("terminal"));
1929 assert_eq!(manager.find_split_by_label("sidebar"), None);
1930 assert_eq!(manager.find_split_by_label("terminal"), Some(split));
1931 }
1932
1933 #[test]
1934 fn test_find_unlabeled_leaf_single_split_no_label() {
1935 let manager = SplitManager::new(BufferId(0));
1936 assert_eq!(manager.find_unlabeled_leaf(), Some(manager.active_split()));
1938 }
1939
1940 #[test]
1941 fn test_find_unlabeled_leaf_single_split_labeled() {
1942 let mut manager = SplitManager::new(BufferId(0));
1943 let split = manager.active_split();
1944 manager.set_label(split, "only".to_string());
1945 assert_eq!(manager.find_unlabeled_leaf(), None);
1947 }
1948
1949 #[test]
1954 fn test_split_root_positioned_with_existing_vertical_split() {
1955 let left = BufferId(0);
1957 let right = BufferId(1);
1958 let dock = BufferId(2);
1959 let mut manager = SplitManager::new(left);
1960 manager
1961 .split_active(SplitDirection::Vertical, right, 0.5)
1962 .expect("vertical split");
1963 assert!(matches!(
1965 manager.root(),
1966 SplitNode::Split {
1967 direction: SplitDirection::Vertical,
1968 ..
1969 }
1970 ));
1971 assert_eq!(manager.root().count_leaves(), 2);
1972 let active_before = manager.active_split();
1976
1977 let dock_leaf = manager
1979 .split_root_positioned(SplitDirection::Horizontal, dock, 0.7, false)
1980 .expect("split_root_positioned");
1981
1982 match manager.root() {
1987 SplitNode::Split {
1988 direction: SplitDirection::Horizontal,
1989 first,
1990 second,
1991 ..
1992 } => {
1993 assert!(
1994 matches!(
1995 first.as_ref(),
1996 SplitNode::Split {
1997 direction: SplitDirection::Vertical,
1998 ..
1999 }
2000 ),
2001 "first child of new root must be the original Vertical split, got {:?}",
2002 first
2003 );
2004 match second.as_ref() {
2005 SplitNode::Leaf {
2006 buffer_id,
2007 split_id,
2008 ..
2009 } => {
2010 assert_eq!(*buffer_id, dock, "second child must be the dock leaf");
2011 assert_eq!(
2012 *split_id, dock_leaf,
2013 "split_root_positioned must return the new leaf id"
2014 );
2015 }
2016 other => panic!("expected dock leaf as second child, got {:?}", other),
2017 }
2018 }
2019 other => {
2020 panic!(
2021 "root must be a Horizontal Split after split_root_positioned, got {:?}",
2022 other
2023 );
2024 }
2025 }
2026 assert_eq!(manager.root().count_leaves(), 3);
2028 assert_ne!(
2031 dock_leaf, active_before,
2032 "dock must be a new sibling of the root, not the previously-active leaf"
2033 );
2034 }
2035}