diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index af8f4b2..55f29c1 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -5,15 +5,12 @@ use std::old_path::Path; use std::old_io::fs::File; use std::old_io::{IoResult, BufferedReader, BufferedWriter}; -use self::line::Line; -use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter}; +pub use self::rope::{Rope, RopeSlice, RopeGraphemeIter, RopeLineIter}; use self::undo_stack::{UndoStack}; use self::undo_stack::Operation::*; -use string_utils::{is_line_ending, grapheme_count}; +use string_utils::grapheme_count; -pub mod line; mod rope; -mod node; mod undo_stack; @@ -23,7 +20,7 @@ mod undo_stack; /// A text buffer pub struct Buffer { - text: BufferNode, + text: Rope, file_path: Option, undo_stack: UndoStack, } @@ -33,7 +30,7 @@ pub struct Buffer { impl Buffer { pub fn new() -> Buffer { Buffer { - text: BufferNode::new(), + text: Rope::new(), file_path: None, undo_stack: UndoStack::new(), } @@ -42,58 +39,23 @@ impl Buffer { pub fn new_from_file(path: &Path) -> IoResult { let mut f = BufferedReader::new(try!(File::open(path))); + let string = f.read_to_string().unwrap(); - let mut buf = Buffer { - text: BufferNode::new(), + let buf = Buffer { + text: Rope::new_from_str(string.as_slice()), file_path: Some(path.clone()), undo_stack: UndoStack::new(), }; - let string = f.read_to_string().unwrap(); - let mut g_iter = string.as_slice().grapheme_indices(true); - let mut done = false; - let mut a = 0; - let mut b = 0; - - while !done { - let mut count = 0; - loop { - if let Some((i, g)) = g_iter.next() { - count += 1; - b = i + g.len(); - if is_line_ending(g) { - break; - } - } - else { - done = true; - break; - } - } - - if a != b { - let substr = &string[a..b]; - let line = Line::new_from_str_with_count_unchecked(substr, count); - let node = BufferNode::new_from_line_with_count_unchecked(line, count); - buf.append_leaf_node_unchecked(node); - } - - a = b; - } - - // Remove initial blank line - buf.remove_lines(0, 1); - return Ok(buf); } pub fn save_to_file(&self, path: &Path) -> IoResult<()> { - // TODO: make more efficient let mut f = BufferedWriter::new(try!(File::create(path))); - for g in self.grapheme_iter() { - let _ = f.write_str(g); + for c in self.text.chunk_iter() { + let _ = f.write_str(c); } return Ok(()); @@ -107,12 +69,12 @@ impl Buffer { //------------------------------------------------------------------------ pub fn grapheme_count(&self) -> usize { - self.text.grapheme_count + self.text.grapheme_count() } pub fn line_count(&self) -> usize { - self.text.line_count + self.text.line_count() } @@ -130,7 +92,7 @@ impl Buffer { } fn _insert_text(&mut self, text: &str, pos: usize) { - self.text.insert_text(text, pos); + self.text.insert_text_at_grapheme_index(text, pos); } @@ -173,16 +135,13 @@ impl Buffer { panic!("Buffer::_remove_text(): attempt to remove text past the end of buffer."); } // Complete removal of all text - else if pos_a == 0 && pos_b == self.text.grapheme_count { - let mut temp_node = BufferNode::new(); + else if pos_a == 0 && pos_b == self.text.grapheme_count() { + let mut temp_node = Rope::new(); mem::swap(&mut (self.text), &mut temp_node); } // All other cases else { - if self.text.remove_text_recursive(pos_a, pos_b, true) { - panic!("Buffer::_remove_text(): dangling left side remains. This should never happen!"); - } - self.text.set_last_line_ending_recursive(); + self.text.remove_text_between_grapheme_indices(pos_a, pos_b); } } @@ -232,6 +191,7 @@ impl Buffer { /// 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 { @@ -246,32 +206,25 @@ impl Buffer { panic!("Buffer::remove_lines(): attempt to remove lines past the last line of text."); } // Complete removal of all lines - else if line_a == 0 && line_b == self.text.line_count { - let mut temp_node = BufferNode::new(); + else if line_a == 0 && line_b == self.text.line_count() { + let mut temp_node = Rope::new(); mem::swap(&mut (self.text), &mut temp_node); } // All other cases else { - self.text.remove_lines_recursive(line_a, line_b); - self.text.set_last_line_ending_recursive(); + let a = self.text.line_index_to_grapheme_index(line_a); + let b = if line_b < self.line_count() { + self.text.line_index_to_grapheme_index(line_b) + } + else { + self.text.grapheme_count() + }; + + self.text.remove_text_between_grapheme_indices(a, b); } } - /// Blindly appends a line to the end of the current text without - /// doing any sanity checks. This is primarily for efficient - /// file loading. - pub fn append_line_unchecked(&mut self, line: Line) { - self.text.append_line_unchecked_recursive(line); - } - - - fn append_leaf_node_unchecked(&mut self, node: BufferNode) { - self.text.append_leaf_node_unchecked_recursive(node); - } - - - //------------------------------------------------------------------------ @@ -359,7 +312,7 @@ impl Buffer { /// 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) { - return self.text.index_to_line_col_recursive(pos); + return self.text.grapheme_index_to_line_col(pos); } @@ -371,7 +324,7 @@ impl Buffer { /// 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 { - return self.text.line_col_to_index_recursive(pos); + return self.text.line_col_to_grapheme_index(pos); } @@ -384,20 +337,25 @@ impl Buffer { panic!("Buffer::get_grapheme(): index past last grapheme."); } else { - return self.text.get_grapheme_recursive(index); + return &self.text[index]; } } - pub fn get_line<'a>(&'a self, index: usize) -> &'a Line { + pub fn get_line<'a>(&'a self, index: usize) -> RopeSlice<'a> { if index >= self.line_count() { panic!("get_line(): index out of bounds."); } - // NOTE: this can be done non-recursively, which would be more - // efficient. However, it seems likely to require unsafe code - // if done that way. - return self.text.get_line_recursive(index); + let a = self.text.line_index_to_grapheme_index(index); + let b = if index+1 < self.line_count() { + self.text.line_index_to_grapheme_index(index+1) + } + else { + self.text.grapheme_count() + }; + + return self.text.slice(a, b); } @@ -416,7 +374,7 @@ impl Buffer { let mut i = 0; let i_end = pos_b - pos_a; - for g in self.grapheme_iter_at_index(pos_a) { + for g in self.text.grapheme_iter_at_index(pos_a) { if i == i_end { break; } @@ -436,34 +394,26 @@ impl Buffer { //------------------------------------------------------------------------ /// Creates an iterator at the first character - pub fn grapheme_iter<'a>(&'a self) -> BufferGraphemeIter<'a> { - BufferGraphemeIter { - gi: self.text.grapheme_iter() - } + pub fn grapheme_iter<'a>(&'a self) -> RopeGraphemeIter<'a> { + self.text.grapheme_iter() } /// 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) -> BufferGraphemeIter<'a> { - BufferGraphemeIter { - gi: self.text.grapheme_iter_at_index(index) - } + pub fn grapheme_iter_at_index<'a>(&'a self, index: usize) -> RopeGraphemeIter<'a> { + self.text.grapheme_iter_at_index(index) } - pub fn line_iter<'a>(&'a self) -> BufferLineIter<'a> { - BufferLineIter { - li: self.text.line_iter() - } + pub fn line_iter<'a>(&'a self) -> RopeLineIter<'a> { + self.text.line_iter() } - pub fn line_iter_at_index<'a>(&'a self, index: usize) -> BufferLineIter<'a> { - BufferLineIter { - li: self.text.line_iter_at_index(index) - } + pub fn line_iter_at_index<'a>(&'a self, index: usize) -> RopeLineIter<'a> { + self.text.line_iter_at_index(index) } @@ -471,77 +421,6 @@ impl Buffer { - -//============================================================= -// Buffer iterators -//============================================================= - -/// An iterator over a text buffer's graphemes -pub struct BufferGraphemeIter<'a> { - gi: BufferNodeGraphemeIter<'a>, -} - - -impl<'a> BufferGraphemeIter<'a> { - // Puts the iterator on the next line. - // Returns true if there was a next line, - // false if there wasn't. - pub fn next_line(&mut self) -> bool { - self.gi.next_line() - } - - - // Skips the iterator n graphemes ahead. - // If it runs out of graphemes before reaching the desired skip count, - // returns false. Otherwise returns true. - pub fn skip_graphemes(&mut self, n: usize) -> bool { - self.gi.skip_graphemes(n) - } - - //pub fn skip_non_newline_graphemes(&mut self, n: usize) -> bool { - // let mut i: usize = 0; - // - // for g in self.gi { - // if is_line_ending(g) { - // return true; - // } - // - // i += 1; - // if i >= n { - // break; - // } - // } - // - // return false; - //} -} - - -impl<'a> Iterator for BufferGraphemeIter<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option<&'a str> { - self.gi.next() - } -} - - -pub struct BufferLineIter<'a> { - li: BufferNodeLineIter<'a>, -} - - -impl<'a> Iterator for BufferLineIter<'a> { - type Item = &'a Line; - - fn next(&mut self) -> Option<&'a Line> { - self.li.next() - } -} - - - - //================================================================ // TESTS //================================================================ @@ -549,7 +428,7 @@ impl<'a> Iterator for BufferLineIter<'a> { #[cfg(test)] mod tests { #![allow(unused_imports)] - use super::{Buffer, BufferGraphemeIter, BufferLineIter}; + use super::{Buffer, BufferLineIter}; #[test] fn insert_text() { diff --git a/src/buffer/rope.rs b/src/buffer/rope.rs index cf70698..cc538b5 100644 --- a/src/buffer/rope.rs +++ b/src/buffer/rope.rs @@ -8,7 +8,9 @@ use string_utils::{ insert_text_at_grapheme_index, remove_text_between_grapheme_indices, split_string_at_grapheme_index, - is_line_ending + is_line_ending, + LineEnding, + str_to_line_ending, }; pub const MIN_NODE_SIZE: usize = 64; @@ -259,6 +261,11 @@ impl Rope { } + pub fn grapheme_at_index<'a>(&'a self, index: usize) -> &'a str { + &self[index] + } + + /// Inserts the given text at the given grapheme index. /// For small lengths of 'text' runs in O(log N) time. /// For large lengths of 'text', dunno. But it seems to perform @@ -478,10 +485,50 @@ impl Rope { return RopeGraphemeIter { chunk_iter: chunk_iter, cur_chunk: giter, + length: None, }; } + /// Creates an iterator that starts a pos_a and stops just before pos_b. + pub fn grapheme_iter_between_indices<'a>(&'a self, pos_a: usize, pos_b: usize) -> RopeGraphemeIter<'a> { + let mut iter = self.grapheme_iter_at_index(pos_a); + iter.length = Some(pos_b - pos_a); + return iter; + } + + + /// Creates an iterator over the lines in the rope. + pub fn line_iter<'a>(&'a self) -> RopeLineIter<'a> { + RopeLineIter { + rope: self, + li: 0, + } + } + + + /// Creates an iterator over the lines in the rope, starting at the given + /// line index. + pub fn line_iter_at_index<'a>(&'a self, index: usize) -> RopeLineIter<'a> { + RopeLineIter { + rope: self, + li: index, + } + } + + + pub fn slice<'a>(&'a self, pos_a: usize, pos_b: usize) -> RopeSlice<'a> { + let a = pos_a; + let b = min(self.grapheme_count_, pos_b); + + RopeSlice { + rope: self, + start: a, + end: b, + } + } + + // Creates a graphviz document of the Rope's structure, and returns // it as a string. For debugging purposes. pub fn to_graphviz(&self) -> String { @@ -944,6 +991,7 @@ impl<'a> Iterator for RopeChunkIter<'a> { pub struct RopeGraphemeIter<'a> { chunk_iter: RopeChunkIter<'a>, cur_chunk: Graphemes<'a>, + length: Option, } @@ -951,8 +999,17 @@ impl<'a> Iterator for RopeGraphemeIter<'a> { type Item = &'a str; fn next(&mut self) -> Option<&'a str> { + if let Some(ref mut l) = self.length { + if *l == 0 { + return None; + } + } + loop { if let Some(g) = self.cur_chunk.next() { + if let Some(ref mut l) = self.length { + *l -= 1; + } return Some(g); } else { @@ -970,6 +1027,106 @@ impl<'a> Iterator for RopeGraphemeIter<'a> { +/// An iterator over a rope's lines, returned as RopeSlice's +pub struct RopeLineIter<'a> { + rope: &'a Rope, + li: usize, +} + + +impl<'a> Iterator for RopeLineIter<'a> { + type Item = RopeSlice<'a>; + + fn next(&mut self) -> Option> { + if self.li >= self.rope.line_count() { + return None; + } + else { + let a = self.rope.line_index_to_grapheme_index(self.li); + let b = if self.li+1 < self.rope.line_count() { + self.rope.line_index_to_grapheme_index(self.li+1) + } + else { + self.rope.grapheme_count() + }; + + self.li += 1; + + return Some(self.rope.slice(a, b)); + } + } +} + + + + +//============================================================= +// Rope slice +//============================================================= + +/// An immutable slice into a Rope +pub struct RopeSlice<'a> { + rope: &'a Rope, + start: usize, + end: usize, +} + + +impl<'a> RopeSlice<'a> { + pub fn grapheme_count(&self) -> usize { + self.end - self.start + } + + + pub fn grapheme_iter(&self) -> RopeGraphemeIter<'a> { + self.rope.grapheme_iter_between_indices(self.start, self.end) + } + + pub fn grapheme_iter_at_index(&self, pos: usize) -> RopeGraphemeIter<'a> { + let a = min(self.end, self.start + pos); + + self.rope.grapheme_iter_between_indices(a, self.end) + } + + pub fn grapheme_iter_between_indices(&self, pos_a: usize, pos_b: usize) -> RopeGraphemeIter<'a> { + let a = min(self.end, self.start + pos_a); + let b = min(self.end, self.start + pos_b); + + self.rope.grapheme_iter_between_indices(a, b) + } + + + pub fn grapheme_at_index(&self, index: usize) -> &'a str { + &self.rope[self.start+index] + } + + + /// Convenience function for when the slice represents a line + pub fn ending(&self) -> LineEnding { + if self.grapheme_count() > 0 { + let g = self.grapheme_at_index(self.grapheme_count() - 1); + return str_to_line_ending(g); + } + else { + return LineEnding::None; + } + } + + + pub fn slice(&self, pos_a: usize, pos_b: usize) -> RopeSlice<'a> { + let a = min(self.end, self.start + pos_a); + let b = min(self.end, self.start + pos_b); + + RopeSlice { + rope: self.rope, + start: a, + end: b, + } + } +} + + + //=================================================================== // Unit test diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 61f0f29..df7aa3d 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,12 +1,11 @@ #![allow(dead_code)] use buffer::Buffer; -use buffer::line::LineEnding; use formatter::LineFormatter; use formatter::RoundingBehavior::*; use std::old_path::Path; use std::cmp::{min, max}; -use string_utils::grapheme_count; +use string_utils::{grapheme_count, LineEnding}; use utils::digit_count; use self::cursor::CursorSet; @@ -103,7 +102,7 @@ impl Editor { // Collect statistics let mut line_i: usize = 0; for line in self.buffer.line_iter() { - match line.ending { + match line.ending() { LineEnding::None => { }, LineEnding::CRLF => { diff --git a/src/formatter.rs b/src/formatter.rs index f14e919..3766d26 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -47,7 +47,7 @@ pub trait LineFormatter { let (line_block, col_i_adjusted) = block_index_and_offset(col_i); // Get an iter into the right block - let g_iter = line.grapheme_iter_at_index_with_max_length(line_block * LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH); + let g_iter = line.grapheme_iter_between_indices(line_block * LINE_BLOCK_LENGTH, line_block * (LINE_BLOCK_LENGTH+1)); return self.index_to_v2d(g_iter, col_i_adjusted).1; } @@ -64,7 +64,7 @@ pub trait LineFormatter { // Find the right block in the line, and the index within that block let (line_block, col_i_adjusted) = block_index_and_offset(col_i); - let (mut y, x) = self.index_to_v2d(buf.get_line(line_i).grapheme_iter_at_index_with_max_length(line_block*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH), col_i_adjusted); + let (mut y, x) = self.index_to_v2d(buf.get_line(line_i).grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1)), col_i_adjusted); let mut new_y = y as isize + offset; // First, find the right line while keeping track of the vertical offset @@ -72,7 +72,7 @@ pub trait LineFormatter { let mut block_index: usize = line_block; loop { line = buf.get_line(line_i); - let (h, _) = self.dimensions(line.grapheme_iter_at_index_with_max_length(line_block*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH)); + let (h, _) = self.dimensions(line.grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1))); if new_y >= 0 && new_y < h as isize { y = new_y as usize; @@ -110,7 +110,7 @@ pub trait LineFormatter { else { block_index -= 1; } - let (h, _) = self.dimensions(line.grapheme_iter_at_index_with_max_length(line_block*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH)); + let (h, _) = self.dimensions(line.grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1))); new_y += h as isize; } else { @@ -121,7 +121,7 @@ pub trait LineFormatter { // Next, convert the resulting coordinates back into buffer-wide // coordinates. - col_i = (block_index * LINE_BLOCK_LENGTH) + self.v2d_to_index(line.grapheme_iter_at_index_with_max_length(block_index*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH), (y, x), rounding); + col_i = (block_index * LINE_BLOCK_LENGTH) + self.v2d_to_index(line.grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1)), (y, x), rounding); return buf.line_col_to_index((line_i, col_i)); } @@ -136,8 +136,8 @@ pub trait LineFormatter { let start_index = line_block * LINE_BLOCK_LENGTH; // Calculate the horizontal position - let (v, _) = self.index_to_v2d(line.grapheme_iter_at_index_with_max_length(start_index, LINE_BLOCK_LENGTH), col_i_adjusted); - let mut new_col_i = start_index + self.v2d_to_index(line.grapheme_iter_at_index_with_max_length(start_index, LINE_BLOCK_LENGTH), (v, horizontal), (RoundingBehavior::Floor, rounding)); + let (v, _) = self.index_to_v2d(line.grapheme_iter_between_indices(start_index, start_index+LINE_BLOCK_LENGTH), col_i_adjusted); + let mut new_col_i = start_index + self.v2d_to_index(line.grapheme_iter_between_indices(start_index, start_index+LINE_BLOCK_LENGTH), (v, horizontal), (RoundingBehavior::Floor, rounding)); // Make sure we're not pushing the index off the end of the line if (line_i + 1) < buf.line_count() diff --git a/src/string_utils.rs b/src/string_utils.rs index 3797e94..4aa7f42 100644 --- a/src/string_utils.rs +++ b/src/string_utils.rs @@ -189,4 +189,95 @@ pub fn split_string_at_grapheme_index(s1: &mut String, pos: usize) -> String { } return s2; -} \ No newline at end of file +} + + + + + + + +/// Represents one of the valid Unicode line endings. +/// Also acts as an index into `LINE_ENDINGS`. +#[derive(PartialEq, Copy)] +pub enum LineEnding { + None = 0, // No line ending + CRLF = 1, // CarriageReturn followed by LineFeed + LF = 2, // U+000A -- LineFeed + VT = 3, // U+000B -- VerticalTab + FF = 4, // U+000C -- FormFeed + CR = 5, // U+000D -- CarriageReturn + NEL = 6, // U+0085 -- NextLine + LS = 7, // U+2028 -- Line Separator + PS = 8, // U+2029 -- ParagraphSeparator +} + +pub fn str_to_line_ending(g: &str) -> LineEnding { + match g { + //============== + // Line endings + //============== + + // CRLF + "\u{000D}\u{000A}" => { + return LineEnding::CRLF; + }, + + // LF + "\u{000A}" => { + return LineEnding::LF; + }, + + // VT + "\u{000B}" => { + return LineEnding::VT; + }, + + // FF + "\u{000C}" => { + return LineEnding::FF; + }, + + // CR + "\u{000D}" => { + return LineEnding::CR; + }, + + // NEL + "\u{0085}" => { + return LineEnding::NEL; + }, + + // LS + "\u{2028}" => { + return LineEnding::LS; + }, + + // PS + "\u{2029}" => { + return LineEnding::PS; + }, + + // Not a line ending + _ => { + return LineEnding::None; + } + } +} + +pub fn line_ending_to_str(ending: LineEnding) -> &'static str { + LINE_ENDINGS[ending as usize] +} + +/// An array of string literals corresponding to the possible +/// unicode line endings. +pub const LINE_ENDINGS: [&'static str; 9] = ["", + "\u{000D}\u{000A}", + "\u{000A}", + "\u{000B}", + "\u{000C}", + "\u{000D}", + "\u{0085}", + "\u{2028}", + "\u{2029}" +]; diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index 1cdb66e..3a8be37 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -6,9 +6,8 @@ use editor::Editor; use formatter::{LineFormatter, LINE_BLOCK_LENGTH, block_index_and_offset}; use std::char; use std::time::duration::Duration; -use string_utils::{is_line_ending}; +use string_utils::{is_line_ending, line_ending_to_str, LineEnding}; use utils::digit_count; -use buffer::line::{line_ending_to_str, LineEnding}; use self::formatter::ConsoleLineFormatter; pub mod formatter; @@ -360,7 +359,7 @@ impl TermUI { let (line_index, col_i) = editor.buffer.index_to_line_col(editor.view_pos.0); let (mut line_block_index, _) = block_index_and_offset(col_i); let mut grapheme_index = editor.buffer.line_col_to_index((line_index, line_block_index * LINE_BLOCK_LENGTH)); - let (vis_line_offset, _) = editor.formatter.index_to_v2d(editor.buffer.get_line(line_index).grapheme_iter_at_index_with_max_length(line_block_index*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH), editor.view_pos.0 - grapheme_index); + let (vis_line_offset, _) = editor.formatter.index_to_v2d(editor.buffer.get_line(line_index).grapheme_iter_between_indices(line_block_index*LINE_BLOCK_LENGTH, (line_block_index+1)*LINE_BLOCK_LENGTH), editor.view_pos.0 - grapheme_index); let mut screen_line = c1.0 as isize - vis_line_offset as isize; let screen_col = c1.1 as isize + gutter_width as isize;