Compare commits

...

4 Commits

7 changed files with 201 additions and 21 deletions

View File

@ -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"]}

View File

@ -1,3 +1,2 @@
pub mod rope;
mod fibbonacci;
mod text_buffer;
pub use text_buffer::TextBuffer;

144
core/src/text_buffer/mod.rs Normal file
View File

@ -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());
}
}

View File

@ -4,8 +4,8 @@ pub fn fibbonacci(index: usize) -> usize {
} else if index < 3 {
1
} else {
let mut values = vec![1;index+1];
for i in 3..index+1 {
let mut values = vec![1; index + 1];
for i in 3..index + 1 {
values[i] = values[i - 1] + values[i - 2];
}
values[index]
@ -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,
];

View File

@ -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),
}
}
}

View File

@ -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);

View File

@ -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]