From c3ebef8fc1feab0db3870e299c26eaf7ea7d71a6 Mon Sep 17 00:00:00 2001 From: Matthew Gordon Date: Sat, 19 Oct 2024 21:47:30 -0300 Subject: [PATCH] Add Rope::insert_char_at_index() --- src/core/rope.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/core/rope.rs b/src/core/rope.rs index 1b313f9..5286a16 100644 --- a/src/core/rope.rs +++ b/src/core/rope.rs @@ -94,15 +94,33 @@ impl Rope { } } - pub fn concat(first: Rc, second: Rc) -> Rc { - Rope::join(first, Some(second)).rebalance() + /// 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.") + } } /// 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) { + pub fn split_at_char_index(self: &Rc, index: usize) -> (Rc, Rc) { match *self.as_ref() { Rope::Branch { chars_weight, @@ -111,14 +129,14 @@ impl Rope { .. } => { if index < chars_weight { - let (first, second) = left.split(index); + 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(index - chars_weight); + let (first, second) = right.split_at_char_index(index - chars_weight); ( Rope::join(left.clone(), Some(first)).rebalance(), second.rebalance(), @@ -430,7 +448,7 @@ mod tests { 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, 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]); @@ -446,7 +464,7 @@ mod tests { .chars() .collect(); for i in 0..(expected_chars.len()) { - let (first, second) = target.split(i); + 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); @@ -525,4 +543,19 @@ mod tests { 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."); + } }