Compare commits
No commits in common. "7b7e307a51c45270c5f909fc49ad27c9d020b870" and "2b959e5a68841bcf75db79f1323cfd450fb542d6" have entirely different histories.
7b7e307a51
...
2b959e5a68
|
|
@ -5,4 +5,4 @@ edition = "2021"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ntest = "0.9.3"
|
ntest = "0.9.3"
|
||||||
rand = {version="0.9.2", features=["small_rng", "alloc"]}
|
rand = {version="0.8.5", features=["small_rng"]}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_first_few_values() {
|
fn test_first_few_values() {
|
||||||
let expected_values = [
|
let expected_values = vec![
|
||||||
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,
|
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,
|
||||||
6765, 10946,
|
6765, 10946,
|
||||||
];
|
];
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
mod text_buffer;
|
pub mod rope;
|
||||||
pub use text_buffer::TextBuffer;
|
|
||||||
|
mod fibbonacci;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
use std::rc::Rc;
|
use super::fibbonacci::fibbonacci;
|
||||||
|
pub use std::rc::Rc;
|
||||||
mod fibbonacci;
|
|
||||||
use fibbonacci::fibbonacci;
|
|
||||||
|
|
||||||
/// [Rope](https://en.wikipedia.org/wiki/Rope_(data_structure)) data structure
|
/// [Rope](https://en.wikipedia.org/wiki/Rope_(data_structure)) data structure
|
||||||
/// implementation.
|
/// implementation.
|
||||||
|
|
@ -93,29 +91,7 @@ impl Rope {
|
||||||
right: Some(right),
|
right: Some(right),
|
||||||
..
|
..
|
||||||
} => lines_weight + right.total_lines(),
|
} => lines_weight + right.total_lines(),
|
||||||
Rope::Leaf { text } => text.chars().filter(|&c| c == '\n').count(),
|
Rope::Leaf { text } => text.lines().count(),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the character as a given character index.
|
|
||||||
pub fn get_char_at_index(&self, index: usize) -> char {
|
|
||||||
match self {
|
|
||||||
Rope::Branch {
|
|
||||||
left, right: None, ..
|
|
||||||
} => left.get_char_at_index(index),
|
|
||||||
Rope::Branch {
|
|
||||||
chars_weight,
|
|
||||||
left,
|
|
||||||
right: Some(right),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if index < *chars_weight {
|
|
||||||
left.get_char_at_index(index)
|
|
||||||
} else {
|
|
||||||
right.get_char_at_index(index - chars_weight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Rope::Leaf { text } => text.chars().nth(index).unwrap(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,7 +303,7 @@ impl Iterator for CharIterator {
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
match self {
|
match self {
|
||||||
CharIterator(inner) => inner.next().map(|p| p.character),
|
CharIterator(inner) => inner.next().map(|p| p.character)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::super::{Rc, Rope};
|
use super::super::{Rc, Rope};
|
||||||
use rand::{
|
use rand::{
|
||||||
distr::{Alphanumeric, SampleString},
|
distributions::{Alphanumeric, DistString},
|
||||||
rngs::SmallRng,
|
rngs::SmallRng,
|
||||||
Rng, SeedableRng,
|
Rng, SeedableRng,
|
||||||
};
|
};
|
||||||
|
|
@ -45,21 +45,21 @@ pub fn generate_random_edit_sequence_with_seed(
|
||||||
seed: u64,
|
seed: u64,
|
||||||
) -> (String, Vec<(Command, String)>) {
|
) -> (String, Vec<(Command, String)>) {
|
||||||
let mut rng = SmallRng::seed_from_u64(seed);
|
let mut rng = SmallRng::seed_from_u64(seed);
|
||||||
let start_text_length = rng.random_range(0..length);
|
let start_text_length = rng.gen_range(0..4000);
|
||||||
let start_text = Alphanumeric.sample_string(&mut rng, start_text_length);
|
let start_text = Alphanumeric.sample_string(&mut rng, start_text_length);
|
||||||
let num_steps = rng.random_range(0..1000);
|
let num_steps = rng.gen_range(0..1000);
|
||||||
let mut steps = Vec::with_capacity(num_steps);
|
let mut steps = Vec::with_capacity(num_steps);
|
||||||
let mut current_text = start_text.clone();
|
let mut current_text = start_text.clone();
|
||||||
for _ in 0..num_steps {
|
for i in 0..num_steps {
|
||||||
let current_text_length = current_text.len();
|
let current_text_length = current_text.len();
|
||||||
let command = if rng.random_bool(0.7) || current_text_length > 0 {
|
let command = if rng.gen_bool(0.7) || current_text_length > 0 {
|
||||||
let index = rng.random_range(0..current_text_length);
|
let index = rng.gen_range(0..current_text_length);
|
||||||
let text_len = rng.random_range(0..100);
|
let text_len = rng.gen_range(0..100);
|
||||||
let text = Alphanumeric.sample_string(&mut rng, text_len);
|
let text = Alphanumeric.sample_string(&mut rng, text_len);
|
||||||
Command::InsertAtCharIndex { index, text }
|
Command::InsertAtCharIndex { index, text }
|
||||||
} else {
|
} else {
|
||||||
let index = rng.random_range(0..current_text_length-1);
|
let index = rng.gen_range(0..current_text_length-1);
|
||||||
let length = rng.random_range(1..(current_text_length - index));
|
let length = rng.gen_range(1..(current_text_length - index));
|
||||||
Command::DeleteAtCharIndex { index, length }
|
Command::DeleteAtCharIndex { index, length }
|
||||||
};
|
};
|
||||||
current_text = command.run_on_string(current_text);
|
current_text = command.run_on_string(current_text);
|
||||||
|
|
@ -80,7 +80,7 @@ fn node_iterator_for_single_node_returns_node_and_only_node() {
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(result[0].total_bytes(), 3);
|
assert_eq!(result[0].total_bytes(), 3);
|
||||||
assert_eq!(result[0].total_chars(), 3);
|
assert_eq!(result[0].total_chars(), 3);
|
||||||
assert_eq!(result[0].total_lines(), 0);
|
assert_eq!(result[0].total_lines(), 1);
|
||||||
match result[0].as_ref() {
|
match result[0].as_ref() {
|
||||||
Rope::Leaf { text, .. } => assert_eq!(text, "The"),
|
Rope::Leaf { text, .. } => assert_eq!(text, "The"),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
|
@ -217,24 +217,13 @@ fn depths_have_correct_values() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_char_at_index() {
|
|
||||||
let target = small_test_rope_with_multibyte_chars();
|
|
||||||
let expected = small_test_rope_with_multibyte_chars_full_string();
|
|
||||||
for (i, c) in expected.chars().enumerate() {
|
|
||||||
assert_eq!(target.get_char_at_index(i), c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_at_char_index() {
|
fn insert_at_char_index() {
|
||||||
let target = Rope::new("The brown dog");
|
let target = Rope::new("The brown dog");
|
||||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||||
assert_eq!(0, target.total_lines());
|
|
||||||
let rope1 = target.insert_at_char_index(4, "quick");
|
let rope1 = target.insert_at_char_index(4, "quick");
|
||||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||||
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
||||||
assert_eq!(0, rope1.total_lines());
|
|
||||||
let rope2 = rope1.insert_at_char_index(9, " ");
|
let rope2 = rope1.insert_at_char_index(9, " ");
|
||||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||||
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
||||||
|
|
@ -242,14 +231,12 @@ fn insert_at_char_index() {
|
||||||
rope2.iter_chars().collect::<String>(),
|
rope2.iter_chars().collect::<String>(),
|
||||||
"The quick brown dog"
|
"The quick brown dog"
|
||||||
);
|
);
|
||||||
assert_eq!(0, rope2.total_lines());
|
|
||||||
let rope3 =
|
let rope3 =
|
||||||
rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox.");
|
rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox.");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rope3.iter_chars().collect::<String>(),
|
rope3.iter_chars().collect::<String>(),
|
||||||
"The quick brown dog jumps over the lazy fox."
|
"The quick brown dog jumps over the lazy fox."
|
||||||
);
|
);
|
||||||
assert_eq!(0, rope3.total_lines());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
mod rope;
|
|
||||||
use rope::Rope;
|
|
||||||
|
|
||||||
pub struct TextBuffer {
|
|
||||||
contents: Rc<Rope>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Point {
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextBuffer {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
contents: Rope::empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn num_bytes(&self) -> usize {
|
|
||||||
self.contents.total_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn num_chars(&self) -> usize {
|
|
||||||
self.contents.total_chars()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn num_lines(&self) -> usize {
|
|
||||||
let num_chars = self.num_chars();
|
|
||||||
if num_chars == 0 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
dbg!(self.contents.total_lines())
|
|
||||||
+ if self
|
|
||||||
.contents
|
|
||||||
.get_char_at_index(self.contents.total_chars() - 1)
|
|
||||||
== '\n'
|
|
||||||
{
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_char(&mut self, c: char, point: Point) {
|
|
||||||
match point {
|
|
||||||
Point::End => {
|
|
||||||
self.contents = self
|
|
||||||
.contents
|
|
||||||
.insert_at_char_index(self.contents.total_chars(), c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TextBuffer {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_creates_empty_buffer() {
|
|
||||||
let target = TextBuffer::new();
|
|
||||||
assert_eq!(0, target.num_bytes());
|
|
||||||
assert_eq!(0, target.num_chars());
|
|
||||||
assert_eq!(0, target.num_lines());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn insert_char_at_end_increases_counts_as_expected() {
|
|
||||||
let mut target = TextBuffer::new();
|
|
||||||
target.insert_char('A', Point::End);
|
|
||||||
assert_eq!(1, target.num_bytes());
|
|
||||||
assert_eq!(1, target.num_chars());
|
|
||||||
assert_eq!(1, target.num_lines());
|
|
||||||
target.insert_char(' ', Point::End);
|
|
||||||
assert_eq!(2, target.num_bytes());
|
|
||||||
assert_eq!(2, target.num_chars());
|
|
||||||
assert_eq!(1, target.num_lines());
|
|
||||||
target.insert_char('c', Point::End);
|
|
||||||
assert_eq!(3, target.num_bytes());
|
|
||||||
assert_eq!(3, target.num_chars());
|
|
||||||
assert_eq!(1, target.num_lines());
|
|
||||||
target.insert_char('a', Point::End);
|
|
||||||
assert_eq!(4, target.num_bytes());
|
|
||||||
assert_eq!(4, target.num_chars());
|
|
||||||
assert_eq!(1, target.num_lines());
|
|
||||||
target.insert_char('t', Point::End);
|
|
||||||
assert_eq!(5, target.num_bytes());
|
|
||||||
assert_eq!(5, target.num_chars());
|
|
||||||
assert_eq!(1, target.num_lines());
|
|
||||||
target.insert_char('\n', Point::End);
|
|
||||||
assert_eq!(6, target.num_bytes());
|
|
||||||
assert_eq!(6, target.num_chars());
|
|
||||||
assert_eq!(1, target.num_lines());
|
|
||||||
target.insert_char('A', Point::End);
|
|
||||||
assert_eq!(7, target.num_bytes());
|
|
||||||
assert_eq!(7, target.num_chars());
|
|
||||||
assert_eq!(2, target.num_lines());
|
|
||||||
target.insert_char('\n', Point::End);
|
|
||||||
assert_eq!(8, target.num_bytes());
|
|
||||||
assert_eq!(8, target.num_chars());
|
|
||||||
assert_eq!(2, target.num_lines());
|
|
||||||
target.insert_char('\n', Point::End);
|
|
||||||
assert_eq!(9, target.num_bytes());
|
|
||||||
assert_eq!(9, target.num_chars());
|
|
||||||
assert_eq!(3, target.num_lines());
|
|
||||||
target.insert_char('*', Point::End);
|
|
||||||
assert_eq!(10, target.num_bytes());
|
|
||||||
assert_eq!(10, target.num_chars());
|
|
||||||
assert_eq!(4, target.num_lines());
|
|
||||||
target.insert_char('*', Point::End);
|
|
||||||
assert_eq!(11, target.num_bytes());
|
|
||||||
assert_eq!(11, target.num_chars());
|
|
||||||
assert_eq!(4, target.num_lines());
|
|
||||||
target.insert_char(' ', Point::End);
|
|
||||||
assert_eq!(12, target.num_bytes());
|
|
||||||
assert_eq!(12, target.num_chars());
|
|
||||||
assert_eq!(4, target.num_lines());
|
|
||||||
target.insert_char('猫', Point::End);
|
|
||||||
assert_eq!(15, target.num_bytes());
|
|
||||||
assert_eq!(13, target.num_chars());
|
|
||||||
assert_eq!(4, target.num_lines());
|
|
||||||
target.insert_char(' ', Point::End);
|
|
||||||
assert_eq!(16, target.num_bytes());
|
|
||||||
assert_eq!(14, target.num_chars());
|
|
||||||
assert_eq!(4, target.num_lines());
|
|
||||||
target.insert_char('\n', Point::End);
|
|
||||||
assert_eq!(17, target.num_bytes());
|
|
||||||
assert_eq!(15, target.num_chars());
|
|
||||||
assert_eq!(4, target.num_lines());
|
|
||||||
target.insert_char('_', Point::End);
|
|
||||||
assert_eq!(18, target.num_bytes());
|
|
||||||
assert_eq!(16, target.num_chars());
|
|
||||||
assert_eq!(5, target.num_lines());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue