From a22968456cd61e2bc88d54729d558f0ef8c5bf93 Mon Sep 17 00:00:00 2001 From: Matthew Gordon Date: Sat, 19 Oct 2024 14:22:53 -0300 Subject: [PATCH] Add Rope::split() and Rope::empty() --- src/core/rope.rs | 188 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 150 insertions(+), 38 deletions(-) diff --git a/src/core/rope.rs b/src/core/rope.rs index ef74e29..92c82dd 100644 --- a/src/core/rope.rs +++ b/src/core/rope.rs @@ -37,6 +37,12 @@ impl Rope { 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 { @@ -91,21 +97,69 @@ impl Rope { /// Concatenate two Ropes without rebalancing. /// /// Combines to ropes by creating a new parent node and adding `left` and - /// `right` as children. Having this separate from [Rope::concatenate()] is mostly - /// useful for testing purposes. - fn join(left: Rc, right: Rc) -> Rc { + /// `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: Some(right), + right, }) } + /// 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(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(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(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()) + } + } + } + } + + fn rebalance(self: Rc) -> Rc { + // TODO + self + } + /// Returns true if this node is a leaf node, false otherwise pub fn is_leaf(&self) -> bool { - match self { + match &self { Rope::Branch { .. } => false, Rope::Leaf { .. } => true, } @@ -191,8 +245,7 @@ impl Iterator for CharIterator { type Item = char; fn next(&mut self) -> Option { - dbg!(self - .current_text + self.current_text .as_ref() .and_then(|text| { let c = text.chars().nth(self.str_index); @@ -207,7 +260,7 @@ impl Iterator for CharIterator { } else { None } - })) + }) } } @@ -216,6 +269,58 @@ 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" + } + #[test] #[timeout(100)] fn node_iterator_for_single_node_returns_node_and_only_node() { @@ -235,22 +340,8 @@ mod tests { #[test] #[timeout(100)] fn node_iterator_returns_nodes_in_correct_order() { - let strings = vec![ - "abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz", - ]; - let target = Rope::join( - Rope::join( - Rope::join(Rope::new("abc"), Rope::new("def")), - Rope::join(Rope::new("g"), Rope::new("hi")), - ), - Rope::join( - Rope::join( - Rope::join(Rope::new("jklm"), Rope::new("n")), - Rope::join(Rope::new("op"), Rope::new("qr")), - ), - Rope::join(Rope::new("stuv"), Rope::new("wxyz")), - ), - ); + 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); @@ -265,25 +356,46 @@ mod tests { #[test] #[timeout(100)] fn char_iterator_returns_chars_in_correct_order() { - let target = Rope::join( - Rope::join( - Rope::join(Rope::new("abc"), Rope::new("def")), - Rope::join(Rope::new("g"), Rope::new("hi")), - ), - Rope::join( - Rope::join( - Rope::join(Rope::new("jklm"), Rope::new("n")), - Rope::join(Rope::new("op"), Rope::new("qr")), - ), - Rope::join(Rope::new("stuv"), Rope::new("wxyz")), - ), - ); + 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("abcdefghijklmnopqrstuvwxyz".chars()) + 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(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(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); + } + } }