Improved file loading.

File loading is now the responsibility of the Buffer, which allows
it to do some weird internal stuff to make it faster.

Also, using custom code for scanning the text, so all line endings
are handled now, not just LF and CRLF.
This commit is contained in:
Nathan Vegdahl 2015-02-01 23:17:43 -08:00
parent 643db93939
commit e29777e33c
6 changed files with 105 additions and 27 deletions

View File

@ -103,16 +103,15 @@ impl Line {
} }
/// Creates a new Line from a string. pub fn new_from_str_unchecked(text: &str) -> Line {
/// Does not check to see if the string has internal newlines.
/// This is primarily used for efficient loading of files.
pub fn new_from_string_unchecked(text: String) -> Line {
// Initialize Line // Initialize Line
let mut tl = Line { let mut tl = Line {
text: text.into_bytes(), text: Vec::new(),
ending: LineEnding::None, ending: LineEnding::None,
}; };
tl.text.push_all(text.as_bytes());
// Check for line ending // Check for line ending
let mut le_size: usize = 0; let mut le_size: usize = 0;
let text_size = tl.text.len(); let text_size = tl.text.len();
@ -191,6 +190,14 @@ impl Line {
} }
/// Creates a new Line from a string.
/// Does not check to see if the string has internal newlines.
/// This is primarily used for efficient loading of files.
pub fn new_from_string_unchecked(text: String) -> Line {
return Line::new_from_str_unchecked(text.as_slice());
}
/// Returns the total number of unicode graphemes in the line /// Returns the total number of unicode graphemes in the line
pub fn grapheme_count(&self) -> usize { pub fn grapheme_count(&self) -> usize {
let mut count = grapheme_count(self.as_str()); let mut count = grapheme_count(self.as_str());

View File

@ -1,6 +1,9 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::mem; use std::mem;
use std::path::Path;
use std::old_io::fs::File;
use std::old_io::{IoResult, BufferedReader};
use self::line::Line; use self::line::Line;
use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter}; use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
@ -22,6 +25,7 @@ mod undo_stack;
/// A text buffer /// A text buffer
pub struct Buffer<T: LineFormatter> { pub struct Buffer<T: LineFormatter> {
text: BufferNode, text: BufferNode,
file_path: Option<Path>,
undo_stack: UndoStack, undo_stack: UndoStack,
pub formatter: T, pub formatter: T,
} }
@ -31,12 +35,62 @@ impl<T: LineFormatter> Buffer<T> {
pub fn new(formatter: T) -> Buffer<T> { pub fn new(formatter: T) -> Buffer<T> {
Buffer { Buffer {
text: BufferNode::new(&formatter), text: BufferNode::new(&formatter),
file_path: None,
undo_stack: UndoStack::new(), undo_stack: UndoStack::new(),
formatter: formatter, formatter: formatter,
} }
} }
pub fn new_from_file(formatter: T, path: &Path) -> IoResult<Buffer<T>> {
let mut f = BufferedReader::new(try!(File::open(path)));
let mut buf = Buffer {
text: BufferNode::new(&formatter),
file_path: Some(path.clone()),
undo_stack: UndoStack::new(),
formatter: formatter,
};
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_unchecked(substr);
let node = BufferNode::new_from_line_with_count_unchecked(&buf.formatter, line, count);
buf.append_leaf_node_unchecked(node);
}
a = b;
}
// Remove initial blank line
buf.remove_lines(0, 1);
return Ok(buf);
}
//------------------------------------------------------------------------ //------------------------------------------------------------------------
// Functions for getting information about the buffer. // Functions for getting information about the buffer.
@ -206,6 +260,11 @@ impl<T: LineFormatter> Buffer<T> {
} }
fn append_leaf_node_unchecked(&mut self, node: BufferNode) {
self.text.append_leaf_node_unchecked_recursive(&self.formatter, node);
}
/// Runs the formatter on all of the text. Should be run whenever the /// Runs the formatter on all of the text. Should be run whenever the
/// formatter has been changed. /// formatter has been changed.
pub fn reformat(&mut self) { pub fn reformat(&mut self) {

View File

@ -49,6 +49,17 @@ impl BufferNode {
} }
pub fn new_from_line_with_count_unchecked<T: LineFormatter>(_: &T, line: Line, grapheme_count: usize) -> BufferNode {
BufferNode {
data: BufferNodeData::Leaf(line),
tree_height: 1,
grapheme_count: grapheme_count,
line_count: 1,
vis_dim: (1, grapheme_count),
}
}
fn update_height(&mut self) { fn update_height(&mut self) {
match self.data { match self.data {
BufferNodeData::Leaf(_) => { BufferNodeData::Leaf(_) => {
@ -585,6 +596,21 @@ impl BufferNode {
} }
pub fn append_leaf_node_unchecked_recursive<T: LineFormatter>(&mut self, f: &T, node: BufferNode) {
if let BufferNodeData::Branch(_, ref mut right) = self.data {
right.append_leaf_node_unchecked_recursive(f, node);
}
else {
let mut new_left_node = BufferNode::new(f);
mem::swap(self, &mut new_left_node);
self.data = BufferNodeData::Branch(Box::new(new_left_node), Box::new(node));
}
self.update_stats(f);
self.rebalance(f);
}
/// Removes lines in line number range [line_a, line_b) /// Removes lines in line number range [line_a, line_b)
pub fn remove_lines_recursive<T: LineFormatter>(&mut self, f: &T, line_a: usize, line_b: usize) { pub fn remove_lines_recursive<T: LineFormatter>(&mut self, f: &T, line_a: usize, line_b: usize) {
let mut remove_left = false; let mut remove_left = false;

View File

@ -6,7 +6,7 @@ use buffer::line_formatter::LineFormatter;
use buffer::line_formatter::RoundingBehavior::*; use buffer::line_formatter::RoundingBehavior::*;
use std::path::Path; use std::path::Path;
use std::cmp::{min, max}; use std::cmp::{min, max};
use files::{load_file_to_buffer, save_buffer_to_file}; use files::{save_buffer_to_file};
use string_utils::grapheme_count; use string_utils::grapheme_count;
use self::cursor::CursorSet; use self::cursor::CursorSet;
@ -47,7 +47,8 @@ impl<T: LineFormatter> Editor<T> {
} }
pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> { pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> {
let buf = match load_file_to_buffer(path, formatter) { //let buf = match load_file_to_buffer(path, formatter) {
let buf = match Buffer::new_from_file(formatter, path) {
Ok(b) => {b}, Ok(b) => {b},
// TODO: handle un-openable file better // TODO: handle un-openable file better
_ => panic!("Could not open file!"), _ => panic!("Could not open file!"),

View File

@ -1,26 +1,11 @@
use std::old_io::{IoResult, BufferedReader, BufferedWriter}; use std::old_io::{IoResult, BufferedWriter};
use std::old_io::fs::File; use std::old_io::fs::File;
use std::path::Path; use std::path::Path;
use buffer::line::{Line, line_ending_to_str}; use buffer::line::{line_ending_to_str};
use buffer::line_formatter::LineFormatter; use buffer::line_formatter::LineFormatter;
use buffer::Buffer as TextBuffer; use buffer::Buffer as TextBuffer;
pub fn load_file_to_buffer<T: LineFormatter>(path: &Path, lf: T) -> IoResult<TextBuffer<T>> {
let mut tb = TextBuffer::new(lf);
let mut f = BufferedReader::new(try!(File::open(path)));
for line in f.lines() {
let l = Line::new_from_string_unchecked(line.unwrap());
tb.append_line_unchecked(l);
}
// Remove initial blank line
tb.remove_lines(0, 1);
return Ok(tb);
}
pub fn save_buffer_to_file<T: LineFormatter>(tb: &TextBuffer<T>, path: &Path) -> IoResult<()> { pub fn save_buffer_to_file<T: LineFormatter>(tb: &TextBuffer<T>, path: &Path) -> IoResult<()> {
// TODO: make save atomic // TODO: make save atomic
let mut iter = tb.line_iter(); let mut iter = tb.line_iter();

View File

@ -48,8 +48,8 @@
- Start with the emacs approach, and you can always migrate to something - Start with the emacs approach, and you can always migrate to something
more sophisticated later. more sophisticated later.
- Custom line iterator code for file loading, because rust's built-in one //- Custom line iterator code for file loading, because rust's built-in one
only recognizes LF and CRLF. // only recognizes LF and CRLF.
- File loading is currently very slow. Investigate. - File loading is currently very slow. Investigate.
- Both Emacs and Vim do line-wrapping extremely efficiently, even for very - Both Emacs and Vim do line-wrapping extremely efficiently, even for very
large files. Investigate how they do this. large files. Investigate how they do this.