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.
/// 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 {
pub fn new_from_str_unchecked(text: &str) -> Line {
// Initialize Line
let mut tl = Line {
text: text.into_bytes(),
text: Vec::new(),
ending: LineEnding::None,
};
tl.text.push_all(text.as_bytes());
// Check for line ending
let mut le_size: usize = 0;
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
pub fn grapheme_count(&self) -> usize {
let mut count = grapheme_count(self.as_str());

View File

@ -1,6 +1,9 @@
#![allow(dead_code)]
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::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
@ -22,6 +25,7 @@ mod undo_stack;
/// A text buffer
pub struct Buffer<T: LineFormatter> {
text: BufferNode,
file_path: Option<Path>,
undo_stack: UndoStack,
pub formatter: T,
}
@ -31,12 +35,62 @@ impl<T: LineFormatter> Buffer<T> {
pub fn new(formatter: T) -> Buffer<T> {
Buffer {
text: BufferNode::new(&formatter),
file_path: None,
undo_stack: UndoStack::new(),
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.
@ -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
/// formatter has been changed.
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) {
match self.data {
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)
pub fn remove_lines_recursive<T: LineFormatter>(&mut self, f: &T, line_a: usize, line_b: usize) {
let mut remove_left = false;

View File

@ -6,7 +6,7 @@ use buffer::line_formatter::LineFormatter;
use buffer::line_formatter::RoundingBehavior::*;
use std::path::Path;
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 self::cursor::CursorSet;
@ -47,7 +47,8 @@ impl<T: LineFormatter> 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},
// TODO: handle un-openable file better
_ => 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::path::Path;
use buffer::line::{Line, line_ending_to_str};
use buffer::line::{line_ending_to_str};
use buffer::line_formatter::LineFormatter;
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<()> {
// TODO: make save atomic
let mut iter = tb.line_iter();

View File

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