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 self.maximized_split = None;
1577 let leaf_ids = self.root.leaf_split_ids();
1578 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1579 let next_pos = (pos + 1) % leaf_ids.len();
1580 self.active_split = leaf_ids[next_pos];
1581 }
1582 }
1583
1584 pub fn prev_split(&mut self) {
1586 self.maximized_split = None;
1588 let leaf_ids = self.root.leaf_split_ids();
1589 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1590 let prev_pos = if pos == 0 { leaf_ids.len() } else { pos } - 1;
1591 self.active_split = leaf_ids[prev_pos];
1592 }
1593 }
1594
1595 pub fn splits_for_buffer(&self, target_buffer_id: BufferId) -> Vec<LeafId> {
1597 self.root
1598 .get_leaves_with_rects(Rect {
1599 x: 0,
1600 y: 0,
1601 width: 1,
1602 height: 1,
1603 })
1604 .into_iter()
1605 .filter(|(_, buffer_id, _)| *buffer_id == target_buffer_id)
1606 .map(|(split_id, _, _)| split_id)
1607 .collect()
1608 }
1609
1610 pub fn buffer_for_split(&self, target_split_id: LeafId) -> Option<BufferId> {
1612 self.root
1613 .get_leaves_with_rects(Rect {
1614 x: 0,
1615 y: 0,
1616 width: 1,
1617 height: 1,
1618 })
1619 .into_iter()
1620 .find(|(split_id, _, _)| *split_id == target_split_id)
1621 .map(|(_, buffer_id, _)| buffer_id)
1622 }
1623
1624 pub fn maximize_split(&mut self) -> Result<(), String> {
1627 if self.root.count_leaves() <= 1 {
1629 return Err("Cannot maximize: only one split exists".to_string());
1630 }
1631
1632 if self.maximized_split.is_some() {
1634 return Err("A split is already maximized".to_string());
1635 }
1636
1637 self.maximized_split = Some(self.active_split.into());
1639 Ok(())
1640 }
1641
1642 pub fn unmaximize_split(&mut self) -> Result<(), String> {
1645 if self.maximized_split.is_none() {
1646 return Err("No split is maximized".to_string());
1647 }
1648
1649 self.maximized_split = None;
1650 Ok(())
1651 }
1652
1653 pub fn is_maximized(&self) -> bool {
1655 self.maximized_split.is_some()
1656 }
1657
1658 pub fn maximized_split(&self) -> Option<SplitId> {
1660 self.maximized_split
1661 }
1662
1663 pub fn toggle_maximize(&mut self) -> Result<bool, String> {
1667 if self.is_maximized() {
1668 self.unmaximize_split()?;
1669 Ok(false)
1670 } else {
1671 self.maximize_split()?;
1672 Ok(true)
1673 }
1674 }
1675
1676 pub fn toggle_maximize_for(&mut self, target: LeafId) -> Result<bool, String> {
1684 if self.is_maximized() {
1685 self.unmaximize_split()?;
1686 Ok(false)
1687 } else {
1688 if self.root.count_leaves() <= 1 {
1689 return Err("Cannot maximize: only one split exists".to_string());
1690 }
1691 if self.root.find(target.into()).is_none() {
1692 return Err("Cannot maximize: split not found".to_string());
1693 }
1694 self.maximized_split = Some(target.into());
1695 Ok(true)
1696 }
1697 }
1698
1699 pub fn get_splits_in_group(
1701 &self,
1702 group_id: u32,
1703 view_states: &std::collections::HashMap<LeafId, SplitViewState>,
1704 ) -> Vec<LeafId> {
1705 self.root
1706 .leaf_split_ids()
1707 .into_iter()
1708 .filter(|id| {
1709 view_states
1710 .get(id)
1711 .and_then(|vs| vs.sync_group)
1712 .is_some_and(|g| g == group_id)
1713 })
1714 .collect()
1715 }
1716
1717 pub fn set_label(&mut self, split_id: LeafId, label: String) {
1721 self.labels.insert(split_id.into(), label);
1722 }
1723
1724 pub fn clear_label(&mut self, split_id: SplitId) {
1726 self.labels.remove(&split_id);
1727 }
1728
1729 pub fn get_label(&self, split_id: SplitId) -> Option<&str> {
1731 self.labels.get(&split_id).map(|s| s.as_str())
1732 }
1733
1734 pub fn labels(&self) -> &HashMap<SplitId, String> {
1736 &self.labels
1737 }
1738
1739 pub fn set_leaf_role(&mut self, split_id: LeafId, new_role: Option<SplitRole>) {
1743 if let Some(node) = self.root.find_mut(split_id.into()) {
1744 node.set_role(new_role);
1745 }
1746 }
1747
1748 pub fn find_leaf_by_role(&self, target: SplitRole) -> Option<LeafId> {
1750 fn walk(node: &SplitNode, target: SplitRole) -> Option<LeafId> {
1751 match node {
1752 SplitNode::Leaf {
1753 role: Some(r),
1754 split_id,
1755 ..
1756 } if *r == target => Some(*split_id),
1757 SplitNode::Leaf { .. } => None,
1758 SplitNode::Split { first, second, .. } => {
1759 walk(first, target).or_else(|| walk(second, target))
1760 }
1761 SplitNode::Grouped { layout, .. } => walk(layout, target),
1762 }
1763 }
1764 walk(&self.root, target)
1765 }
1766
1767 pub fn clear_role(&mut self, target: SplitRole) -> Option<LeafId> {
1771 let leaf = self.find_leaf_by_role(target)?;
1772 self.set_leaf_role(leaf, None);
1773 Some(leaf)
1774 }
1775
1776 pub fn find_split_by_label(&self, label: &str) -> Option<LeafId> {
1778 self.root
1779 .leaf_split_ids()
1780 .into_iter()
1781 .find(|id| self.labels.get(&(*id).into()).is_some_and(|l| l == label))
1782 }
1783
1784 pub fn find_unlabeled_leaf(&self) -> Option<LeafId> {
1786 self.root
1787 .leaf_split_ids()
1788 .into_iter()
1789 .find(|id| !self.labels.contains_key(&(*id).into()))
1790 }
1791}
1792
1793#[cfg(test)]
1794mod tests {
1795 use super::*;
1796
1797 #[test]
1798 fn test_create_split_manager() {
1799 let buffer_id = BufferId(0);
1800 let manager = SplitManager::new(buffer_id);
1801
1802 assert_eq!(manager.active_buffer_id(), Some(buffer_id));
1803 assert_eq!(manager.root().count_leaves(), 1);
1804 }
1805
1806 #[test]
1807 fn test_horizontal_split() {
1808 let buffer_a = BufferId(0);
1809 let buffer_b = BufferId(1);
1810
1811 let mut manager = SplitManager::new(buffer_a);
1812 let result = manager.split_active(SplitDirection::Horizontal, buffer_b, 0.5);
1813
1814 assert!(result.is_ok());
1815 assert_eq!(manager.root().count_leaves(), 2);
1816 }
1817
1818 #[test]
1819 fn test_vertical_split() {
1820 let buffer_a = BufferId(0);
1821 let buffer_b = BufferId(1);
1822
1823 let mut manager = SplitManager::new(buffer_a);
1824 let result = manager.split_active(SplitDirection::Vertical, buffer_b, 0.5);
1825
1826 assert!(result.is_ok());
1827 assert_eq!(manager.root().count_leaves(), 2);
1828 }
1829
1830 #[test]
1831 fn test_nested_splits() {
1832 let buffer_a = BufferId(0);
1833 let buffer_b = BufferId(1);
1834 let buffer_c = BufferId(2);
1835
1836 let mut manager = SplitManager::new(buffer_a);
1837
1838 manager
1840 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1841 .unwrap();
1842
1843 manager
1845 .split_active(SplitDirection::Vertical, buffer_c, 0.5)
1846 .unwrap();
1847
1848 assert_eq!(manager.root().count_leaves(), 3);
1849 }
1850
1851 #[test]
1852 fn test_close_split() {
1853 let buffer_a = BufferId(0);
1854 let buffer_b = BufferId(1);
1855
1856 let mut manager = SplitManager::new(buffer_a);
1857 let new_split = manager
1858 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1859 .unwrap();
1860
1861 assert_eq!(manager.root().count_leaves(), 2);
1862
1863 let result = manager.close_split(new_split);
1865 assert!(result.is_ok());
1866 assert_eq!(manager.root().count_leaves(), 1);
1867 }
1868
1869 #[test]
1870 fn test_cannot_close_last_split() {
1871 let buffer_a = BufferId(0);
1872 let mut manager = SplitManager::new(buffer_a);
1873
1874 let result = manager.close_split(manager.active_split());
1875 assert!(result.is_err());
1876 }
1877
1878 #[test]
1879 fn test_split_rect_horizontal() {
1880 let rect = Rect {
1881 x: 0,
1882 y: 0,
1883 width: 100,
1884 height: 100,
1885 };
1886
1887 let (first, second) = split_rect(rect, SplitDirection::Horizontal, 0.5);
1888
1889 assert_eq!(first.height, 50);
1891 assert_eq!(second.height, 49);
1892 assert_eq!(first.width, 100);
1893 assert_eq!(second.width, 100);
1894 assert_eq!(first.y, 0);
1895 assert_eq!(second.y, 51); }
1897
1898 #[test]
1899 fn test_split_rect_vertical() {
1900 let rect = Rect {
1901 x: 0,
1902 y: 0,
1903 width: 100,
1904 height: 100,
1905 };
1906
1907 let (first, second) = split_rect(rect, SplitDirection::Vertical, 0.5);
1908
1909 assert_eq!(first.width, 50);
1911 assert_eq!(second.width, 49);
1912 assert_eq!(first.height, 100);
1913 assert_eq!(second.height, 100);
1914 assert_eq!(first.x, 0);
1915 assert_eq!(second.x, 51); }
1917
1918 #[test]
1921 fn test_set_and_get_label() {
1922 let mut manager = SplitManager::new(BufferId(0));
1923 let split = manager.active_split();
1924
1925 assert_eq!(manager.get_label(split.into()), None);
1926
1927 manager.set_label(split, "sidebar".to_string());
1928 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
1929 }
1930
1931 #[test]
1932 fn test_clear_label() {
1933 let mut manager = SplitManager::new(BufferId(0));
1934 let split = manager.active_split();
1935
1936 manager.set_label(split, "sidebar".to_string());
1937 assert!(manager.get_label(split.into()).is_some());
1938
1939 manager.clear_label(split.into());
1940 assert_eq!(manager.get_label(split.into()), None);
1941 }
1942
1943 #[test]
1944 fn test_find_split_by_label() {
1945 let mut manager = SplitManager::new(BufferId(0));
1946 let first_split = manager.active_split();
1947
1948 let second_split = manager
1949 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1950 .unwrap();
1951
1952 manager.set_label(first_split, "sidebar".to_string());
1953
1954 assert_eq!(manager.find_split_by_label("sidebar"), Some(first_split));
1955 assert_eq!(manager.find_split_by_label("terminal"), None);
1956
1957 assert_ne!(manager.find_split_by_label("sidebar"), Some(second_split));
1959 }
1960
1961 #[test]
1962 fn test_find_unlabeled_leaf() {
1963 let mut manager = SplitManager::new(BufferId(0));
1964 let first_split = manager.active_split();
1965
1966 let second_split = manager
1967 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1968 .unwrap();
1969
1970 assert!(manager.find_unlabeled_leaf().is_some());
1972
1973 manager.set_label(first_split, "sidebar".to_string());
1975 assert_eq!(manager.find_unlabeled_leaf(), Some(second_split));
1976
1977 manager.set_label(second_split, "terminal".to_string());
1979 assert_eq!(manager.find_unlabeled_leaf(), None);
1980 }
1981
1982 #[test]
1983 fn test_close_split_cleans_up_label() {
1984 let mut manager = SplitManager::new(BufferId(0));
1985 let _first_split = manager.active_split();
1986
1987 let second_split = manager
1988 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1989 .unwrap();
1990
1991 manager.set_label(second_split, "sidebar".to_string());
1992 assert_eq!(manager.find_split_by_label("sidebar"), Some(second_split));
1993
1994 manager.close_split(second_split).unwrap();
1995
1996 assert_eq!(manager.find_split_by_label("sidebar"), None);
1998 assert_eq!(manager.get_label(second_split.into()), None);
1999 }
2000
2001 #[test]
2002 fn test_label_overwrite() {
2003 let mut manager = SplitManager::new(BufferId(0));
2004 let split = manager.active_split();
2005
2006 manager.set_label(split, "sidebar".to_string());
2007 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
2008
2009 manager.set_label(split, "terminal".to_string());
2010 assert_eq!(manager.get_label(split.into()), Some("terminal"));
2011 assert_eq!(manager.find_split_by_label("sidebar"), None);
2012 assert_eq!(manager.find_split_by_label("terminal"), Some(split));
2013 }
2014
2015 #[test]
2016 fn test_find_unlabeled_leaf_single_split_no_label() {
2017 let manager = SplitManager::new(BufferId(0));
2018 assert_eq!(manager.find_unlabeled_leaf(), Some(manager.active_split()));
2020 }
2021
2022 #[test]
2023 fn test_find_unlabeled_leaf_single_split_labeled() {
2024 let mut manager = SplitManager::new(BufferId(0));
2025 let split = manager.active_split();
2026 manager.set_label(split, "only".to_string());
2027 assert_eq!(manager.find_unlabeled_leaf(), None);
2029 }
2030
2031 #[test]
2036 fn test_split_root_positioned_with_existing_vertical_split() {
2037 let left = BufferId(0);
2039 let right = BufferId(1);
2040 let dock = BufferId(2);
2041 let mut manager = SplitManager::new(left);
2042 manager
2043 .split_active(SplitDirection::Vertical, right, 0.5)
2044 .expect("vertical split");
2045 assert!(matches!(
2047 manager.root(),
2048 SplitNode::Split {
2049 direction: SplitDirection::Vertical,
2050 ..
2051 }
2052 ));
2053 assert_eq!(manager.root().count_leaves(), 2);
2054 let active_before = manager.active_split();
2058
2059 let dock_leaf = manager
2061 .split_root_positioned(SplitDirection::Horizontal, dock, 0.7, false)
2062 .expect("split_root_positioned");
2063
2064 match manager.root() {
2069 SplitNode::Split {
2070 direction: SplitDirection::Horizontal,
2071 first,
2072 second,
2073 ..
2074 } => {
2075 assert!(
2076 matches!(
2077 first.as_ref(),
2078 SplitNode::Split {
2079 direction: SplitDirection::Vertical,
2080 ..
2081 }
2082 ),
2083 "first child of new root must be the original Vertical split, got {:?}",
2084 first
2085 );
2086 match second.as_ref() {
2087 SplitNode::Leaf {
2088 buffer_id,
2089 split_id,
2090 ..
2091 } => {
2092 assert_eq!(*buffer_id, dock, "second child must be the dock leaf");
2093 assert_eq!(
2094 *split_id, dock_leaf,
2095 "split_root_positioned must return the new leaf id"
2096 );
2097 }
2098 other => panic!("expected dock leaf as second child, got {:?}", other),
2099 }
2100 }
2101 other => {
2102 panic!(
2103 "root must be a Horizontal Split after split_root_positioned, got {:?}",
2104 other
2105 );
2106 }
2107 }
2108 assert_eq!(manager.root().count_leaves(), 3);
2110 assert_ne!(
2113 dock_leaf, active_before,
2114 "dock must be a new sibling of the root, not the previously-active leaf"
2115 );
2116 }
2117
2118 #[test]
2123 fn test_next_split_unmaximizes_when_maximized() {
2124 let buffer_a = BufferId(0);
2125 let buffer_b = BufferId(1);
2126
2127 let mut manager = SplitManager::new(buffer_a);
2128 manager
2129 .split_active(SplitDirection::Vertical, buffer_b, 0.5)
2130 .expect("vertical split");
2131 let first_active = manager.active_split();
2132
2133 manager.maximize_split().expect("maximize");
2134 assert!(manager.is_maximized());
2135
2136 manager.next_split();
2137
2138 assert!(
2139 !manager.is_maximized(),
2140 "next_split must unmaximize so the newly-active split is visible"
2141 );
2142 assert_ne!(
2143 manager.active_split(),
2144 first_active,
2145 "next_split must actually move to a different split"
2146 );
2147 }
2148
2149 #[test]
2152 fn test_prev_split_unmaximizes_when_maximized() {
2153 let buffer_a = BufferId(0);
2154 let buffer_b = BufferId(1);
2155
2156 let mut manager = SplitManager::new(buffer_a);
2157 manager
2158 .split_active(SplitDirection::Vertical, buffer_b, 0.5)
2159 .expect("vertical split");
2160 let first_active = manager.active_split();
2161
2162 manager.maximize_split().expect("maximize");
2163 assert!(manager.is_maximized());
2164
2165 manager.prev_split();
2166
2167 assert!(
2168 !manager.is_maximized(),
2169 "prev_split must unmaximize so the newly-active split is visible"
2170 );
2171 assert_ne!(
2172 manager.active_split(),
2173 first_active,
2174 "prev_split must actually move to a different split"
2175 );
2176 }
2177}