diff --git a/core/src/rope.rs b/core/src/rope.rs index ff314a4..57bdd32 100644 --- a/core/src/rope.rs +++ b/core/src/rope.rs @@ -213,6 +213,12 @@ impl Rope { 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) -> CharWithPointIterator { + CharWithPointIterator::new(self) + } + /// Concatenate two Ropes without rebalancing. /// /// Combines to ropes by creating a new parent node and adding `left` and @@ -283,20 +289,44 @@ impl Iterator for NodeIterator { } } -pub struct CharIterator { - node_iterator: NodeIterator, - current_text: Option, - str_index: usize, -} +pub struct CharIterator(CharWithPointIterator); impl CharIterator { + fn new(rope: &Rc) -> Self { + CharIterator(CharWithPointIterator::new(rope)) + } +} + +impl Iterator for CharIterator { + type Item = char; + + fn next(&mut self) -> Option { + match self { + CharIterator(inner) => inner.next().map(|p| p.character) + } + } +} + +pub struct CharWithPointIterator { + node_iterator: NodeIterator, + current_text: Option, + str_index: usize, + line_num: usize, + char_num: usize, + eol: bool, +} + +impl CharWithPointIterator { fn new(rope: &Rc) -> Self { let mut node_iterator = NodeIterator::new(rope.clone()); let current_text = Self::next_string(&mut node_iterator); - CharIterator { + CharWithPointIterator { node_iterator, current_text, str_index: 0, + char_num: 0, + line_num: 0, + eol: false, } } @@ -309,18 +339,41 @@ impl CharIterator { } }) } + + pub fn get_point(&self) -> (usize, usize) { + (self.line_num, self.char_num) + } } -impl Iterator for CharIterator { - type Item = char; +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.current_text .as_ref() .and_then(|text| { - let c = text.chars().nth(self.str_index); + let next_char = text.chars().nth(self.str_index); self.str_index += 1; - c + 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); @@ -581,13 +634,232 @@ mod tests { 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::(), "The quick brown fox jumps over the lazy dog."); - assert_eq!(test.iter_chars().collect::(), "The quick fox jumps over the lazy dog."); + assert_eq!( + target.iter_chars().collect::(), + "The quick brown fox jumps over the lazy dog." + ); + assert_eq!( + test.iter_chars().collect::(), + "The quick fox jumps over the lazy dog." + ); let test = target.delete_at_char_index(0, 4); - assert_eq!(target.iter_chars().collect::(), "The quick brown fox jumps over the lazy dog."); - assert_eq!(test.iter_chars().collect::(), "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::(), "The quick brown fox jumps over the lazy dog."); - assert_eq!(test.iter_chars().collect::(), "The quick brown fox jumps over the lazy"); + assert_eq!( + target.iter_chars().collect::(), + "The quick brown fox jumps over the lazy dog." + ); + assert_eq!( + test.iter_chars().collect::(), + "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::(), + "The quick brown fox jumps over the lazy dog." + ); + assert_eq!( + test.iter_chars().collect::(), + "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); + } } }