diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs deleted file mode 100644 index 7b13630..0000000 --- a/src/buffer/mod.rs +++ /dev/null @@ -1,1488 +0,0 @@ -#![allow(dead_code)] - -mod undo_stack; - -use std::{ - fs::File, - io::{self, BufReader, BufWriter, Write}, - path::{Path, PathBuf}, -}; - -use ropey::{self, Rope, RopeSlice}; - -use crate::{ - string_utils::char_count, - utils::{is_grapheme_boundary, next_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes}, -}; - -use self::undo_stack::{Operation::*, UndoStack}; - -// ============================================================= -// Buffer -// ============================================================= - -/// A text buffer -pub struct Buffer { - pub text: Rope, - file_path: Option, - undo_stack: UndoStack, -} - -impl Buffer { - pub fn new() -> Buffer { - Buffer { - text: Rope::new(), - file_path: None, - undo_stack: UndoStack::new(), - } - } - - pub fn new_from_str(s: &str) -> Buffer { - Buffer { - text: Rope::from_str(s), - file_path: None, - undo_stack: UndoStack::new(), - } - } - - pub fn new_from_file(path: &Path) -> io::Result { - let buf = Buffer { - text: Rope::from_reader(BufReader::new(File::open(path)?))?, - file_path: Some(path.to_path_buf()), - undo_stack: UndoStack::new(), - }; - - return Ok(buf); - } - - pub fn save_to_file(&self, path: &Path) -> io::Result<()> { - let mut f = BufWriter::new(File::create(path)?); - - for c in self.text.chunks() { - let _ = f.write(c.as_bytes()); - } - - return Ok(()); - } - - // ------------------------------------------------------------------------ - // Functions for getting information about the buffer. - // ------------------------------------------------------------------------ - - pub fn char_count(&self) -> usize { - self.text.len_chars() - } - - pub fn is_grapheme(&self, char_idx: usize) -> bool { - is_grapheme_boundary(&self.text.slice(..), char_idx) - } - - /// Finds the nth next grapheme boundary after the given char position. - pub fn nth_next_grapheme(&self, char_idx: usize, n: usize) -> usize { - let mut char_idx = char_idx; - for _ in 0..n { - char_idx = next_grapheme_boundary(&self.text.slice(..), char_idx); - } - char_idx - } - - /// Finds the nth previous grapheme boundary before the given char position. - pub fn nth_prev_grapheme(&self, char_idx: usize, n: usize) -> usize { - let mut char_idx = char_idx; - for _ in 0..n { - char_idx = prev_grapheme_boundary(&self.text.slice(..), char_idx); - } - char_idx - } - - pub fn line_count(&self) -> usize { - self.text.len_lines() - } - - // ------------------------------------------------------------------------ - // Editing operations - // ------------------------------------------------------------------------ - - /// Insert 'text' at grapheme position 'pos'. - pub fn insert_text(&mut self, text: &str, pos: usize) { - self.text.insert(pos, text); - - self.undo_stack.push(InsertText(text.to_string(), pos)); - } - - /// Remove the text before grapheme position 'pos' of length 'len'. - pub fn remove_text_before(&mut self, pos: usize, len: usize) { - if pos >= len { - let removed_text = self.text.slice((pos - len)..pos).to_string(); - - self.text.remove((pos - len)..pos); - - // Push operation to the undo stack - self.undo_stack - .push(RemoveTextBefore(removed_text, pos - len)); - } else { - panic!( - "Buffer::remove_text_before(): attempt to remove text before beginning of \ - buffer." - ); - } - } - - /// Remove the text after grapheme position 'pos' of length 'len'. - pub fn remove_text_after(&mut self, pos: usize, len: usize) { - let removed_text = self.text.slice(pos..(pos + len)).to_string(); - - self.text.remove(pos..(pos + len)); - - // Push operation to the undo stack - self.undo_stack.push(RemoveTextAfter(removed_text, pos)); - } - - /// Moves the text in [pos_a, pos_b) to begin at index pos_to. - /// - /// Note that pos_to is the desired index that the text will start at - /// _after_ the operation, not the index before the operation. This is a - /// subtle but important distinction. - pub fn move_text(&mut self, pos_a: usize, pos_b: usize, pos_to: usize) { - self._move_text(pos_a, pos_b, pos_to); - - // Push operation to the undo stack - self.undo_stack.push(MoveText(pos_a, pos_b, pos_to)); - } - - fn _move_text(&mut self, pos_a: usize, pos_b: usize, pos_to: usize) { - // Nothing to do - if pos_a == pos_b || pos_a == pos_to { - return; - } - // Bounds error - else if pos_a > pos_b { - panic!("Buffer::_move_text(): pos_a must be less than or equal to pos_b."); - } - // Bounds error - else if pos_b > self.text.len_chars() { - panic!("Buffer::_move_text(): specified text range is beyond end of buffer."); - } - // Bounds error - else if pos_to > (self.text.len_chars() - (pos_b - pos_a)) { - panic!("Buffer::_move_text(): specified text destination is beyond end of buffer."); - } - // Nothing to do, because entire text specified - else if pos_a == 0 && pos_b == self.text.len_chars() { - return; - } - // All other cases - else { - // TODO: a more efficient implementation that directly - // manipulates the node tree. - let s = self.text.slice(pos_a..pos_b).to_string(); - self.text.remove(pos_a..pos_b); - self.text.insert(pos_to, &s); - } - } - - /// Removes the lines in line indices [line_a, line_b). - /// TODO: undo - pub fn remove_lines(&mut self, line_a: usize, line_b: usize) { - // Nothing to do - if line_a == line_b { - return; - } - // Bounds error - else if line_a > line_b { - panic!("Buffer::remove_lines(): line_a must be less than or equal to line_b."); - } - // Bounds error - else if line_b > self.line_count() { - panic!("Buffer::remove_lines(): attempt to remove lines past the last line of text."); - } - // All other cases - else { - let a = if line_a == 0 { - 0 - } else if line_a == self.text.len_lines() { - self.text.len_chars() - } else { - self.text.line_to_char(line_a) - 1 - }; - let b = if line_b == 0 { - 0 - } else if line_b == self.text.len_lines() { - self.text.len_chars() - } else if line_a == 0 { - self.text.line_to_char(line_b) - } else { - self.text.line_to_char(line_b) - 1 - }; - - self.text.remove(a..b); - } - } - - // ------------------------------------------------------------------------ - // Undo/redo functionality - // ------------------------------------------------------------------------ - - /// Undoes operations that were pushed to the undo stack, and returns a - /// cursor position that the cursor should jump to, if any. - pub fn undo(&mut self) -> Option { - if let Some(op) = self.undo_stack.prev() { - match op { - InsertText(ref s, p) => { - let size = char_count(s); - self.text.remove(p..(p + size)); - return Some(p); - } - - RemoveTextBefore(ref s, p) => { - let size = char_count(s); - self.text.insert(p, s); - return Some(p + size); - } - - RemoveTextAfter(ref s, p) => { - self.text.insert(p, s); - return Some(p); - } - - MoveText(pa, pb, pto) => { - let size = pb - pa; - self._move_text(pto, pto + size, pa); - return Some(pa); - } - - _ => { - return None; - } - } - } - - return None; - } - - /// Redoes the last undone operation, and returns a cursor position that - /// the cursor should jump to, if any. - pub fn redo(&mut self) -> Option { - if let Some(op) = self.undo_stack.next() { - match op { - InsertText(ref s, p) => { - let size = char_count(s); - self.text.insert(p, s); - return Some(p + size); - } - - RemoveTextBefore(ref s, p) | RemoveTextAfter(ref s, p) => { - let size = char_count(s); - self.text.remove(p..(p + size)); - return Some(p); - } - - MoveText(pa, pb, pto) => { - self._move_text(pa, pb, pto); - return Some(pa); - } - - _ => { - return None; - } - } - } - - return None; - } - - // ------------------------------------------------------------------------ - // Position conversions - // ------------------------------------------------------------------------ - - /// Converts a char index into a line number and char-column - /// number. - /// - /// If the index is off the end of the text, returns the line and column - /// number of the last valid text position. - pub fn index_to_line_col(&self, pos: usize) -> (usize, usize) { - if pos < self.text.len_chars() { - let line = self.text.char_to_line(pos); - let line_pos = self.text.line_to_char(line); - - return (line, pos - line_pos); - } else { - let line = self.text.len_lines() - 1; - let line_pos = self.text.line_to_char(line); - - return (line, self.text.len_chars() - line_pos); - } - } - - /// Converts a line number and char-column number into a char - /// index. - /// - /// If the column number given is beyond the end of the line, returns the - /// index of the line's last valid position. If the line number given is - /// beyond the end of the buffer, returns the index of the buffer's last - /// valid position. - pub fn line_col_to_index(&self, pos: (usize, usize)) -> usize { - if pos.0 < self.text.len_lines() { - let l_start = self.text.line_to_char(pos.0); - let l_end = self.text.line_to_char(pos.0 + 1); - return (l_start + pos.1) - .min(l_start.max(l_end.saturating_sub(1))) - .min(self.text.len_chars()); - } else { - return self.text.len_chars(); - } - } - - // ------------------------------------------------------------------------ - // Text reading functions - // ------------------------------------------------------------------------ - - pub fn get_grapheme<'a>(&'a self, index: usize) -> RopeSlice<'a> { - RopeGraphemes::new(&self.text.slice(index..)) - .nth(0) - .unwrap() - } - - pub fn get_line<'a>(&'a self, index: usize) -> RopeSlice<'a> { - self.text.line(index) - } - - /// Creates a String from the buffer text in grapheme range [pos_a, posb). - fn string_from_range(&self, pos_a: usize, pos_b: usize) -> String { - self.text.slice(pos_a..pos_b).to_string() - } - - // ------------------------------------------------------------------------ - // Iterator creators - // ------------------------------------------------------------------------ - - /// Creates an iterator at the first character - pub fn grapheme_iter<'a>(&'a self) -> RopeGraphemes<'a> { - RopeGraphemes::new(&self.text.slice(..)) - } - - /// Creates an iterator starting at the specified grapheme index. - /// If the index is past the end of the text, then the iterator will - /// return None on next(). - pub fn grapheme_iter_at_index<'a>(&'a self, index: usize) -> RopeGraphemes<'a> { - let len = self.text.len_chars(); - RopeGraphemes::new(&self.text.slice(index..len)) - } - - pub fn line_iter<'a>(&'a self) -> ropey::iter::Lines<'a> { - self.text.lines() - } - - pub fn line_iter_at_index<'a>(&'a self, line_idx: usize) -> ropey::iter::Lines<'a> { - let start = self.text.line_to_char(line_idx); - self.text.slice(start..).lines() - } -} - -// ================================================================ -// TESTS -// ================================================================ - -#[cfg(test)] -mod tests { - #![allow(unused_imports)] - use super::*; - - #[test] - fn insert_text() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello 世界!", 0); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 9); - assert!(buf.line_count() == 1); - assert!("H" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!(" " == iter.next().unwrap()); - assert!("世" == iter.next().unwrap()); - assert!("界" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn insert_text_with_newlines() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 12); - assert!(buf.line_count() == 3); - assert!("H" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!(" " == iter.next().unwrap()); - assert!("世" == iter.next().unwrap()); - assert!("界" == iter.next().unwrap()); - assert!("\r\n" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn insert_text_in_non_empty_buffer_1() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - buf.insert_text("Again ", 0); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 18); - assert_eq!(buf.line_count(), 3); - assert_eq!("A", iter.next().unwrap()); - assert_eq!("g", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("世", iter.next().unwrap()); - assert_eq!("界", iter.next().unwrap()); - assert_eq!("\r\n", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn insert_text_in_non_empty_buffer_2() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - buf.insert_text(" again", 5); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 18); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("g", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("n", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("世", iter.next().unwrap()); - assert_eq!("界", iter.next().unwrap()); - assert_eq!("\r\n", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn insert_text_in_non_empty_buffer_3() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - buf.insert_text("again", 6); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 17); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("g", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("世", iter.next().unwrap()); - assert_eq!("界", iter.next().unwrap()); - assert_eq!("\r\n", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn insert_text_in_non_empty_buffer_4() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - buf.insert_text("again", 12); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 17); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("世", iter.next().unwrap()); - assert_eq!("界", iter.next().unwrap()); - assert_eq!("\r\n", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("g", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("n", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn insert_text_in_non_empty_buffer_5() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - buf.insert_text("again", 2); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 17); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("g", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("n", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("世", iter.next().unwrap()); - assert_eq!("界", iter.next().unwrap()); - assert_eq!("\r\n", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - - assert_eq!(None, iter.next()); - } - - #[test] - fn insert_text_in_non_empty_buffer_6() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - buf.insert_text("again", 8); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 17); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("世", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("g", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("n", iter.next().unwrap()); - assert_eq!("界", iter.next().unwrap()); - assert_eq!("\r\n", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - - assert_eq!(None, iter.next()); - } - - #[test] - fn insert_text_in_non_empty_buffer_7() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\n 世界\r\n!", 0); - buf.insert_text("\nag\n\nain\n", 2); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 21); - assert_eq!(buf.line_count(), 7); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("g", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("a", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("n", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!(" ", iter.next().unwrap()); - assert_eq!("世", iter.next().unwrap()); - assert_eq!("界", iter.next().unwrap()); - assert_eq!("\r\n", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - - assert_eq!(None, iter.next()); - } - - #[test] - fn remove_text_1() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - - buf.text.remove(0..3); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 26); - assert!(buf.line_count() == 5); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_2() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - - buf.text.remove(0..12); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 17); - assert!(buf.line_count() == 4); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_3() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - - buf.text.remove(5..17); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 17); - assert!(buf.line_count() == 4); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_4() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - - buf.text.remove(23..29); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 23); - assert!(buf.line_count() == 6); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_5() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - - buf.text.remove(17..29); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 17); - assert!(buf.line_count() == 4); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_6() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\nworld!", 0); - assert!(buf.char_count() == 12); - assert!(buf.line_count() == 2); - - buf.text.remove(3..12); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 3); - assert!(buf.line_count() == 1); - assert!("H" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_7() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\nworld!", 0); - assert!(buf.char_count() == 15); - assert!(buf.line_count() == 3); - - buf.text.remove(5..15); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 5); - assert!(buf.line_count() == 2); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_8() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\nworld!", 0); - assert!(buf.char_count() == 12); - assert!(buf.line_count() == 2); - - buf.text.remove(3..11); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 4); - assert!(buf.line_count() == 1); - assert!("H" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_9() { - let mut buf = Buffer::new(); - - buf.insert_text("Hello\nworld!", 0); - assert!(buf.char_count() == 12); - assert!(buf.line_count() == 2); - - buf.text.remove(8..12); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 8); - assert!(buf.line_count() == 2); - assert!("H" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_10() { - let mut buf = Buffer::new(); - - buf.insert_text("12\n34\n56\n78", 0); - assert!(buf.char_count() == 11); - assert!(buf.line_count() == 4); - - buf.text.remove(4..11); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 4); - assert!(buf.line_count() == 2); - assert!("1" == iter.next().unwrap()); - assert!("2" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("3" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_text_11() { - let mut buf = Buffer::new(); - - buf.insert_text("1234567890", 0); - assert!(buf.char_count() == 10); - assert!(buf.line_count() == 1); - - buf.text.remove(9..10); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 9); - assert!(buf.line_count() == 1); - assert!("1" == iter.next().unwrap()); - assert!("2" == iter.next().unwrap()); - assert!("3" == iter.next().unwrap()); - assert!("4" == iter.next().unwrap()); - assert!("5" == iter.next().unwrap()); - assert!("6" == iter.next().unwrap()); - assert!("7" == iter.next().unwrap()); - assert!("8" == iter.next().unwrap()); - assert!("9" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn move_text_1() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - buf.move_text(0, 3, 2); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn move_text_2() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - buf.move_text(3, 8, 6); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn move_text_3() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - buf.move_text(12, 17, 6); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn move_text_4() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - buf.move_text(23, 29, 20); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn move_text_5() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - buf.move_text(0, 29, 0); - - let mut iter = buf.grapheme_iter(); - - assert!(buf.char_count() == 29); - assert!(buf.line_count() == 6); - assert!("H" == iter.next().unwrap()); - assert!("i" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("p" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn remove_lines_1() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert_eq!(buf.char_count(), 29); - assert_eq!(buf.line_count(), 6); - - buf.remove_lines(0, 3); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 13); - assert_eq!(buf.line_count(), 3); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("f", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("t", iter.next().unwrap()); - assert_eq!("h", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("w", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("r", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("d", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn remove_lines_2() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert_eq!(buf.char_count(), 29); - assert_eq!(buf.line_count(), 6); - - buf.remove_lines(1, 4); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 13); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("t", iter.next().unwrap()); - assert_eq!("h", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("w", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("r", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("d", iter.next().unwrap()); - assert_eq!("!", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn remove_lines_3() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert_eq!(buf.char_count(), 29); - assert_eq!(buf.line_count(), 6); - - // "Hi\nthere\npeople\nof\nthe\nworld!" - - buf.remove_lines(3, 6); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 15); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("t", iter.next().unwrap()); - assert_eq!("h", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("r", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("p", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("p", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn remove_lines_4() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\n", 0); - assert_eq!(buf.char_count(), 23); - assert_eq!(buf.line_count(), 6); - - buf.remove_lines(3, 6); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 15); - assert_eq!(buf.line_count(), 3); - assert_eq!("H", iter.next().unwrap()); - assert_eq!("i", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("t", iter.next().unwrap()); - assert_eq!("h", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("r", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("\n", iter.next().unwrap()); - assert_eq!("p", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!("o", iter.next().unwrap()); - assert_eq!("p", iter.next().unwrap()); - assert_eq!("l", iter.next().unwrap()); - assert_eq!("e", iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn remove_lines_5() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - assert_eq!(buf.char_count(), 29); - assert_eq!(buf.line_count(), 6); - - buf.remove_lines(0, 6); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 0); - assert_eq!(buf.line_count(), 1); - assert_eq!(None, iter.next()); - } - - #[test] - fn remove_lines_6() { - let mut buf = Buffer::new(); - - buf.insert_text("Hi\nthere\npeople\nof\nthe\n", 0); - assert_eq!(buf.char_count(), 23); - assert_eq!(buf.line_count(), 6); - - buf.remove_lines(0, 6); - - let mut iter = buf.grapheme_iter(); - - assert_eq!(buf.char_count(), 0); - assert_eq!(buf.line_count(), 1); - assert_eq!(None, iter.next()); - } - - #[test] - fn line_col_to_index_1() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let pos = buf.line_col_to_index((2, 3)); - - assert!(pos == 12); - } - - #[test] - fn line_col_to_index_2() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let pos = buf.line_col_to_index((2, 10)); - - assert_eq!(pos, 15); - } - - #[test] - fn line_col_to_index_3() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let pos = buf.line_col_to_index((10, 2)); - - assert!(pos == 29); - } - - #[test] - fn line_col_to_index_4() { - let mut buf = Buffer::new(); - buf.insert_text("Hello\nworld!\n", 0); - - assert_eq!(buf.line_col_to_index((0, 0)), 0); - assert_eq!(buf.line_col_to_index((0, 5)), 5); - assert_eq!(buf.line_col_to_index((0, 6)), 5); - - assert_eq!(buf.line_col_to_index((1, 0)), 6); - assert_eq!(buf.line_col_to_index((1, 6)), 12); - assert_eq!(buf.line_col_to_index((1, 7)), 12); - - assert_eq!(buf.line_col_to_index((2, 0)), 13); - assert_eq!(buf.line_col_to_index((2, 1)), 13); - } - - #[test] - fn index_to_line_col_1() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let pos = buf.index_to_line_col(5); - - assert!(pos == (1, 2)); - } - - #[test] - fn index_to_line_col_2() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let pos = buf.index_to_line_col(50); - - assert_eq!(pos, (5, 6)); - } - - #[test] - fn index_to_line_col_3() { - let mut buf = Buffer::new(); - buf.insert_text("Hello\nworld!\n", 0); - - assert_eq!(buf.index_to_line_col(0), (0, 0)); - assert_eq!(buf.index_to_line_col(5), (0, 5)); - assert_eq!(buf.index_to_line_col(6), (1, 0)); - assert_eq!(buf.index_to_line_col(12), (1, 6)); - assert_eq!(buf.index_to_line_col(13), (2, 0)); - assert_eq!(buf.index_to_line_col(14), (2, 0)); - } - - #[test] - fn string_from_range_1() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let s = buf.string_from_range(1, 12); - - assert!(&s[..] == "i\nthere\npeo"); - } - - #[test] - fn string_from_range_2() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let s = buf.string_from_range(0, 29); - - assert!(&s[..] == "Hi\nthere\npeople\nof\nthe\nworld!"); - } - - #[test] - fn grapheme_iter_at_index_1() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let mut iter = buf.grapheme_iter_at_index(16); - - assert!("o" == iter.next().unwrap()); - assert!("f" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("t" == iter.next().unwrap()); - assert!("h" == iter.next().unwrap()); - assert!("e" == iter.next().unwrap()); - assert!("\n" == iter.next().unwrap()); - assert!("w" == iter.next().unwrap()); - assert!("o" == iter.next().unwrap()); - assert!("r" == iter.next().unwrap()); - assert!("l" == iter.next().unwrap()); - assert!("d" == iter.next().unwrap()); - assert!("!" == iter.next().unwrap()); - assert!(None == iter.next()); - } - - #[test] - fn grapheme_iter_at_index_2() { - let mut buf = Buffer::new(); - buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - - let mut iter = buf.grapheme_iter_at_index(29); - - assert!(None == iter.next()); - } -} diff --git a/src/buffer/undo_stack.rs b/src/buffer/undo_stack.rs deleted file mode 100644 index bd646c2..0000000 --- a/src/buffer/undo_stack.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::collections::LinkedList; - -/// A text editing operation -#[derive(Clone)] -pub enum Operation { - InsertText(String, usize), - RemoveTextBefore(String, usize), - RemoveTextAfter(String, usize), - MoveText(usize, usize, usize), - CompositeOp(Vec), -} - -/// An undo/redo stack of text editing operations -pub struct UndoStack { - stack_a: LinkedList, - stack_b: LinkedList, -} - -impl UndoStack { - pub fn new() -> UndoStack { - UndoStack { - stack_a: LinkedList::new(), - stack_b: LinkedList::new(), - } - } - - pub fn push(&mut self, op: Operation) { - self.stack_a.push_back(op); - self.stack_b.clear(); - } - - pub fn prev(&mut self) -> Option { - if let Some(op) = self.stack_a.pop_back() { - self.stack_b.push_back(op.clone()); - return Some(op); - } else { - return None; - } - } - - pub fn next(&mut self) -> Option { - if let Some(op) = self.stack_b.pop_back() { - self.stack_a.push_back(op.clone()); - return Some(op); - } else { - return None; - } - } -} diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 8400c1d..6395a81 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,21 +1,25 @@ #![allow(dead_code)] -mod cursor; - use std::{ cmp::{max, min}, collections::HashMap, + fs::File, + io::{self, BufReader, BufWriter, Write}, path::{Path, PathBuf}, }; -use crate::{ - buffer::Buffer, - formatter::LineFormatter, - string_utils::{char_count, rope_slice_to_line_ending, LineEnding}, - utils::{digit_count, RopeGraphemes}, -}; +use ropey::Rope; -use self::cursor::CursorSet; +use backend::{buffer::Buffer, marks::Mark}; + +use crate::{ + formatter::LineFormatter, + graphemes::{ + is_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes, + }, + string_utils::{rope_slice_to_line_ending, LineEnding}, + utils::digit_count, +}; pub struct Editor { pub buffer: Buffer, @@ -24,7 +28,6 @@ pub struct Editor { pub line_ending_type: LineEnding, pub soft_tabs: bool, pub soft_tab_width: u8, - pub dirty: bool, // The dimensions of the total editor in screen space, including the // header, gutter, etc. @@ -32,76 +35,90 @@ pub struct Editor { // The dimensions and position of just the text view portion of the editor pub view_dim: (usize, usize), // (height, width) - pub view_pos: (usize, usize), // (char index, visual horizontal offset) - // The editing cursor position - pub cursors: CursorSet, + // Indices into the mark sets of the buffer. + pub v_msi: usize, // View position MarkSet index. + pub c_msi: usize, // Cursors MarkSet index. } impl Editor { /// Create a new blank editor pub fn new(formatter: LineFormatter) -> Editor { + let (buffer, v_msi, c_msi) = { + let mut buffer = Buffer::new("".into()); + let view_idx = buffer.add_mark_set(); + let cursors_idx = buffer.add_mark_set(); + + buffer.mark_sets[view_idx].add_mark(Mark::new(0, 0)); + buffer.mark_sets[cursors_idx].add_mark(Mark::new(0, 0)); + + (buffer, view_idx, cursors_idx) + }; + Editor { - buffer: Buffer::new(), + buffer: buffer, formatter: formatter, file_path: PathBuf::new(), line_ending_type: LineEnding::LF, soft_tabs: false, soft_tab_width: 4, - dirty: false, editor_dim: (0, 0), view_dim: (0, 0), - view_pos: (0, 0), - cursors: CursorSet::new(), + v_msi: v_msi, + c_msi: c_msi, } } - pub fn new_from_file(formatter: LineFormatter, path: &Path) -> Editor { - let buf = match Buffer::new_from_file(path) { - Ok(b) => b, - // TODO: handle un-openable file better - _ => panic!("Could not open file!"), + pub fn new_from_file(formatter: LineFormatter, path: &Path) -> io::Result { + let (buffer, v_msi, c_msi) = { + let mut buffer = Buffer::new(Rope::from_reader(BufReader::new(File::open(path)?))?); + let view_idx = buffer.add_mark_set(); + let cursors_idx = buffer.add_mark_set(); + + buffer.mark_sets[view_idx].add_mark(Mark::new(0, 0)); + buffer.mark_sets[cursors_idx].add_mark(Mark::new(0, 0)); + + (buffer, view_idx, cursors_idx) }; let mut ed = Editor { - buffer: buf, + buffer: buffer, formatter: formatter, file_path: path.to_path_buf(), line_ending_type: LineEnding::LF, soft_tabs: false, soft_tab_width: 4, - dirty: false, editor_dim: (0, 0), view_dim: (0, 0), - view_pos: (0, 0), - cursors: CursorSet::new(), + v_msi: v_msi, + c_msi: c_msi, }; - // For multiple-cursor testing - // let mut cur = Cursor::new(); - // cur.range.0 = 30; - // cur.range.1 = 30; - // cur.update_vis_start(&(ed.buffer), &(ed.formatter)); - // ed.cursors.add_cursor(cur); - ed.auto_detect_line_ending(); ed.auto_detect_indentation_style(); - return ed; + Ok(ed) } - pub fn save_if_dirty(&mut self) { - if self.dirty && self.file_path != PathBuf::new() { - let _ = self.buffer.save_to_file(&self.file_path); - self.dirty = false; + pub fn save_if_dirty(&mut self) -> io::Result<()> { + if self.buffer.is_dirty && self.file_path != PathBuf::new() { + let mut f = BufWriter::new(File::create(&self.file_path)?); + + for c in self.buffer.text.chunks() { + f.write(c.as_bytes())?; + } + + self.buffer.is_dirty = false; } + + Ok(()) } pub fn auto_detect_line_ending(&mut self) { let mut line_ending_histogram: [usize; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; // Collect statistics on the first 100 lines - for line in self.buffer.line_iter().take(100) { + for line in self.buffer.text.lines().take(100) { // Get the line ending let ending = if line.len_chars() == 1 { let g = RopeGraphemes::new(&line.slice((line.len_chars() - 1)..)) @@ -181,7 +198,7 @@ impl Editor { let mut last_indent = (false, 0usize); // (was_tabs, indent_count) // Collect statistics on the first 1000 lines - for line in self.buffer.line_iter().take(1000) { + for line in self.buffer.text.lines().take(1000) { let mut c_iter = line.chars(); match c_iter.next() { Some('\t') => { @@ -254,7 +271,7 @@ impl Editor { /// Updates the view dimensions. pub fn update_dim(&mut self, h: usize, w: usize) { - let line_count_digits = digit_count(self.buffer.line_count() as u32, 10) as usize; + let line_count_digits = digit_count(self.buffer.text.len_lines() as u32, 10) as usize; self.editor_dim = (h, w); // Minus 1 vertically for the header, minus two more than the digits in @@ -267,332 +284,258 @@ impl Editor { pub fn undo(&mut self) { // TODO: handle multiple cursors properly - if let Some(pos) = self.buffer.undo() { - self.cursors.truncate(1); - self.cursors[0].range.0 = pos; - self.cursors[0].range.1 = pos; - self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); + if let Some((_start, end)) = self.buffer.undo() { + self.buffer.mark_sets[self.c_msi].reduce_to_main(); + self.buffer.mark_sets[self.c_msi][0].head = end; + self.buffer.mark_sets[self.c_msi][0].tail = end; + self.buffer.mark_sets[self.c_msi][0].hh_pos = None; self.move_view_to_cursor(); - - self.dirty = true; - - self.cursors.make_consistent(); } } pub fn redo(&mut self) { // TODO: handle multiple cursors properly - if let Some(pos) = self.buffer.redo() { - self.cursors.truncate(1); - self.cursors[0].range.0 = pos; - self.cursors[0].range.1 = pos; - self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); + if let Some((_start, end)) = self.buffer.redo() { + self.buffer.mark_sets[self.c_msi].reduce_to_main(); + self.buffer.mark_sets[self.c_msi][0].head = end; + self.buffer.mark_sets[self.c_msi][0].tail = end; + self.buffer.mark_sets[self.c_msi][0].hh_pos = None; self.move_view_to_cursor(); - - self.dirty = true; - - self.cursors.make_consistent(); } } /// Moves the editor's view the minimum amount to show the cursor pub fn move_view_to_cursor(&mut self) { // Find the first and last char index visible within the editor. - let c_first = self - .formatter - .set_horizontal(&self.buffer, self.view_pos.0, 0); - let mut c_last = - self.formatter - .offset_vertical(&self.buffer, c_first, self.view_dim.0 as isize - 1); + let c_first = self.formatter.set_horizontal( + &self.buffer.text, + self.buffer.mark_sets[self.v_msi][0].head, + 0, + ); + let mut c_last = self.formatter.offset_vertical( + &self.buffer.text, + c_first, + self.view_dim.0 as isize - 1, + ); c_last = self .formatter - .set_horizontal(&self.buffer, c_last, self.view_dim.1); + .set_horizontal(&self.buffer.text, c_last, self.view_dim.1); // Adjust the view depending on where the cursor is - if self.cursors[0].range.0 < c_first { - self.view_pos.0 = self.cursors[0].range.0; - } else if self.cursors[0].range.0 > c_last { - self.view_pos.0 = self.formatter.offset_vertical( - &self.buffer, - self.cursors[0].range.0, + let cursor_head = self.buffer.mark_sets[self.c_msi].main().unwrap().head; + if cursor_head < c_first { + self.buffer.mark_sets[self.v_msi][0].head = cursor_head; + } else if cursor_head > c_last { + self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical( + &self.buffer.text, + cursor_head, -(self.view_dim.0 as isize), ); } } pub fn insert_text_at_cursor(&mut self, text: &str) { - self.cursors.make_consistent(); + // TODO: handle multiple cursors. + let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let range = mark.range(); - let str_len = char_count(text); - let mut offset = 0; - - for c in self.cursors.iter_mut() { - // Insert text - self.buffer.insert_text(text, c.range.0 + offset); - self.dirty = true; - - // Move cursor - c.range.0 += str_len + offset; - c.range.1 += str_len + offset; - c.update_vis_start(&(self.buffer), &(self.formatter)); - - // Update offset - offset += str_len; - } + self.buffer.edit((range.start, range.end), text); // Adjust view self.move_view_to_cursor(); } pub fn insert_tab_at_cursor(&mut self) { - self.cursors.make_consistent(); + // TODO: handle multiple cursors. + let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let range = mark.range(); if self.soft_tabs { - let mut offset = 0; + // Figure out how many spaces to insert + let vis_pos = self + .formatter + .get_horizontal(&self.buffer.text, range.start); + // TODO: handle tab settings + let next_tab_stop = + ((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize; + let space_count = min(next_tab_stop - vis_pos, 8); - for c in self.cursors.iter_mut() { - // Update cursor with offset - c.range.0 += offset; - c.range.1 += offset; - - // Figure out how many spaces to insert - let vis_pos = self.formatter.get_horizontal(&self.buffer, c.range.0); - // TODO: handle tab settings - let next_tab_stop = - ((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize; - let space_count = min(next_tab_stop - vis_pos, 8); - - // Insert spaces - let space_strs = [ - "", " ", " ", " ", " ", " ", " ", " ", " ", - ]; - self.buffer.insert_text(space_strs[space_count], c.range.0); - self.dirty = true; - - // Move cursor - c.range.0 += space_count; - c.range.1 += space_count; - c.update_vis_start(&(self.buffer), &(self.formatter)); - - // Update offset - offset += space_count; - } - - // Adjust view - self.move_view_to_cursor(); + // Insert spaces + let space_strs = [ + "", " ", " ", " ", " ", " ", " ", " ", " ", + ]; + self.buffer + .edit((range.start, range.end), space_strs[space_count]); } else { - self.insert_text_at_cursor("\t"); + self.buffer.edit((range.start, range.end), "\t"); } - } - pub fn backspace_at_cursor(&mut self) { - self.remove_text_behind_cursor(1); + // Adjust view + self.move_view_to_cursor(); } pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) { - self.cursors.make_consistent(); + // TODO: handle multiple cursors. + let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let range = mark.range(); - let mut offset = 0; - - for c in self.cursors.iter_mut() { - // Update cursor with offset - c.range.0 -= offset; - c.range.1 -= offset; - - // Do nothing if there's nothing to delete. - if c.range.0 == 0 { - continue; - } - - let len = c.range.0 - self.buffer.nth_prev_grapheme(c.range.0, grapheme_count); - - // Remove text - self.buffer.remove_text_before(c.range.0, len); - self.dirty = true; - - // Move cursor - c.range.0 -= len; - c.range.1 -= len; - c.update_vis_start(&(self.buffer), &(self.formatter)); - - // Update offset - offset += len; + // Do nothing if there's nothing to delete. + if range.start == 0 { + return; } - self.cursors.make_consistent(); + let pre = + nth_prev_grapheme_boundary(&self.buffer.text.slice(..), range.start, grapheme_count); + + // Remove text + self.buffer.edit((pre, range.start), ""); // Adjust view self.move_view_to_cursor(); } pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) { - self.cursors.make_consistent(); + // TODO: handle multiple cursors. + let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let range = mark.range(); - let mut offset = 0; - - for c in self.cursors.iter_mut() { - // Update cursor with offset - c.range.0 -= min(c.range.0, offset); - c.range.1 -= min(c.range.1, offset); - - // Do nothing if there's nothing to delete. - if c.range.1 == self.buffer.char_count() { - return; - } - - let len = self.buffer.nth_next_grapheme(c.range.1, grapheme_count) - c.range.1; - - // Remove text - self.buffer.remove_text_after(c.range.1, len); - self.dirty = true; - - // Move cursor - c.update_vis_start(&(self.buffer), &(self.formatter)); - - // Update offset - offset += len; + // Do nothing if there's nothing to delete. + if range.end == self.buffer.text.len_chars() { + return; } - self.cursors.make_consistent(); + let post = + nth_next_grapheme_boundary(&self.buffer.text.slice(..), range.end, grapheme_count); + + // Remove text + self.buffer.edit((range.end, post), ""); // Adjust view self.move_view_to_cursor(); } pub fn remove_text_inside_cursor(&mut self) { - self.cursors.make_consistent(); + // TODO: handle multiple cursors. + let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let range = mark.range(); - let mut offset = 0; - - for c in self.cursors.iter_mut() { - // Update cursor with offset - c.range.0 -= min(c.range.0, offset); - c.range.1 -= min(c.range.1, offset); - - // If selection, remove text - if c.range.0 < c.range.1 { - let len = c.range.1 - c.range.0; - - self.buffer - .remove_text_before(c.range.0, c.range.1 - c.range.0); - self.dirty = true; - - // Move cursor - c.range.1 = c.range.0; - - // Update offset - offset += len; - } - - c.update_vis_start(&(self.buffer), &(self.formatter)); + if range.start < range.end { + self.buffer.edit((range.start, range.end), ""); } - self.cursors.make_consistent(); - // Adjust view self.move_view_to_cursor(); } pub fn cursor_to_beginning_of_buffer(&mut self) { - self.cursors = CursorSet::new(); + self.buffer.mark_sets[self.c_msi].clear(); + self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(0, 0)); - self.cursors[0].range = (0, 0); - self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); - - // Adjust view + // Adjust view. self.move_view_to_cursor(); } pub fn cursor_to_end_of_buffer(&mut self) { - let end = self.buffer.char_count(); + let end = self.buffer.text.len_chars(); - self.cursors = CursorSet::new(); - self.cursors[0].range = (end, end); - self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); + self.buffer.mark_sets[self.c_msi].clear(); + self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(end, end)); - // Adjust view + // Adjust view. self.move_view_to_cursor(); } pub fn cursor_left(&mut self, n: usize) { - for c in self.cursors.iter_mut() { - c.range.0 = self.buffer.nth_prev_grapheme(c.range.0, n); - c.range.1 = c.range.0; - c.update_vis_start(&(self.buffer), &(self.formatter)); + for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + mark.head = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n); + mark.tail = mark.head; + mark.hh_pos = None; } + self.buffer.mark_sets[self.c_msi].make_consistent(); // Adjust view self.move_view_to_cursor(); } pub fn cursor_right(&mut self, n: usize) { - for c in self.cursors.iter_mut() { - c.range.1 = self.buffer.nth_next_grapheme(c.range.1, n); - c.range.0 = c.range.1; - c.update_vis_start(&(self.buffer), &(self.formatter)); + for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + mark.head = nth_next_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n); + mark.tail = mark.head; + mark.hh_pos = None; } + self.buffer.mark_sets[self.c_msi].make_consistent(); // Adjust view self.move_view_to_cursor(); } pub fn cursor_up(&mut self, n: usize) { - for c in self.cursors.iter_mut() { + for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + if mark.hh_pos == None { + mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head)); + } + let vmove = -1 * n as isize; - let mut temp_index = self - .formatter - .offset_vertical(&self.buffer, c.range.0, vmove); - temp_index = self - .formatter - .set_horizontal(&self.buffer, temp_index, c.vis_start); + let mut temp_index = + self.formatter + .offset_vertical(&self.buffer.text, mark.head, vmove); + temp_index = + self.formatter + .set_horizontal(&self.buffer.text, temp_index, mark.hh_pos.unwrap()); - if !self.buffer.is_grapheme(temp_index) { - temp_index = self.buffer.nth_prev_grapheme(temp_index, 1); + if !is_grapheme_boundary(&self.buffer.text.slice(..), temp_index) { + temp_index = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), temp_index, 1); } - if temp_index == c.range.0 { + if temp_index == mark.head { // We were already at the top. - c.range.0 = 0; - c.range.1 = 0; - c.update_vis_start(&(self.buffer), &(self.formatter)); + mark.head = 0; + mark.tail = 0; + mark.hh_pos = None; } else { - c.range.0 = temp_index; - c.range.1 = temp_index; + mark.head = temp_index; + mark.tail = temp_index; } } + self.buffer.mark_sets[self.c_msi].make_consistent(); // Adjust view self.move_view_to_cursor(); } pub fn cursor_down(&mut self, n: usize) { - for c in self.cursors.iter_mut() { + for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + if mark.hh_pos == None { + mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head)); + } + let vmove = n as isize; - let mut temp_index = self - .formatter - .offset_vertical(&self.buffer, c.range.0, vmove); - temp_index = self - .formatter - .set_horizontal(&self.buffer, temp_index, c.vis_start); + let mut temp_index = + self.formatter + .offset_vertical(&self.buffer.text, mark.head, vmove); + temp_index = + self.formatter + .set_horizontal(&self.buffer.text, temp_index, mark.hh_pos.unwrap()); - if !self.buffer.is_grapheme(temp_index) { - temp_index = self.buffer.nth_prev_grapheme(temp_index, 1); + if !is_grapheme_boundary(&self.buffer.text.slice(..), temp_index) { + temp_index = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), temp_index, 1); } - if temp_index == c.range.0 { + if temp_index == mark.head { // We were already at the bottom. - c.range.0 = self.buffer.char_count(); - c.range.1 = self.buffer.char_count(); - c.update_vis_start(&(self.buffer), &(self.formatter)); + mark.head = self.buffer.text.len_chars(); + mark.tail = self.buffer.text.len_chars(); + mark.hh_pos = None; } else { - c.range.0 = temp_index; - c.range.1 = temp_index; + mark.head = temp_index; + mark.tail = temp_index; } } + self.buffer.mark_sets[self.c_msi].make_consistent(); // Adjust view self.move_view_to_cursor(); @@ -600,9 +543,9 @@ impl Editor { pub fn page_up(&mut self) { let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); - self.view_pos.0 = self.formatter.offset_vertical( - &self.buffer, - self.view_pos.0, + self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical( + &self.buffer.text, + self.buffer.mark_sets[self.v_msi][0].head, -1 * move_amount as isize, ); @@ -614,9 +557,11 @@ impl Editor { pub fn page_down(&mut self) { let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); - self.view_pos.0 = - self.formatter - .offset_vertical(&self.buffer, self.view_pos.0, move_amount as isize); + self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical( + &self.buffer.text, + self.buffer.mark_sets[self.v_msi][0].head, + move_amount as isize, + ); self.cursor_down(move_amount); @@ -625,12 +570,23 @@ impl Editor { } pub fn jump_to_line(&mut self, n: usize) { - let pos = self.buffer.line_col_to_index((n, 0)); - self.cursors.truncate(1); - self.cursors[0].range.0 = - self.formatter - .set_horizontal(&self.buffer, pos, self.cursors[0].vis_start); - self.cursors[0].range.1 = self.cursors[0].range.0; + self.buffer.mark_sets[self.c_msi].reduce_to_main(); + if self.buffer.mark_sets[self.c_msi][0].hh_pos == None { + self.buffer.mark_sets[self.c_msi][0].hh_pos = Some( + self.formatter + .get_horizontal(&self.buffer.text, self.buffer.mark_sets[self.c_msi][0].head), + ); + } + + let pos = self.buffer.text.line_to_char(n); + let pos = self.formatter.set_horizontal( + &self.buffer.text, + pos, + self.buffer.mark_sets[self.c_msi][0].hh_pos.unwrap(), + ); + + self.buffer.mark_sets[self.c_msi][0].head = pos; + self.buffer.mark_sets[self.c_msi][0].tail = pos; // Adjust view self.move_view_to_cursor(); diff --git a/src/formatter.rs b/src/formatter.rs index 2ebe7fc..e940c89 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -3,10 +3,9 @@ use std::borrow::Cow; use ropey::{Rope, RopeSlice}; use crate::{ - buffer::Buffer, + graphemes::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes}, string_utils::char_count, string_utils::str_is_whitespace, - utils::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes}, }; // Maximum chars in a line before a soft line break is forced. @@ -42,10 +41,14 @@ impl LineFormatter { /// Returns an iterator over the blocks of the buffer, starting at the /// block containing the given char. Also returns the offset of that char /// relative to the start of the first block. - pub fn iter<'b>(&'b self, buf: &'b Buffer, char_idx: usize) -> (Blocks<'b>, usize) { + pub fn iter<'b>(&'b self, buf: &'b Rope, char_idx: usize) -> (Blocks<'b>, usize) { // Get the line. - let (line_i, col_i) = buf.index_to_line_col(char_idx); - let line = buf.get_line(line_i); + let (line_i, col_i) = { + let line_idx = buf.char_to_line(char_idx); + let col_idx = char_idx - buf.line_to_char(line_idx); + (line_idx, col_idx) + }; + let line = buf.line(line_i); // Find the right block in the line, and the index within that block let (block_index, block_range) = block_index_and_range(&line, col_i); @@ -54,7 +57,7 @@ impl LineFormatter { ( Blocks { formatter: self, - buf: &buf.text, + buf: buf, line_idx: line_i, line_block_count: block_count(&line), block_idx: block_index, @@ -63,8 +66,8 @@ impl LineFormatter { ) } - /// Converts from char index to the horizontal 2d char index. - pub fn get_horizontal(&self, buf: &Buffer, char_idx: usize) -> usize { + /// Converts from char index to its formatted horizontal 2d position. + pub fn get_horizontal(&self, buf: &Rope, char_idx: usize) -> usize { let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx); // Traverse the iterator and find the horizontal position of the char @@ -92,7 +95,7 @@ impl LineFormatter { /// returns a char index on the same visual line as the given index, /// but offset to have the desired horizontal position (or as close as is /// possible. - pub fn set_horizontal(&self, buf: &Buffer, char_idx: usize, horizontal: usize) -> usize { + pub fn set_horizontal(&self, buf: &Rope, char_idx: usize, horizontal: usize) -> usize { let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx); let mut hpos_char_idx = None; @@ -134,7 +137,7 @@ impl LineFormatter { // If we reached the end of the text, return the last char index. let end_i = char_idx - char_offset + i; let end_last_i = char_idx - char_offset + last_i; - if buf.text.len_chars() == end_i { + if buf.len_chars() == end_i { return end_i; } else { return end_last_i; @@ -143,7 +146,7 @@ impl LineFormatter { /// Takes a char index and a visual vertical offset, and returns the char /// index after that visual offset is applied. - pub fn offset_vertical(&self, buf: &Buffer, char_idx: usize, v_offset: isize) -> usize { + pub fn offset_vertical(&self, buf: &Rope, char_idx: usize, v_offset: isize) -> usize { let mut char_idx = char_idx; let mut v_offset = v_offset; while v_offset != 0 { @@ -171,9 +174,9 @@ impl LineFormatter { } } else if offset_char_v_pos >= block_v_dim as isize { // If we're off the end of the block. - char_idx = (char_idx + block.len_chars() - char_offset).min(buf.text.len_chars()); + char_idx = (char_idx + block.len_chars() - char_offset).min(buf.len_chars()); v_offset -= block_v_dim as isize - char_v_pos as isize; - if char_idx == buf.text.len_chars() { + if char_idx == buf.len_chars() { break; } } else { @@ -230,13 +233,13 @@ impl LineFormatter { /// char's offset within that iter. fn block_vis_iter_and_char_offset<'b>( &self, - buf: &'b Buffer, + buf: &'b Rope, char_idx: usize, ) -> (RopeSlice<'b>, BlockVisIter<'b>, usize) { - let line_i = buf.text.char_to_line(char_idx); - let line_start = buf.text.line_to_char(line_i); - let line_end = buf.text.line_to_char(line_i + 1); - let line = buf.text.slice(line_start..line_end); + let line_i = buf.char_to_line(char_idx); + let line_start = buf.line_to_char(line_i); + let line_end = buf.line_to_char(line_i + 1); + let line = buf.slice(line_start..line_end); // Find the right block in the line, and the index within that block let (block_index, block_range) = block_index_and_range(&line, char_idx - line_start); diff --git a/src/graphemes.rs b/src/graphemes.rs new file mode 100644 index 0000000..891018a --- /dev/null +++ b/src/graphemes.rs @@ -0,0 +1,212 @@ +use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice}; +use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete}; +use unicode_width::UnicodeWidthStr; + +pub fn grapheme_width(g: &str) -> usize { + if g.as_bytes()[0] <= 127 { + // Fast-path ascii. + // Point 1: theoretically, ascii control characters should have zero + // width, but in our case we actually want them to have width: if they + // show up in text, we want to treat them as textual elements that can + // be editied. So we can get away with making all ascii single width + // here. + // Point 2: we're only examining the first codepoint here, which means + // we're ignoring graphemes formed with combining characters. However, + // if it starts with ascii, it's going to be a single-width grapeheme + // regardless, so, again, we can get away with that here. + // Point 3: we're only examining the first _byte_. But for utf8, when + // checking for ascii range values only, that works. + 1 + } else { + // We use max(1) here because all grapeheme clusters--even illformed + // ones--should have at least some width so they can be edited + // properly. + UnicodeWidthStr::width(g).max(1) + } +} + +pub fn nth_prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize, n: usize) -> usize { + // TODO: implement this more efficiently. This has to do a lot of + // re-scanning of rope chunks. Probably move the main implementation here, + // and have prev_grapheme_boundary call this instead. + let mut char_idx = char_idx; + for _ in 0..n { + char_idx = prev_grapheme_boundary(slice, char_idx); + } + char_idx +} + +/// Finds the previous grapheme boundary before the given char position. +pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize { + // Bounds check + debug_assert!(char_idx <= slice.len_chars()); + + // We work with bytes for this, so convert. + let byte_idx = slice.char_to_byte(char_idx); + + // Get the chunk with our byte index in it. + let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx); + + // Set up the grapheme cursor. + let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); + + // Find the previous grapheme cluster boundary. + loop { + match gc.prev_boundary(chunk, chunk_byte_idx) { + Ok(None) => return 0, + Ok(Some(n)) => { + let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx); + return chunk_char_idx + tmp; + } + Err(GraphemeIncomplete::PrevChunk) => { + let (a, b, c, _) = slice.chunk_at_byte(chunk_byte_idx - 1); + chunk = a; + chunk_byte_idx = b; + chunk_char_idx = c; + } + Err(GraphemeIncomplete::PreContext(n)) => { + let ctx_chunk = slice.chunk_at_byte(n - 1).0; + gc.provide_context(ctx_chunk, n - ctx_chunk.len()); + } + _ => unreachable!(), + } + } +} + +pub fn nth_next_grapheme_boundary(slice: &RopeSlice, char_idx: usize, n: usize) -> usize { + // TODO: implement this more efficiently. This has to do a lot of + // re-scanning of rope chunks. Probably move the main implementation here, + // and have next_grapheme_boundary call this instead. + let mut char_idx = char_idx; + for _ in 0..n { + char_idx = next_grapheme_boundary(slice, char_idx); + } + char_idx +} + +/// Finds the next grapheme boundary after the given char position. +pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize { + // Bounds check + debug_assert!(char_idx <= slice.len_chars()); + + // We work with bytes for this, so convert. + let byte_idx = slice.char_to_byte(char_idx); + + // Get the chunk with our byte index in it. + let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx); + + // Set up the grapheme cursor. + let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); + + // Find the next grapheme cluster boundary. + loop { + match gc.next_boundary(chunk, chunk_byte_idx) { + Ok(None) => return slice.len_chars(), + Ok(Some(n)) => { + let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx); + return chunk_char_idx + tmp; + } + Err(GraphemeIncomplete::NextChunk) => { + chunk_byte_idx += chunk.len(); + let (a, _, c, _) = slice.chunk_at_byte(chunk_byte_idx); + chunk = a; + chunk_char_idx = c; + } + Err(GraphemeIncomplete::PreContext(n)) => { + let ctx_chunk = slice.chunk_at_byte(n - 1).0; + gc.provide_context(ctx_chunk, n - ctx_chunk.len()); + } + _ => unreachable!(), + } + } +} + +/// Returns whether the given char position is a grapheme boundary. +pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool { + // Bounds check + debug_assert!(char_idx <= slice.len_chars()); + + // We work with bytes for this, so convert. + let byte_idx = slice.char_to_byte(char_idx); + + // Get the chunk with our byte index in it. + let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx); + + // Set up the grapheme cursor. + let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); + + // Determine if the given position is a grapheme cluster boundary. + loop { + match gc.is_boundary(chunk, chunk_byte_idx) { + Ok(n) => return n, + Err(GraphemeIncomplete::PreContext(n)) => { + let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1); + gc.provide_context(ctx_chunk, ctx_byte_start); + } + _ => unreachable!(), + } + } +} + +/// An iterator over the graphemes of a RopeSlice. +#[derive(Clone)] +pub struct RopeGraphemes<'a> { + text: RopeSlice<'a>, + chunks: Chunks<'a>, + cur_chunk: &'a str, + cur_chunk_start: usize, + cursor: GraphemeCursor, +} + +impl<'a> RopeGraphemes<'a> { + pub fn new<'b>(slice: &RopeSlice<'b>) -> RopeGraphemes<'b> { + let mut chunks = slice.chunks(); + let first_chunk = chunks.next().unwrap_or(""); + RopeGraphemes { + text: *slice, + chunks: chunks, + cur_chunk: first_chunk, + cur_chunk_start: 0, + cursor: GraphemeCursor::new(0, slice.len_bytes(), true), + } + } +} + +impl<'a> Iterator for RopeGraphemes<'a> { + type Item = RopeSlice<'a>; + + fn next(&mut self) -> Option> { + let a = self.cursor.cur_cursor(); + let b; + loop { + match self + .cursor + .next_boundary(self.cur_chunk, self.cur_chunk_start) + { + Ok(None) => { + return None; + } + Ok(Some(n)) => { + b = n; + break; + } + Err(GraphemeIncomplete::NextChunk) => { + self.cur_chunk_start += self.cur_chunk.len(); + self.cur_chunk = self.chunks.next().unwrap_or(""); + } + _ => unreachable!(), + } + } + + if a < self.cur_chunk_start { + let a_char = self.text.byte_to_char(a); + let b_char = self.text.byte_to_char(b); + + Some(self.text.slice(a_char..b_char)) + } else { + let a2 = a - self.cur_chunk_start; + let b2 = b - self.cur_chunk_start; + Some((&self.cur_chunk[a2..b2]).into()) + } + } +} diff --git a/src/main.rs b/src/main.rs index 07b71cb..389a605 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,9 +5,9 @@ use editor::Editor; use formatter::LineFormatter; use term_ui::TermUI; -mod buffer; mod editor; mod formatter; +mod graphemes; mod string_utils; mod term_ui; mod utils; @@ -30,6 +30,7 @@ fn main() { // Load file, if specified let editor = if let Some(filepath) = args.value_of("file") { Editor::new_from_file(LineFormatter::new(4), &Path::new(&filepath[..])) + .expect(&format!("Couldn't open file '{}'.", filepath)) } else { Editor::new(LineFormatter::new(4)) }; diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index 506d530..a518e6a 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -243,7 +243,7 @@ impl TermUI { code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, } => { - self.editor.save_if_dirty(); + self.editor.save_if_dirty().expect("For some reason the file couldn't be saved. Also, TODO: this code path shouldn't panic."); } KeyEvent { @@ -342,7 +342,7 @@ impl TermUI { code: KeyCode::Backspace, modifiers: EMPTY_MOD, } => { - self.editor.backspace_at_cursor(); + self.editor.remove_text_behind_cursor(1); } KeyEvent { @@ -464,16 +464,17 @@ impl TermUI { // Filename and dirty marker let filename = editor.file_path.display(); - let dirty_char = if editor.dirty { "*" } else { "" }; + let dirty_char = if editor.buffer.is_dirty { "*" } else { "" }; let name = format!("{}{}", filename, dirty_char); self.screen.draw(c1.1 + 1, c1.0, &name[..], STYLE_INFO); // Percentage position in document // TODO: use view instead of cursor for calculation if there is more // than one cursor. - let percentage: usize = if editor.buffer.char_count() > 0 { - (((editor.cursors[0].range.0 as f32) / (editor.buffer.char_count() as f32)) * 100.0) - as usize + let percentage: usize = if editor.buffer.text.len_chars() > 0 { + (((editor.buffer.mark_sets[editor.c_msi].main().unwrap().head as f32) + / (editor.buffer.text.len_chars() as f32)) + * 100.0) as usize } else { 100 }; @@ -510,12 +511,15 @@ impl TermUI { } fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { + let view_pos = editor.buffer.mark_sets[editor.v_msi][0].head; + let cursors = &editor.buffer.mark_sets[editor.c_msi]; + // Calculate all the starting info let gutter_width = editor.editor_dim.1 - editor.view_dim.1; let blank_gutter = &" "[..gutter_width - 1]; - let line_index = editor.buffer.text.char_to_line(editor.view_pos.0); + let line_index = editor.buffer.text.char_to_line(view_pos); - let (blocks_iter, char_offset) = editor.formatter.iter(&editor.buffer, editor.view_pos.0); + let (blocks_iter, char_offset) = editor.formatter.iter(&editor.buffer.text, view_pos); let vis_line_offset = blocks_iter.clone().next().unwrap().0.vpos(char_offset); @@ -537,7 +541,7 @@ impl TermUI { // Loop through the blocks, printing them to the screen. let mut is_first_loop = true; let mut line_num = line_index + 1; - let mut char_index = editor.view_pos.0 - char_offset; + let mut char_index = view_pos - char_offset; for (block_vis_iter, is_line_start) in blocks_iter { if is_line_start && !is_first_loop { line_num += 1; @@ -578,7 +582,7 @@ impl TermUI { screen_line += 1; last_pos_y = pos_y; } - let px = pos_x as isize + screen_col - editor.view_pos.1 as isize; + let px = pos_x as isize + screen_col; let py = screen_line; // If we're off the bottom, we're done @@ -590,8 +594,8 @@ impl TermUI { if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) { // Check if the character is within a cursor let mut at_cursor = false; - for c in editor.cursors.iter() { - if char_index >= c.range.0 && char_index <= c.range.1 { + for c in cursors.iter() { + if char_index >= c.range().start && char_index <= c.range().end { at_cursor = true; self.screen.set_cursor(px as usize, py as usize); } @@ -636,18 +640,19 @@ impl TermUI { // Check if the character is within a cursor let mut at_cursor = false; - for c in editor.cursors.iter() { - if char_index >= c.range.0 && char_index <= c.range.1 { + for c in cursors.iter() { + if char_index >= c.range().start && char_index <= c.range().end { at_cursor = true; } } if at_cursor { // Calculate the cell coordinates at which to draw the cursor - let pos_x = editor - .formatter - .get_horizontal(&self.editor.buffer, self.editor.buffer.char_count()); - let mut px = pos_x as isize + screen_col - editor.view_pos.1 as isize; + let pos_x = editor.formatter.get_horizontal( + &self.editor.buffer.text, + self.editor.buffer.text.len_chars(), + ); + let mut px = pos_x as isize + screen_col; let mut py = screen_line - 1; if px > c2.1 as isize { px = c1.1 as isize + screen_col; diff --git a/src/utils.rs b/src/utils.rs index e2417be..e863332 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,4 @@ -use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice}; use time::Instant; -use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete}; -use unicode_width::UnicodeWidthStr; pub fn digit_count(mut n: u32, b: u32) -> u32 { let mut d = 0; @@ -14,197 +11,6 @@ pub fn digit_count(mut n: u32, b: u32) -> u32 { } } -//============================================================= - -pub fn grapheme_width(g: &str) -> usize { - if g.as_bytes()[0] <= 127 { - // Fast-path ascii. - // Point 1: theoretically, ascii control characters should have zero - // width, but in our case we actually want them to have width: if they - // show up in text, we want to treat them as textual elements that can - // be editied. So we can get away with making all ascii single width - // here. - // Point 2: we're only examining the first codepoint here, which means - // we're ignoring graphemes formed with combining characters. However, - // if it starts with ascii, it's going to be a single-width grapeheme - // regardless, so, again, we can get away with that here. - // Point 3: we're only examining the first _byte_. But for utf8, when - // checking for ascii range values only, that works. - 1 - } else { - // We use max(1) here because all grapeheme clusters--even illformed - // ones--should have at least some width so they can be edited - // properly. - UnicodeWidthStr::width(g).max(1) - } -} - -/// Finds the previous grapheme boundary before the given char position. -pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize { - // Bounds check - debug_assert!(char_idx <= slice.len_chars()); - - // We work with bytes for this, so convert. - let byte_idx = slice.char_to_byte(char_idx); - - // Get the chunk with our byte index in it. - let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx); - - // Set up the grapheme cursor. - let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); - - // Find the previous grapheme cluster boundary. - loop { - match gc.prev_boundary(chunk, chunk_byte_idx) { - Ok(None) => return 0, - Ok(Some(n)) => { - let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx); - return chunk_char_idx + tmp; - } - Err(GraphemeIncomplete::PrevChunk) => { - let (a, b, c, _) = slice.chunk_at_byte(chunk_byte_idx - 1); - chunk = a; - chunk_byte_idx = b; - chunk_char_idx = c; - } - Err(GraphemeIncomplete::PreContext(n)) => { - let ctx_chunk = slice.chunk_at_byte(n - 1).0; - gc.provide_context(ctx_chunk, n - ctx_chunk.len()); - } - _ => unreachable!(), - } - } -} - -/// Finds the next grapheme boundary after the given char position. -pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize { - // Bounds check - debug_assert!(char_idx <= slice.len_chars()); - - // We work with bytes for this, so convert. - let byte_idx = slice.char_to_byte(char_idx); - - // Get the chunk with our byte index in it. - let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx); - - // Set up the grapheme cursor. - let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); - - // Find the next grapheme cluster boundary. - loop { - match gc.next_boundary(chunk, chunk_byte_idx) { - Ok(None) => return slice.len_chars(), - Ok(Some(n)) => { - let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx); - return chunk_char_idx + tmp; - } - Err(GraphemeIncomplete::NextChunk) => { - chunk_byte_idx += chunk.len(); - let (a, _, c, _) = slice.chunk_at_byte(chunk_byte_idx); - chunk = a; - chunk_char_idx = c; - } - Err(GraphemeIncomplete::PreContext(n)) => { - let ctx_chunk = slice.chunk_at_byte(n - 1).0; - gc.provide_context(ctx_chunk, n - ctx_chunk.len()); - } - _ => unreachable!(), - } - } -} - -/// Returns whether the given char position is a grapheme boundary. -pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool { - // Bounds check - debug_assert!(char_idx <= slice.len_chars()); - - // We work with bytes for this, so convert. - let byte_idx = slice.char_to_byte(char_idx); - - // Get the chunk with our byte index in it. - let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx); - - // Set up the grapheme cursor. - let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); - - // Determine if the given position is a grapheme cluster boundary. - loop { - match gc.is_boundary(chunk, chunk_byte_idx) { - Ok(n) => return n, - Err(GraphemeIncomplete::PreContext(n)) => { - let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1); - gc.provide_context(ctx_chunk, ctx_byte_start); - } - _ => unreachable!(), - } - } -} - -/// An iterator over the graphemes of a RopeSlice. -#[derive(Clone)] -pub struct RopeGraphemes<'a> { - text: RopeSlice<'a>, - chunks: Chunks<'a>, - cur_chunk: &'a str, - cur_chunk_start: usize, - cursor: GraphemeCursor, -} - -impl<'a> RopeGraphemes<'a> { - pub fn new<'b>(slice: &RopeSlice<'b>) -> RopeGraphemes<'b> { - let mut chunks = slice.chunks(); - let first_chunk = chunks.next().unwrap_or(""); - RopeGraphemes { - text: *slice, - chunks: chunks, - cur_chunk: first_chunk, - cur_chunk_start: 0, - cursor: GraphemeCursor::new(0, slice.len_bytes(), true), - } - } -} - -impl<'a> Iterator for RopeGraphemes<'a> { - type Item = RopeSlice<'a>; - - fn next(&mut self) -> Option> { - let a = self.cursor.cur_cursor(); - let b; - loop { - match self - .cursor - .next_boundary(self.cur_chunk, self.cur_chunk_start) - { - Ok(None) => { - return None; - } - Ok(Some(n)) => { - b = n; - break; - } - Err(GraphemeIncomplete::NextChunk) => { - self.cur_chunk_start += self.cur_chunk.len(); - self.cur_chunk = self.chunks.next().unwrap_or(""); - } - _ => unreachable!(), - } - } - - if a < self.cur_chunk_start { - let a_char = self.text.byte_to_char(a); - let b_char = self.text.byte_to_char(b); - - Some(self.text.slice(a_char..b_char)) - } else { - let a2 = a - self.cur_chunk_start; - let b2 = b - self.cur_chunk_start; - Some((&self.cur_chunk[a2..b2]).into()) - } - } -} - -//============================================================= - #[derive(Copy, Clone)] pub struct Timer { last_instant: Instant, diff --git a/sub_crates/backend/src/buffer.rs b/sub_crates/backend/src/buffer.rs index 80050d0..ac42098 100644 --- a/sub_crates/backend/src/buffer.rs +++ b/sub_crates/backend/src/buffer.rs @@ -61,7 +61,7 @@ impl Buffer { *mark = mark.edit((start, end), post_len); } - mark_set.merge_touching(); + mark_set.make_consistent(); } // Do removal if needed. @@ -92,7 +92,7 @@ impl Buffer { *mark = mark.edit((start, end), post_len); } - mark_set.merge_touching(); + mark_set.make_consistent(); } // Do removal if needed. @@ -128,7 +128,7 @@ impl Buffer { *mark = mark.edit((start, end), post_len); } - mark_set.merge_touching(); + mark_set.make_consistent(); } // Do removal if needed. diff --git a/sub_crates/backend/src/history.rs b/sub_crates/backend/src/history.rs index aafce83..5b06e6b 100644 --- a/sub_crates/backend/src/history.rs +++ b/sub_crates/backend/src/history.rs @@ -15,6 +15,7 @@ impl History { pub fn push_edit(&mut self, edit: Edit) { self.edits.truncate(self.position); self.edits.push(edit); + self.position += 1; } pub fn undo(&mut self) -> Option<&Edit> { diff --git a/sub_crates/backend/src/marks.rs b/sub_crates/backend/src/marks.rs index 383769d..b8714b9 100644 --- a/sub_crates/backend/src/marks.rs +++ b/sub_crates/backend/src/marks.rs @@ -111,7 +111,7 @@ impl Mark { /// and code that modifies a MarkSet should ensure that the invariants remain /// true. /// -/// The `merge_touching` method will ensure that all expected invariants hold, +/// The `make_consistent` method will ensure that all expected invariants hold, /// modifying the set to meet the invariants if needed. #[derive(Debug, Clone)] pub struct MarkSet { @@ -133,6 +133,23 @@ impl MarkSet { self.marks.clear(); } + pub fn truncate(&mut self, len: usize) { + self.marks.truncate(len); + self.main_mark_idx = self.main_mark_idx.min(self.marks.len().saturating_sub(1)); + } + + /// Returns the main mark, if it exists. + pub fn main(&self) -> Option { + self.marks.get(self.main_mark_idx).map(|m| *m) + } + + /// Removes all marks except the main one. + pub fn reduce_to_main(&mut self) { + self.marks.swap(0, self.main_mark_idx); + self.main_mark_idx = 0; + self.marks.truncate(1); + } + /// Adds a new mark to the set, inserting it into its sorted position, and /// returns the index where it was inserted. /// @@ -140,7 +157,7 @@ impl MarkSet { /// range. /// /// This does *not* preserve disjointedness. You should call - /// `merge_touching` after you have added all the marks you want. + /// `make_consistent` after you have added all the marks you want. /// /// Runs in O(N + log N) time worst-case, but when the new mark is /// inserted at the end of the set it is amortized O(1). @@ -185,7 +202,7 @@ impl MarkSet { /// into one. /// /// Runs in O(N) time. - pub fn merge_touching(&mut self) { + pub fn make_consistent(&mut self) { let mut i1 = 0; let mut i2 = 1;