Make rope module directory
Replace `rope.rs` with `rope/mod.rs` and `rope/tests/mod.rs` in preparation for adding more elaborate testing for `Rope`.
This commit is contained in:
parent
c2f71aac22
commit
454e2f12fe
865
core/src/rope.rs
865
core/src/rope.rs
|
|
@ -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<Rope>,
|
||||
/// The root of the right subtree
|
||||
right: Option<Rc<Rope>>,
|
||||
},
|
||||
Leaf {
|
||||
text: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Rope {
|
||||
/// Create a new Rope containing the passed text in a single node.
|
||||
pub fn new(contents: impl Into<String>) -> Rc<Self> {
|
||||
let text = contents.into();
|
||||
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 {
|
||||
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<Rope>, other: Rc<Rope>) -> Rc<Rope> {
|
||||
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<Rope>,
|
||||
index: usize,
|
||||
text: impl Into<String>,
|
||||
) -> Rc<Rope> {
|
||||
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<Rope>, start: usize, length: usize) -> Rc<Rope> {
|
||||
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<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_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<Rope>) -> Rc<Rope> {
|
||||
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<Self>) -> 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<Self>) -> 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<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,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterater over the leaf nodes
|
||||
fn iter_nodes(self: Rc<Self>) -> NodeIterator {
|
||||
NodeIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(leaf_nodes: &[Rc<Rope>]) -> Rc<Rope> {
|
||||
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<Rc<Rope>>,
|
||||
}
|
||||
|
||||
impl NodeIterator {
|
||||
fn new(rope: Rc<Rope>) -> Self {
|
||||
let mut result = NodeIterator { stack: vec![] };
|
||||
result.push_tree(rope);
|
||||
result
|
||||
}
|
||||
|
||||
fn push_tree(&mut self, node: Rc<Rope>) {
|
||||
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<Rope>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<Rope>) -> Self {
|
||||
CharIterator(CharWithPointIterator::new(rope))
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for CharIterator {
|
||||
type Item = char;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
CharIterator(inner) => inner.next().map(|p| p.character)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CharWithPointIterator {
|
||||
node_iterator: NodeIterator,
|
||||
current_text: Option<String>,
|
||||
str_index: usize,
|
||||
line_num: usize,
|
||||
char_num: usize,
|
||||
eol: bool,
|
||||
}
|
||||
|
||||
impl CharWithPointIterator {
|
||||
fn new(rope: &Rc<Rope>) -> 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<String> {
|
||||
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::Item> {
|
||||
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> {
|
||||
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"
|
||||
}
|
||||
|
||||
impl Rope {
|
||||
fn unwrap_branch_left(&self) -> &Rc<Rope> {
|
||||
match self {
|
||||
Rope::Branch { left, .. } => left,
|
||||
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_branch_right(&self) -> Option<&Rc<Rope>> {
|
||||
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::<String>(), "The brown dog");
|
||||
let rope1 = target.insert_at_char_index(4, "quick");
|
||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
||||
let rope2 = rope1.insert_at_char_index(9, " ");
|
||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
||||
assert_eq!(
|
||||
rope2.iter_chars().collect::<String>(),
|
||||
"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::<String>(),
|
||||
"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::<String>(),
|
||||
"The quick brown fox jumps over the lazy dog."
|
||||
);
|
||||
assert_eq!(
|
||||
test.iter_chars().collect::<String>(),
|
||||
"The quick fox jumps over the lazy dog."
|
||||
);
|
||||
let test = target.delete_at_char_index(0, 4);
|
||||
assert_eq!(
|
||||
target.iter_chars().collect::<String>(),
|
||||
"The quick brown fox jumps over the lazy dog."
|
||||
);
|
||||
assert_eq!(
|
||||
test.iter_chars().collect::<String>(),
|
||||
"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::<String>(),
|
||||
"The quick brown fox jumps over the lazy dog."
|
||||
);
|
||||
assert_eq!(
|
||||
test.iter_chars().collect::<String>(),
|
||||
"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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Rope>,
|
||||
/// The root of the right subtree
|
||||
right: Option<Rc<Rope>>,
|
||||
},
|
||||
Leaf {
|
||||
text: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Rope {
|
||||
/// Create a new Rope containing the passed text in a single node.
|
||||
pub fn new(contents: impl Into<String>) -> Rc<Self> {
|
||||
let text = contents.into();
|
||||
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 {
|
||||
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<Rope>, other: Rc<Rope>) -> Rc<Rope> {
|
||||
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<Rope>,
|
||||
index: usize,
|
||||
text: impl Into<String>,
|
||||
) -> Rc<Rope> {
|
||||
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<Rope>, start: usize, length: usize) -> Rc<Rope> {
|
||||
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<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_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<Rope>) -> Rc<Rope> {
|
||||
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<Self>) -> 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<Self>) -> 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<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,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterater over the leaf nodes
|
||||
fn iter_nodes(self: Rc<Self>) -> NodeIterator {
|
||||
NodeIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(leaf_nodes: &[Rc<Rope>]) -> Rc<Rope> {
|
||||
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<Rc<Rope>>,
|
||||
}
|
||||
|
||||
impl NodeIterator {
|
||||
fn new(rope: Rc<Rope>) -> Self {
|
||||
let mut result = NodeIterator { stack: vec![] };
|
||||
result.push_tree(rope);
|
||||
result
|
||||
}
|
||||
|
||||
fn push_tree(&mut self, node: Rc<Rope>) {
|
||||
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<Rope>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<Rope>) -> Self {
|
||||
CharIterator(CharWithPointIterator::new(rope))
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for CharIterator {
|
||||
type Item = char;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
CharIterator(inner) => inner.next().map(|p| p.character)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CharWithPointIterator {
|
||||
node_iterator: NodeIterator,
|
||||
current_text: Option<String>,
|
||||
str_index: usize,
|
||||
line_num: usize,
|
||||
char_num: usize,
|
||||
eol: bool,
|
||||
}
|
||||
|
||||
impl CharWithPointIterator {
|
||||
fn new(rope: &Rc<Rope>) -> 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<String> {
|
||||
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::Item> {
|
||||
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;
|
||||
|
|
@ -0,0 +1,471 @@
|
|||
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"
|
||||
}
|
||||
|
||||
impl Rope {
|
||||
fn unwrap_branch_left(&self) -> &Rc<Rope> {
|
||||
match self {
|
||||
Rope::Branch { left, .. } => left,
|
||||
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_branch_right(&self) -> Option<&Rc<Rope>> {
|
||||
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::<String>(), "The brown dog");
|
||||
let rope1 = target.insert_at_char_index(4, "quick");
|
||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
||||
let rope2 = rope1.insert_at_char_index(9, " ");
|
||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
||||
assert_eq!(
|
||||
rope2.iter_chars().collect::<String>(),
|
||||
"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::<String>(),
|
||||
"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::<String>(),
|
||||
"The quick brown fox jumps over the lazy dog."
|
||||
);
|
||||
assert_eq!(
|
||||
test.iter_chars().collect::<String>(),
|
||||
"The quick fox jumps over the lazy dog."
|
||||
);
|
||||
let test = target.delete_at_char_index(0, 4);
|
||||
assert_eq!(
|
||||
target.iter_chars().collect::<String>(),
|
||||
"The quick brown fox jumps over the lazy dog."
|
||||
);
|
||||
assert_eq!(
|
||||
test.iter_chars().collect::<String>(),
|
||||
"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::<String>(),
|
||||
"The quick brown fox jumps over the lazy dog."
|
||||
);
|
||||
assert_eq!(
|
||||
test.iter_chars().collect::<String>(),
|
||||
"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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue