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 scroll_offset: usize,
235 ) {
236 self.show_line_numbers = line_numbers;
237 self.highlight_current_line = highlight_current_line;
238 self.viewport.line_wrap_enabled = line_wrap;
239 self.viewport.wrap_indent = wrap_indent;
240 self.viewport.wrap_column = wrap_column;
241 self.rulers = rulers;
242 self.viewport.set_scroll_offset(scroll_offset);
243 }
244
245 pub fn activate_page_view(&mut self, page_width: Option<usize>) {
251 self.view_mode = ViewMode::PageView;
252 self.show_line_numbers = false;
253 self.viewport.line_wrap_enabled = false;
254 if let Some(width) = page_width {
255 self.compose_width = Some(width as u16);
256 }
257 }
258}
259
260impl Clone for BufferViewState {
261 fn clone(&self) -> Self {
262 Self {
263 cursors: self.cursors.clone(),
264 viewport: self.viewport.clone(),
265 view_mode: self.view_mode.clone(),
266 compose_width: self.compose_width,
267 compose_column_guides: self.compose_column_guides.clone(),
268 rulers: self.rulers.clone(),
269 show_line_numbers: self.show_line_numbers,
270 highlight_current_line: self.highlight_current_line,
271 view_transform: self.view_transform.clone(),
272 view_transform_stale: self.view_transform_stale,
273 plugin_state: self.plugin_state.clone(),
274 folds: FoldManager::new(),
276 }
277 }
278}
279
280#[derive(Debug, Clone)]
292pub struct SplitViewState {
293 pub active_buffer: BufferId,
295
296 pub keyed_states: HashMap<BufferId, BufferViewState>,
298
299 pub open_buffers: Vec<TabTarget>,
305
306 pub tab_scroll_offset: usize,
308
309 pub layout: Option<Layout>,
312
313 pub layout_dirty: bool,
315
316 pub focus_history: Vec<TabTarget>,
320
321 pub sync_group: Option<u32>,
324
325 pub composite_view: Option<BufferId>,
330
331 pub suppress_chrome: bool,
334
335 pub hide_tilde: bool,
338
339 pub active_group_tab: Option<LeafId>,
343
344 pub focused_group_leaf: Option<LeafId>,
347}
348
349impl std::ops::Deref for SplitViewState {
350 type Target = BufferViewState;
351
352 fn deref(&self) -> &BufferViewState {
353 self.active_state()
354 }
355}
356
357impl std::ops::DerefMut for SplitViewState {
358 fn deref_mut(&mut self) -> &mut BufferViewState {
359 self.active_state_mut()
360 }
361}
362
363impl SplitViewState {
364 pub fn with_buffer(width: u16, height: u16, buffer_id: BufferId) -> Self {
366 let buf_state = BufferViewState::new(width, height);
367 let mut keyed_states = HashMap::new();
368 keyed_states.insert(buffer_id, buf_state);
369 Self {
370 active_buffer: buffer_id,
371 keyed_states,
372 open_buffers: vec![TabTarget::Buffer(buffer_id)],
373 tab_scroll_offset: 0,
374 layout: None,
375 layout_dirty: true,
376 focus_history: Vec::new(),
377 sync_group: None,
378 composite_view: None,
379 suppress_chrome: false,
380 hide_tilde: false,
381 active_group_tab: None,
382 focused_group_leaf: None,
383 }
384 }
385
386 pub fn active_state(&self) -> &BufferViewState {
388 self.keyed_states
389 .get(&self.active_buffer)
390 .expect("active_buffer must always have an entry in keyed_states")
391 }
392
393 pub fn active_state_mut(&mut self) -> &mut BufferViewState {
395 self.keyed_states
396 .get_mut(&self.active_buffer)
397 .expect("active_buffer must always have an entry in keyed_states")
398 }
399
400 pub fn switch_buffer(&mut self, new_buffer_id: BufferId) {
406 if new_buffer_id == self.active_buffer {
407 return;
408 }
409 if !self.keyed_states.contains_key(&new_buffer_id) {
411 let active = self.active_state();
412 let width = active.viewport.width;
413 let height = active.viewport.height;
414 self.keyed_states
415 .insert(new_buffer_id, BufferViewState::new(width, height));
416 }
417 self.active_buffer = new_buffer_id;
418 self.layout_dirty = true;
420 }
421
422 pub fn buffer_state(&self, buffer_id: BufferId) -> Option<&BufferViewState> {
424 self.keyed_states.get(&buffer_id)
425 }
426
427 pub fn buffer_state_mut(&mut self, buffer_id: BufferId) -> Option<&mut BufferViewState> {
429 self.keyed_states.get_mut(&buffer_id)
430 }
431
432 pub fn ensure_buffer_state(&mut self, buffer_id: BufferId) -> &mut BufferViewState {
435 let (width, height) = {
436 let active = self.active_state();
437 (active.viewport.width, active.viewport.height)
438 };
439 self.keyed_states
440 .entry(buffer_id)
441 .or_insert_with(|| BufferViewState::new(width, height))
442 }
443
444 pub fn remove_buffer_state(&mut self, buffer_id: BufferId) {
446 if buffer_id != self.active_buffer {
447 self.keyed_states.remove(&buffer_id);
448 }
449 }
450
451 pub fn invalidate_layout(&mut self) {
453 self.layout_dirty = true;
454 }
455
456 pub fn ensure_layout(
464 &mut self,
465 tokens: &[fresh_core::api::ViewTokenWire],
466 source_range: std::ops::Range<usize>,
467 tab_size: usize,
468 ) -> &Layout {
469 if self.layout.is_none() || self.layout_dirty {
470 self.layout = Some(Layout::from_tokens(tokens, source_range, tab_size));
471 self.layout_dirty = false;
472 }
473 self.layout.as_ref().unwrap()
474 }
475
476 pub fn get_layout(&self) -> Option<&Layout> {
478 if self.layout_dirty {
479 None
480 } else {
481 self.layout.as_ref()
482 }
483 }
484
485 pub fn add_buffer(&mut self, buffer_id: BufferId) {
487 if !self.has_buffer(buffer_id) {
488 self.open_buffers.push(TabTarget::Buffer(buffer_id));
489 }
490 }
491
492 pub fn remove_buffer(&mut self, buffer_id: BufferId) {
494 self.open_buffers
495 .retain(|t| *t != TabTarget::Buffer(buffer_id));
496 if buffer_id != self.active_buffer {
498 self.keyed_states.remove(&buffer_id);
499 }
500 }
501
502 pub fn has_buffer(&self, buffer_id: BufferId) -> bool {
504 self.open_buffers.contains(&TabTarget::Buffer(buffer_id))
505 }
506
507 pub fn add_group(&mut self, leaf_id: LeafId) {
509 if !self.has_group(leaf_id) {
510 self.open_buffers.push(TabTarget::Group(leaf_id));
511 }
512 }
513
514 pub fn remove_group(&mut self, leaf_id: LeafId) {
516 self.open_buffers
517 .retain(|t| *t != TabTarget::Group(leaf_id));
518 }
519
520 pub fn has_group(&self, leaf_id: LeafId) -> bool {
522 self.open_buffers.contains(&TabTarget::Group(leaf_id))
523 }
524
525 pub fn buffer_tab_ids(&self) -> impl Iterator<Item = BufferId> + '_ {
527 self.open_buffers.iter().filter_map(|t| t.as_buffer())
528 }
529
530 pub fn buffer_tab_ids_vec(&self) -> Vec<BufferId> {
533 self.buffer_tab_ids().collect()
534 }
535
536 pub fn buffer_tab_count(&self) -> usize {
538 self.open_buffers
539 .iter()
540 .filter(|t| matches!(t, TabTarget::Buffer(_)))
541 .count()
542 }
543
544 pub fn active_target(&self) -> TabTarget {
548 match self.active_group_tab {
549 Some(leaf_id) => TabTarget::Group(leaf_id),
550 None => TabTarget::Buffer(self.active_buffer),
551 }
552 }
553
554 pub fn set_active_buffer_tab(&mut self, buffer_id: BufferId) {
557 self.active_group_tab = None;
558 self.focused_group_leaf = None;
559 self.switch_buffer(buffer_id);
560 }
561
562 pub fn set_active_group_tab(&mut self, leaf_id: LeafId) {
564 self.active_group_tab = Some(leaf_id);
565 }
566
567 pub fn push_focus(&mut self, target: TabTarget) {
570 self.focus_history.retain(|t| *t != target);
571 self.focus_history.push(target);
572 if self.focus_history.len() > 50 {
573 self.focus_history.remove(0);
574 }
575 }
576
577 pub fn previous_tab(&self) -> Option<TabTarget> {
579 self.focus_history.last().copied()
580 }
581
582 pub fn remove_from_history(&mut self, buffer_id: BufferId) {
584 self.focus_history
585 .retain(|t| *t != TabTarget::Buffer(buffer_id));
586 }
587
588 pub fn remove_group_from_history(&mut self, leaf_id: LeafId) {
590 self.focus_history
591 .retain(|t| *t != TabTarget::Group(leaf_id));
592 }
593}
594
595impl SplitNode {
596 pub fn leaf(buffer_id: BufferId, split_id: SplitId) -> Self {
598 Self::Leaf {
599 buffer_id,
600 split_id: LeafId(split_id),
601 role: None,
602 }
603 }
604
605 pub fn leaf_with_role(buffer_id: BufferId, split_id: SplitId, role: SplitRole) -> Self {
607 Self::Leaf {
608 buffer_id,
609 split_id: LeafId(split_id),
610 role: Some(role),
611 }
612 }
613
614 pub fn role(&self) -> Option<SplitRole> {
616 match self {
617 Self::Leaf { role, .. } => *role,
618 _ => None,
619 }
620 }
621
622 pub fn set_role(&mut self, new_role: Option<SplitRole>) {
624 if let Self::Leaf { role, .. } = self {
625 *role = new_role;
626 }
627 }
628
629 pub fn split(
631 direction: SplitDirection,
632 first: SplitNode,
633 second: SplitNode,
634 ratio: f32,
635 split_id: SplitId,
636 ) -> Self {
637 SplitNode::Split {
638 direction,
639 first: Box::new(first),
640 second: Box::new(second),
641 ratio: ratio.clamp(0.1, 0.9), split_id: ContainerId(split_id),
643 fixed_first: None,
644 fixed_second: None,
645 }
646 }
647
648 pub fn id(&self) -> SplitId {
650 match self {
651 Self::Leaf { split_id, .. } => split_id.0,
652 Self::Split { split_id, .. } => split_id.0,
653 Self::Grouped { split_id, .. } => split_id.0,
654 }
655 }
656
657 pub fn buffer_id(&self) -> Option<BufferId> {
659 match self {
660 Self::Leaf { buffer_id, .. } => Some(*buffer_id),
661 Self::Split { .. } | Self::Grouped { .. } => None,
662 }
663 }
664
665 pub fn find_mut(&mut self, target_id: SplitId) -> Option<&mut Self> {
669 if self.id() == target_id {
670 return Some(self);
671 }
672
673 match self {
674 Self::Leaf { .. } => None,
675 Self::Split { first, second, .. } => first
676 .find_mut(target_id)
677 .or_else(|| second.find_mut(target_id)),
678 Self::Grouped { layout, .. } => layout.find_mut(target_id),
679 }
680 }
681
682 pub fn find(&self, target_id: SplitId) -> Option<&Self> {
686 if self.id() == target_id {
687 return Some(self);
688 }
689
690 match self {
691 Self::Leaf { .. } => None,
692 Self::Split { first, second, .. } => {
693 first.find(target_id).or_else(|| second.find(target_id))
694 }
695 Self::Grouped { layout, .. } => layout.find(target_id),
696 }
697 }
698
699 pub fn parent_container_of(&self, target_id: SplitId) -> Option<ContainerId> {
703 match self {
704 Self::Leaf { .. } => None,
705 Self::Split {
706 split_id,
707 first,
708 second,
709 ..
710 } => {
711 if first.id() == target_id || second.id() == target_id {
712 Some(*split_id)
713 } else {
714 first
715 .parent_container_of(target_id)
716 .or_else(|| second.parent_container_of(target_id))
717 }
718 }
719 Self::Grouped { layout, .. } => layout.parent_container_of(target_id),
720 }
721 }
722
723 pub fn grouped_ancestor_of(&self, target_id: SplitId) -> Option<LeafId> {
726 match self {
727 Self::Leaf { .. } => None,
728 Self::Split { first, second, .. } => first
729 .grouped_ancestor_of(target_id)
730 .or_else(|| second.grouped_ancestor_of(target_id)),
731 Self::Grouped {
732 split_id, layout, ..
733 } => {
734 if layout.find(target_id).is_some() {
735 Some(*split_id)
736 } else {
737 layout.grouped_ancestor_of(target_id)
738 }
739 }
740 }
741 }
742
743 pub fn find_grouped(&self, target: LeafId) -> Option<&Self> {
746 match self {
747 Self::Leaf { .. } => None,
748 Self::Split { first, second, .. } => first
749 .find_grouped(target)
750 .or_else(|| second.find_grouped(target)),
751 Self::Grouped {
752 split_id, layout, ..
753 } => {
754 if *split_id == target {
755 Some(self)
756 } else {
757 layout.find_grouped(target)
758 }
759 }
760 }
761 }
762
763 pub fn get_leaves_with_rects(&self, rect: Rect) -> Vec<(LeafId, BufferId, Rect)> {
769 match self {
770 Self::Leaf {
771 buffer_id,
772 split_id,
773 ..
774 } => {
775 vec![(*split_id, *buffer_id, rect)]
776 }
777 Self::Split {
778 direction,
779 first,
780 second,
781 ratio,
782 fixed_first,
783 fixed_second,
784 ..
785 } => {
786 let (first_rect, second_rect) =
787 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
788 let mut leaves = first.get_leaves_with_rects(first_rect);
789 leaves.extend(second.get_leaves_with_rects(second_rect));
790 leaves
791 }
792 Self::Grouped { layout, .. } => layout.get_leaves_with_rects(rect),
793 }
794 }
795
796 pub fn get_visible_leaves_with_rects<F>(
802 &self,
803 rect: Rect,
804 is_group_active: &F,
805 ) -> Vec<(LeafId, BufferId, Rect)>
806 where
807 F: Fn(LeafId) -> bool,
808 {
809 match self {
810 Self::Leaf {
811 buffer_id,
812 split_id,
813 ..
814 } => {
815 vec![(*split_id, *buffer_id, rect)]
816 }
817 Self::Split {
818 direction,
819 first,
820 second,
821 ratio,
822 fixed_first,
823 fixed_second,
824 ..
825 } => {
826 let (first_rect, second_rect) =
827 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
828 let mut leaves = first.get_visible_leaves_with_rects(first_rect, is_group_active);
829 leaves.extend(second.get_visible_leaves_with_rects(second_rect, is_group_active));
830 leaves
831 }
832 Self::Grouped {
833 split_id, layout, ..
834 } => {
835 if is_group_active(*split_id) {
836 layout.get_visible_leaves_with_rects(rect, is_group_active)
837 } else {
838 Vec::new()
839 }
840 }
841 }
842 }
843
844 pub fn get_separators(&self, rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
847 self.get_separators_with_ids(rect)
848 .into_iter()
849 .map(|(_, dir, x, y, len)| (dir, x, y, len))
850 .collect()
851 }
852
853 pub fn get_separators_with_ids(
856 &self,
857 rect: Rect,
858 ) -> Vec<(ContainerId, SplitDirection, u16, u16, u16)> {
859 match self {
860 Self::Leaf { .. } => vec![],
861 Self::Grouped { layout, .. } => layout.get_separators_with_ids(rect),
862 Self::Split {
863 direction,
864 first,
865 second,
866 ratio,
867 split_id,
868 fixed_first,
869 fixed_second,
870 } => {
871 let (first_rect, second_rect) =
872 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
873 let mut separators = Vec::new();
874
875 match direction {
877 SplitDirection::Horizontal => {
878 separators.push((
881 *split_id,
882 SplitDirection::Horizontal,
883 rect.x,
884 first_rect.y + first_rect.height,
885 rect.width,
886 ));
887 }
888 SplitDirection::Vertical => {
889 separators.push((
892 *split_id,
893 SplitDirection::Vertical,
894 first_rect.x + first_rect.width,
895 rect.y,
896 rect.height,
897 ));
898 }
899 }
900
901 separators.extend(first.get_separators_with_ids(first_rect));
903 separators.extend(second.get_separators_with_ids(second_rect));
904 separators
905 }
906 }
907 }
908
909 pub fn all_split_ids(&self) -> Vec<SplitId> {
911 let mut ids = vec![self.id()];
912 match self {
913 Self::Leaf { .. } => ids,
914 Self::Split { first, second, .. } => {
915 ids.extend(first.all_split_ids());
916 ids.extend(second.all_split_ids());
917 ids
918 }
919 Self::Grouped { layout, .. } => {
920 ids.extend(layout.all_split_ids());
921 ids
922 }
923 }
924 }
925
926 pub fn leaf_split_ids(&self) -> Vec<LeafId> {
929 match self {
930 Self::Leaf { split_id, .. } => vec![*split_id],
931 Self::Split { first, second, .. } => {
932 let mut ids = first.leaf_split_ids();
933 ids.extend(second.leaf_split_ids());
934 ids
935 }
936 Self::Grouped { layout, .. } => layout.leaf_split_ids(),
937 }
938 }
939
940 pub fn count_leaves(&self) -> usize {
943 match self {
944 Self::Leaf { .. } => 1,
945 Self::Split { first, second, .. } => first.count_leaves() + second.count_leaves(),
946 Self::Grouped { layout, .. } => layout.count_leaves(),
947 }
948 }
949
950 pub fn collect_group_names(&self) -> HashMap<LeafId, String> {
953 let mut map = HashMap::new();
954 self.collect_group_names_into(&mut map);
955 map
956 }
957
958 fn collect_group_names_into(&self, map: &mut HashMap<LeafId, String>) {
959 match self {
960 Self::Leaf { .. } => {}
961 Self::Split { first, second, .. } => {
962 first.collect_group_names_into(map);
963 second.collect_group_names_into(map);
964 }
965 Self::Grouped {
966 split_id,
967 name,
968 layout,
969 ..
970 } => {
971 map.insert(*split_id, name.clone());
972 layout.collect_group_names_into(map);
973 }
974 }
975 }
976}
977
978#[cfg(test)]
981fn split_rect(rect: Rect, direction: SplitDirection, ratio: f32) -> (Rect, Rect) {
982 split_rect_ext(rect, direction, ratio, None, None)
983}
984
985fn split_rect_ext(
986 rect: Rect,
987 direction: SplitDirection,
988 ratio: f32,
989 fixed_first: Option<u16>,
990 fixed_second: Option<u16>,
991) -> (Rect, Rect) {
992 match direction {
993 SplitDirection::Horizontal => {
994 let total_height = rect.height.saturating_sub(1); let first_height = if let Some(f) = fixed_first {
997 f.min(total_height)
998 } else if let Some(s) = fixed_second {
999 total_height.saturating_sub(s.min(total_height))
1000 } else {
1001 (total_height as f32 * ratio).round() as u16
1002 };
1003 let second_height = total_height.saturating_sub(first_height);
1004
1005 let first = Rect {
1006 x: rect.x,
1007 y: rect.y,
1008 width: rect.width,
1009 height: first_height,
1010 };
1011
1012 let second = Rect {
1013 x: rect.x,
1014 y: rect.y + first_height + 1, width: rect.width,
1016 height: second_height,
1017 };
1018
1019 (first, second)
1020 }
1021 SplitDirection::Vertical => {
1022 let total_width = rect.width.saturating_sub(1); let first_width = if let Some(f) = fixed_first {
1025 f.min(total_width)
1026 } else if let Some(s) = fixed_second {
1027 total_width.saturating_sub(s.min(total_width))
1028 } else {
1029 (total_width as f32 * ratio).round() as u16
1030 };
1031 let second_width = total_width.saturating_sub(first_width);
1032
1033 let first = Rect {
1034 x: rect.x,
1035 y: rect.y,
1036 width: first_width,
1037 height: rect.height,
1038 };
1039
1040 let second = Rect {
1041 x: rect.x + first_width + 1, y: rect.y,
1043 width: second_width,
1044 height: rect.height,
1045 };
1046
1047 (first, second)
1048 }
1049 }
1050}
1051
1052#[derive(Debug)]
1054pub struct SplitManager {
1055 root: SplitNode,
1057
1058 active_split: LeafId,
1060
1061 next_split_id: usize,
1063
1064 maximized_split: Option<SplitId>,
1066
1067 labels: HashMap<SplitId, String>,
1069
1070 focus_history: Vec<LeafId>,
1078}
1079
1080const FOCUS_HISTORY_CAP: usize = 50;
1083
1084impl SplitManager {
1085 pub fn new(buffer_id: BufferId) -> Self {
1087 let split_id = SplitId(0);
1088 Self {
1089 root: SplitNode::leaf(buffer_id, split_id),
1090 active_split: LeafId(split_id),
1091 next_split_id: 1,
1092 maximized_split: None,
1093 labels: HashMap::new(),
1094 focus_history: vec![LeafId(split_id)],
1095 }
1096 }
1097
1098 pub fn root(&self) -> &SplitNode {
1100 &self.root
1101 }
1102
1103 pub fn allocate_split_id(&mut self) -> SplitId {
1105 let id = SplitId(self.next_split_id);
1106 self.next_split_id += 1;
1107 id
1108 }
1109
1110 pub fn replace_root(&mut self, new_root: SplitNode, new_active: LeafId) {
1114 self.root = new_root;
1115 self.active_split = new_active;
1116 self.focus_history.clear();
1119 self.focus_history.push(new_active);
1120 }
1121
1122 pub fn active_split(&self) -> LeafId {
1124 self.active_split
1125 }
1126
1127 pub fn set_active_split(&mut self, split_id: LeafId) -> bool {
1129 if self.root.find(split_id.into()).is_some() {
1131 self.active_split = split_id;
1132 self.focus_history.retain(|leaf| *leaf != split_id);
1136 self.focus_history.push(split_id);
1137 if self.focus_history.len() > FOCUS_HISTORY_CAP {
1138 self.focus_history.remove(0);
1139 }
1140 true
1141 } else {
1142 false
1143 }
1144 }
1145
1146 pub fn leaf_role(&self, split_id: LeafId) -> Option<SplitRole> {
1149 self.root.find(split_id.into()).and_then(|node| node.role())
1150 }
1151
1152 pub fn last_focused_where<F>(&self, mut predicate: F) -> Option<LeafId>
1162 where
1163 F: FnMut(LeafId) -> bool,
1164 {
1165 self.focus_history
1166 .iter()
1167 .rev()
1168 .copied()
1169 .find(|leaf| self.root.find((*leaf).into()).is_some() && predicate(*leaf))
1170 }
1171
1172 pub fn active_buffer_id(&self) -> Option<BufferId> {
1174 self.root
1175 .find(self.active_split.into())
1176 .and_then(|node| node.buffer_id())
1177 }
1178
1179 pub fn get_buffer_id(&self, split_id: SplitId) -> Option<BufferId> {
1181 self.root.find(split_id).and_then(|node| node.buffer_id())
1182 }
1183
1184 pub fn set_active_buffer_id(&mut self, new_buffer_id: BufferId) -> bool {
1186 if let Some(SplitNode::Leaf { buffer_id, .. }) =
1187 self.root.find_mut(self.active_split.into())
1188 {
1189 *buffer_id = new_buffer_id;
1190 return true;
1191 }
1192 false
1193 }
1194
1195 pub fn set_split_buffer(&mut self, leaf_id: LeafId, new_buffer_id: BufferId) {
1197 match self.root.find_mut(leaf_id.into()) {
1198 Some(SplitNode::Leaf { buffer_id, .. }) => {
1199 *buffer_id = new_buffer_id;
1200 }
1201 Some(SplitNode::Split { .. }) => {
1202 unreachable!("LeafId {:?} points to a container", leaf_id)
1203 }
1204 Some(SplitNode::Grouped { .. }) => {
1205 unreachable!("LeafId {:?} points to a Grouped node", leaf_id)
1206 }
1207 None => {
1208 unreachable!("LeafId {:?} not found in split tree", leaf_id)
1209 }
1210 }
1211 }
1212
1213 pub fn split_active(
1217 &mut self,
1218 direction: SplitDirection,
1219 new_buffer_id: BufferId,
1220 ratio: f32,
1221 ) -> Result<LeafId, String> {
1222 self.split_active_positioned(direction, new_buffer_id, ratio, false)
1223 }
1224
1225 pub fn split_active_before(
1228 &mut self,
1229 direction: SplitDirection,
1230 new_buffer_id: BufferId,
1231 ratio: f32,
1232 ) -> Result<LeafId, String> {
1233 self.split_active_positioned(direction, new_buffer_id, ratio, true)
1234 }
1235
1236 pub fn split_active_positioned(
1237 &mut self,
1238 direction: SplitDirection,
1239 new_buffer_id: BufferId,
1240 ratio: f32,
1241 before: bool,
1242 ) -> Result<LeafId, String> {
1243 let active_id: SplitId = self.active_split.into();
1244
1245 let result =
1247 self.replace_split_with_split(active_id, direction, new_buffer_id, ratio, before);
1248
1249 if let Ok(new_split_id) = &result {
1250 self.active_split = *new_split_id;
1252 }
1253 result
1254 }
1255
1256 pub fn split_root_positioned(
1265 &mut self,
1266 direction: SplitDirection,
1267 new_buffer_id: BufferId,
1268 ratio: f32,
1269 before: bool,
1270 ) -> Result<LeafId, String> {
1271 let root_id = self.root.id();
1272 let result =
1273 self.replace_split_with_split(root_id, direction, new_buffer_id, ratio, before);
1274 if let Ok(new_split_id) = &result {
1275 self.active_split = *new_split_id;
1276 }
1277 result
1278 }
1279
1280 fn replace_split_with_split(
1283 &mut self,
1284 target_id: SplitId,
1285 direction: SplitDirection,
1286 new_buffer_id: BufferId,
1287 ratio: f32,
1288 before: bool,
1289 ) -> Result<LeafId, String> {
1290 let temp_id = self.allocate_split_id();
1292 let new_split_id = self.allocate_split_id();
1293 let new_leaf_id = self.allocate_split_id();
1294
1295 if self.root.id() == target_id {
1297 let old_root =
1298 std::mem::replace(&mut self.root, SplitNode::leaf(new_buffer_id, temp_id));
1299 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1300
1301 let (first, second) = if before {
1302 (new_leaf, old_root)
1303 } else {
1304 (old_root, new_leaf)
1305 };
1306
1307 self.root = SplitNode::split(direction, first, second, ratio, new_split_id);
1308
1309 return Ok(LeafId(new_leaf_id));
1310 }
1311
1312 if let Some(node) = self.root.find_mut(target_id) {
1314 let old_node = std::mem::replace(node, SplitNode::leaf(new_buffer_id, temp_id));
1315 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1316
1317 let (first, second) = if before {
1318 (new_leaf, old_node)
1319 } else {
1320 (old_node, new_leaf)
1321 };
1322
1323 *node = SplitNode::split(direction, first, second, ratio, new_split_id);
1324
1325 Ok(LeafId(new_leaf_id))
1326 } else {
1327 Err(format!("Split {:?} not found", target_id))
1328 }
1329 }
1330
1331 pub fn close_split(&mut self, split_id: LeafId) -> Result<(), String> {
1333 if self.root.count_leaves() <= 1 {
1335 return Err("Cannot close the last split".to_string());
1336 }
1337
1338 if self.root.id() == split_id.into() && self.root.buffer_id().is_some() {
1340 return Err("Cannot close the only split".to_string());
1341 }
1342
1343 if self.maximized_split == Some(split_id.into()) {
1345 self.maximized_split = None;
1346 }
1347
1348 let removed_ids: Vec<SplitId> = self
1350 .root
1351 .find(split_id.into())
1352 .map(|node| node.all_split_ids())
1353 .unwrap_or_default();
1354
1355 let result = self.remove_split_node(split_id.into());
1358
1359 if result.is_ok() {
1360 for id in &removed_ids {
1362 self.labels.remove(id);
1363 }
1364
1365 if self.active_split == split_id {
1367 let leaf_ids = self.root.leaf_split_ids();
1368 if let Some(&first_leaf) = leaf_ids.first() {
1369 self.active_split = first_leaf;
1370 }
1371 }
1372 }
1373
1374 result
1375 }
1376
1377 fn remove_split_node(&mut self, target_id: SplitId) -> Result<(), String> {
1379 if self.root.id() == target_id {
1381 if let SplitNode::Split { first, .. } = &self.root {
1382 self.root = (**first).clone();
1385 return Ok(());
1386 }
1387 }
1388
1389 Self::remove_child_static(&mut self.root, target_id)
1391 }
1392
1393 fn remove_child_static(node: &mut SplitNode, target_id: SplitId) -> Result<(), String> {
1395 match node {
1396 SplitNode::Leaf { .. } => Err("Target not found".to_string()),
1397 SplitNode::Grouped { layout, .. } => Self::remove_child_static(layout, target_id),
1398 SplitNode::Split { first, second, .. } => {
1399 if first.id() == target_id {
1401 *node = (**second).clone();
1403 Ok(())
1404 } else if second.id() == target_id {
1405 *node = (**first).clone();
1407 Ok(())
1408 } else {
1409 Self::remove_child_static(first, target_id)
1411 .or_else(|_| Self::remove_child_static(second, target_id))
1412 }
1413 }
1414 }
1415 }
1416
1417 pub fn remove_grouped(&mut self, target: LeafId) -> Result<(), String> {
1425 let target_id: SplitId = target.into();
1426 if self.root.id() == target_id {
1427 return Err("Cannot remove root Grouped node".to_string());
1428 }
1429 Self::remove_child_static(&mut self.root, target_id)
1430 }
1431
1432 pub fn adjust_ratio(&mut self, container_id: ContainerId, delta: f32) {
1434 match self.root.find_mut(container_id.into()) {
1435 Some(SplitNode::Split { ratio, .. }) => {
1436 *ratio = (*ratio + delta).clamp(0.1, 0.9);
1437 }
1438 Some(SplitNode::Leaf { .. }) => {
1439 unreachable!("ContainerId {:?} points to a leaf", container_id)
1440 }
1441 Some(SplitNode::Grouped { .. }) => {
1442 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1443 }
1444 None => {
1445 unreachable!("ContainerId {:?} not found in split tree", container_id)
1446 }
1447 }
1448 }
1449
1450 pub fn parent_container_of(&self, leaf_id: LeafId) -> Option<ContainerId> {
1452 self.root.parent_container_of(leaf_id.into())
1453 }
1454
1455 pub fn get_visible_buffers(&self, viewport_rect: Rect) -> Vec<(LeafId, BufferId, Rect)> {
1457 if let Some(maximized_id) = self.maximized_split {
1459 if let Some(SplitNode::Leaf {
1460 buffer_id,
1461 split_id,
1462 ..
1463 }) = self.root.find(maximized_id)
1464 {
1465 return vec![(*split_id, *buffer_id, viewport_rect)];
1466 }
1467 }
1469 self.root.get_leaves_with_rects(viewport_rect)
1470 }
1471
1472 pub fn get_separators(&self, viewport_rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
1475 if self.maximized_split.is_some() {
1477 return vec![];
1478 }
1479 self.root.get_separators(viewport_rect)
1480 }
1481
1482 pub fn get_separators_with_ids(
1485 &self,
1486 viewport_rect: Rect,
1487 ) -> Vec<(ContainerId, SplitDirection, u16, u16, u16)> {
1488 if self.maximized_split.is_some() {
1490 return vec![];
1491 }
1492 self.root.get_separators_with_ids(viewport_rect)
1493 }
1494
1495 pub fn get_ratio(&self, split_id: SplitId) -> Option<f32> {
1497 if let Some(SplitNode::Split { ratio, .. }) = self.root.find(split_id) {
1498 Some(*ratio)
1499 } else {
1500 None
1501 }
1502 }
1503
1504 pub fn set_ratio(&mut self, container_id: ContainerId, new_ratio: f32) {
1506 match self.root.find_mut(container_id.into()) {
1507 Some(SplitNode::Split { ratio, .. }) => {
1508 *ratio = new_ratio.clamp(0.1, 0.9);
1509 }
1510 Some(SplitNode::Leaf { .. }) => {
1511 unreachable!("ContainerId {:?} points to a leaf", container_id)
1512 }
1513 Some(SplitNode::Grouped { .. }) => {
1514 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1515 }
1516 None => {
1517 unreachable!("ContainerId {:?} not found in split tree", container_id)
1518 }
1519 }
1520 }
1521
1522 pub fn set_fixed_size(
1525 &mut self,
1526 container_id: ContainerId,
1527 first: Option<u16>,
1528 second: Option<u16>,
1529 ) {
1530 if let Some(SplitNode::Split {
1531 fixed_first,
1532 fixed_second,
1533 ..
1534 }) = self.root.find_mut(container_id.into())
1535 {
1536 *fixed_first = first;
1537 *fixed_second = second;
1538 }
1539 }
1540
1541 pub fn distribute_splits_evenly(&mut self) {
1544 Self::distribute_node_evenly(&mut self.root);
1545 }
1546
1547 fn distribute_node_evenly(node: &mut SplitNode) -> usize {
1550 match node {
1551 SplitNode::Leaf { .. } => 1,
1552 SplitNode::Grouped { layout, .. } => Self::distribute_node_evenly(layout),
1553 SplitNode::Split {
1554 first,
1555 second,
1556 ratio,
1557 ..
1558 } => {
1559 let first_leaves = Self::distribute_node_evenly(first);
1560 let second_leaves = Self::distribute_node_evenly(second);
1561 let total_leaves = first_leaves + second_leaves;
1562
1563 *ratio = (first_leaves as f32 / total_leaves as f32).clamp(0.1, 0.9);
1566
1567 total_leaves
1568 }
1569 }
1570 }
1571
1572 pub fn next_split(&mut self) {
1574 self.maximized_split = None;
1579 let leaf_ids = self.root.leaf_split_ids();
1580 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1581 let next_pos = (pos + 1) % leaf_ids.len();
1582 self.active_split = leaf_ids[next_pos];
1583 }
1584 }
1585
1586 pub fn prev_split(&mut self) {
1588 self.maximized_split = None;
1590 let leaf_ids = self.root.leaf_split_ids();
1591 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1592 let prev_pos = if pos == 0 { leaf_ids.len() } else { pos } - 1;
1593 self.active_split = leaf_ids[prev_pos];
1594 }
1595 }
1596
1597 pub fn splits_for_buffer(&self, target_buffer_id: BufferId) -> Vec<LeafId> {
1599 self.root
1600 .get_leaves_with_rects(Rect {
1601 x: 0,
1602 y: 0,
1603 width: 1,
1604 height: 1,
1605 })
1606 .into_iter()
1607 .filter(|(_, buffer_id, _)| *buffer_id == target_buffer_id)
1608 .map(|(split_id, _, _)| split_id)
1609 .collect()
1610 }
1611
1612 pub fn buffer_for_split(&self, target_split_id: LeafId) -> Option<BufferId> {
1614 self.root
1615 .get_leaves_with_rects(Rect {
1616 x: 0,
1617 y: 0,
1618 width: 1,
1619 height: 1,
1620 })
1621 .into_iter()
1622 .find(|(split_id, _, _)| *split_id == target_split_id)
1623 .map(|(_, buffer_id, _)| buffer_id)
1624 }
1625
1626 pub fn maximize_split(&mut self) -> Result<(), String> {
1629 if self.root.count_leaves() <= 1 {
1631 return Err("Cannot maximize: only one split exists".to_string());
1632 }
1633
1634 if self.maximized_split.is_some() {
1636 return Err("A split is already maximized".to_string());
1637 }
1638
1639 self.maximized_split = Some(self.active_split.into());
1641 Ok(())
1642 }
1643
1644 pub fn unmaximize_split(&mut self) -> Result<(), String> {
1647 if self.maximized_split.is_none() {
1648 return Err("No split is maximized".to_string());
1649 }
1650
1651 self.maximized_split = None;
1652 Ok(())
1653 }
1654
1655 pub fn is_maximized(&self) -> bool {
1657 self.maximized_split.is_some()
1658 }
1659
1660 pub fn maximized_split(&self) -> Option<SplitId> {
1662 self.maximized_split
1663 }
1664
1665 pub fn toggle_maximize(&mut self) -> Result<bool, String> {
1669 if self.is_maximized() {
1670 self.unmaximize_split()?;
1671 Ok(false)
1672 } else {
1673 self.maximize_split()?;
1674 Ok(true)
1675 }
1676 }
1677
1678 pub fn toggle_maximize_for(&mut self, target: LeafId) -> Result<bool, String> {
1686 if self.is_maximized() {
1687 self.unmaximize_split()?;
1688 Ok(false)
1689 } else {
1690 if self.root.count_leaves() <= 1 {
1691 return Err("Cannot maximize: only one split exists".to_string());
1692 }
1693 if self.root.find(target.into()).is_none() {
1694 return Err("Cannot maximize: split not found".to_string());
1695 }
1696 self.maximized_split = Some(target.into());
1697 Ok(true)
1698 }
1699 }
1700
1701 pub fn get_splits_in_group(
1703 &self,
1704 group_id: u32,
1705 view_states: &std::collections::HashMap<LeafId, SplitViewState>,
1706 ) -> Vec<LeafId> {
1707 self.root
1708 .leaf_split_ids()
1709 .into_iter()
1710 .filter(|id| {
1711 view_states
1712 .get(id)
1713 .and_then(|vs| vs.sync_group)
1714 .is_some_and(|g| g == group_id)
1715 })
1716 .collect()
1717 }
1718
1719 pub fn set_label(&mut self, split_id: LeafId, label: String) {
1723 self.labels.insert(split_id.into(), label);
1724 }
1725
1726 pub fn clear_label(&mut self, split_id: SplitId) {
1728 self.labels.remove(&split_id);
1729 }
1730
1731 pub fn get_label(&self, split_id: SplitId) -> Option<&str> {
1733 self.labels.get(&split_id).map(|s| s.as_str())
1734 }
1735
1736 pub fn labels(&self) -> &HashMap<SplitId, String> {
1738 &self.labels
1739 }
1740
1741 pub fn set_leaf_role(&mut self, split_id: LeafId, new_role: Option<SplitRole>) {
1745 if let Some(node) = self.root.find_mut(split_id.into()) {
1746 node.set_role(new_role);
1747 }
1748 }
1749
1750 pub fn find_leaf_by_role(&self, target: SplitRole) -> Option<LeafId> {
1752 fn walk(node: &SplitNode, target: SplitRole) -> Option<LeafId> {
1753 match node {
1754 SplitNode::Leaf {
1755 role: Some(r),
1756 split_id,
1757 ..
1758 } if *r == target => Some(*split_id),
1759 SplitNode::Leaf { .. } => None,
1760 SplitNode::Split { first, second, .. } => {
1761 walk(first, target).or_else(|| walk(second, target))
1762 }
1763 SplitNode::Grouped { layout, .. } => walk(layout, target),
1764 }
1765 }
1766 walk(&self.root, target)
1767 }
1768
1769 pub fn clear_role(&mut self, target: SplitRole) -> Option<LeafId> {
1773 let leaf = self.find_leaf_by_role(target)?;
1774 self.set_leaf_role(leaf, None);
1775 Some(leaf)
1776 }
1777
1778 pub fn find_split_by_label(&self, label: &str) -> Option<LeafId> {
1780 self.root
1781 .leaf_split_ids()
1782 .into_iter()
1783 .find(|id| self.labels.get(&(*id).into()).is_some_and(|l| l == label))
1784 }
1785
1786 pub fn find_unlabeled_leaf(&self) -> Option<LeafId> {
1788 self.root
1789 .leaf_split_ids()
1790 .into_iter()
1791 .find(|id| !self.labels.contains_key(&(*id).into()))
1792 }
1793}
1794
1795#[cfg(test)]
1796mod tests {
1797 use super::*;
1798
1799 #[test]
1800 fn test_create_split_manager() {
1801 let buffer_id = BufferId(0);
1802 let manager = SplitManager::new(buffer_id);
1803
1804 assert_eq!(manager.active_buffer_id(), Some(buffer_id));
1805 assert_eq!(manager.root().count_leaves(), 1);
1806 }
1807
1808 #[test]
1809 fn test_horizontal_split() {
1810 let buffer_a = BufferId(0);
1811 let buffer_b = BufferId(1);
1812
1813 let mut manager = SplitManager::new(buffer_a);
1814 let result = manager.split_active(SplitDirection::Horizontal, buffer_b, 0.5);
1815
1816 assert!(result.is_ok());
1817 assert_eq!(manager.root().count_leaves(), 2);
1818 }
1819
1820 #[test]
1821 fn test_vertical_split() {
1822 let buffer_a = BufferId(0);
1823 let buffer_b = BufferId(1);
1824
1825 let mut manager = SplitManager::new(buffer_a);
1826 let result = manager.split_active(SplitDirection::Vertical, buffer_b, 0.5);
1827
1828 assert!(result.is_ok());
1829 assert_eq!(manager.root().count_leaves(), 2);
1830 }
1831
1832 #[test]
1833 fn test_nested_splits() {
1834 let buffer_a = BufferId(0);
1835 let buffer_b = BufferId(1);
1836 let buffer_c = BufferId(2);
1837
1838 let mut manager = SplitManager::new(buffer_a);
1839
1840 manager
1842 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1843 .unwrap();
1844
1845 manager
1847 .split_active(SplitDirection::Vertical, buffer_c, 0.5)
1848 .unwrap();
1849
1850 assert_eq!(manager.root().count_leaves(), 3);
1851 }
1852
1853 #[test]
1854 fn test_close_split() {
1855 let buffer_a = BufferId(0);
1856 let buffer_b = BufferId(1);
1857
1858 let mut manager = SplitManager::new(buffer_a);
1859 let new_split = manager
1860 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1861 .unwrap();
1862
1863 assert_eq!(manager.root().count_leaves(), 2);
1864
1865 let result = manager.close_split(new_split);
1867 assert!(result.is_ok());
1868 assert_eq!(manager.root().count_leaves(), 1);
1869 }
1870
1871 #[test]
1872 fn test_cannot_close_last_split() {
1873 let buffer_a = BufferId(0);
1874 let mut manager = SplitManager::new(buffer_a);
1875
1876 let result = manager.close_split(manager.active_split());
1877 assert!(result.is_err());
1878 }
1879
1880 #[test]
1881 fn test_split_rect_horizontal() {
1882 let rect = Rect {
1883 x: 0,
1884 y: 0,
1885 width: 100,
1886 height: 100,
1887 };
1888
1889 let (first, second) = split_rect(rect, SplitDirection::Horizontal, 0.5);
1890
1891 assert_eq!(first.height, 50);
1893 assert_eq!(second.height, 49);
1894 assert_eq!(first.width, 100);
1895 assert_eq!(second.width, 100);
1896 assert_eq!(first.y, 0);
1897 assert_eq!(second.y, 51); }
1899
1900 #[test]
1901 fn test_split_rect_vertical() {
1902 let rect = Rect {
1903 x: 0,
1904 y: 0,
1905 width: 100,
1906 height: 100,
1907 };
1908
1909 let (first, second) = split_rect(rect, SplitDirection::Vertical, 0.5);
1910
1911 assert_eq!(first.width, 50);
1913 assert_eq!(second.width, 49);
1914 assert_eq!(first.height, 100);
1915 assert_eq!(second.height, 100);
1916 assert_eq!(first.x, 0);
1917 assert_eq!(second.x, 51); }
1919
1920 #[test]
1923 fn test_set_and_get_label() {
1924 let mut manager = SplitManager::new(BufferId(0));
1925 let split = manager.active_split();
1926
1927 assert_eq!(manager.get_label(split.into()), None);
1928
1929 manager.set_label(split, "sidebar".to_string());
1930 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
1931 }
1932
1933 #[test]
1934 fn test_clear_label() {
1935 let mut manager = SplitManager::new(BufferId(0));
1936 let split = manager.active_split();
1937
1938 manager.set_label(split, "sidebar".to_string());
1939 assert!(manager.get_label(split.into()).is_some());
1940
1941 manager.clear_label(split.into());
1942 assert_eq!(manager.get_label(split.into()), None);
1943 }
1944
1945 #[test]
1946 fn test_find_split_by_label() {
1947 let mut manager = SplitManager::new(BufferId(0));
1948 let first_split = manager.active_split();
1949
1950 let second_split = manager
1951 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1952 .unwrap();
1953
1954 manager.set_label(first_split, "sidebar".to_string());
1955
1956 assert_eq!(manager.find_split_by_label("sidebar"), Some(first_split));
1957 assert_eq!(manager.find_split_by_label("terminal"), None);
1958
1959 assert_ne!(manager.find_split_by_label("sidebar"), Some(second_split));
1961 }
1962
1963 #[test]
1964 fn test_find_unlabeled_leaf() {
1965 let mut manager = SplitManager::new(BufferId(0));
1966 let first_split = manager.active_split();
1967
1968 let second_split = manager
1969 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1970 .unwrap();
1971
1972 assert!(manager.find_unlabeled_leaf().is_some());
1974
1975 manager.set_label(first_split, "sidebar".to_string());
1977 assert_eq!(manager.find_unlabeled_leaf(), Some(second_split));
1978
1979 manager.set_label(second_split, "terminal".to_string());
1981 assert_eq!(manager.find_unlabeled_leaf(), None);
1982 }
1983
1984 #[test]
1985 fn test_close_split_cleans_up_label() {
1986 let mut manager = SplitManager::new(BufferId(0));
1987 let _first_split = manager.active_split();
1988
1989 let second_split = manager
1990 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1991 .unwrap();
1992
1993 manager.set_label(second_split, "sidebar".to_string());
1994 assert_eq!(manager.find_split_by_label("sidebar"), Some(second_split));
1995
1996 manager.close_split(second_split).unwrap();
1997
1998 assert_eq!(manager.find_split_by_label("sidebar"), None);
2000 assert_eq!(manager.get_label(second_split.into()), None);
2001 }
2002
2003 #[test]
2004 fn test_label_overwrite() {
2005 let mut manager = SplitManager::new(BufferId(0));
2006 let split = manager.active_split();
2007
2008 manager.set_label(split, "sidebar".to_string());
2009 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
2010
2011 manager.set_label(split, "terminal".to_string());
2012 assert_eq!(manager.get_label(split.into()), Some("terminal"));
2013 assert_eq!(manager.find_split_by_label("sidebar"), None);
2014 assert_eq!(manager.find_split_by_label("terminal"), Some(split));
2015 }
2016
2017 #[test]
2018 fn test_find_unlabeled_leaf_single_split_no_label() {
2019 let manager = SplitManager::new(BufferId(0));
2020 assert_eq!(manager.find_unlabeled_leaf(), Some(manager.active_split()));
2022 }
2023
2024 #[test]
2025 fn test_find_unlabeled_leaf_single_split_labeled() {
2026 let mut manager = SplitManager::new(BufferId(0));
2027 let split = manager.active_split();
2028 manager.set_label(split, "only".to_string());
2029 assert_eq!(manager.find_unlabeled_leaf(), None);
2031 }
2032
2033 #[test]
2038 fn test_split_root_positioned_with_existing_vertical_split() {
2039 let left = BufferId(0);
2041 let right = BufferId(1);
2042 let dock = BufferId(2);
2043 let mut manager = SplitManager::new(left);
2044 manager
2045 .split_active(SplitDirection::Vertical, right, 0.5)
2046 .expect("vertical split");
2047 assert!(matches!(
2049 manager.root(),
2050 SplitNode::Split {
2051 direction: SplitDirection::Vertical,
2052 ..
2053 }
2054 ));
2055 assert_eq!(manager.root().count_leaves(), 2);
2056 let active_before = manager.active_split();
2060
2061 let dock_leaf = manager
2063 .split_root_positioned(SplitDirection::Horizontal, dock, 0.7, false)
2064 .expect("split_root_positioned");
2065
2066 match manager.root() {
2071 SplitNode::Split {
2072 direction: SplitDirection::Horizontal,
2073 first,
2074 second,
2075 ..
2076 } => {
2077 assert!(
2078 matches!(
2079 first.as_ref(),
2080 SplitNode::Split {
2081 direction: SplitDirection::Vertical,
2082 ..
2083 }
2084 ),
2085 "first child of new root must be the original Vertical split, got {:?}",
2086 first
2087 );
2088 match second.as_ref() {
2089 SplitNode::Leaf {
2090 buffer_id,
2091 split_id,
2092 ..
2093 } => {
2094 assert_eq!(*buffer_id, dock, "second child must be the dock leaf");
2095 assert_eq!(
2096 *split_id, dock_leaf,
2097 "split_root_positioned must return the new leaf id"
2098 );
2099 }
2100 other => panic!("expected dock leaf as second child, got {:?}", other),
2101 }
2102 }
2103 other => {
2104 panic!(
2105 "root must be a Horizontal Split after split_root_positioned, got {:?}",
2106 other
2107 );
2108 }
2109 }
2110 assert_eq!(manager.root().count_leaves(), 3);
2112 assert_ne!(
2115 dock_leaf, active_before,
2116 "dock must be a new sibling of the root, not the previously-active leaf"
2117 );
2118 }
2119
2120 #[test]
2125 fn test_next_split_unmaximizes_when_maximized() {
2126 let buffer_a = BufferId(0);
2127 let buffer_b = BufferId(1);
2128
2129 let mut manager = SplitManager::new(buffer_a);
2130 manager
2131 .split_active(SplitDirection::Vertical, buffer_b, 0.5)
2132 .expect("vertical split");
2133 let first_active = manager.active_split();
2134
2135 manager.maximize_split().expect("maximize");
2136 assert!(manager.is_maximized());
2137
2138 manager.next_split();
2139
2140 assert!(
2141 !manager.is_maximized(),
2142 "next_split must unmaximize so the newly-active split is visible"
2143 );
2144 assert_ne!(
2145 manager.active_split(),
2146 first_active,
2147 "next_split must actually move to a different split"
2148 );
2149 }
2150
2151 #[test]
2154 fn test_prev_split_unmaximizes_when_maximized() {
2155 let buffer_a = BufferId(0);
2156 let buffer_b = BufferId(1);
2157
2158 let mut manager = SplitManager::new(buffer_a);
2159 manager
2160 .split_active(SplitDirection::Vertical, buffer_b, 0.5)
2161 .expect("vertical split");
2162 let first_active = manager.active_split();
2163
2164 manager.maximize_split().expect("maximize");
2165 assert!(manager.is_maximized());
2166
2167 manager.prev_split();
2168
2169 assert!(
2170 !manager.is_maximized(),
2171 "prev_split must unmaximize so the newly-active split is visible"
2172 );
2173 assert_ne!(
2174 manager.active_split(),
2175 first_active,
2176 "prev_split must actually move to a different split"
2177 );
2178 }
2179
2180 #[test]
2181 fn test_apply_config_defaults_applies_scroll_offset() {
2182 let mut view_state = BufferViewState::new(80, 24);
2183 assert_eq!(
2184 view_state.viewport.scroll_offset, 3,
2185 "default scroll_offset should be 3"
2186 );
2187
2188 view_state.apply_config_defaults(
2189 true, true, false, false, None, vec![], 7, );
2197 assert_eq!(
2198 view_state.viewport.scroll_offset, 7,
2199 "apply_config_defaults should set scroll_offset on the viewport"
2200 );
2201 }
2202}