WIP refactor to switch over to Rope from BufferNode.

This commit is contained in:
Nathan Vegdahl 2015-02-22 11:45:57 -08:00
parent 0ee364f6ce
commit 02f24e4e6f
6 changed files with 311 additions and 186 deletions

View File

@ -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<Path>,
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<Buffer> {
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() {

View File

@ -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<usize>,
}
@ -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<RopeSlice<'a>> {
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

View File

@ -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<T: LineFormatter> Editor<T> {
// 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 => {

View File

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

View File

@ -190,3 +190,94 @@ pub fn split_string_at_grapheme_index(s1: &mut String, pos: usize) -> String {
return s2;
}
/// 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}"
];

View File

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