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 line_numbers_override: Option<bool>,
176
177 pub line_wrap_override: Option<bool>,
181
182 pub view_transform: Option<ViewTransformPayload>,
184
185 pub view_transform_stale: bool,
189
190 pub plugin_state: std::collections::HashMap<String, serde_json::Value>,
194
195 pub folds: FoldManager,
197}
198
199impl BufferViewState {
200 pub fn ensure_cursor_visible(&mut self, buffer: &mut Buffer, marker_list: &MarkerList) {
206 let hidden: Vec<(usize, usize)> = self
207 .folds
208 .resolved_ranges(buffer, marker_list)
209 .into_iter()
210 .map(|r| (r.start_byte, r.end_byte))
211 .collect();
212 let cursor = *self.cursors.primary();
213 self.viewport.ensure_visible(buffer, &cursor, &hidden);
214 }
215
216 pub fn new(width: u16, height: u16) -> Self {
218 Self {
219 cursors: Cursors::new(),
220 viewport: Viewport::new(width, height),
221 view_mode: ViewMode::Source,
222 compose_width: None,
223 compose_column_guides: None,
224 rulers: Vec::new(),
225 show_line_numbers: true,
226 highlight_current_line: true,
227 line_numbers_override: None,
228 line_wrap_override: None,
229 view_transform: None,
230 view_transform_stale: false,
231 plugin_state: std::collections::HashMap::new(),
232 folds: FoldManager::new(),
233 }
234 }
235
236 pub fn apply_config_defaults(
243 &mut self,
244 line_numbers: bool,
245 highlight_current_line: bool,
246 line_wrap: bool,
247 wrap_indent: bool,
248 wrap_column: Option<usize>,
249 rulers: Vec<usize>,
250 scroll_offset: usize,
251 ) {
252 self.show_line_numbers = line_numbers;
253 self.highlight_current_line = highlight_current_line;
254 self.viewport.line_wrap_enabled = line_wrap;
255 self.viewport.wrap_indent = wrap_indent;
256 self.viewport.wrap_column = wrap_column;
257 self.rulers = rulers;
258 self.viewport.set_scroll_offset(scroll_offset);
259 }
260
261 pub fn activate_page_view(&mut self, page_width: Option<usize>) {
267 self.view_mode = ViewMode::PageView;
268 self.show_line_numbers = false;
269 self.viewport.line_wrap_enabled = false;
270 if let Some(width) = page_width {
271 self.compose_width = Some(width as u16);
272 }
273 }
274}
275
276impl Clone for BufferViewState {
277 fn clone(&self) -> Self {
278 Self {
279 cursors: self.cursors.clone(),
280 viewport: self.viewport.clone(),
281 view_mode: self.view_mode.clone(),
282 compose_width: self.compose_width,
283 compose_column_guides: self.compose_column_guides.clone(),
284 rulers: self.rulers.clone(),
285 show_line_numbers: self.show_line_numbers,
286 highlight_current_line: self.highlight_current_line,
287 line_numbers_override: self.line_numbers_override,
288 line_wrap_override: self.line_wrap_override,
289 view_transform: self.view_transform.clone(),
290 view_transform_stale: self.view_transform_stale,
291 plugin_state: self.plugin_state.clone(),
292 folds: FoldManager::new(),
294 }
295 }
296}
297
298#[derive(Debug, Clone)]
310pub struct SplitViewState {
311 pub active_buffer: BufferId,
313
314 pub keyed_states: HashMap<BufferId, BufferViewState>,
316
317 pub open_buffers: Vec<TabTarget>,
323
324 pub tab_scroll_offset: usize,
326
327 pub layout: Option<Layout>,
330
331 pub layout_dirty: bool,
333
334 pub focus_history: Vec<TabTarget>,
338
339 pub sync_group: Option<u32>,
342
343 pub composite_view: Option<BufferId>,
348
349 pub suppress_chrome: bool,
352
353 pub hide_tilde: bool,
356
357 pub active_group_tab: Option<LeafId>,
361
362 pub focused_group_leaf: Option<LeafId>,
365}
366
367impl std::ops::Deref for SplitViewState {
368 type Target = BufferViewState;
369
370 fn deref(&self) -> &BufferViewState {
371 self.active_state()
372 }
373}
374
375impl std::ops::DerefMut for SplitViewState {
376 fn deref_mut(&mut self) -> &mut BufferViewState {
377 self.active_state_mut()
378 }
379}
380
381impl SplitViewState {
382 pub fn with_buffer(width: u16, height: u16, buffer_id: BufferId) -> Self {
384 let buf_state = BufferViewState::new(width, height);
385 let mut keyed_states = HashMap::new();
386 keyed_states.insert(buffer_id, buf_state);
387 Self {
388 active_buffer: buffer_id,
389 keyed_states,
390 open_buffers: vec![TabTarget::Buffer(buffer_id)],
391 tab_scroll_offset: 0,
392 layout: None,
393 layout_dirty: true,
394 focus_history: Vec::new(),
395 sync_group: None,
396 composite_view: None,
397 suppress_chrome: false,
398 hide_tilde: false,
399 active_group_tab: None,
400 focused_group_leaf: None,
401 }
402 }
403
404 pub fn active_state(&self) -> &BufferViewState {
406 self.keyed_states
407 .get(&self.active_buffer)
408 .expect("active_buffer must always have an entry in keyed_states")
409 }
410
411 pub fn active_state_mut(&mut self) -> &mut BufferViewState {
413 self.keyed_states
414 .get_mut(&self.active_buffer)
415 .expect("active_buffer must always have an entry in keyed_states")
416 }
417
418 pub fn switch_buffer(&mut self, new_buffer_id: BufferId) {
424 if new_buffer_id == self.active_buffer {
425 return;
426 }
427 if !self.keyed_states.contains_key(&new_buffer_id) {
429 let active = self.active_state();
430 let width = active.viewport.width;
431 let height = active.viewport.height;
432 self.keyed_states
433 .insert(new_buffer_id, BufferViewState::new(width, height));
434 }
435 self.active_buffer = new_buffer_id;
436 self.layout_dirty = true;
438 }
439
440 pub fn buffer_state(&self, buffer_id: BufferId) -> Option<&BufferViewState> {
442 self.keyed_states.get(&buffer_id)
443 }
444
445 pub fn buffer_state_mut(&mut self, buffer_id: BufferId) -> Option<&mut BufferViewState> {
447 self.keyed_states.get_mut(&buffer_id)
448 }
449
450 pub fn ensure_buffer_state(&mut self, buffer_id: BufferId) -> &mut BufferViewState {
453 let (width, height) = {
454 let active = self.active_state();
455 (active.viewport.width, active.viewport.height)
456 };
457 self.keyed_states
458 .entry(buffer_id)
459 .or_insert_with(|| BufferViewState::new(width, height))
460 }
461
462 pub fn remove_buffer_state(&mut self, buffer_id: BufferId) {
464 if buffer_id != self.active_buffer {
465 self.keyed_states.remove(&buffer_id);
466 }
467 }
468
469 pub fn invalidate_layout(&mut self) {
471 self.layout_dirty = true;
472 }
473
474 pub fn ensure_layout(
482 &mut self,
483 tokens: &[fresh_core::api::ViewTokenWire],
484 source_range: std::ops::Range<usize>,
485 tab_size: usize,
486 ) -> &Layout {
487 if self.layout.is_none() || self.layout_dirty {
488 self.layout = Some(Layout::from_tokens(tokens, source_range, tab_size));
489 self.layout_dirty = false;
490 }
491 self.layout.as_ref().unwrap()
492 }
493
494 pub fn get_layout(&self) -> Option<&Layout> {
496 if self.layout_dirty {
497 None
498 } else {
499 self.layout.as_ref()
500 }
501 }
502
503 pub fn add_buffer(&mut self, buffer_id: BufferId) {
505 if !self.has_buffer(buffer_id) {
506 self.open_buffers.push(TabTarget::Buffer(buffer_id));
507 }
508 }
509
510 pub fn remove_buffer(&mut self, buffer_id: BufferId) {
512 self.open_buffers
513 .retain(|t| *t != TabTarget::Buffer(buffer_id));
514 if buffer_id != self.active_buffer {
516 self.keyed_states.remove(&buffer_id);
517 }
518 }
519
520 pub fn has_buffer(&self, buffer_id: BufferId) -> bool {
522 self.open_buffers.contains(&TabTarget::Buffer(buffer_id))
523 }
524
525 pub fn add_group(&mut self, leaf_id: LeafId) {
527 if !self.has_group(leaf_id) {
528 self.open_buffers.push(TabTarget::Group(leaf_id));
529 }
530 }
531
532 pub fn remove_group(&mut self, leaf_id: LeafId) {
534 self.open_buffers
535 .retain(|t| *t != TabTarget::Group(leaf_id));
536 }
537
538 pub fn has_group(&self, leaf_id: LeafId) -> bool {
540 self.open_buffers.contains(&TabTarget::Group(leaf_id))
541 }
542
543 pub fn buffer_tab_ids(&self) -> impl Iterator<Item = BufferId> + '_ {
545 self.open_buffers.iter().filter_map(|t| t.as_buffer())
546 }
547
548 pub fn buffer_tab_ids_vec(&self) -> Vec<BufferId> {
551 self.buffer_tab_ids().collect()
552 }
553
554 pub fn buffer_tab_count(&self) -> usize {
556 self.open_buffers
557 .iter()
558 .filter(|t| matches!(t, TabTarget::Buffer(_)))
559 .count()
560 }
561
562 pub fn active_target(&self) -> TabTarget {
566 match self.active_group_tab {
567 Some(leaf_id) => TabTarget::Group(leaf_id),
568 None => TabTarget::Buffer(self.active_buffer),
569 }
570 }
571
572 pub fn set_active_buffer_tab(&mut self, buffer_id: BufferId) {
575 self.active_group_tab = None;
576 self.focused_group_leaf = None;
577 self.switch_buffer(buffer_id);
578 }
579
580 pub fn set_active_group_tab(&mut self, leaf_id: LeafId) {
582 self.active_group_tab = Some(leaf_id);
583 }
584
585 pub fn push_focus(&mut self, target: TabTarget) {
588 self.focus_history.retain(|t| *t != target);
589 self.focus_history.push(target);
590 if self.focus_history.len() > 50 {
591 self.focus_history.remove(0);
592 }
593 }
594
595 pub fn previous_tab(&self) -> Option<TabTarget> {
597 self.focus_history.last().copied()
598 }
599
600 pub fn remove_from_history(&mut self, buffer_id: BufferId) {
602 self.focus_history
603 .retain(|t| *t != TabTarget::Buffer(buffer_id));
604 }
605
606 pub fn remove_group_from_history(&mut self, leaf_id: LeafId) {
608 self.focus_history
609 .retain(|t| *t != TabTarget::Group(leaf_id));
610 }
611}
612
613impl SplitNode {
614 pub fn leaf(buffer_id: BufferId, split_id: SplitId) -> Self {
616 Self::Leaf {
617 buffer_id,
618 split_id: LeafId(split_id),
619 role: None,
620 }
621 }
622
623 pub fn leaf_with_role(buffer_id: BufferId, split_id: SplitId, role: SplitRole) -> Self {
625 Self::Leaf {
626 buffer_id,
627 split_id: LeafId(split_id),
628 role: Some(role),
629 }
630 }
631
632 pub fn role(&self) -> Option<SplitRole> {
634 match self {
635 Self::Leaf { role, .. } => *role,
636 _ => None,
637 }
638 }
639
640 pub fn set_role(&mut self, new_role: Option<SplitRole>) {
642 if let Self::Leaf { role, .. } = self {
643 *role = new_role;
644 }
645 }
646
647 pub fn split(
649 direction: SplitDirection,
650 first: SplitNode,
651 second: SplitNode,
652 ratio: f32,
653 split_id: SplitId,
654 ) -> Self {
655 SplitNode::Split {
656 direction,
657 first: Box::new(first),
658 second: Box::new(second),
659 ratio: ratio.clamp(0.1, 0.9), split_id: ContainerId(split_id),
661 fixed_first: None,
662 fixed_second: None,
663 }
664 }
665
666 pub fn id(&self) -> SplitId {
668 match self {
669 Self::Leaf { split_id, .. } => split_id.0,
670 Self::Split { split_id, .. } => split_id.0,
671 Self::Grouped { split_id, .. } => split_id.0,
672 }
673 }
674
675 pub fn buffer_id(&self) -> Option<BufferId> {
677 match self {
678 Self::Leaf { buffer_id, .. } => Some(*buffer_id),
679 Self::Split { .. } | Self::Grouped { .. } => None,
680 }
681 }
682
683 pub fn find_mut(&mut self, target_id: SplitId) -> Option<&mut Self> {
687 if self.id() == target_id {
688 return Some(self);
689 }
690
691 match self {
692 Self::Leaf { .. } => None,
693 Self::Split { first, second, .. } => first
694 .find_mut(target_id)
695 .or_else(|| second.find_mut(target_id)),
696 Self::Grouped { layout, .. } => layout.find_mut(target_id),
697 }
698 }
699
700 pub fn find(&self, target_id: SplitId) -> Option<&Self> {
704 if self.id() == target_id {
705 return Some(self);
706 }
707
708 match self {
709 Self::Leaf { .. } => None,
710 Self::Split { first, second, .. } => {
711 first.find(target_id).or_else(|| second.find(target_id))
712 }
713 Self::Grouped { layout, .. } => layout.find(target_id),
714 }
715 }
716
717 pub fn parent_container_of(&self, target_id: SplitId) -> Option<ContainerId> {
721 match self {
722 Self::Leaf { .. } => None,
723 Self::Split {
724 split_id,
725 first,
726 second,
727 ..
728 } => {
729 if first.id() == target_id || second.id() == target_id {
730 Some(*split_id)
731 } else {
732 first
733 .parent_container_of(target_id)
734 .or_else(|| second.parent_container_of(target_id))
735 }
736 }
737 Self::Grouped { layout, .. } => layout.parent_container_of(target_id),
738 }
739 }
740
741 pub fn grouped_ancestor_of(&self, target_id: SplitId) -> Option<LeafId> {
744 match self {
745 Self::Leaf { .. } => None,
746 Self::Split { first, second, .. } => first
747 .grouped_ancestor_of(target_id)
748 .or_else(|| second.grouped_ancestor_of(target_id)),
749 Self::Grouped {
750 split_id, layout, ..
751 } => {
752 if layout.find(target_id).is_some() {
753 Some(*split_id)
754 } else {
755 layout.grouped_ancestor_of(target_id)
756 }
757 }
758 }
759 }
760
761 pub fn find_grouped(&self, target: LeafId) -> Option<&Self> {
764 match self {
765 Self::Leaf { .. } => None,
766 Self::Split { first, second, .. } => first
767 .find_grouped(target)
768 .or_else(|| second.find_grouped(target)),
769 Self::Grouped {
770 split_id, layout, ..
771 } => {
772 if *split_id == target {
773 Some(self)
774 } else {
775 layout.find_grouped(target)
776 }
777 }
778 }
779 }
780
781 pub fn get_leaves_with_rects(&self, rect: Rect) -> Vec<(LeafId, BufferId, Rect)> {
787 match self {
788 Self::Leaf {
789 buffer_id,
790 split_id,
791 ..
792 } => {
793 vec![(*split_id, *buffer_id, rect)]
794 }
795 Self::Split {
796 direction,
797 first,
798 second,
799 ratio,
800 fixed_first,
801 fixed_second,
802 ..
803 } => {
804 let (first_rect, second_rect) =
805 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
806 let mut leaves = first.get_leaves_with_rects(first_rect);
807 leaves.extend(second.get_leaves_with_rects(second_rect));
808 leaves
809 }
810 Self::Grouped { layout, .. } => layout.get_leaves_with_rects(rect),
811 }
812 }
813
814 pub fn get_visible_leaves_with_rects<F>(
820 &self,
821 rect: Rect,
822 is_group_active: &F,
823 ) -> Vec<(LeafId, BufferId, Rect)>
824 where
825 F: Fn(LeafId) -> bool,
826 {
827 match self {
828 Self::Leaf {
829 buffer_id,
830 split_id,
831 ..
832 } => {
833 vec![(*split_id, *buffer_id, rect)]
834 }
835 Self::Split {
836 direction,
837 first,
838 second,
839 ratio,
840 fixed_first,
841 fixed_second,
842 ..
843 } => {
844 let (first_rect, second_rect) =
845 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
846 let mut leaves = first.get_visible_leaves_with_rects(first_rect, is_group_active);
847 leaves.extend(second.get_visible_leaves_with_rects(second_rect, is_group_active));
848 leaves
849 }
850 Self::Grouped {
851 split_id, layout, ..
852 } => {
853 if is_group_active(*split_id) {
854 layout.get_visible_leaves_with_rects(rect, is_group_active)
855 } else {
856 Vec::new()
857 }
858 }
859 }
860 }
861
862 pub fn get_separators(&self, rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
865 self.get_separators_with_ids(rect)
866 .into_iter()
867 .map(|(_, dir, x, y, len)| (dir, x, y, len))
868 .collect()
869 }
870
871 pub fn get_separators_with_ids(
874 &self,
875 rect: Rect,
876 ) -> Vec<(ContainerId, SplitDirection, u16, u16, u16)> {
877 match self {
878 Self::Leaf { .. } => vec![],
879 Self::Grouped { layout, .. } => layout.get_separators_with_ids(rect),
880 Self::Split {
881 direction,
882 first,
883 second,
884 ratio,
885 split_id,
886 fixed_first,
887 fixed_second,
888 } => {
889 let (first_rect, second_rect) =
890 split_rect_ext(rect, *direction, *ratio, *fixed_first, *fixed_second);
891 let mut separators = Vec::new();
892
893 match direction {
895 SplitDirection::Horizontal => {
896 separators.push((
899 *split_id,
900 SplitDirection::Horizontal,
901 rect.x,
902 first_rect.y + first_rect.height,
903 rect.width,
904 ));
905 }
906 SplitDirection::Vertical => {
907 separators.push((
910 *split_id,
911 SplitDirection::Vertical,
912 first_rect.x + first_rect.width,
913 rect.y,
914 rect.height,
915 ));
916 }
917 }
918
919 separators.extend(first.get_separators_with_ids(first_rect));
921 separators.extend(second.get_separators_with_ids(second_rect));
922 separators
923 }
924 }
925 }
926
927 pub fn all_split_ids(&self) -> Vec<SplitId> {
929 let mut ids = vec![self.id()];
930 match self {
931 Self::Leaf { .. } => ids,
932 Self::Split { first, second, .. } => {
933 ids.extend(first.all_split_ids());
934 ids.extend(second.all_split_ids());
935 ids
936 }
937 Self::Grouped { layout, .. } => {
938 ids.extend(layout.all_split_ids());
939 ids
940 }
941 }
942 }
943
944 pub fn leaf_split_ids(&self) -> Vec<LeafId> {
947 match self {
948 Self::Leaf { split_id, .. } => vec![*split_id],
949 Self::Split { first, second, .. } => {
950 let mut ids = first.leaf_split_ids();
951 ids.extend(second.leaf_split_ids());
952 ids
953 }
954 Self::Grouped { layout, .. } => layout.leaf_split_ids(),
955 }
956 }
957
958 pub fn count_leaves(&self) -> usize {
961 match self {
962 Self::Leaf { .. } => 1,
963 Self::Split { first, second, .. } => first.count_leaves() + second.count_leaves(),
964 Self::Grouped { layout, .. } => layout.count_leaves(),
965 }
966 }
967
968 pub fn collect_group_names(&self) -> HashMap<LeafId, String> {
971 let mut map = HashMap::new();
972 self.collect_group_names_into(&mut map);
973 map
974 }
975
976 fn collect_group_names_into(&self, map: &mut HashMap<LeafId, String>) {
977 match self {
978 Self::Leaf { .. } => {}
979 Self::Split { first, second, .. } => {
980 first.collect_group_names_into(map);
981 second.collect_group_names_into(map);
982 }
983 Self::Grouped {
984 split_id,
985 name,
986 layout,
987 ..
988 } => {
989 map.insert(*split_id, name.clone());
990 layout.collect_group_names_into(map);
991 }
992 }
993 }
994}
995
996#[cfg(test)]
999fn split_rect(rect: Rect, direction: SplitDirection, ratio: f32) -> (Rect, Rect) {
1000 split_rect_ext(rect, direction, ratio, None, None)
1001}
1002
1003fn split_rect_ext(
1004 rect: Rect,
1005 direction: SplitDirection,
1006 ratio: f32,
1007 fixed_first: Option<u16>,
1008 fixed_second: Option<u16>,
1009) -> (Rect, Rect) {
1010 match direction {
1011 SplitDirection::Horizontal => {
1012 let total_height = rect.height.saturating_sub(1); let first_height = if let Some(f) = fixed_first {
1015 f.min(total_height)
1016 } else if let Some(s) = fixed_second {
1017 total_height.saturating_sub(s.min(total_height))
1018 } else {
1019 (total_height as f32 * ratio).round() as u16
1020 };
1021 let second_height = total_height.saturating_sub(first_height);
1022
1023 let first = Rect {
1024 x: rect.x,
1025 y: rect.y,
1026 width: rect.width,
1027 height: first_height,
1028 };
1029
1030 let second = Rect {
1031 x: rect.x,
1032 y: rect.y + first_height + 1, width: rect.width,
1034 height: second_height,
1035 };
1036
1037 (first, second)
1038 }
1039 SplitDirection::Vertical => {
1040 let total_width = rect.width.saturating_sub(1); let first_width = if let Some(f) = fixed_first {
1043 f.min(total_width)
1044 } else if let Some(s) = fixed_second {
1045 total_width.saturating_sub(s.min(total_width))
1046 } else {
1047 (total_width as f32 * ratio).round() as u16
1048 };
1049 let second_width = total_width.saturating_sub(first_width);
1050
1051 let first = Rect {
1052 x: rect.x,
1053 y: rect.y,
1054 width: first_width,
1055 height: rect.height,
1056 };
1057
1058 let second = Rect {
1059 x: rect.x + first_width + 1, y: rect.y,
1061 width: second_width,
1062 height: rect.height,
1063 };
1064
1065 (first, second)
1066 }
1067 }
1068}
1069
1070#[derive(Debug)]
1072pub struct SplitManager {
1073 root: SplitNode,
1075
1076 active_split: LeafId,
1078
1079 next_split_id: usize,
1081
1082 maximized_split: Option<SplitId>,
1084
1085 labels: HashMap<SplitId, String>,
1087
1088 focus_history: Vec<LeafId>,
1096}
1097
1098const FOCUS_HISTORY_CAP: usize = 50;
1101
1102impl SplitManager {
1103 pub fn new(buffer_id: BufferId) -> Self {
1105 let split_id = SplitId(0);
1106 Self {
1107 root: SplitNode::leaf(buffer_id, split_id),
1108 active_split: LeafId(split_id),
1109 next_split_id: 1,
1110 maximized_split: None,
1111 labels: HashMap::new(),
1112 focus_history: vec![LeafId(split_id)],
1113 }
1114 }
1115
1116 pub fn root(&self) -> &SplitNode {
1118 &self.root
1119 }
1120
1121 pub fn allocate_split_id(&mut self) -> SplitId {
1123 let id = SplitId(self.next_split_id);
1124 self.next_split_id += 1;
1125 id
1126 }
1127
1128 pub fn replace_root(&mut self, new_root: SplitNode, new_active: LeafId) {
1132 self.root = new_root;
1133 self.active_split = new_active;
1134 self.focus_history.clear();
1137 self.focus_history.push(new_active);
1138 }
1139
1140 pub fn active_split(&self) -> LeafId {
1142 self.active_split
1143 }
1144
1145 pub fn set_active_split(&mut self, split_id: LeafId) -> bool {
1147 if self.root.find(split_id.into()).is_some() {
1149 self.active_split = split_id;
1150 self.focus_history.retain(|leaf| *leaf != split_id);
1154 self.focus_history.push(split_id);
1155 if self.focus_history.len() > FOCUS_HISTORY_CAP {
1156 self.focus_history.remove(0);
1157 }
1158 true
1159 } else {
1160 false
1161 }
1162 }
1163
1164 pub fn leaf_role(&self, split_id: LeafId) -> Option<SplitRole> {
1167 self.root.find(split_id.into()).and_then(|node| node.role())
1168 }
1169
1170 pub fn last_focused_where<F>(&self, mut predicate: F) -> Option<LeafId>
1180 where
1181 F: FnMut(LeafId) -> bool,
1182 {
1183 self.focus_history
1184 .iter()
1185 .rev()
1186 .copied()
1187 .find(|leaf| self.root.find((*leaf).into()).is_some() && predicate(*leaf))
1188 }
1189
1190 pub fn active_buffer_id(&self) -> Option<BufferId> {
1192 self.root
1193 .find(self.active_split.into())
1194 .and_then(|node| node.buffer_id())
1195 }
1196
1197 pub fn get_buffer_id(&self, split_id: SplitId) -> Option<BufferId> {
1199 self.root.find(split_id).and_then(|node| node.buffer_id())
1200 }
1201
1202 pub fn set_active_buffer_id(&mut self, new_buffer_id: BufferId) -> bool {
1204 if let Some(SplitNode::Leaf { buffer_id, .. }) =
1205 self.root.find_mut(self.active_split.into())
1206 {
1207 *buffer_id = new_buffer_id;
1208 return true;
1209 }
1210 false
1211 }
1212
1213 pub fn set_split_buffer(&mut self, leaf_id: LeafId, new_buffer_id: BufferId) {
1215 match self.root.find_mut(leaf_id.into()) {
1216 Some(SplitNode::Leaf { buffer_id, .. }) => {
1217 *buffer_id = new_buffer_id;
1218 }
1219 Some(SplitNode::Split { .. }) => {
1220 unreachable!("LeafId {:?} points to a container", leaf_id)
1221 }
1222 Some(SplitNode::Grouped { .. }) => {
1223 unreachable!("LeafId {:?} points to a Grouped node", leaf_id)
1224 }
1225 None => {
1226 unreachable!("LeafId {:?} not found in split tree", leaf_id)
1227 }
1228 }
1229 }
1230
1231 pub fn split_active(
1235 &mut self,
1236 direction: SplitDirection,
1237 new_buffer_id: BufferId,
1238 ratio: f32,
1239 ) -> Result<LeafId, String> {
1240 self.split_active_positioned(direction, new_buffer_id, ratio, false)
1241 }
1242
1243 pub fn split_active_before(
1246 &mut self,
1247 direction: SplitDirection,
1248 new_buffer_id: BufferId,
1249 ratio: f32,
1250 ) -> Result<LeafId, String> {
1251 self.split_active_positioned(direction, new_buffer_id, ratio, true)
1252 }
1253
1254 pub fn split_active_positioned(
1255 &mut self,
1256 direction: SplitDirection,
1257 new_buffer_id: BufferId,
1258 ratio: f32,
1259 before: bool,
1260 ) -> Result<LeafId, String> {
1261 let active_id: SplitId = self.active_split.into();
1262
1263 let result =
1265 self.replace_split_with_split(active_id, direction, new_buffer_id, ratio, before);
1266
1267 if let Ok(new_split_id) = &result {
1268 self.active_split = *new_split_id;
1270 }
1271 result
1272 }
1273
1274 pub fn split_root_positioned(
1283 &mut self,
1284 direction: SplitDirection,
1285 new_buffer_id: BufferId,
1286 ratio: f32,
1287 before: bool,
1288 ) -> Result<LeafId, String> {
1289 let root_id = self.root.id();
1290 let result =
1291 self.replace_split_with_split(root_id, direction, new_buffer_id, ratio, before);
1292 if let Ok(new_split_id) = &result {
1293 self.active_split = *new_split_id;
1294 }
1295 result
1296 }
1297
1298 fn replace_split_with_split(
1301 &mut self,
1302 target_id: SplitId,
1303 direction: SplitDirection,
1304 new_buffer_id: BufferId,
1305 ratio: f32,
1306 before: bool,
1307 ) -> Result<LeafId, String> {
1308 let temp_id = self.allocate_split_id();
1310 let new_split_id = self.allocate_split_id();
1311 let new_leaf_id = self.allocate_split_id();
1312
1313 if self.root.id() == target_id {
1315 let old_root =
1316 std::mem::replace(&mut self.root, SplitNode::leaf(new_buffer_id, temp_id));
1317 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1318
1319 let (first, second) = if before {
1320 (new_leaf, old_root)
1321 } else {
1322 (old_root, new_leaf)
1323 };
1324
1325 self.root = SplitNode::split(direction, first, second, ratio, new_split_id);
1326
1327 return Ok(LeafId(new_leaf_id));
1328 }
1329
1330 if let Some(node) = self.root.find_mut(target_id) {
1332 let old_node = std::mem::replace(node, SplitNode::leaf(new_buffer_id, temp_id));
1333 let new_leaf = SplitNode::leaf(new_buffer_id, new_leaf_id);
1334
1335 let (first, second) = if before {
1336 (new_leaf, old_node)
1337 } else {
1338 (old_node, new_leaf)
1339 };
1340
1341 *node = SplitNode::split(direction, first, second, ratio, new_split_id);
1342
1343 Ok(LeafId(new_leaf_id))
1344 } else {
1345 Err(format!("Split {:?} not found", target_id))
1346 }
1347 }
1348
1349 pub fn close_split(&mut self, split_id: LeafId) -> Result<(), String> {
1351 if self.root.count_leaves() <= 1 {
1353 return Err("Cannot close the last split".to_string());
1354 }
1355
1356 if self.root.id() == split_id.into() && self.root.buffer_id().is_some() {
1358 return Err("Cannot close the only split".to_string());
1359 }
1360
1361 if self.maximized_split == Some(split_id.into()) {
1363 self.maximized_split = None;
1364 }
1365
1366 let removed_ids: Vec<SplitId> = self
1368 .root
1369 .find(split_id.into())
1370 .map(|node| node.all_split_ids())
1371 .unwrap_or_default();
1372
1373 let result = self.remove_split_node(split_id.into());
1376
1377 if result.is_ok() {
1378 for id in &removed_ids {
1380 self.labels.remove(id);
1381 }
1382
1383 if self.active_split == split_id {
1385 let leaf_ids = self.root.leaf_split_ids();
1386 if let Some(&first_leaf) = leaf_ids.first() {
1387 self.active_split = first_leaf;
1388 }
1389 }
1390 }
1391
1392 result
1393 }
1394
1395 fn remove_split_node(&mut self, target_id: SplitId) -> Result<(), String> {
1397 if self.root.id() == target_id {
1399 if let SplitNode::Split { first, .. } = &self.root {
1400 self.root = (**first).clone();
1403 return Ok(());
1404 }
1405 }
1406
1407 Self::remove_child_static(&mut self.root, target_id)
1409 }
1410
1411 fn remove_child_static(node: &mut SplitNode, target_id: SplitId) -> Result<(), String> {
1413 match node {
1414 SplitNode::Leaf { .. } => Err("Target not found".to_string()),
1415 SplitNode::Grouped { layout, .. } => Self::remove_child_static(layout, target_id),
1416 SplitNode::Split { first, second, .. } => {
1417 if first.id() == target_id {
1419 *node = (**second).clone();
1421 Ok(())
1422 } else if second.id() == target_id {
1423 *node = (**first).clone();
1425 Ok(())
1426 } else {
1427 Self::remove_child_static(first, target_id)
1429 .or_else(|_| Self::remove_child_static(second, target_id))
1430 }
1431 }
1432 }
1433 }
1434
1435 pub fn remove_grouped(&mut self, target: LeafId) -> Result<(), String> {
1443 let target_id: SplitId = target.into();
1444 if self.root.id() == target_id {
1445 return Err("Cannot remove root Grouped node".to_string());
1446 }
1447 Self::remove_child_static(&mut self.root, target_id)
1448 }
1449
1450 pub fn adjust_ratio(&mut self, container_id: ContainerId, delta: f32) {
1452 match self.root.find_mut(container_id.into()) {
1453 Some(SplitNode::Split { ratio, .. }) => {
1454 *ratio = (*ratio + delta).clamp(0.1, 0.9);
1455 }
1456 Some(SplitNode::Leaf { .. }) => {
1457 unreachable!("ContainerId {:?} points to a leaf", container_id)
1458 }
1459 Some(SplitNode::Grouped { .. }) => {
1460 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1461 }
1462 None => {
1463 unreachable!("ContainerId {:?} not found in split tree", container_id)
1464 }
1465 }
1466 }
1467
1468 pub fn parent_container_of(&self, leaf_id: LeafId) -> Option<ContainerId> {
1470 self.root.parent_container_of(leaf_id.into())
1471 }
1472
1473 pub fn get_visible_buffers(&self, viewport_rect: Rect) -> Vec<(LeafId, BufferId, Rect)> {
1475 if let Some(maximized_id) = self.maximized_split {
1477 if let Some(SplitNode::Leaf {
1478 buffer_id,
1479 split_id,
1480 ..
1481 }) = self.root.find(maximized_id)
1482 {
1483 return vec![(*split_id, *buffer_id, viewport_rect)];
1484 }
1485 }
1487 self.root.get_leaves_with_rects(viewport_rect)
1488 }
1489
1490 pub fn get_separators(&self, viewport_rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
1493 if self.maximized_split.is_some() {
1495 return vec![];
1496 }
1497 self.root.get_separators(viewport_rect)
1498 }
1499
1500 pub fn get_separators_with_ids(
1503 &self,
1504 viewport_rect: Rect,
1505 ) -> Vec<(ContainerId, SplitDirection, u16, u16, u16)> {
1506 if self.maximized_split.is_some() {
1508 return vec![];
1509 }
1510 self.root.get_separators_with_ids(viewport_rect)
1511 }
1512
1513 pub fn get_ratio(&self, split_id: SplitId) -> Option<f32> {
1515 if let Some(SplitNode::Split { ratio, .. }) = self.root.find(split_id) {
1516 Some(*ratio)
1517 } else {
1518 None
1519 }
1520 }
1521
1522 pub fn set_ratio(&mut self, container_id: ContainerId, new_ratio: f32) {
1524 match self.root.find_mut(container_id.into()) {
1525 Some(SplitNode::Split { ratio, .. }) => {
1526 *ratio = new_ratio.clamp(0.1, 0.9);
1527 }
1528 Some(SplitNode::Leaf { .. }) => {
1529 unreachable!("ContainerId {:?} points to a leaf", container_id)
1530 }
1531 Some(SplitNode::Grouped { .. }) => {
1532 unreachable!("ContainerId {:?} points to a Grouped node", container_id)
1533 }
1534 None => {
1535 unreachable!("ContainerId {:?} not found in split tree", container_id)
1536 }
1537 }
1538 }
1539
1540 pub fn set_fixed_size(
1543 &mut self,
1544 container_id: ContainerId,
1545 first: Option<u16>,
1546 second: Option<u16>,
1547 ) {
1548 if let Some(SplitNode::Split {
1549 fixed_first,
1550 fixed_second,
1551 ..
1552 }) = self.root.find_mut(container_id.into())
1553 {
1554 *fixed_first = first;
1555 *fixed_second = second;
1556 }
1557 }
1558
1559 pub fn distribute_splits_evenly(&mut self) {
1562 Self::distribute_node_evenly(&mut self.root);
1563 }
1564
1565 fn distribute_node_evenly(node: &mut SplitNode) -> usize {
1568 match node {
1569 SplitNode::Leaf { .. } => 1,
1570 SplitNode::Grouped { layout, .. } => Self::distribute_node_evenly(layout),
1571 SplitNode::Split {
1572 first,
1573 second,
1574 ratio,
1575 ..
1576 } => {
1577 let first_leaves = Self::distribute_node_evenly(first);
1578 let second_leaves = Self::distribute_node_evenly(second);
1579 let total_leaves = first_leaves + second_leaves;
1580
1581 *ratio = (first_leaves as f32 / total_leaves as f32).clamp(0.1, 0.9);
1584
1585 total_leaves
1586 }
1587 }
1588 }
1589
1590 pub fn next_split(&mut self) {
1592 self.maximized_split = None;
1597 let leaf_ids = self.root.leaf_split_ids();
1598 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1599 let next_pos = (pos + 1) % leaf_ids.len();
1600 self.active_split = leaf_ids[next_pos];
1601 }
1602 }
1603
1604 pub fn prev_split(&mut self) {
1606 self.maximized_split = None;
1608 let leaf_ids = self.root.leaf_split_ids();
1609 if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
1610 let prev_pos = if pos == 0 { leaf_ids.len() } else { pos } - 1;
1611 self.active_split = leaf_ids[prev_pos];
1612 }
1613 }
1614
1615 pub fn splits_for_buffer(&self, target_buffer_id: BufferId) -> Vec<LeafId> {
1617 self.root
1618 .get_leaves_with_rects(Rect {
1619 x: 0,
1620 y: 0,
1621 width: 1,
1622 height: 1,
1623 })
1624 .into_iter()
1625 .filter(|(_, buffer_id, _)| *buffer_id == target_buffer_id)
1626 .map(|(split_id, _, _)| split_id)
1627 .collect()
1628 }
1629
1630 pub fn buffer_for_split(&self, target_split_id: LeafId) -> Option<BufferId> {
1632 self.root
1633 .get_leaves_with_rects(Rect {
1634 x: 0,
1635 y: 0,
1636 width: 1,
1637 height: 1,
1638 })
1639 .into_iter()
1640 .find(|(split_id, _, _)| *split_id == target_split_id)
1641 .map(|(_, buffer_id, _)| buffer_id)
1642 }
1643
1644 pub fn maximize_split(&mut self) -> Result<(), String> {
1647 if self.root.count_leaves() <= 1 {
1649 return Err("Cannot maximize: only one split exists".to_string());
1650 }
1651
1652 if self.maximized_split.is_some() {
1654 return Err("A split is already maximized".to_string());
1655 }
1656
1657 self.maximized_split = Some(self.active_split.into());
1659 Ok(())
1660 }
1661
1662 pub fn unmaximize_split(&mut self) -> Result<(), String> {
1665 if self.maximized_split.is_none() {
1666 return Err("No split is maximized".to_string());
1667 }
1668
1669 self.maximized_split = None;
1670 Ok(())
1671 }
1672
1673 pub fn is_maximized(&self) -> bool {
1675 self.maximized_split.is_some()
1676 }
1677
1678 pub fn maximized_split(&self) -> Option<SplitId> {
1680 self.maximized_split
1681 }
1682
1683 pub fn toggle_maximize(&mut self) -> Result<bool, String> {
1687 if self.is_maximized() {
1688 self.unmaximize_split()?;
1689 Ok(false)
1690 } else {
1691 self.maximize_split()?;
1692 Ok(true)
1693 }
1694 }
1695
1696 pub fn toggle_maximize_for(&mut self, target: LeafId) -> Result<bool, String> {
1704 if self.is_maximized() {
1705 self.unmaximize_split()?;
1706 Ok(false)
1707 } else {
1708 if self.root.count_leaves() <= 1 {
1709 return Err("Cannot maximize: only one split exists".to_string());
1710 }
1711 if self.root.find(target.into()).is_none() {
1712 return Err("Cannot maximize: split not found".to_string());
1713 }
1714 self.maximized_split = Some(target.into());
1715 Ok(true)
1716 }
1717 }
1718
1719 pub fn get_splits_in_group(
1721 &self,
1722 group_id: u32,
1723 view_states: &std::collections::HashMap<LeafId, SplitViewState>,
1724 ) -> Vec<LeafId> {
1725 self.root
1726 .leaf_split_ids()
1727 .into_iter()
1728 .filter(|id| {
1729 view_states
1730 .get(id)
1731 .and_then(|vs| vs.sync_group)
1732 .is_some_and(|g| g == group_id)
1733 })
1734 .collect()
1735 }
1736
1737 pub fn set_label(&mut self, split_id: LeafId, label: String) {
1741 self.labels.insert(split_id.into(), label);
1742 }
1743
1744 pub fn clear_label(&mut self, split_id: SplitId) {
1746 self.labels.remove(&split_id);
1747 }
1748
1749 pub fn get_label(&self, split_id: SplitId) -> Option<&str> {
1751 self.labels.get(&split_id).map(|s| s.as_str())
1752 }
1753
1754 pub fn labels(&self) -> &HashMap<SplitId, String> {
1756 &self.labels
1757 }
1758
1759 pub fn set_leaf_role(&mut self, split_id: LeafId, new_role: Option<SplitRole>) {
1763 if let Some(node) = self.root.find_mut(split_id.into()) {
1764 node.set_role(new_role);
1765 }
1766 }
1767
1768 pub fn find_leaf_by_role(&self, target: SplitRole) -> Option<LeafId> {
1770 fn walk(node: &SplitNode, target: SplitRole) -> Option<LeafId> {
1771 match node {
1772 SplitNode::Leaf {
1773 role: Some(r),
1774 split_id,
1775 ..
1776 } if *r == target => Some(*split_id),
1777 SplitNode::Leaf { .. } => None,
1778 SplitNode::Split { first, second, .. } => {
1779 walk(first, target).or_else(|| walk(second, target))
1780 }
1781 SplitNode::Grouped { layout, .. } => walk(layout, target),
1782 }
1783 }
1784 walk(&self.root, target)
1785 }
1786
1787 pub fn clear_role(&mut self, target: SplitRole) -> Option<LeafId> {
1791 let leaf = self.find_leaf_by_role(target)?;
1792 self.set_leaf_role(leaf, None);
1793 Some(leaf)
1794 }
1795
1796 pub fn find_split_by_label(&self, label: &str) -> Option<LeafId> {
1798 self.root
1799 .leaf_split_ids()
1800 .into_iter()
1801 .find(|id| self.labels.get(&(*id).into()).is_some_and(|l| l == label))
1802 }
1803
1804 pub fn find_unlabeled_leaf(&self) -> Option<LeafId> {
1806 self.root
1807 .leaf_split_ids()
1808 .into_iter()
1809 .find(|id| !self.labels.contains_key(&(*id).into()))
1810 }
1811}
1812
1813#[cfg(test)]
1814mod tests {
1815 use super::*;
1816
1817 #[test]
1818 fn test_create_split_manager() {
1819 let buffer_id = BufferId(0);
1820 let manager = SplitManager::new(buffer_id);
1821
1822 assert_eq!(manager.active_buffer_id(), Some(buffer_id));
1823 assert_eq!(manager.root().count_leaves(), 1);
1824 }
1825
1826 #[test]
1827 fn test_horizontal_split() {
1828 let buffer_a = BufferId(0);
1829 let buffer_b = BufferId(1);
1830
1831 let mut manager = SplitManager::new(buffer_a);
1832 let result = manager.split_active(SplitDirection::Horizontal, buffer_b, 0.5);
1833
1834 assert!(result.is_ok());
1835 assert_eq!(manager.root().count_leaves(), 2);
1836 }
1837
1838 #[test]
1839 fn test_vertical_split() {
1840 let buffer_a = BufferId(0);
1841 let buffer_b = BufferId(1);
1842
1843 let mut manager = SplitManager::new(buffer_a);
1844 let result = manager.split_active(SplitDirection::Vertical, buffer_b, 0.5);
1845
1846 assert!(result.is_ok());
1847 assert_eq!(manager.root().count_leaves(), 2);
1848 }
1849
1850 #[test]
1851 fn test_nested_splits() {
1852 let buffer_a = BufferId(0);
1853 let buffer_b = BufferId(1);
1854 let buffer_c = BufferId(2);
1855
1856 let mut manager = SplitManager::new(buffer_a);
1857
1858 manager
1860 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1861 .unwrap();
1862
1863 manager
1865 .split_active(SplitDirection::Vertical, buffer_c, 0.5)
1866 .unwrap();
1867
1868 assert_eq!(manager.root().count_leaves(), 3);
1869 }
1870
1871 #[test]
1872 fn test_close_split() {
1873 let buffer_a = BufferId(0);
1874 let buffer_b = BufferId(1);
1875
1876 let mut manager = SplitManager::new(buffer_a);
1877 let new_split = manager
1878 .split_active(SplitDirection::Horizontal, buffer_b, 0.5)
1879 .unwrap();
1880
1881 assert_eq!(manager.root().count_leaves(), 2);
1882
1883 let result = manager.close_split(new_split);
1885 assert!(result.is_ok());
1886 assert_eq!(manager.root().count_leaves(), 1);
1887 }
1888
1889 #[test]
1890 fn test_cannot_close_last_split() {
1891 let buffer_a = BufferId(0);
1892 let mut manager = SplitManager::new(buffer_a);
1893
1894 let result = manager.close_split(manager.active_split());
1895 assert!(result.is_err());
1896 }
1897
1898 #[test]
1899 fn test_split_rect_horizontal() {
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::Horizontal, 0.5);
1908
1909 assert_eq!(first.height, 50);
1911 assert_eq!(second.height, 49);
1912 assert_eq!(first.width, 100);
1913 assert_eq!(second.width, 100);
1914 assert_eq!(first.y, 0);
1915 assert_eq!(second.y, 51); }
1917
1918 #[test]
1919 fn test_split_rect_vertical() {
1920 let rect = Rect {
1921 x: 0,
1922 y: 0,
1923 width: 100,
1924 height: 100,
1925 };
1926
1927 let (first, second) = split_rect(rect, SplitDirection::Vertical, 0.5);
1928
1929 assert_eq!(first.width, 50);
1931 assert_eq!(second.width, 49);
1932 assert_eq!(first.height, 100);
1933 assert_eq!(second.height, 100);
1934 assert_eq!(first.x, 0);
1935 assert_eq!(second.x, 51); }
1937
1938 #[test]
1941 fn test_set_and_get_label() {
1942 let mut manager = SplitManager::new(BufferId(0));
1943 let split = manager.active_split();
1944
1945 assert_eq!(manager.get_label(split.into()), None);
1946
1947 manager.set_label(split, "sidebar".to_string());
1948 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
1949 }
1950
1951 #[test]
1952 fn test_clear_label() {
1953 let mut manager = SplitManager::new(BufferId(0));
1954 let split = manager.active_split();
1955
1956 manager.set_label(split, "sidebar".to_string());
1957 assert!(manager.get_label(split.into()).is_some());
1958
1959 manager.clear_label(split.into());
1960 assert_eq!(manager.get_label(split.into()), None);
1961 }
1962
1963 #[test]
1964 fn test_find_split_by_label() {
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 manager.set_label(first_split, "sidebar".to_string());
1973
1974 assert_eq!(manager.find_split_by_label("sidebar"), Some(first_split));
1975 assert_eq!(manager.find_split_by_label("terminal"), None);
1976
1977 assert_ne!(manager.find_split_by_label("sidebar"), Some(second_split));
1979 }
1980
1981 #[test]
1982 fn test_find_unlabeled_leaf() {
1983 let mut manager = SplitManager::new(BufferId(0));
1984 let first_split = manager.active_split();
1985
1986 let second_split = manager
1987 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
1988 .unwrap();
1989
1990 assert!(manager.find_unlabeled_leaf().is_some());
1992
1993 manager.set_label(first_split, "sidebar".to_string());
1995 assert_eq!(manager.find_unlabeled_leaf(), Some(second_split));
1996
1997 manager.set_label(second_split, "terminal".to_string());
1999 assert_eq!(manager.find_unlabeled_leaf(), None);
2000 }
2001
2002 #[test]
2003 fn test_close_split_cleans_up_label() {
2004 let mut manager = SplitManager::new(BufferId(0));
2005 let _first_split = manager.active_split();
2006
2007 let second_split = manager
2008 .split_active(SplitDirection::Vertical, BufferId(1), 0.5)
2009 .unwrap();
2010
2011 manager.set_label(second_split, "sidebar".to_string());
2012 assert_eq!(manager.find_split_by_label("sidebar"), Some(second_split));
2013
2014 manager.close_split(second_split).unwrap();
2015
2016 assert_eq!(manager.find_split_by_label("sidebar"), None);
2018 assert_eq!(manager.get_label(second_split.into()), None);
2019 }
2020
2021 #[test]
2022 fn test_label_overwrite() {
2023 let mut manager = SplitManager::new(BufferId(0));
2024 let split = manager.active_split();
2025
2026 manager.set_label(split, "sidebar".to_string());
2027 assert_eq!(manager.get_label(split.into()), Some("sidebar"));
2028
2029 manager.set_label(split, "terminal".to_string());
2030 assert_eq!(manager.get_label(split.into()), Some("terminal"));
2031 assert_eq!(manager.find_split_by_label("sidebar"), None);
2032 assert_eq!(manager.find_split_by_label("terminal"), Some(split));
2033 }
2034
2035 #[test]
2036 fn test_find_unlabeled_leaf_single_split_no_label() {
2037 let manager = SplitManager::new(BufferId(0));
2038 assert_eq!(manager.find_unlabeled_leaf(), Some(manager.active_split()));
2040 }
2041
2042 #[test]
2043 fn test_find_unlabeled_leaf_single_split_labeled() {
2044 let mut manager = SplitManager::new(BufferId(0));
2045 let split = manager.active_split();
2046 manager.set_label(split, "only".to_string());
2047 assert_eq!(manager.find_unlabeled_leaf(), None);
2049 }
2050
2051 #[test]
2056 fn test_split_root_positioned_with_existing_vertical_split() {
2057 let left = BufferId(0);
2059 let right = BufferId(1);
2060 let dock = BufferId(2);
2061 let mut manager = SplitManager::new(left);
2062 manager
2063 .split_active(SplitDirection::Vertical, right, 0.5)
2064 .expect("vertical split");
2065 assert!(matches!(
2067 manager.root(),
2068 SplitNode::Split {
2069 direction: SplitDirection::Vertical,
2070 ..
2071 }
2072 ));
2073 assert_eq!(manager.root().count_leaves(), 2);
2074 let active_before = manager.active_split();
2078
2079 let dock_leaf = manager
2081 .split_root_positioned(SplitDirection::Horizontal, dock, 0.7, false)
2082 .expect("split_root_positioned");
2083
2084 match manager.root() {
2089 SplitNode::Split {
2090 direction: SplitDirection::Horizontal,
2091 first,
2092 second,
2093 ..
2094 } => {
2095 assert!(
2096 matches!(
2097 first.as_ref(),
2098 SplitNode::Split {
2099 direction: SplitDirection::Vertical,
2100 ..
2101 }
2102 ),
2103 "first child of new root must be the original Vertical split, got {:?}",
2104 first
2105 );
2106 match second.as_ref() {
2107 SplitNode::Leaf {
2108 buffer_id,
2109 split_id,
2110 ..
2111 } => {
2112 assert_eq!(*buffer_id, dock, "second child must be the dock leaf");
2113 assert_eq!(
2114 *split_id, dock_leaf,
2115 "split_root_positioned must return the new leaf id"
2116 );
2117 }
2118 other => panic!("expected dock leaf as second child, got {:?}", other),
2119 }
2120 }
2121 other => {
2122 panic!(
2123 "root must be a Horizontal Split after split_root_positioned, got {:?}",
2124 other
2125 );
2126 }
2127 }
2128 assert_eq!(manager.root().count_leaves(), 3);
2130 assert_ne!(
2133 dock_leaf, active_before,
2134 "dock must be a new sibling of the root, not the previously-active leaf"
2135 );
2136 }
2137
2138 #[test]
2143 fn test_next_split_unmaximizes_when_maximized() {
2144 let buffer_a = BufferId(0);
2145 let buffer_b = BufferId(1);
2146
2147 let mut manager = SplitManager::new(buffer_a);
2148 manager
2149 .split_active(SplitDirection::Vertical, buffer_b, 0.5)
2150 .expect("vertical split");
2151 let first_active = manager.active_split();
2152
2153 manager.maximize_split().expect("maximize");
2154 assert!(manager.is_maximized());
2155
2156 manager.next_split();
2157
2158 assert!(
2159 !manager.is_maximized(),
2160 "next_split must unmaximize so the newly-active split is visible"
2161 );
2162 assert_ne!(
2163 manager.active_split(),
2164 first_active,
2165 "next_split must actually move to a different split"
2166 );
2167 }
2168
2169 #[test]
2172 fn test_prev_split_unmaximizes_when_maximized() {
2173 let buffer_a = BufferId(0);
2174 let buffer_b = BufferId(1);
2175
2176 let mut manager = SplitManager::new(buffer_a);
2177 manager
2178 .split_active(SplitDirection::Vertical, buffer_b, 0.5)
2179 .expect("vertical split");
2180 let first_active = manager.active_split();
2181
2182 manager.maximize_split().expect("maximize");
2183 assert!(manager.is_maximized());
2184
2185 manager.prev_split();
2186
2187 assert!(
2188 !manager.is_maximized(),
2189 "prev_split must unmaximize so the newly-active split is visible"
2190 );
2191 assert_ne!(
2192 manager.active_split(),
2193 first_active,
2194 "prev_split must actually move to a different split"
2195 );
2196 }
2197
2198 #[test]
2199 fn test_apply_config_defaults_applies_scroll_offset() {
2200 let mut view_state = BufferViewState::new(80, 24);
2201 assert_eq!(
2202 view_state.viewport.scroll_offset, 3,
2203 "default scroll_offset should be 3"
2204 );
2205
2206 view_state.apply_config_defaults(
2207 true, true, false, false, None, vec![], 7, );
2215 assert_eq!(
2216 view_state.viewport.scroll_offset, 7,
2217 "apply_config_defaults should set scroll_offset on the viewport"
2218 );
2219 }
2220}