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 focus_history: Vec<LeafId>,
1076}
1077
1078const FOCUS_HISTORY_CAP: usize = 50;
1081
1082impl SplitManager {
1083 pub fn new(buffer_id: BufferId) -> Self {
1085 let split_id = SplitId(0);
1086 Self {
1087 root: SplitNode::leaf(buffer_id, split_id),
1088 active_split: LeafId(split_id),
1089 next_split_id: 1,
1090 maximized_split: None,
1091 labels: HashMap::new(),
1092 focus_history: vec![LeafId(split_id)],
1093 }
1094 }
1095
1096 pub fn root(&self) -> &SplitNode {
1098 &self.root
1099 }
1100
1101 pub fn allocate_split_id(&mut self) -> SplitId {
1103 let id = SplitId(self.next_split_id);
1104 self.next_split_id += 1;
1105 id
1106 }
1107
1108 pub fn replace_root(&mut self, new_root: SplitNode, new_active: LeafId) {
1112 self.root = new_root;
1113 self.active_split = new_active;
1114 self.focus_history.clear();
1117 self.focus_history.push(new_active);
1118 }
1119
1120 pub fn active_split(&self) -> LeafId {
1122 self.active_split
1123 }
1124
1125 pub fn set_active_split(&mut self, split_id: LeafId) -> bool {
1127 if self.root.find(split_id.into()).is_some() {
1129 self.active_split = split_id;
1130 self.focus_history.retain(|leaf| *leaf != split_id);
1134 self.focus_history.push(split_id);
1135 if self.focus_history.len() > FOCUS_HISTORY_CAP {
1136 self.focus_history.remove(0);
1137 }
1138 true
1139 } else {
1140 false
1141 }
1142 }
1143
1144 pub fn leaf_role(&self, split_id: LeafId) -> Option<SplitRole> {
1147 self.root.find(split_id.into()).and_then(|node| node.role())
1148 }
1149
1150 pub fn last_focused_where<F>(&self, mut predicate: F) -> Option<LeafId>
1160 where
1161 F: FnMut(LeafId) -> bool,
1162 {
1163 self.focus_history
1164 .iter()
1165 .rev()
1166 .copied()
1167 .find(|leaf| self.root.find((*leaf).into()).is_some() && predicate(*leaf))
1168 }
1169
1170 pub fn active_buffer_id(&self) -> Option<BufferId> {
1172 self.root
1173 .find(self.active_split.into())
1174 .and_then(|node| node.buffer_id())
1175 }
1176
1177 pub fn get_buffer_id(&self, split_id: SplitId) -> Option<BufferId> {
1179 self.root.find(split_id).and_then(|node| node.buffer_id())
1180 }
1181
1182 pub fn set_active_buffer_id(&mut self, new_buffer_id: BufferId) -> bool {
1184 if let Some(SplitNode::Leaf { buffer_id, .. }) =
1185 self.root.find_mut(self.active_split.into())
1186 {
1187 *buffer_id = new_buffer_id;
1188 return true;
1189 }
1190 false
1191 }
1192
1193 pub fn set_split_buffer(&mut self, leaf_id: LeafId, new_buffer_id: BufferId) {
1195 match self.root.find_mut(leaf_id.into()) {
1196 Some(SplitNode::Leaf { buffer_id, .. }) => {
1197 *buffer_id = new_buffer_id;
1198 }
1199 Some(SplitNode::Split { .. }) => {
1200 unreachable!("LeafId {:?} points to a container", leaf_id)
1201 }
1202 Some(SplitNode::Grouped { .. }) => {
1203 unreachable!("LeafId {:?} points to a Grouped node", leaf_id)
1204 }
1205 None => {
1206 unreachable!("LeafId {:?} not found in split tree", leaf_id)
1207 }
1208 }
1209 }
1210
1211 pub fn split_active(
1215 &mut self,
1216 direction: SplitDirection,
1217 new_buffer_id: BufferId,
1218 ratio: f32,
1219 ) -> Result<LeafId, String> {
1220 self.split_active_positioned(direction, new_buffer_id, ratio, false)
1221 }
1222
1223 pub fn split_active_before(
1226 &mut self,
1227 direction: SplitDirection,
1228 new_buffer_id: BufferId,
1229 ratio: f32,
1230 ) -> Result<LeafId, String> {
1231 self.split_active_positioned(direction, new_buffer_id, ratio, true)
1232 }
1233
1234 pub fn split_active_positioned(
1235 &mut self,
1236 direction: SplitDirection,
1237 new_buffer_id: BufferId,
1238 ratio: f32,
1239 before: bool,
1240 ) -> Result<LeafId, String> {
1241 let active_id: SplitId = self.active_split.into();
1242
1243 let result =
1245 self.replace_split_with_split(active_id, direction, new_buffer_id, ratio, before);
1246
1247 if let Ok(new_split_id) = &result {
1248 self.active_split = *new_split_id;
1250 }
1251 result
1252 }
1253
1254 pub fn split_root_positioned(
1263 &mut self,
1264 direction: SplitDirection,
1265 new_buffer_id: BufferId,
1266 ratio: f32,
1267 before: bool,
1268 ) -> Result<LeafId, String> {
1269 let root_id = self.root.id();
1270 let result =
1271 self.replace_split_with_split(root_id, direction, new_buffer_id, ratio, before);
1272 if let Ok(new_split_id) = &result {
1273 self.active_split = *new_split_id;
1274 }
1275 result
1276 }
1277
1278 fn replace_split_with_split(
1281 &mut self,
1282 target_id: SplitId,
1283 direction: SplitDirection,
1284 new_buffer_id: BufferId,
1285 ratio: f32,
1286 before: bool,
1287 ) -> Result<LeafId, String> {
1288 let temp_id = self.allocate_split_id();
1290 let new_split_id = self.allocate_split_id();
1291 let new_leaf_id = self.allocate_split_id();
1292
1293 if self.root.id() == target_id {
1295 let old_root =
1296 std::mem::replace(&mut self.root, SplitNode::leaf(new_buffer_id, temp_id));
1297 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1298
1299 let (first, second) = if before {
1300 (new_leaf, old_root)
1301 } else {
1302 (old_root, new_leaf)
1303 };
1304
1305 self.root = SplitNode::split(direction, first, second, ratio, new_split_id);
1306
1307 return Ok(LeafId(new_leaf_id));
1308 }
1309
1310 if let Some(node) = self.root.find_mut(target_id) {
1312 let old_node = std::mem::replace(node, SplitNode::leaf(new_buffer_id, temp_id));
1313 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1314
1315 let (first, second) = if before {
1316 (new_leaf, old_node)
1317 } else {
1318 (old_node, new_leaf)
1319 };
1320
1321 *node = SplitNode::split(direction, first, second, ratio, new_split_id);
1322
1323 Ok(LeafId(new_leaf_id))
1324 } else {
1325 Err(format!("Split {:?} not found", target_id))
1326 }
1327 }
1328
1329 pub fn close_split(&mut self, split_id: LeafId) -> Result<(), String> {
1331 if self.root.count_leaves() <= 1 {
1333 return Err("Cannot close the last split".to_string());
1334 }
1335
1336 if self.root.id() == split_id.into() && self.root.buffer_id().is_some() {
1338 return Err("Cannot close the only split".to_string());
1339 }
1340
1341 if self.maximized_split == Some(split_id.into()) {
1343 self.maximized_split = None;
1344 }
1345
1346 let removed_ids: Vec<SplitId> = self
1348 .root
1349 .find(split_id.into())
1350 .map(|node| node.all_split_ids())
1351 .unwrap_or_default();
1352
1353 let result = self.remove_split_node(split_id.into());
1356
1357 if result.is_ok() {
1358 for id in &removed_ids {
1360 self.labels.remove(id);
1361 }
1362
1363 if self.active_split == split_id {
1365 let leaf_ids = self.root.leaf_split_ids();
1366 if let Some(&first_leaf) = leaf_ids.first() {
1367 self.active_split = first_leaf;
1368 }
1369 }
1370 }
1371
1372 result
1373 }
1374
1375 fn remove_split_node(&mut self, target_id: SplitId) -> Result<(), String> {
1377 if self.root.id() == target_id {
1379 if let SplitNode::Split { first, .. } = &self.root {
1380 self.root = (**first).clone();
1383 return Ok(());
1384 }
1385 }
1386
1387 Self::remove_child_static(&mut self.root, target_id)
1389 }
1390
1391 fn remove_child_static(node: &mut SplitNode, target_id: SplitId) -> Result<(), String> {
1393 match node {
1394 SplitNode::Leaf { .. } => Err("Target not found".to_string()),
1395 SplitNode::Grouped { layout, .. } => Self::remove_child_static(layout, target_id),
1396 SplitNode::Split { first, second, .. } => {
1397 if first.id() == target_id {
1399 *node = (**second).clone();
1401 Ok(())
1402 } else if second.id() == target_id {
1403 *node = (**first).clone();
1405 Ok(())
1406 } else {
1407 Self::remove_child_static(first, target_id)
1409 .or_else(|_| Self::remove_child_static(second, target_id))
1410 }
1411 }
1412 }
1413 }
1414
1415 pub fn remove_grouped(&mut self, target: LeafId) -> Result<(), String> {
1423 let target_id: SplitId = target.into();
1424 if self.root.id() == target_id {
1425 return Err("Cannot remove root Grouped node".to_string());
1426 }
1427 Self::remove_child_static(&mut self.root, target_id)
1428 }
1429
1430 pub fn adjust_ratio(&mut self, container_id: ContainerId, delta: f32) {
1432 match self.root.find_mut(container_id.into()) {
1433 Some(SplitNode::Split { ratio, .. }) => {
1434 *ratio = (*ratio + delta).clamp(0.1, 0.9);
1435 }
1436 Some(SplitNode::Leaf { .. }) => {
1437 unreachable!("ContainerId {:?} points to a leaf", container_id)
1438 }
1439 Some(SplitNode::Grouped { .. }) => {
1440 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1441 }
1442 None => {
1443 unreachable!("ContainerId {:?} not found in split tree", container_id)
1444 }
1445 }
1446 }
1447
1448 pub fn parent_container_of(&self, leaf_id: LeafId) -> Option<ContainerId> {
1450 self.root.parent_container_of(leaf_id.into())
1451 }
1452
1453 pub fn get_visible_buffers(&self, viewport_rect: Rect) -> Vec<(LeafId, BufferId, Rect)> {
1455 if let Some(maximized_id) = self.maximized_split {
1457 if let Some(SplitNode::Leaf {
1458 buffer_id,
1459 split_id,
1460 ..
1461 }) = self.root.find(maximized_id)
1462 {
1463 return vec![(*split_id, *buffer_id, viewport_rect)];
1464 }
1465 }
1467 self.root.get_leaves_with_rects(viewport_rect)
1468 }
1469
1470 pub fn get_separators(&self, viewport_rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
1473 if self.maximized_split.is_some() {
1475 return vec![];
1476 }
1477 self.root.get_separators(viewport_rect)
1478 }
1479
1480 pub fn get_separators_with_ids(
1483 &self,
1484 viewport_rect: Rect,
1485 ) -> Vec<(ContainerId, SplitDirection, u16, u16, u16)> {
1486 if self.maximized_split.is_some() {
1488 return vec![];
1489 }
1490 self.root.get_separators_with_ids(viewport_rect)
1491 }
1492
1493 pub fn get_ratio(&self, split_id: SplitId) -> Option<f32> {
1495 if let Some(SplitNode::Split { ratio, .. }) = self.root.find(split_id) {
1496 Some(*ratio)
1497 } else {
1498 None
1499 }
1500 }
1501
1502 pub fn set_ratio(&mut self, container_id: ContainerId, new_ratio: f32) {
1504 match self.root.find_mut(container_id.into()) {
1505 Some(SplitNode::Split { ratio, .. }) => {
1506 *ratio = new_ratio.clamp(0.1, 0.9);
1507 }
1508 Some(SplitNode::Leaf { .. }) => {
1509 unreachable!("ContainerId {:?} points to a leaf", container_id)
1510 }
1511 Some(SplitNode::Grouped { .. }) => {
1512 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1513 }
1514 None => {
1515 unreachable!("ContainerId {:?} not found in split tree", container_id)
1516 }
1517 }
1518 }
1519
1520 pub fn set_fixed_size(
1523 &mut self,
1524 container_id: ContainerId,
1525 first: Option<u16>,
1526 second: Option<u16>,
1527 ) {
1528 if let Some(SplitNode::Split {
1529 fixed_first,
1530 fixed_second,
1531 ..
1532 }) = self.root.find_mut(container_id.into())
1533 {
1534 *fixed_first = first;
1535 *fixed_second = second;
1536 }
1537 }
1538
1539 pub fn distribute_splits_evenly(&mut self) {
1542 Self::distribute_node_evenly(&mut self.root);
1543 }
1544
1545 fn distribute_node_evenly(node: &mut SplitNode) -> usize {
1548 match node {
1549 SplitNode::Leaf { .. } => 1,
1550 SplitNode::Grouped { layout, .. } => Self::distribute_node_evenly(layout),
1551 SplitNode::Split {
1552 first,
1553 second,
1554 ratio,
1555 ..
1556 } => {
1557 let first_leaves = Self::distribute_node_evenly(first);
1558 let second_leaves = Self::distribute_node_evenly(second);
1559 let total_leaves = first_leaves + second_leaves;
1560
1561 *ratio = (first_leaves as f32 / total_leaves as f32).clamp(0.1, 0.9);
1564
1565 total_leaves
1566 }
1567 }
1568 }
1569
1570 pub fn next_split(&mut self) {
1572 let leaf_ids = self.root.leaf_split_ids();
1573 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1574 let next_pos = (pos + 1) % leaf_ids.len();
1575 self.active_split = leaf_ids[next_pos];
1576 }
1577 }
1578
1579 pub fn prev_split(&mut self) {
1581 let leaf_ids = self.root.leaf_split_ids();
1582 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1583 let prev_pos = if pos == 0 { leaf_ids.len() } else { pos } - 1;
1584 self.active_split = leaf_ids[prev_pos];
1585 }
1586 }
1587
1588 pub fn splits_for_buffer(&self, target_buffer_id: BufferId) -> Vec<LeafId> {
1590 self.root
1591 .get_leaves_with_rects(Rect {
1592 x: 0,
1593 y: 0,
1594 width: 1,
1595 height: 1,
1596 })
1597 .into_iter()
1598 .filter(|(_, buffer_id, _)| *buffer_id == target_buffer_id)
1599 .map(|(split_id, _, _)| split_id)
1600 .collect()
1601 }
1602
1603 pub fn buffer_for_split(&self, target_split_id: LeafId) -> Option<BufferId> {
1605 self.root
1606 .get_leaves_with_rects(Rect {
1607 x: 0,
1608 y: 0,
1609 width: 1,
1610 height: 1,
1611 })
1612 .into_iter()
1613 .find(|(split_id, _, _)| *split_id == target_split_id)
1614 .map(|(_, buffer_id, _)| buffer_id)
1615 }
1616
1617 pub fn maximize_split(&mut self) -> Result<(), String> {
1620 if self.root.count_leaves() <= 1 {
1622 return Err("Cannot maximize: only one split exists".to_string());
1623 }
1624
1625 if self.maximized_split.is_some() {
1627 return Err("A split is already maximized".to_string());
1628 }
1629
1630 self.maximized_split = Some(self.active_split.into());
1632 Ok(())
1633 }
1634
1635 pub fn unmaximize_split(&mut self) -> Result<(), String> {
1638 if self.maximized_split.is_none() {
1639 return Err("No split is maximized".to_string());
1640 }
1641
1642 self.maximized_split = None;
1643 Ok(())
1644 }
1645
1646 pub fn is_maximized(&self) -> bool {
1648 self.maximized_split.is_some()
1649 }
1650
1651 pub fn maximized_split(&self) -> Option<SplitId> {
1653 self.maximized_split
1654 }
1655
1656 pub fn toggle_maximize(&mut self) -> Result<bool, String> {
1660 if self.is_maximized() {
1661 self.unmaximize_split()?;
1662 Ok(false)
1663 } else {
1664 self.maximize_split()?;
1665 Ok(true)
1666 }
1667 }
1668
1669 pub fn toggle_maximize_for(&mut self, target: LeafId) -> Result<bool, String> {
1677 if self.is_maximized() {
1678 self.unmaximize_split()?;
1679 Ok(false)
1680 } else {
1681 if self.root.count_leaves() <= 1 {
1682 return Err("Cannot maximize: only one split exists".to_string());
1683 }
1684 if self.root.find(target.into()).is_none() {
1685 return Err("Cannot maximize: split not found".to_string());
1686 }
1687 self.maximized_split = Some(target.into());
1688 Ok(true)
1689 }
1690 }
1691
1692 pub fn get_splits_in_group(
1694 &self,
1695 group_id: u32,
1696 view_states: &std::collections::HashMap<LeafId, SplitViewState>,
1697 ) -> Vec<LeafId> {
1698 self.root
1699 .leaf_split_ids()
1700 .into_iter()
1701 .filter(|id| {
1702 view_states
1703 .get(id)
1704 .and_then(|vs| vs.sync_group)
1705 .is_some_and(|g| g == group_id)
1706 })
1707 .collect()
1708 }
1709
1710 pub fn set_label(&mut self, split_id: LeafId, label: String) {
1714 self.labels.insert(split_id.into(), label);
1715 }
1716
1717 pub fn clear_label(&mut self, split_id: SplitId) {
1719 self.labels.remove(&split_id);
1720 }
1721
1722 pub fn get_label(&self, split_id: SplitId) -> Option<&str> {
1724 self.labels.get(&split_id).map(|s| s.as_str())
1725 }
1726
1727 pub fn labels(&self) -> &HashMap<SplitId, String> {
1729 &self.labels
1730 }
1731
1732 pub fn set_leaf_role(&mut self, split_id: LeafId, new_role: Option<SplitRole>) {
1736 if let Some(node) = self.root.find_mut(split_id.into()) {
1737 node.set_role(new_role);
1738 }
1739 }
1740
1741 pub fn find_leaf_by_role(&self, target: SplitRole) -> Option<LeafId> {
1743 fn walk(node: &SplitNode, target: SplitRole) -> Option<LeafId> {
1744 match node {
1745 SplitNode::Leaf {
1746 role: Some(r),
1747 split_id,
1748 ..
1749 } if *r == target => Some(*split_id),
1750 SplitNode::Leaf { .. } => None,
1751 SplitNode::Split { first, second, .. } => {
1752 walk(first, target).or_else(|| walk(second, target))
1753 }
1754 SplitNode::Grouped { layout, .. } => walk(layout, target),
1755 }
1756 }
1757 walk(&self.root, target)
1758 }
1759
1760 pub fn clear_role(&mut self, target: SplitRole) -> Option<LeafId> {
1764 let leaf = self.find_leaf_by_role(target)?;
1765 self.set_leaf_role(leaf, None);
1766 Some(leaf)
1767 }
1768
1769 pub fn find_split_by_label(&self, label: &str) -> Option<LeafId> {
1771 self.root
1772 .leaf_split_ids()
1773 .into_iter()
1774 .find(|id| self.labels.get(&(*id).into()).is_some_and(|l| l == label))
1775 }
1776
1777 pub fn find_unlabeled_leaf(&self) -> Option<LeafId> {
1779 self.root
1780 .leaf_split_ids()
1781 .into_iter()
1782 .find(|id| !self.labels.contains_key(&(*id).into()))
1783 }
1784}
1785
1786#[cfg(test)]
1787mod tests {
1788 use super::*;
1789
1790 #[test]
1791 fn test_create_split_manager() {
1792 let buffer_id = BufferId(0);
1793 let manager = SplitManager::new(buffer_id);
1794
1795 assert_eq!(manager.active_buffer_id(), Some(buffer_id));
1796 assert_eq!(manager.root().count_leaves(), 1);
1797 }
1798
1799 #[test]
1800 fn test_horizontal_split() {
1801 let buffer_a = BufferId(0);
1802 let buffer_b = BufferId(1);
1803
1804 let mut manager = SplitManager::new(buffer_a);
1805 let result = manager.split_active(SplitDirection::Horizontal, buffer_b, 0.5);
1806
1807 assert!(result.is_ok());
1808 assert_eq!(manager.root().count_leaves(), 2);
1809 }
1810
1811 #[test]
1812 fn test_vertical_split() {
1813 let buffer_a = BufferId(0);
1814 let buffer_b = BufferId(1);
1815
1816 let mut manager = SplitManager::new(buffer_a);
1817 let result = manager.split_active(SplitDirection::Vertical, buffer_b, 0.5);
1818
1819 assert!(result.is_ok());
1820 assert_eq!(manager.root().count_leaves(), 2);
1821 }
1822
1823 #[test]
1824 fn test_nested_splits() {
1825 let buffer_a = BufferId(0);
1826 let buffer_b = BufferId(1);
1827 let buffer_c = BufferId(2);
1828
1829 let mut manager = SplitManager::new(buffer_a);
1830
1831 manager
1833 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1834 .unwrap();
1835
1836 manager
1838 .split_active(SplitDirection::Vertical, buffer_c, 0.5)
1839 .unwrap();
1840
1841 assert_eq!(manager.root().count_leaves(), 3);
1842 }
1843
1844 #[test]
1845 fn test_close_split() {
1846 let buffer_a = BufferId(0);
1847 let buffer_b = BufferId(1);
1848
1849 let mut manager = SplitManager::new(buffer_a);
1850 let new_split = manager
1851 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1852 .unwrap();
1853
1854 assert_eq!(manager.root().count_leaves(), 2);
1855
1856 let result = manager.close_split(new_split);
1858 assert!(result.is_ok());
1859 assert_eq!(manager.root().count_leaves(), 1);
1860 }
1861
1862 #[test]
1863 fn test_cannot_close_last_split() {
1864 let buffer_a = BufferId(0);
1865 let mut manager = SplitManager::new(buffer_a);
1866
1867 let result = manager.close_split(manager.active_split());
1868 assert!(result.is_err());
1869 }
1870
1871 #[test]
1872 fn test_split_rect_horizontal() {
1873 let rect = Rect {
1874 x: 0,
1875 y: 0,
1876 width: 100,
1877 height: 100,
1878 };
1879
1880 let (first, second) = split_rect(rect, SplitDirection::Horizontal, 0.5);
1881
1882 assert_eq!(first.height, 50);
1884 assert_eq!(second.height, 49);
1885 assert_eq!(first.width, 100);
1886 assert_eq!(second.width, 100);
1887 assert_eq!(first.y, 0);
1888 assert_eq!(second.y, 51); }
1890
1891 #[test]
1892 fn test_split_rect_vertical() {
1893 let rect = Rect {
1894 x: 0,
1895 y: 0,
1896 width: 100,
1897 height: 100,
1898 };
1899
1900 let (first, second) = split_rect(rect, SplitDirection::Vertical, 0.5);
1901
1902 assert_eq!(first.width, 50);
1904 assert_eq!(second.width, 49);
1905 assert_eq!(first.height, 100);
1906 assert_eq!(second.height, 100);
1907 assert_eq!(first.x, 0);
1908 assert_eq!(second.x, 51); }
1910
1911 #[test]
1914 fn test_set_and_get_label() {
1915 let mut manager = SplitManager::new(BufferId(0));
1916 let split = manager.active_split();
1917
1918 assert_eq!(manager.get_label(split.into()), None);
1919
1920 manager.set_label(split, "sidebar".to_string());
1921 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
1922 }
1923
1924 #[test]
1925 fn test_clear_label() {
1926 let mut manager = SplitManager::new(BufferId(0));
1927 let split = manager.active_split();
1928
1929 manager.set_label(split, "sidebar".to_string());
1930 assert!(manager.get_label(split.into()).is_some());
1931
1932 manager.clear_label(split.into());
1933 assert_eq!(manager.get_label(split.into()), None);
1934 }
1935
1936 #[test]
1937 fn test_find_split_by_label() {
1938 let mut manager = SplitManager::new(BufferId(0));
1939 let first_split = manager.active_split();
1940
1941 let second_split = manager
1942 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1943 .unwrap();
1944
1945 manager.set_label(first_split, "sidebar".to_string());
1946
1947 assert_eq!(manager.find_split_by_label("sidebar"), Some(first_split));
1948 assert_eq!(manager.find_split_by_label("terminal"), None);
1949
1950 assert_ne!(manager.find_split_by_label("sidebar"), Some(second_split));
1952 }
1953
1954 #[test]
1955 fn test_find_unlabeled_leaf() {
1956 let mut manager = SplitManager::new(BufferId(0));
1957 let first_split = manager.active_split();
1958
1959 let second_split = manager
1960 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1961 .unwrap();
1962
1963 assert!(manager.find_unlabeled_leaf().is_some());
1965
1966 manager.set_label(first_split, "sidebar".to_string());
1968 assert_eq!(manager.find_unlabeled_leaf(), Some(second_split));
1969
1970 manager.set_label(second_split, "terminal".to_string());
1972 assert_eq!(manager.find_unlabeled_leaf(), None);
1973 }
1974
1975 #[test]
1976 fn test_close_split_cleans_up_label() {
1977 let mut manager = SplitManager::new(BufferId(0));
1978 let _first_split = manager.active_split();
1979
1980 let second_split = manager
1981 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1982 .unwrap();
1983
1984 manager.set_label(second_split, "sidebar".to_string());
1985 assert_eq!(manager.find_split_by_label("sidebar"), Some(second_split));
1986
1987 manager.close_split(second_split).unwrap();
1988
1989 assert_eq!(manager.find_split_by_label("sidebar"), None);
1991 assert_eq!(manager.get_label(second_split.into()), None);
1992 }
1993
1994 #[test]
1995 fn test_label_overwrite() {
1996 let mut manager = SplitManager::new(BufferId(0));
1997 let split = manager.active_split();
1998
1999 manager.set_label(split, "sidebar".to_string());
2000 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
2001
2002 manager.set_label(split, "terminal".to_string());
2003 assert_eq!(manager.get_label(split.into()), Some("terminal"));
2004 assert_eq!(manager.find_split_by_label("sidebar"), None);
2005 assert_eq!(manager.find_split_by_label("terminal"), Some(split));
2006 }
2007
2008 #[test]
2009 fn test_find_unlabeled_leaf_single_split_no_label() {
2010 let manager = SplitManager::new(BufferId(0));
2011 assert_eq!(manager.find_unlabeled_leaf(), Some(manager.active_split()));
2013 }
2014
2015 #[test]
2016 fn test_find_unlabeled_leaf_single_split_labeled() {
2017 let mut manager = SplitManager::new(BufferId(0));
2018 let split = manager.active_split();
2019 manager.set_label(split, "only".to_string());
2020 assert_eq!(manager.find_unlabeled_leaf(), None);
2022 }
2023
2024 #[test]
2029 fn test_split_root_positioned_with_existing_vertical_split() {
2030 let left = BufferId(0);
2032 let right = BufferId(1);
2033 let dock = BufferId(2);
2034 let mut manager = SplitManager::new(left);
2035 manager
2036 .split_active(SplitDirection::Vertical, right, 0.5)
2037 .expect("vertical split");
2038 assert!(matches!(
2040 manager.root(),
2041 SplitNode::Split {
2042 direction: SplitDirection::Vertical,
2043 ..
2044 }
2045 ));
2046 assert_eq!(manager.root().count_leaves(), 2);
2047 let active_before = manager.active_split();
2051
2052 let dock_leaf = manager
2054 .split_root_positioned(SplitDirection::Horizontal, dock, 0.7, false)
2055 .expect("split_root_positioned");
2056
2057 match manager.root() {
2062 SplitNode::Split {
2063 direction: SplitDirection::Horizontal,
2064 first,
2065 second,
2066 ..
2067 } => {
2068 assert!(
2069 matches!(
2070 first.as_ref(),
2071 SplitNode::Split {
2072 direction: SplitDirection::Vertical,
2073 ..
2074 }
2075 ),
2076 "first child of new root must be the original Vertical split, got {:?}",
2077 first
2078 );
2079 match second.as_ref() {
2080 SplitNode::Leaf {
2081 buffer_id,
2082 split_id,
2083 ..
2084 } => {
2085 assert_eq!(*buffer_id, dock, "second child must be the dock leaf");
2086 assert_eq!(
2087 *split_id, dock_leaf,
2088 "split_root_positioned must return the new leaf id"
2089 );
2090 }
2091 other => panic!("expected dock leaf as second child, got {:?}", other),
2092 }
2093 }
2094 other => {
2095 panic!(
2096 "root must be a Horizontal Split after split_root_positioned, got {:?}",
2097 other
2098 );
2099 }
2100 }
2101 assert_eq!(manager.root().count_leaves(), 3);
2103 assert_ne!(
2106 dock_leaf, active_before,
2107 "dock must be a new sibling of the root, not the previously-active leaf"
2108 );
2109 }
2110}