use crate::model::cursor::Cursors;
use crate::model::event::{BufferId, SplitDirection, SplitId};
use crate::view::ui::view_pipeline::Layout;
use crate::view::viewport::Viewport;
use crate::{services::plugins::api::ViewTransformPayload, state::ViewMode};
use ratatui::layout::Rect;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SplitNode {
Leaf {
buffer_id: BufferId,
split_id: SplitId,
},
Split {
direction: SplitDirection,
first: Box<SplitNode>,
second: Box<SplitNode>,
ratio: f32,
split_id: SplitId,
},
}
#[derive(Debug, Clone)]
pub struct SplitViewState {
pub cursors: Cursors,
pub viewport: Viewport,
pub open_buffers: Vec<BufferId>,
pub tab_scroll_offset: usize,
pub view_mode: ViewMode,
pub compose_width: Option<u16>,
pub compose_column_guides: Option<Vec<u16>>,
pub compose_prev_line_numbers: Option<bool>,
pub view_transform: Option<ViewTransformPayload>,
pub layout: Option<Layout>,
pub layout_dirty: bool,
pub previous_buffer: Option<BufferId>,
}
impl SplitViewState {
pub fn new(width: u16, height: u16) -> Self {
Self {
cursors: Cursors::new(),
viewport: Viewport::new(width, height),
open_buffers: Vec::new(),
tab_scroll_offset: 0,
view_mode: ViewMode::Source,
compose_width: None,
compose_column_guides: None,
compose_prev_line_numbers: None,
view_transform: None,
layout: None,
layout_dirty: true, previous_buffer: None,
}
}
pub fn with_buffer(width: u16, height: u16, buffer_id: BufferId) -> Self {
Self {
cursors: Cursors::new(),
viewport: Viewport::new(width, height),
open_buffers: vec![buffer_id],
tab_scroll_offset: 0,
view_mode: ViewMode::Source,
compose_width: None,
compose_column_guides: None,
compose_prev_line_numbers: None,
view_transform: None,
layout: None,
layout_dirty: true, previous_buffer: None,
}
}
pub fn invalidate_layout(&mut self) {
self.layout_dirty = true;
}
pub fn ensure_layout(
&mut self,
tokens: &[crate::services::plugins::api::ViewTokenWire],
source_range: std::ops::Range<usize>,
) -> &Layout {
if self.layout.is_none() || self.layout_dirty {
self.layout = Some(Layout::from_tokens(tokens, source_range));
self.layout_dirty = false;
}
self.layout.as_ref().unwrap()
}
pub fn get_layout(&self) -> Option<&Layout> {
if self.layout_dirty {
None
} else {
self.layout.as_ref()
}
}
pub fn add_buffer(&mut self, buffer_id: BufferId) {
if !self.open_buffers.contains(&buffer_id) {
self.open_buffers.push(buffer_id);
}
}
pub fn remove_buffer(&mut self, buffer_id: BufferId) {
self.open_buffers.retain(|&id| id != buffer_id);
}
pub fn has_buffer(&self, buffer_id: BufferId) -> bool {
self.open_buffers.contains(&buffer_id)
}
}
impl SplitNode {
pub fn leaf(buffer_id: BufferId, split_id: SplitId) -> Self {
SplitNode::Leaf {
buffer_id,
split_id,
}
}
pub fn split(
direction: SplitDirection,
first: SplitNode,
second: SplitNode,
ratio: f32,
split_id: SplitId,
) -> Self {
SplitNode::Split {
direction,
first: Box::new(first),
second: Box::new(second),
ratio: ratio.clamp(0.1, 0.9), split_id,
}
}
pub fn id(&self) -> SplitId {
match self {
SplitNode::Leaf { split_id, .. } => *split_id,
SplitNode::Split { split_id, .. } => *split_id,
}
}
pub fn buffer_id(&self) -> Option<BufferId> {
match self {
SplitNode::Leaf { buffer_id, .. } => Some(*buffer_id),
SplitNode::Split { .. } => None,
}
}
pub fn find_mut(&mut self, target_id: SplitId) -> Option<&mut SplitNode> {
if self.id() == target_id {
return Some(self);
}
match self {
SplitNode::Leaf { .. } => None,
SplitNode::Split { first, second, .. } => first
.find_mut(target_id)
.or_else(|| second.find_mut(target_id)),
}
}
pub fn find(&self, target_id: SplitId) -> Option<&SplitNode> {
if self.id() == target_id {
return Some(self);
}
match self {
SplitNode::Leaf { .. } => None,
SplitNode::Split { first, second, .. } => {
first.find(target_id).or_else(|| second.find(target_id))
}
}
}
pub fn get_leaves_with_rects(&self, rect: Rect) -> Vec<(SplitId, BufferId, Rect)> {
match self {
SplitNode::Leaf {
buffer_id,
split_id,
} => {
vec![(*split_id, *buffer_id, rect)]
}
SplitNode::Split {
direction,
first,
second,
ratio,
..
} => {
let (first_rect, second_rect) = split_rect(rect, *direction, *ratio);
let mut leaves = first.get_leaves_with_rects(first_rect);
leaves.extend(second.get_leaves_with_rects(second_rect));
leaves
}
}
}
pub fn get_separators(&self, rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
self.get_separators_with_ids(rect)
.into_iter()
.map(|(_, dir, x, y, len)| (dir, x, y, len))
.collect()
}
pub fn get_separators_with_ids(
&self,
rect: Rect,
) -> Vec<(SplitId, SplitDirection, u16, u16, u16)> {
match self {
SplitNode::Leaf { .. } => vec![],
SplitNode::Split {
direction,
first,
second,
ratio,
split_id,
} => {
let (first_rect, second_rect) = split_rect(rect, *direction, *ratio);
let mut separators = Vec::new();
match direction {
SplitDirection::Horizontal => {
separators.push((
*split_id,
SplitDirection::Horizontal,
rect.x,
first_rect.y + first_rect.height,
rect.width,
));
}
SplitDirection::Vertical => {
separators.push((
*split_id,
SplitDirection::Vertical,
first_rect.x + first_rect.width,
rect.y,
rect.height,
));
}
}
separators.extend(first.get_separators_with_ids(first_rect));
separators.extend(second.get_separators_with_ids(second_rect));
separators
}
}
}
pub fn all_split_ids(&self) -> Vec<SplitId> {
let mut ids = vec![self.id()];
match self {
SplitNode::Leaf { .. } => ids,
SplitNode::Split { first, second, .. } => {
ids.extend(first.all_split_ids());
ids.extend(second.all_split_ids());
ids
}
}
}
pub fn leaf_split_ids(&self) -> Vec<SplitId> {
match self {
SplitNode::Leaf { split_id, .. } => vec![*split_id],
SplitNode::Split { first, second, .. } => {
let mut ids = first.leaf_split_ids();
ids.extend(second.leaf_split_ids());
ids
}
}
}
pub fn count_leaves(&self) -> usize {
match self {
SplitNode::Leaf { .. } => 1,
SplitNode::Split { first, second, .. } => first.count_leaves() + second.count_leaves(),
}
}
}
fn split_rect(rect: Rect, direction: SplitDirection, ratio: f32) -> (Rect, Rect) {
match direction {
SplitDirection::Horizontal => {
let total_height = rect.height.saturating_sub(1); let first_height = (total_height as f32 * ratio).round() as u16;
let second_height = total_height.saturating_sub(first_height);
let first = Rect {
x: rect.x,
y: rect.y,
width: rect.width,
height: first_height,
};
let second = Rect {
x: rect.x,
y: rect.y + first_height + 1, width: rect.width,
height: second_height,
};
(first, second)
}
SplitDirection::Vertical => {
let total_width = rect.width.saturating_sub(1); let first_width = (total_width as f32 * ratio).round() as u16;
let second_width = total_width.saturating_sub(first_width);
let first = Rect {
x: rect.x,
y: rect.y,
width: first_width,
height: rect.height,
};
let second = Rect {
x: rect.x + first_width + 1, y: rect.y,
width: second_width,
height: rect.height,
};
(first, second)
}
}
}
#[derive(Debug)]
pub struct SplitManager {
root: SplitNode,
active_split: SplitId,
next_split_id: usize,
maximized_split: Option<SplitId>,
}
impl SplitManager {
pub fn new(buffer_id: BufferId) -> Self {
let split_id = SplitId(0);
Self {
root: SplitNode::leaf(buffer_id, split_id),
active_split: split_id,
next_split_id: 1,
maximized_split: None,
}
}
pub fn root(&self) -> &SplitNode {
&self.root
}
pub fn active_split(&self) -> SplitId {
self.active_split
}
pub fn set_active_split(&mut self, split_id: SplitId) -> bool {
if self.root.find(split_id).is_some() {
self.active_split = split_id;
true
} else {
false
}
}
pub fn active_buffer_id(&self) -> Option<BufferId> {
self.root
.find(self.active_split)
.and_then(|node| node.buffer_id())
}
pub fn get_buffer_id(&self, split_id: SplitId) -> Option<BufferId> {
self.root.find(split_id).and_then(|node| node.buffer_id())
}
pub fn set_active_buffer_id(&mut self, new_buffer_id: BufferId) -> bool {
if let Some(node) = self.root.find_mut(self.active_split) {
if let SplitNode::Leaf { buffer_id, .. } = node {
*buffer_id = new_buffer_id;
return true;
}
}
false
}
pub fn set_split_buffer(
&mut self,
split_id: SplitId,
new_buffer_id: BufferId,
) -> Result<(), String> {
if let Some(node) = self.root.find_mut(split_id) {
if let SplitNode::Leaf { buffer_id, .. } = node {
*buffer_id = new_buffer_id;
return Ok(());
}
return Err(format!("Split {:?} is not a leaf", split_id));
}
Err(format!("Split {:?} not found", split_id))
}
fn allocate_split_id(&mut self) -> SplitId {
let id = SplitId(self.next_split_id);
self.next_split_id += 1;
id
}
pub fn split_active(
&mut self,
direction: SplitDirection,
new_buffer_id: BufferId,
ratio: f32,
) -> Result<SplitId, String> {
let active_id = self.active_split;
let result = self.replace_split_with_split(active_id, direction, new_buffer_id, ratio);
if let Ok(new_split_id) = result {
self.active_split = new_split_id;
Ok(new_split_id)
} else {
result
}
}
fn replace_split_with_split(
&mut self,
target_id: SplitId,
direction: SplitDirection,
new_buffer_id: BufferId,
ratio: f32,
) -> Result<SplitId, String> {
let temp_id = self.allocate_split_id();
let new_split_id = self.allocate_split_id();
let new_leaf_id = self.allocate_split_id();
if self.root.id() == target_id {
let old_root =
std::mem::replace(&mut self.root, SplitNode::leaf(new_buffer_id, temp_id));
self.root = SplitNode::split(
direction,
old_root,
SplitNode::leaf(new_buffer_id, new_leaf_id),
ratio,
new_split_id,
);
return Ok(new_leaf_id);
}
if let Some(node) = self.root.find_mut(target_id) {
let old_node = std::mem::replace(node, SplitNode::leaf(new_buffer_id, temp_id));
*node = SplitNode::split(
direction,
old_node,
SplitNode::leaf(new_buffer_id, new_leaf_id),
ratio,
new_split_id,
);
Ok(new_leaf_id)
} else {
Err(format!("Split {:?} not found", target_id))
}
}
pub fn close_split(&mut self, split_id: SplitId) -> Result<(), String> {
if self.root.count_leaves() <= 1 {
return Err("Cannot close the last split".to_string());
}
if self.root.id() == split_id && self.root.buffer_id().is_some() {
return Err("Cannot close the only split".to_string());
}
if self.maximized_split == Some(split_id) {
self.maximized_split = None;
}
let result = self.remove_split_node(split_id);
if result.is_ok() && self.active_split == split_id {
let leaf_ids = self.root.leaf_split_ids();
if let Some(&first_leaf) = leaf_ids.first() {
self.active_split = first_leaf;
}
}
result
}
fn remove_split_node(&mut self, target_id: SplitId) -> Result<(), String> {
if self.root.id() == target_id {
if let SplitNode::Split { first, .. } = &self.root {
self.root = (**first).clone();
return Ok(());
}
}
Self::remove_child_static(&mut self.root, target_id)
}
fn remove_child_static(node: &mut SplitNode, target_id: SplitId) -> Result<(), String> {
match node {
SplitNode::Leaf { .. } => Err("Target not found".to_string()),
SplitNode::Split { first, second, .. } => {
if first.id() == target_id {
*node = (**second).clone();
Ok(())
} else if second.id() == target_id {
*node = (**first).clone();
Ok(())
} else {
Self::remove_child_static(first, target_id)
.or_else(|_| Self::remove_child_static(second, target_id))
}
}
}
}
pub fn adjust_ratio(&mut self, split_id: SplitId, delta: f32) -> Result<(), String> {
if let Some(node) = self.root.find_mut(split_id) {
if let SplitNode::Split { ratio, .. } = node {
*ratio = (*ratio + delta).clamp(0.1, 0.9);
Ok(())
} else {
Err("Target is not a split container".to_string())
}
} else {
Err("Split not found".to_string())
}
}
pub fn get_visible_buffers(&self, viewport_rect: Rect) -> Vec<(SplitId, BufferId, Rect)> {
if let Some(maximized_id) = self.maximized_split {
if let Some(node) = self.root.find(maximized_id) {
if let Some(buffer_id) = node.buffer_id() {
return vec![(maximized_id, buffer_id, viewport_rect)];
}
}
}
self.root.get_leaves_with_rects(viewport_rect)
}
pub fn get_separators(&self, viewport_rect: Rect) -> Vec<(SplitDirection, u16, u16, u16)> {
if self.maximized_split.is_some() {
return vec![];
}
self.root.get_separators(viewport_rect)
}
pub fn get_separators_with_ids(
&self,
viewport_rect: Rect,
) -> Vec<(SplitId, SplitDirection, u16, u16, u16)> {
if self.maximized_split.is_some() {
return vec![];
}
self.root.get_separators_with_ids(viewport_rect)
}
pub fn get_ratio(&self, split_id: SplitId) -> Option<f32> {
if let Some(node) = self.root.find(split_id) {
if let SplitNode::Split { ratio, .. } = node {
Some(*ratio)
} else {
None
}
} else {
None
}
}
pub fn set_ratio(&mut self, split_id: SplitId, new_ratio: f32) -> Result<(), String> {
if let Some(node) = self.root.find_mut(split_id) {
if let SplitNode::Split { ratio, .. } = node {
*ratio = new_ratio.clamp(0.1, 0.9);
Ok(())
} else {
Err("Target is not a split container".to_string())
}
} else {
Err("Split not found".to_string())
}
}
pub fn distribute_splits_evenly(&mut self) {
Self::distribute_node_evenly(&mut self.root);
}
fn distribute_node_evenly(node: &mut SplitNode) -> usize {
match node {
SplitNode::Leaf { .. } => 1,
SplitNode::Split {
first,
second,
ratio,
..
} => {
let first_leaves = Self::distribute_node_evenly(first);
let second_leaves = Self::distribute_node_evenly(second);
let total_leaves = first_leaves + second_leaves;
*ratio = (first_leaves as f32 / total_leaves as f32).clamp(0.1, 0.9);
total_leaves
}
}
}
pub fn next_split(&mut self) {
let leaf_ids = self.root.leaf_split_ids();
if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
let next_pos = (pos + 1) % leaf_ids.len();
self.active_split = leaf_ids[next_pos];
}
}
pub fn prev_split(&mut self) {
let leaf_ids = self.root.leaf_split_ids();
if let Some(pos) = leaf_ids.iter().position(|id| *id == self.active_split) {
let prev_pos = if pos == 0 {
leaf_ids.len() - 1
} else {
pos - 1
};
self.active_split = leaf_ids[prev_pos];
}
}
pub fn splits_for_buffer(&self, target_buffer_id: BufferId) -> Vec<SplitId> {
self.root
.get_leaves_with_rects(Rect {
x: 0,
y: 0,
width: 1,
height: 1,
})
.into_iter()
.filter(|(_, buffer_id, _)| *buffer_id == target_buffer_id)
.map(|(split_id, _, _)| split_id)
.collect()
}
pub fn buffer_for_split(&self, target_split_id: SplitId) -> Option<BufferId> {
self.root
.get_leaves_with_rects(Rect {
x: 0,
y: 0,
width: 1,
height: 1,
})
.into_iter()
.find(|(split_id, _, _)| *split_id == target_split_id)
.map(|(_, buffer_id, _)| buffer_id)
}
pub fn maximize_split(&mut self) -> Result<(), String> {
if self.root.count_leaves() <= 1 {
return Err("Cannot maximize: only one split exists".to_string());
}
if self.maximized_split.is_some() {
return Err("A split is already maximized".to_string());
}
self.maximized_split = Some(self.active_split);
Ok(())
}
pub fn unmaximize_split(&mut self) -> Result<(), String> {
if self.maximized_split.is_none() {
return Err("No split is maximized".to_string());
}
self.maximized_split = None;
Ok(())
}
pub fn is_maximized(&self) -> bool {
self.maximized_split.is_some()
}
pub fn maximized_split(&self) -> Option<SplitId> {
self.maximized_split
}
pub fn toggle_maximize(&mut self) -> Result<bool, String> {
if self.is_maximized() {
self.unmaximize_split()?;
Ok(false)
} else {
self.maximize_split()?;
Ok(true)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_split_manager() {
let buffer_id = BufferId(0);
let manager = SplitManager::new(buffer_id);
assert_eq!(manager.active_buffer_id(), Some(buffer_id));
assert_eq!(manager.root().count_leaves(), 1);
}
#[test]
fn test_horizontal_split() {
let buffer_a = BufferId(0);
let buffer_b = BufferId(1);
let mut manager = SplitManager::new(buffer_a);
let result = manager.split_active(SplitDirection::Horizontal, buffer_b, 0.5);
assert!(result.is_ok());
assert_eq!(manager.root().count_leaves(), 2);
}
#[test]
fn test_vertical_split() {
let buffer_a = BufferId(0);
let buffer_b = BufferId(1);
let mut manager = SplitManager::new(buffer_a);
let result = manager.split_active(SplitDirection::Vertical, buffer_b, 0.5);
assert!(result.is_ok());
assert_eq!(manager.root().count_leaves(), 2);
}
#[test]
fn test_nested_splits() {
let buffer_a = BufferId(0);
let buffer_b = BufferId(1);
let buffer_c = BufferId(2);
let mut manager = SplitManager::new(buffer_a);
manager
.split_active(SplitDirection::Horizontal, buffer_b, 0.5)
.unwrap();
manager
.split_active(SplitDirection::Vertical, buffer_c, 0.5)
.unwrap();
assert_eq!(manager.root().count_leaves(), 3);
}
#[test]
fn test_close_split() {
let buffer_a = BufferId(0);
let buffer_b = BufferId(1);
let mut manager = SplitManager::new(buffer_a);
let new_split = manager
.split_active(SplitDirection::Horizontal, buffer_b, 0.5)
.unwrap();
assert_eq!(manager.root().count_leaves(), 2);
let result = manager.close_split(new_split);
assert!(result.is_ok());
assert_eq!(manager.root().count_leaves(), 1);
}
#[test]
fn test_cannot_close_last_split() {
let buffer_a = BufferId(0);
let mut manager = SplitManager::new(buffer_a);
let result = manager.close_split(manager.active_split());
assert!(result.is_err());
}
#[test]
fn test_split_rect_horizontal() {
let rect = Rect {
x: 0,
y: 0,
width: 100,
height: 100,
};
let (first, second) = split_rect(rect, SplitDirection::Horizontal, 0.5);
assert_eq!(first.height, 50);
assert_eq!(second.height, 49);
assert_eq!(first.width, 100);
assert_eq!(second.width, 100);
assert_eq!(first.y, 0);
assert_eq!(second.y, 51); }
#[test]
fn test_split_rect_vertical() {
let rect = Rect {
x: 0,
y: 0,
width: 100,
height: 100,
};
let (first, second) = split_rect(rect, SplitDirection::Vertical, 0.5);
assert_eq!(first.width, 50);
assert_eq!(second.width, 49);
assert_eq!(first.height, 100);
assert_eq!(second.height, 100);
assert_eq!(first.x, 0);
assert_eq!(second.x, 51); }
}