From e29777e33c9e2e13ee7ffc9f600b1464702167a8 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 1 Feb 2015 23:17:43 -0800 Subject: [PATCH] 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. --- src/buffer/line.rs | 17 +++++++++---- src/buffer/mod.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/buffer/node.rs | 26 ++++++++++++++++++++ src/editor/mod.rs | 5 ++-- src/files.rs | 21 +++-------------- todo.md | 4 ++-- 6 files changed, 105 insertions(+), 27 deletions(-) diff --git a/src/buffer/line.rs b/src/buffer/line.rs index 14e38a8..2142af7 100644 --- a/src/buffer/line.rs +++ b/src/buffer/line.rs @@ -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()); diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index ef64675..5d30951 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -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 { text: BufferNode, + file_path: Option, undo_stack: UndoStack, pub formatter: T, } @@ -31,10 +35,60 @@ impl Buffer { pub fn new(formatter: T) -> Buffer { Buffer { text: BufferNode::new(&formatter), + file_path: None, undo_stack: UndoStack::new(), formatter: formatter, } } + + + pub fn new_from_file(formatter: T, path: &Path) -> IoResult> { + 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); + } @@ -206,6 +260,11 @@ impl Buffer { } + 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) { diff --git a/src/buffer/node.rs b/src/buffer/node.rs index 414d649..366b38c 100644 --- a/src/buffer/node.rs +++ b/src/buffer/node.rs @@ -49,6 +49,17 @@ impl BufferNode { } + pub fn new_from_line_with_count_unchecked(_: &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(&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(&mut self, f: &T, line_a: usize, line_b: usize) { let mut remove_left = false; diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 606624f..0aefeb8 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -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 Editor { } pub fn new_from_file(formatter: T, path: &Path) -> Editor { - 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!"), diff --git a/src/files.rs b/src/files.rs index 01f53c3..b06751d 100644 --- a/src/files.rs +++ b/src/files.rs @@ -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(path: &Path, lf: T) -> IoResult> { - 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(tb: &TextBuffer, path: &Path) -> IoResult<()> { // TODO: make save atomic let mut iter = tb.line_iter(); @@ -32,4 +17,4 @@ pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> } return Ok(()); -} \ No newline at end of file +} diff --git a/todo.md b/todo.md index e0f89de..7a8df3e 100644 --- a/todo.md +++ b/todo.md @@ -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.