use std::rc::Rc; mod fibbonacci; use fibbonacci::fibbonacci; /// [Rope](https://en.wikipedia.org/wiki/Rope_(data_structure)) data structure /// implementation. /// /// This is the main data structure used to store text file contents while they /// are being edited. #[derive(Debug)] pub enum Rope { Branch { /// Total number of bytes in the string contained in the left subtree, or /// the number of bytes in the string if this is a leaf. bytes_weight: usize, /// Total number of characters in the string contained in the left subtree, /// or the number of characters in the string if this is a leaf. chars_weight: usize, /// Total number of line endings in the string contained in the left /// subtree, or the number of line endings in the string if this is a leaf. lines_weight: usize, /// The root of the left subtree left: Rc, /// The root of the right subtree right: Option>, }, Leaf { text: String, }, } impl Rope { /// Create a new Rope containing the passed text in a single node. pub fn new(contents: impl Into) -> Rc { let text = contents.into(); Rc::new(Rope::Leaf { text }) } /// Create a new empty Rope pub fn empty() -> Rc { let text = "".into(); Rc::new(Rope::Leaf { text }) } /// Return the total number of bytes in the text. pub fn total_bytes(&self) -> usize { match self { Rope::Branch { bytes_weight, right: None, .. } => *bytes_weight, Rope::Branch { bytes_weight, right: Some(right), .. } => bytes_weight + right.total_bytes(), Rope::Leaf { text } => text.len(), } } /// Return the total number of characters in the text. pub fn total_chars(&self) -> usize { match self { Rope::Branch { chars_weight, right: None, .. } => *chars_weight, Rope::Branch { chars_weight, right: Some(right), .. } => chars_weight + right.total_chars(), Rope::Leaf { text } => text.chars().count(), } } /// Return the total number of lines in the text pub fn total_lines(&self) -> usize { match self { Rope::Branch { lines_weight, right: None, .. } => *lines_weight, Rope::Branch { lines_weight, right: Some(right), .. } => lines_weight + right.total_lines(), Rope::Leaf { text } => text.chars().filter(|&c| c == '\n').count(), } } /// Return the character as a given character index. pub fn get_char_at_index(&self, index: usize) -> char { match self { Rope::Branch { left, right: None, .. } => left.get_char_at_index(index), Rope::Branch { chars_weight, left, right: Some(right), .. } => { if index < *chars_weight { left.get_char_at_index(index) } else { right.get_char_at_index(index - chars_weight) } } Rope::Leaf { text } => text.chars().nth(index).unwrap(), } } /// Returns a new rope created from concatenating `other` onto the end of /// this one. pub fn concat(self: Rc, other: Rc) -> Rc { Rope::join(self, Some(other)).rebalance() } /// Insert new text into the rope at a given character index. pub fn insert_at_char_index( self: &Rc, index: usize, text: impl Into, ) -> Rc { let new_node = Rope::new(text); let total_chars = self.total_chars(); if index == 0 { new_node.concat(self.clone()) } else if index < total_chars { let (before, after) = self.split_at_char_index(index); before.concat(new_node).concat(after) } else if index == total_chars { self.clone().concat(new_node) } else { panic!("Attempt to insert past end of rope.") } } /// Return a new rope with `length` characters removed after `start`. pub fn delete_at_char_index(self: &Rc, start: usize, length: usize) -> Rc { let (beginning, rest) = self.split_at_char_index(start); let (_, end) = rest.split_at_char_index(length); beginning.concat(end) } /// Split the rope in two at character `index`. /// /// The result is two Ropes—one containing the first 'i' characters of the /// text and the other containing the rest of the text. pub fn split_at_char_index(self: &Rc, index: usize) -> (Rc, Rc) { match *self.as_ref() { Rope::Branch { chars_weight, ref left, ref right, .. } => { if index < chars_weight { let (first, second) = left.split_at_char_index(index); ( first.rebalance(), Rope::join(second, right.as_ref().map(|r| r.clone())).rebalance(), ) } else if let Some(right) = right { if index > chars_weight { let (first, second) = right.split_at_char_index(index - chars_weight); ( Rope::join(left.clone(), Some(first)).rebalance(), second.rebalance(), ) } else { (left.clone(), right.clone()) } } else { (left.clone(), Rope::empty()) } } Rope::Leaf { ref text } => { if let Some((byte_index, _)) = text.char_indices().nth(index) { let (first, second) = text.split_at(byte_index); (Rope::new(first), Rope::new(second)) } else { (self.clone(), Rope::empty()) } } } } pub fn is_balanced(&self) -> bool { match self { Rope::Branch { bytes_weight, .. } => fibbonacci(self.depth() + 2) <= *bytes_weight, Rope::Leaf { .. } => true, } } pub fn rebalance(self: Rc) -> Rc { if self.is_balanced() { return self; } let leaf_nodes: Vec<_> = self.iter_nodes().collect(); merge(&leaf_nodes) } /// Number of steps between this node and its most distance descendant /// /// Leaf nodes have a depth of 0 and each branch node has a depth one /// greater than the depth of deepest child. pub fn depth(&self) -> usize { match self { Rope::Branch { left, right, .. } => { left.depth() .max(right.as_ref().map(|r| r.depth()).unwrap_or(0)) + 1 } Rope::Leaf { .. } => 0, } } /// Returns an iterator over the chars of the text pub fn iter_chars(self: &Rc) -> CharIterator { CharIterator::new(self) } /// Returns an iterator over the chars of the text which also contains the /// current line and column. pub fn iter_chars_with_point(self: &Rc) -> CharWithPointIterator { CharWithPointIterator::new(self) } /// Concatenate two Ropes without rebalancing. /// /// Combines to ropes by creating a new parent node and adding `left` and /// `right` as children. If `right` is [None] then the resulting node will /// only have one child. fn join(left: Rc, right: Option>) -> Rc { Rc::new(Rope::Branch { bytes_weight: left.total_bytes(), chars_weight: left.total_chars(), lines_weight: left.total_lines(), left, right, }) } /// Returns an iterater over the leaf nodes pub fn iter_nodes(self: Rc) -> NodeIterator { NodeIterator::new(self) } } fn merge(leaf_nodes: &[Rc]) -> Rc { match leaf_nodes.len() { 0 => panic!("Attempt to merge empty list"), 1 => leaf_nodes[0].clone(), 2 => Rope::join(leaf_nodes[0].clone(), Some(leaf_nodes[1].clone())), 3.. => { let mid = leaf_nodes.len() / 2; Rope::join(merge(&leaf_nodes[..mid]), Some(merge(&leaf_nodes[mid..]))) } } } pub struct NodeIterator { stack: Vec>, } impl NodeIterator { fn new(rope: Rc) -> Self { let mut result = NodeIterator { stack: vec![] }; result.push_tree(rope); result } fn push_tree(&mut self, node: Rc) { let mut curr = node; self.stack.push(curr.clone()); while let Rope::Branch { left, .. } = curr.as_ref() { self.stack.push(left.clone()); curr = left.clone(); } } } impl Iterator for NodeIterator { type Item = Rc; fn next(&mut self) -> Option { let curr = self.stack.pop()?; if let Rope::Branch { right, .. } = curr.as_ref() { if let Some(right) = right { self.push_tree(right.clone()); } self.next() } else { Some(curr) } } } pub struct CharIterator(CharWithPointIterator); impl CharIterator { fn new(rope: &Rc) -> Self { CharIterator(CharWithPointIterator::new(rope)) } } impl Iterator for CharIterator { type Item = char; fn next(&mut self) -> Option { match self { CharIterator(inner) => inner.next().map(|p| p.character), } } } pub struct CharWithPointIterator { node_iterator: NodeIterator, current_text: Option, str_index: usize, line_num: usize, char_num: usize, eol: bool, } impl CharWithPointIterator { fn new(rope: &Rc) -> Self { let mut node_iterator = NodeIterator::new(rope.clone()); let current_text = Self::next_string(&mut node_iterator); CharWithPointIterator { node_iterator, current_text, str_index: 0, char_num: 0, line_num: 0, eol: false, } } fn next_string(node_iterator: &mut NodeIterator) -> Option { node_iterator.next().map(|rope| { if let Rope::Leaf { text } = rope.as_ref() { text.clone() } else { panic!("Rope NodeIterator yielded non-leaf node.") } }) } pub fn get_point(&self) -> (usize, usize) { (self.line_num, self.char_num) } } pub struct CharWithPoint { pub character: char, pub line: usize, pub column: usize, } impl Iterator for CharWithPointIterator { type Item = CharWithPoint; fn next(&mut self) -> Option { self.current_text .as_ref() .and_then(|text| { let next_char = text.chars().nth(self.str_index); self.str_index += 1; if let Some(c) = next_char { if self.eol { self.char_num = 1; self.line_num += 1; } else { self.char_num += 1 } self.eol = c == '\n'; } next_char.map(|c| CharWithPoint { character: c, line: self.line_num, column: self.char_num, }) }) .or_else(|| { self.current_text = Self::next_string(&mut self.node_iterator); if self.current_text.is_some() { self.str_index = 0; self.next() } else { None } }) } } #[cfg(test)] mod tests;