Add Rope::split() and Rope::empty()

This commit is contained in:
Matthew Gordon 2024-10-19 14:22:53 -03:00
parent f85a771071
commit a22968456c
1 changed files with 150 additions and 38 deletions

View File

@ -37,6 +37,12 @@ impl Rope {
Rc::new(Rope::Leaf { text })
}
/// Create a new empty Rope
pub fn empty() -> Rc<Self> {
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<Rope>, right: Rc<Rope>) -> Rc<Self> {
/// `right` as children. If `right` is [None] then the resulting node will
/// only have one child.
fn join(left: Rc<Rope>, right: Option<Rc<Rope>>) -> Rc<Self> {
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<Self>, index: usize) -> (Rc<Self>, Rc<Self>) {
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<Rope>) -> Rc<Rope> {
// 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<Self::Item> {
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> {
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> {
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);
}
}
}