From 7099e4d0d3c08084f61dcc63e02955fe16e166a8 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Thu, 1 Jan 2015 13:14:32 -0800 Subject: [PATCH] Sped up file loading and saving. --- src/buffer/line.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++ src/buffer/mod.rs | 10 +++++- src/buffer/node.rs | 21 +++++++++++ src/files.rs | 31 +++++++++------- 4 files changed, 139 insertions(+), 13 deletions(-) diff --git a/src/buffer/line.rs b/src/buffer/line.rs index cae9073..164dd99 100644 --- a/src/buffer/line.rs +++ b/src/buffer/line.rs @@ -102,6 +102,92 @@ 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 { + // Initialize Line + let mut tl = Line { + text: text.into_bytes(), + ending: LineEnding::None, + }; + + // Check for line ending + let mut le_size: uint = 0; + let text_size = tl.text.len(); + if tl.text.len() >= 3 { + match unsafe{mem::transmute::<&[u8], &str>(tl.text.slice_from(text_size-3))} { + // LS + "\u{2028}" => { + tl.ending = LineEnding::LS; + le_size = 3; + }, + + // PS + "\u{2029}" => { + tl.ending = LineEnding::PS; + le_size = 3; + }, + + _ => {} + } + } + else if le_size == 0 && tl.text.len() >= 2 { + match unsafe{mem::transmute::<&[u8], &str>(tl.text.slice_from(text_size-2))} { + // CRLF + "\u{000D}\u{000A}" => { + tl.ending = LineEnding::CRLF; + le_size = 2; + }, + + _ => {} + } + } + else if le_size == 0 && tl.text.len() >= 1 { + match unsafe{mem::transmute::<&[u8], &str>(tl.text.slice_from(text_size-1))} { + // LF or CRLF + "\u{000A}" => { + tl.ending = LineEnding::LF; + le_size = 1; + }, + + // VT + "\u{000B}" => { + tl.ending = LineEnding::VT; + le_size = 1; + }, + + // FF + "\u{000C}" => { + tl.ending = LineEnding::FF; + le_size = 1; + }, + + // CR + "\u{000D}" => { + tl.ending = LineEnding::CR; + le_size = 1; + }, + + // NEL + "\u{0085}" => { + tl.ending = LineEnding::NEL; + le_size = 1; + }, + + _ => {} + } + } + + // Truncate off the line ending, if any + let trunc_size = text_size - le_size; + tl.text.truncate(trunc_size); + + // Done! + return tl; + } + + /// Returns the total number of unicode graphemes in the line pub fn grapheme_count(&self) -> uint { let mut count = grapheme_count(self.as_str()); @@ -340,6 +426,10 @@ pub fn str_to_line_ending(g: &str) -> LineEnding { } } +pub fn line_ending_to_str(ending: LineEnding) -> &'static str { + LINE_ENDINGS[ending as uint] +} + /// An array of string literals corresponding to the possible /// unicode line endings. pub const LINE_ENDINGS: [&'static str, ..9] = ["", diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 9dc6d91..238a242 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -6,7 +6,7 @@ use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter}; use self::line::{Line}; use string_utils::{is_line_ending}; -mod line; +pub mod line; mod node; @@ -50,6 +50,14 @@ impl Buffer { } + /// 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.root.append_line_unchecked_recursive(line); + } + + /// Removes the lines in line indices [line_a, line_b). pub fn remove_lines(&mut self, line_a: uint, line_b: uint) { // Nothing to do diff --git a/src/buffer/node.rs b/src/buffer/node.rs index 8237e6b..495e52e 100644 --- a/src/buffer/node.rs +++ b/src/buffer/node.rs @@ -462,6 +462,27 @@ impl BufferNode { } + pub fn append_line_unchecked_recursive(&mut self, line: Line) { + let mut other_line = Line::new(); + + if let BufferNodeData::Branch(_, ref mut right) = self.data { + right.append_line_unchecked_recursive(line); + } + else { + if let BufferNodeData::Leaf(ref mut this_line) = self.data { + mem::swap(this_line, &mut other_line); + } + + let new_node_a = box BufferNode::new_from_line(other_line); + let new_node_b = box BufferNode::new_from_line(line); + self.data = BufferNodeData::Branch(new_node_a, new_node_b); + } + + self.update_stats(); + self.rebalance(); + } + + /// Removes lines in line number range [line_a, line_b) pub fn remove_lines_recursive(&mut self, line_a: uint, line_b: uint) { let mut remove_left = false; diff --git a/src/files.rs b/src/files.rs index 2a8aec9..03d3b5e 100644 --- a/src/files.rs +++ b/src/files.rs @@ -2,33 +2,40 @@ use std::io::{IoResult, BufferedReader, BufferedWriter}; use std::io::fs::File; use std::path::Path; +use buffer::line::{Line, LineEnding, line_ending_to_str}; use buffer::Buffer as TextBuffer; pub fn load_file_to_buffer(path: &Path) -> IoResult { let mut tb = TextBuffer::new(); let mut f = BufferedReader::new(try!(File::open(path))); + let mut last_line_breaks = true; - loop { - let line = f.read_line(); - if let Ok(ref s) = line { - let tbl = tb.len(); - tb.insert_text(s.as_slice(), tbl); - } - else { - break; - } + for line in f.lines() { + let l = Line::new_from_string_unchecked(line.unwrap()); + last_line_breaks = l.ending != LineEnding::None; + tb.append_line_unchecked(l); } + // If the last line had a line break, we need to add a final + // blank line. + if last_line_breaks { + tb.append_line_unchecked(Line::new()); + } + + // 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.grapheme_iter(); + let mut iter = tb.line_iter(); let mut f = BufferedWriter::new(try!(File::create(path))); - for g in iter { - let _ = f.write_str(g); + for l in iter { + let _ = f.write_str(l.as_str()); + let _ = f.write_str(line_ending_to_str(l.ending)); } return Ok(());