diff --git a/core/src/lib.rs b/core/src/lib.rs index 6a1ef73..142fe23 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,3 +1,2 @@ -pub mod rope; - -mod fibbonacci; +mod text_buffer; +pub use text_buffer::TextBuffer; diff --git a/core/src/text_buffer/mod.rs b/core/src/text_buffer/mod.rs new file mode 100644 index 0000000..b673f2a --- /dev/null +++ b/core/src/text_buffer/mod.rs @@ -0,0 +1,138 @@ +use std::rc::Rc; + +mod rope; +use rope::Rope; + +pub struct TextBuffer { + contents: Rc, +} + +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) + } + } + } +} + +#[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()); + } +} diff --git a/core/src/fibbonacci.rs b/core/src/text_buffer/rope/fibbonacci.rs similarity index 100% rename from core/src/fibbonacci.rs rename to core/src/text_buffer/rope/fibbonacci.rs diff --git a/core/src/rope/mod.rs b/core/src/text_buffer/rope/mod.rs similarity index 93% rename from core/src/rope/mod.rs rename to core/src/text_buffer/rope/mod.rs index 10d6b12..4cb1e83 100644 --- a/core/src/rope/mod.rs +++ b/core/src/text_buffer/rope/mod.rs @@ -1,6 +1,8 @@ -use super::fibbonacci::fibbonacci; 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 { match self { - CharIterator(inner) => inner.next().map(|p| p.character) + CharIterator(inner) => inner.next().map(|p| p.character), } } } diff --git a/core/src/rope/tests/command_list.rs b/core/src/text_buffer/rope/tests/command_list.rs similarity index 100% rename from core/src/rope/tests/command_list.rs rename to core/src/text_buffer/rope/tests/command_list.rs diff --git a/core/src/rope/tests/mod.rs b/core/src/text_buffer/rope/tests/mod.rs similarity index 97% rename from core/src/rope/tests/mod.rs rename to core/src/text_buffer/rope/tests/mod.rs index 4c0a259..9d54188 100644 --- a/core/src/rope/tests/mod.rs +++ b/core/src/text_buffer/rope/tests/mod.rs @@ -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::(), "The brown dog"); + assert_eq!(0, target.total_lines()); let rope1 = target.insert_at_char_index(4, "quick"); assert_eq!(target.iter_chars().collect::(), "The brown dog"); assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); + assert_eq!(0, rope1.total_lines()); let rope2 = rope1.insert_at_char_index(9, " "); assert_eq!(target.iter_chars().collect::(), "The brown dog"); assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); @@ -231,12 +242,14 @@ fn insert_at_char_index() { rope2.iter_chars().collect::(), "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::(), "The quick brown dog jumps over the lazy fox." ); + assert_eq!(0, rope3.total_lines()); } #[test]