Compare commits
4 Commits
2b959e5a68
...
7b7e307a51
| Author | SHA1 | Date |
|---|---|---|
|
|
7b7e307a51 | |
|
|
a621985347 | |
|
|
fff7595b9a | |
|
|
aa250a8145 |
|
|
@ -5,4 +5,4 @@ edition = "2021"
|
|||
|
||||
[dev-dependencies]
|
||||
ntest = "0.9.3"
|
||||
rand = {version="0.8.5", features=["small_rng"]}
|
||||
rand = {version="0.9.2", features=["small_rng", "alloc"]}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
pub mod rope;
|
||||
|
||||
mod fibbonacci;
|
||||
mod text_buffer;
|
||||
pub use text_buffer::TextBuffer;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_first_few_values() {
|
||||
let expected_values = vec![
|
||||
let expected_values = [
|
||||
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,
|
||||
6765, 10946,
|
||||
];
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use super::fibbonacci::fibbonacci;
|
||||
pub use std::rc::Rc;
|
||||
use std::rc::Rc;
|
||||
|
||||
mod fibbonacci;
|
||||
use fibbonacci::fibbonacci;
|
||||
|
||||
/// [Rope](https://en.wikipedia.org/wiki/Rope_(data_structure)) data structure
|
||||
/// implementation.
|
||||
|
|
@ -91,7 +93,29 @@ impl Rope {
|
|||
right: Some(right),
|
||||
..
|
||||
} => lines_weight + right.total_lines(),
|
||||
Rope::Leaf { text } => text.lines().count(),
|
||||
Rope::Leaf { text } => text.chars().filter(|&c| c == '\n').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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +327,7 @@ impl Iterator for CharIterator {
|
|||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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 rand::{
|
||||
distributions::{Alphanumeric, DistString},
|
||||
distr::{Alphanumeric, SampleString},
|
||||
rngs::SmallRng,
|
||||
Rng, SeedableRng,
|
||||
};
|
||||
|
|
@ -45,21 +45,21 @@ pub fn generate_random_edit_sequence_with_seed(
|
|||
seed: u64,
|
||||
) -> (String, Vec<(Command, String)>) {
|
||||
let mut rng = SmallRng::seed_from_u64(seed);
|
||||
let start_text_length = rng.gen_range(0..4000);
|
||||
let start_text_length = rng.random_range(0..length);
|
||||
let start_text = Alphanumeric.sample_string(&mut rng, start_text_length);
|
||||
let num_steps = rng.gen_range(0..1000);
|
||||
let num_steps = rng.random_range(0..1000);
|
||||
let mut steps = Vec::with_capacity(num_steps);
|
||||
let mut current_text = start_text.clone();
|
||||
for i in 0..num_steps {
|
||||
for _ in 0..num_steps {
|
||||
let current_text_length = current_text.len();
|
||||
let command = if rng.gen_bool(0.7) || current_text_length > 0 {
|
||||
let index = rng.gen_range(0..current_text_length);
|
||||
let text_len = rng.gen_range(0..100);
|
||||
let command = if rng.random_bool(0.7) || current_text_length > 0 {
|
||||
let index = rng.random_range(0..current_text_length);
|
||||
let text_len = rng.random_range(0..100);
|
||||
let text = Alphanumeric.sample_string(&mut rng, text_len);
|
||||
Command::InsertAtCharIndex { index, text }
|
||||
} else {
|
||||
let index = rng.gen_range(0..current_text_length-1);
|
||||
let length = rng.gen_range(1..(current_text_length - index));
|
||||
let index = rng.random_range(0..current_text_length-1);
|
||||
let length = rng.random_range(1..(current_text_length - index));
|
||||
Command::DeleteAtCharIndex { index, length }
|
||||
};
|
||||
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[0].total_bytes(), 3);
|
||||
assert_eq!(result[0].total_chars(), 3);
|
||||
assert_eq!(result[0].total_lines(), 1);
|
||||
assert_eq!(result[0].total_lines(), 0);
|
||||
match result[0].as_ref() {
|
||||
Rope::Leaf { text, .. } => assert_eq!(text, "The"),
|
||||
_ => panic!(),
|
||||
|
|
@ -217,13 +217,24 @@ 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]
|
||||
fn insert_at_char_index() {
|
||||
let target = Rope::new("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");
|
||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown 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, " ");
|
||||
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
|
||||
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
|
||||
|
|
@ -231,12 +242,14 @@ fn insert_at_char_index() {
|
|||
rope2.iter_chars().collect::<String>(),
|
||||
"The quick brown dog"
|
||||
);
|
||||
assert_eq!(0, rope2.total_lines());
|
||||
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."
|
||||
);
|
||||
assert_eq!(0, rope3.total_lines());
|
||||
}
|
||||
|
||||
#[test]
|
||||
Loading…
Reference in New Issue