diff --git a/core/src/rope.rs b/core/src/rope.rs deleted file mode 100644 index 57bdd32..0000000 --- a/core/src/rope.rs +++ /dev/null @@ -1,865 +0,0 @@ -use {super::fibbonacci::fibbonacci, std::rc::Rc}; - -/// [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.lines().count(), - } - } - - /// 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 true if this node is a leaf node, false otherwise - pub fn is_leaf(&self) -> bool { - match &self { - Rope::Branch { .. } => false, - Rope::Leaf { .. } => true, - } - } - - /// 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 - 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..]))) - } - } -} - -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 { - use super::*; - use ntest::timeout; - - fn small_test_rope() -> Rc { - Rope::join( - Rope::join( - Rope::join(Rope::new("abc"), Some(Rope::new("def"))), - Some(Rope::join(Rope::new("g"), Some(Rope::new("hi")))), - ), - Some(Rope::join( - Rope::join( - Rope::join(Rope::new("jklm"), Some(Rope::new("n"))), - Some(Rope::join(Rope::new("op"), Some(Rope::new("qr")))), - ), - Some(Rope::join(Rope::new("stuv"), Some(Rope::new("wxyz")))), - )), - ) - } - - fn small_test_rope_leaf_strings() -> Vec<&'static str> { - vec![ - "abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz", - ] - } - - fn small_test_rope_full_string() -> &'static str { - "abcdefghijklmnopqrstuvwxyz" - } - - fn small_test_rope_with_multibyte_chars() -> Rc { - Rope::join( - Rope::join( - Rope::join(Rope::new("aあbc"), Some(Rope::new("deえf"))), - Some(Rope::join(Rope::new("g"), Some(Rope::new("hiい")))), - ), - Some(Rope::join( - Rope::join( - Rope::join(Rope::new("jklm"), Some(Rope::new("n"))), - Some(Rope::join(Rope::new("おop"), Some(Rope::new("qr")))), - ), - Some(Rope::join(Rope::new("stuうv"), Some(Rope::new("wxyz")))), - )), - ) - } - - fn small_test_rope_with_multibyte_chars_leaf_strings() -> Vec<&'static str> { - vec![ - "aあbc", "deえf", "g", "hiい", "jklm", "n", "おop", "qr", "stuうv", "wxyz", - ] - } - - fn small_test_rope_with_multibyte_chars_full_string() -> &'static str { - "aあbcdeえfghiいjklmnおopqrstuうvwxyz" - } - - impl Rope { - fn unwrap_branch_left(&self) -> &Rc { - match self { - Rope::Branch { left, .. } => left, - Rope::Leaf { .. } => panic!("Expected branch node but found leaf"), - } - } - - fn unwrap_branch_right(&self) -> Option<&Rc> { - match self { - Rope::Branch { right, .. } => right.as_ref(), - Rope::Leaf { .. } => panic!("Expected branch node but found leaf"), - } - } - } - - #[test] - #[timeout(100)] - fn node_iterator_for_single_node_returns_node_and_only_node() { - let target = Rope::new("The"); - - let result: Vec<_> = target.iter_nodes().collect(); - assert_eq!(result.len(), 1); - assert_eq!(result[0].total_bytes(), 3); - assert_eq!(result[0].total_chars(), 3); - assert_eq!(result[0].total_lines(), 1); - match result[0].as_ref() { - Rope::Leaf { text, .. } => assert_eq!(text, "The"), - _ => panic!(), - } - } - - #[test] - #[timeout(100)] - fn node_iterator_returns_nodes_in_correct_order() { - let strings = small_test_rope_leaf_strings(); - let target = small_test_rope(); - - let result: Vec<_> = target.iter_nodes().take(26).collect(); - assert_eq!(result.len(), 10); - for (node, string) in result.iter().zip(strings) { - match &node.as_ref() { - Rope::Leaf { text } => assert_eq!(text, string), - _ => panic!(), - } - } - } - - #[test] - #[timeout(100)] - fn char_iterator_returns_chars_in_correct_order() { - let target = small_test_rope(); - - let result: Vec<_> = target.iter_chars().collect(); - assert_eq!(result.len(), 26); - for (&target_char, expected_char) in - result.iter().zip(small_test_rope_full_string().chars()) - { - assert_eq!(target_char, expected_char); - } - } - - #[test] - #[timeout(100)] - fn split_splits_at_correct_location() { - let target = small_test_rope(); - let full_string = small_test_rope_full_string(); - for i in 0..(full_string.chars().count()) { - let (first, second) = target.split_at_char_index(i); - let first_string: String = first.iter_chars().collect(); - let second_string: String = second.iter_chars().collect(); - assert_eq!(first_string, full_string[0..i]); - assert_eq!(second_string, full_string[i..]); - } - } - - #[test] - #[timeout(100)] - fn split_splits_at_correct_location_with_multibyte_chars() { - let target = small_test_rope_with_multibyte_chars(); - let expected_chars: Vec<_> = small_test_rope_with_multibyte_chars_full_string() - .chars() - .collect(); - for i in 0..(expected_chars.len()) { - let (first, second) = target.split_at_char_index(i); - let string: String = first.iter_chars().collect(); - let expected: String = expected_chars[0..i].iter().collect(); - assert_eq!(string, expected); - let string: String = second.iter_chars().collect(); - let expected: String = expected_chars[i..].iter().collect(); - assert_eq!(string, expected); - } - } - - #[test] - fn depths_have_correct_values() { - let target = small_test_rope_with_multibyte_chars(); - assert_eq!(target.depth(), 4); - assert_eq!(target.unwrap_branch_left().depth(), 2); - assert_eq!(target.unwrap_branch_left().unwrap_branch_left().depth(), 1); - assert_eq!( - target - .unwrap_branch_left() - .unwrap_branch_left() - .unwrap_branch_left() - .depth(), - 0 - ); - assert_eq!( - target - .unwrap_branch_left() - .unwrap_branch_left() - .unwrap_branch_right() - .unwrap() - .depth(), - 0 - ); - assert_eq!( - target - .unwrap_branch_left() - .unwrap_branch_right() - .unwrap() - .depth(), - 1 - ); - assert_eq!( - target - .unwrap_branch_left() - .unwrap_branch_right() - .unwrap() - .unwrap_branch_left() - .depth(), - 0 - ); - assert_eq!( - target - .unwrap_branch_left() - .unwrap_branch_right() - .unwrap() - .unwrap_branch_right() - .unwrap() - .depth(), - 0 - ); - assert_eq!(target.unwrap_branch_right().unwrap().depth(), 3); - assert_eq!( - target - .unwrap_branch_right() - .unwrap() - .unwrap_branch_left() - .depth(), - 2 - ); - assert_eq!( - target - .unwrap_branch_right() - .unwrap() - .unwrap_branch_right() - .unwrap() - .depth(), - 1 - ); - } - - #[test] - fn insert_at_char_index() { - let target = Rope::new("The brown dog"); - assert_eq!(target.iter_chars().collect::(), "The brown dog"); - let rope1 = target.insert_at_char_index(4, "quick"); - assert_eq!(target.iter_chars().collect::(), "The brown dog"); - assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); - let rope2 = rope1.insert_at_char_index(9, " "); - assert_eq!(target.iter_chars().collect::(), "The brown dog"); - assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); - assert_eq!( - rope2.iter_chars().collect::(), - "The quick brown dog" - ); - let rope3 = - rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox."); - assert_eq!( - rope3.iter_chars().collect::(), - "The quick brown dog jumps over the lazy fox." - ); - } - - #[test] - fn delete_at_char_index() { - let target = Rope::new("The quick brown fox jumps over the lazy dog."); - let test = target.delete_at_char_index(10, 6); - assert_eq!( - target.iter_chars().collect::(), - "The quick brown fox jumps over the lazy dog." - ); - assert_eq!( - test.iter_chars().collect::(), - "The quick fox jumps over the lazy dog." - ); - let test = target.delete_at_char_index(0, 4); - assert_eq!( - target.iter_chars().collect::(), - "The quick brown fox jumps over the lazy dog." - ); - assert_eq!( - test.iter_chars().collect::(), - "quick brown fox jumps over the lazy dog." - ); - let test = target - .delete_at_char_index("The quick brown fox jumps over the lazy dog.".len() - 5, 5); - assert_eq!( - target.iter_chars().collect::(), - "The quick brown fox jumps over the lazy dog." - ); - assert_eq!( - test.iter_chars().collect::(), - "The quick brown fox jumps over the lazy" - ); - } - - #[test] - fn char_iterator_reports_correct_point() { - // Build a rope from fragments so we get a proper tree structure. - let target = Rope::new("This") - .concat(Rope::new(" is the first ")) - .concat(Rope::new("line.\n")) - .concat(Rope::new("This is the ")) - .concat(Rope::new("second line.\nThis is the third line.")) - .concat(Rope::new("\n")) - .concat(Rope::new( - "This is the fourth line.\nThis is the fifth line.\nThis is ", - )) - .concat(Rope::new("the")) - .concat(Rope::new(" sixth")) - .concat(Rope::new(" line.")) - .concat(Rope::new("\nThis")) - .concat(Rope::new(" is the seventh line.")); - let expected_values = vec![ - ('T', 0, 1), - ('h', 0, 2), - ('i', 0, 3), - ('s', 0, 4), - (' ', 0, 5), - ('i', 0, 6), - ('s', 0, 7), - (' ', 0, 8), - ('t', 0, 9), - ('h', 0, 10), - ('e', 0, 11), - (' ', 0, 12), - ('f', 0, 13), - ('i', 0, 14), - ('r', 0, 15), - ('s', 0, 16), - ('t', 0, 17), - (' ', 0, 18), - ('l', 0, 19), - ('i', 0, 20), - ('n', 0, 21), - ('e', 0, 22), - ('.', 0, 23), - ('\n', 0, 24), - ('T', 1, 1), - ('h', 1, 2), - ('i', 1, 3), - ('s', 1, 4), - (' ', 1, 5), - ('i', 1, 6), - ('s', 1, 7), - (' ', 1, 8), - ('t', 1, 9), - ('h', 1, 10), - ('e', 1, 11), - (' ', 1, 12), - ('s', 1, 13), - ('e', 1, 14), - ('c', 1, 15), - ('o', 1, 16), - ('n', 1, 17), - ('d', 1, 18), - (' ', 1, 19), - ('l', 1, 20), - ('i', 1, 21), - ('n', 1, 22), - ('e', 1, 23), - ('.', 1, 24), - ('\n', 1, 25), - ('T', 2, 1), - ('h', 2, 2), - ('i', 2, 3), - ('s', 2, 4), - (' ', 2, 5), - ('i', 2, 6), - ('s', 2, 7), - (' ', 2, 8), - ('t', 2, 9), - ('h', 2, 10), - ('e', 2, 11), - (' ', 2, 12), - ('t', 2, 13), - ('h', 2, 14), - ('i', 2, 15), - ('r', 2, 16), - ('d', 2, 17), - (' ', 2, 18), - ('l', 2, 19), - ('i', 2, 20), - ('n', 2, 21), - ('e', 2, 22), - ('.', 2, 23), - ('\n', 2, 24), - ('T', 3, 1), - ('h', 3, 2), - ('i', 3, 3), - ('s', 3, 4), - (' ', 3, 5), - ('i', 3, 6), - ('s', 3, 7), - (' ', 3, 8), - ('t', 3, 9), - ('h', 3, 10), - ('e', 3, 11), - (' ', 3, 12), - ('f', 3, 13), - ('o', 3, 14), - ('u', 3, 15), - ('r', 3, 16), - ('t', 3, 17), - ('h', 3, 18), - (' ', 3, 19), - ('l', 3, 20), - ('i', 3, 21), - ('n', 3, 22), - ('e', 3, 23), - ('.', 3, 24), - ('\n', 3, 25), - ('T', 4, 1), - ('h', 4, 2), - ('i', 4, 3), - ('s', 4, 4), - (' ', 4, 5), - ('i', 4, 6), - ('s', 4, 7), - (' ', 4, 8), - ('t', 4, 9), - ('h', 4, 10), - ('e', 4, 11), - (' ', 4, 12), - ('f', 4, 13), - ('i', 4, 14), - ('f', 4, 15), - ('t', 4, 16), - ('h', 4, 17), - (' ', 4, 18), - ('l', 4, 19), - ('i', 4, 20), - ('n', 4, 21), - ('e', 4, 22), - ('.', 4, 23), - ('\n', 4, 24), - ('T', 5, 1), - ('h', 5, 2), - ('i', 5, 3), - ('s', 5, 4), - (' ', 5, 5), - ('i', 5, 6), - ('s', 5, 7), - (' ', 5, 8), - ('t', 5, 9), - ('h', 5, 10), - ('e', 5, 11), - (' ', 5, 12), - ('s', 5, 13), - ('i', 5, 14), - ('x', 5, 15), - ('t', 5, 16), - ('h', 5, 17), - (' ', 5, 18), - ('l', 5, 19), - ('i', 5, 20), - ('n', 5, 21), - ('e', 5, 22), - ('.', 5, 23), - ('\n', 5, 24), - ('T', 6, 1), - ('h', 6, 2), - ('i', 6, 3), - ('s', 6, 4), - (' ', 6, 5), - ('i', 6, 6), - ('s', 6, 7), - (' ', 6, 8), - ('t', 6, 9), - ('h', 6, 10), - ('e', 6, 11), - (' ', 6, 12), - ('s', 6, 13), - ('e', 6, 14), - ('v', 6, 15), - ('e', 6, 16), - ('n', 6, 17), - ('t', 6, 18), - ('h', 6, 19), - (' ', 6, 20), - ('l', 6, 21), - ('i', 6, 22), - ('n', 6, 23), - ('e', 6, 24), - ('.', 6, 25), - ('\n', 7, 26), - ]; - for (found, (expected_char, expected_line, expected_column)) in - target.iter_chars_with_point().zip(expected_values) - { - assert_eq!(found.character, expected_char); - assert_eq!(found.line, expected_line); - assert_eq!(found.column, expected_column); - } - } -} diff --git a/core/src/rope/mod.rs b/core/src/rope/mod.rs new file mode 100644 index 0000000..6b9374b --- /dev/null +++ b/core/src/rope/mod.rs @@ -0,0 +1,391 @@ +use {super::fibbonacci::fibbonacci, std::rc::Rc}; + +/// [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.lines().count(), + } + } + + /// 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 true if this node is a leaf node, false otherwise + pub fn is_leaf(&self) -> bool { + match &self { + Rope::Branch { .. } => false, + Rope::Leaf { .. } => true, + } + } + + /// 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 + 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..]))) + } + } +} + +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; diff --git a/core/src/rope/tests/mod.rs b/core/src/rope/tests/mod.rs new file mode 100644 index 0000000..d7aaf5d --- /dev/null +++ b/core/src/rope/tests/mod.rs @@ -0,0 +1,471 @@ +use super::*; +use ntest::timeout; + +fn small_test_rope() -> Rc { + Rope::join( + Rope::join( + Rope::join(Rope::new("abc"), Some(Rope::new("def"))), + Some(Rope::join(Rope::new("g"), Some(Rope::new("hi")))), + ), + Some(Rope::join( + Rope::join( + Rope::join(Rope::new("jklm"), Some(Rope::new("n"))), + Some(Rope::join(Rope::new("op"), Some(Rope::new("qr")))), + ), + Some(Rope::join(Rope::new("stuv"), Some(Rope::new("wxyz")))), + )), + ) +} + +fn small_test_rope_leaf_strings() -> Vec<&'static str> { + vec![ + "abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz", + ] +} + +fn small_test_rope_full_string() -> &'static str { + "abcdefghijklmnopqrstuvwxyz" +} + +fn small_test_rope_with_multibyte_chars() -> Rc { + Rope::join( + Rope::join( + Rope::join(Rope::new("aあbc"), Some(Rope::new("deえf"))), + Some(Rope::join(Rope::new("g"), Some(Rope::new("hiい")))), + ), + Some(Rope::join( + Rope::join( + Rope::join(Rope::new("jklm"), Some(Rope::new("n"))), + Some(Rope::join(Rope::new("おop"), Some(Rope::new("qr")))), + ), + Some(Rope::join(Rope::new("stuうv"), Some(Rope::new("wxyz")))), + )), + ) +} + +fn small_test_rope_with_multibyte_chars_leaf_strings() -> Vec<&'static str> { + vec![ + "aあbc", "deえf", "g", "hiい", "jklm", "n", "おop", "qr", "stuうv", "wxyz", + ] +} + +fn small_test_rope_with_multibyte_chars_full_string() -> &'static str { + "aあbcdeえfghiいjklmnおopqrstuうvwxyz" +} + +impl Rope { + fn unwrap_branch_left(&self) -> &Rc { + match self { + Rope::Branch { left, .. } => left, + Rope::Leaf { .. } => panic!("Expected branch node but found leaf"), + } + } + + fn unwrap_branch_right(&self) -> Option<&Rc> { + match self { + Rope::Branch { right, .. } => right.as_ref(), + Rope::Leaf { .. } => panic!("Expected branch node but found leaf"), + } + } +} + +#[test] +#[timeout(100)] +fn node_iterator_for_single_node_returns_node_and_only_node() { + let target = Rope::new("The"); + + let result: Vec<_> = target.iter_nodes().collect(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].total_bytes(), 3); + assert_eq!(result[0].total_chars(), 3); + assert_eq!(result[0].total_lines(), 1); + match result[0].as_ref() { + Rope::Leaf { text, .. } => assert_eq!(text, "The"), + _ => panic!(), + } +} + +#[test] +#[timeout(100)] +fn node_iterator_returns_nodes_in_correct_order() { + let strings = small_test_rope_leaf_strings(); + let target = small_test_rope(); + + let result: Vec<_> = target.iter_nodes().take(26).collect(); + assert_eq!(result.len(), 10); + for (node, string) in result.iter().zip(strings) { + match &node.as_ref() { + Rope::Leaf { text } => assert_eq!(text, string), + _ => panic!(), + } + } +} + +#[test] +#[timeout(100)] +fn char_iterator_returns_chars_in_correct_order() { + let target = small_test_rope(); + + let result: Vec<_> = target.iter_chars().collect(); + assert_eq!(result.len(), 26); + for (&target_char, expected_char) in result.iter().zip(small_test_rope_full_string().chars()) { + assert_eq!(target_char, expected_char); + } +} + +#[test] +#[timeout(100)] +fn split_splits_at_correct_location() { + let target = small_test_rope(); + let full_string = small_test_rope_full_string(); + for i in 0..(full_string.chars().count()) { + let (first, second) = target.split_at_char_index(i); + let first_string: String = first.iter_chars().collect(); + let second_string: String = second.iter_chars().collect(); + assert_eq!(first_string, full_string[0..i]); + assert_eq!(second_string, full_string[i..]); + } +} + +#[test] +#[timeout(100)] +fn split_splits_at_correct_location_with_multibyte_chars() { + let target = small_test_rope_with_multibyte_chars(); + let expected_chars: Vec<_> = small_test_rope_with_multibyte_chars_full_string() + .chars() + .collect(); + for i in 0..(expected_chars.len()) { + let (first, second) = target.split_at_char_index(i); + let string: String = first.iter_chars().collect(); + let expected: String = expected_chars[0..i].iter().collect(); + assert_eq!(string, expected); + let string: String = second.iter_chars().collect(); + let expected: String = expected_chars[i..].iter().collect(); + assert_eq!(string, expected); + } +} + +#[test] +fn depths_have_correct_values() { + let target = small_test_rope_with_multibyte_chars(); + assert_eq!(target.depth(), 4); + assert_eq!(target.unwrap_branch_left().depth(), 2); + assert_eq!(target.unwrap_branch_left().unwrap_branch_left().depth(), 1); + assert_eq!( + target + .unwrap_branch_left() + .unwrap_branch_left() + .unwrap_branch_left() + .depth(), + 0 + ); + assert_eq!( + target + .unwrap_branch_left() + .unwrap_branch_left() + .unwrap_branch_right() + .unwrap() + .depth(), + 0 + ); + assert_eq!( + target + .unwrap_branch_left() + .unwrap_branch_right() + .unwrap() + .depth(), + 1 + ); + assert_eq!( + target + .unwrap_branch_left() + .unwrap_branch_right() + .unwrap() + .unwrap_branch_left() + .depth(), + 0 + ); + assert_eq!( + target + .unwrap_branch_left() + .unwrap_branch_right() + .unwrap() + .unwrap_branch_right() + .unwrap() + .depth(), + 0 + ); + assert_eq!(target.unwrap_branch_right().unwrap().depth(), 3); + assert_eq!( + target + .unwrap_branch_right() + .unwrap() + .unwrap_branch_left() + .depth(), + 2 + ); + assert_eq!( + target + .unwrap_branch_right() + .unwrap() + .unwrap_branch_right() + .unwrap() + .depth(), + 1 + ); +} + +#[test] +fn insert_at_char_index() { + let target = Rope::new("The brown dog"); + assert_eq!(target.iter_chars().collect::(), "The brown dog"); + let rope1 = target.insert_at_char_index(4, "quick"); + assert_eq!(target.iter_chars().collect::(), "The brown dog"); + assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); + let rope2 = rope1.insert_at_char_index(9, " "); + assert_eq!(target.iter_chars().collect::(), "The brown dog"); + assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); + assert_eq!( + rope2.iter_chars().collect::(), + "The quick brown dog" + ); + let rope3 = + rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox."); + assert_eq!( + rope3.iter_chars().collect::(), + "The quick brown dog jumps over the lazy fox." + ); +} + +#[test] +fn delete_at_char_index() { + let target = Rope::new("The quick brown fox jumps over the lazy dog."); + let test = target.delete_at_char_index(10, 6); + assert_eq!( + target.iter_chars().collect::(), + "The quick brown fox jumps over the lazy dog." + ); + assert_eq!( + test.iter_chars().collect::(), + "The quick fox jumps over the lazy dog." + ); + let test = target.delete_at_char_index(0, 4); + assert_eq!( + target.iter_chars().collect::(), + "The quick brown fox jumps over the lazy dog." + ); + assert_eq!( + test.iter_chars().collect::(), + "quick brown fox jumps over the lazy dog." + ); + let test = + target.delete_at_char_index("The quick brown fox jumps over the lazy dog.".len() - 5, 5); + assert_eq!( + target.iter_chars().collect::(), + "The quick brown fox jumps over the lazy dog." + ); + assert_eq!( + test.iter_chars().collect::(), + "The quick brown fox jumps over the lazy" + ); +} + +#[test] +fn char_iterator_reports_correct_point() { + // Build a rope from fragments so we get a proper tree structure. + let target = Rope::new("This") + .concat(Rope::new(" is the first ")) + .concat(Rope::new("line.\n")) + .concat(Rope::new("This is the ")) + .concat(Rope::new("second line.\nThis is the third line.")) + .concat(Rope::new("\n")) + .concat(Rope::new( + "This is the fourth line.\nThis is the fifth line.\nThis is ", + )) + .concat(Rope::new("the")) + .concat(Rope::new(" sixth")) + .concat(Rope::new(" line.")) + .concat(Rope::new("\nThis")) + .concat(Rope::new(" is the seventh line.")); + let expected_values = vec![ + ('T', 0, 1), + ('h', 0, 2), + ('i', 0, 3), + ('s', 0, 4), + (' ', 0, 5), + ('i', 0, 6), + ('s', 0, 7), + (' ', 0, 8), + ('t', 0, 9), + ('h', 0, 10), + ('e', 0, 11), + (' ', 0, 12), + ('f', 0, 13), + ('i', 0, 14), + ('r', 0, 15), + ('s', 0, 16), + ('t', 0, 17), + (' ', 0, 18), + ('l', 0, 19), + ('i', 0, 20), + ('n', 0, 21), + ('e', 0, 22), + ('.', 0, 23), + ('\n', 0, 24), + ('T', 1, 1), + ('h', 1, 2), + ('i', 1, 3), + ('s', 1, 4), + (' ', 1, 5), + ('i', 1, 6), + ('s', 1, 7), + (' ', 1, 8), + ('t', 1, 9), + ('h', 1, 10), + ('e', 1, 11), + (' ', 1, 12), + ('s', 1, 13), + ('e', 1, 14), + ('c', 1, 15), + ('o', 1, 16), + ('n', 1, 17), + ('d', 1, 18), + (' ', 1, 19), + ('l', 1, 20), + ('i', 1, 21), + ('n', 1, 22), + ('e', 1, 23), + ('.', 1, 24), + ('\n', 1, 25), + ('T', 2, 1), + ('h', 2, 2), + ('i', 2, 3), + ('s', 2, 4), + (' ', 2, 5), + ('i', 2, 6), + ('s', 2, 7), + (' ', 2, 8), + ('t', 2, 9), + ('h', 2, 10), + ('e', 2, 11), + (' ', 2, 12), + ('t', 2, 13), + ('h', 2, 14), + ('i', 2, 15), + ('r', 2, 16), + ('d', 2, 17), + (' ', 2, 18), + ('l', 2, 19), + ('i', 2, 20), + ('n', 2, 21), + ('e', 2, 22), + ('.', 2, 23), + ('\n', 2, 24), + ('T', 3, 1), + ('h', 3, 2), + ('i', 3, 3), + ('s', 3, 4), + (' ', 3, 5), + ('i', 3, 6), + ('s', 3, 7), + (' ', 3, 8), + ('t', 3, 9), + ('h', 3, 10), + ('e', 3, 11), + (' ', 3, 12), + ('f', 3, 13), + ('o', 3, 14), + ('u', 3, 15), + ('r', 3, 16), + ('t', 3, 17), + ('h', 3, 18), + (' ', 3, 19), + ('l', 3, 20), + ('i', 3, 21), + ('n', 3, 22), + ('e', 3, 23), + ('.', 3, 24), + ('\n', 3, 25), + ('T', 4, 1), + ('h', 4, 2), + ('i', 4, 3), + ('s', 4, 4), + (' ', 4, 5), + ('i', 4, 6), + ('s', 4, 7), + (' ', 4, 8), + ('t', 4, 9), + ('h', 4, 10), + ('e', 4, 11), + (' ', 4, 12), + ('f', 4, 13), + ('i', 4, 14), + ('f', 4, 15), + ('t', 4, 16), + ('h', 4, 17), + (' ', 4, 18), + ('l', 4, 19), + ('i', 4, 20), + ('n', 4, 21), + ('e', 4, 22), + ('.', 4, 23), + ('\n', 4, 24), + ('T', 5, 1), + ('h', 5, 2), + ('i', 5, 3), + ('s', 5, 4), + (' ', 5, 5), + ('i', 5, 6), + ('s', 5, 7), + (' ', 5, 8), + ('t', 5, 9), + ('h', 5, 10), + ('e', 5, 11), + (' ', 5, 12), + ('s', 5, 13), + ('i', 5, 14), + ('x', 5, 15), + ('t', 5, 16), + ('h', 5, 17), + (' ', 5, 18), + ('l', 5, 19), + ('i', 5, 20), + ('n', 5, 21), + ('e', 5, 22), + ('.', 5, 23), + ('\n', 5, 24), + ('T', 6, 1), + ('h', 6, 2), + ('i', 6, 3), + ('s', 6, 4), + (' ', 6, 5), + ('i', 6, 6), + ('s', 6, 7), + (' ', 6, 8), + ('t', 6, 9), + ('h', 6, 10), + ('e', 6, 11), + (' ', 6, 12), + ('s', 6, 13), + ('e', 6, 14), + ('v', 6, 15), + ('e', 6, 16), + ('n', 6, 17), + ('t', 6, 18), + ('h', 6, 19), + (' ', 6, 20), + ('l', 6, 21), + ('i', 6, 22), + ('n', 6, 23), + ('e', 6, 24), + ('.', 6, 25), + ('\n', 7, 26), + ]; + for (found, (expected_char, expected_line, expected_column)) in + target.iter_chars_with_point().zip(expected_values) + { + assert_eq!(found.character, expected_char); + assert_eq!(found.line, expected_line); + assert_eq!(found.column, expected_column); + } +}