Sped up file loading and saving.
This commit is contained in:
parent
02fdbcf027
commit
7099e4d0d3
|
@ -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
|
/// Returns the total number of unicode graphemes in the line
|
||||||
pub fn grapheme_count(&self) -> uint {
|
pub fn grapheme_count(&self) -> uint {
|
||||||
let mut count = grapheme_count(self.as_str());
|
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
|
/// An array of string literals corresponding to the possible
|
||||||
/// unicode line endings.
|
/// unicode line endings.
|
||||||
pub const LINE_ENDINGS: [&'static str, ..9] = ["",
|
pub const LINE_ENDINGS: [&'static str, ..9] = ["",
|
||||||
|
|
|
@ -6,7 +6,7 @@ use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
|
||||||
use self::line::{Line};
|
use self::line::{Line};
|
||||||
use string_utils::{is_line_ending};
|
use string_utils::{is_line_ending};
|
||||||
|
|
||||||
mod line;
|
pub mod line;
|
||||||
mod node;
|
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).
|
/// Removes the lines in line indices [line_a, line_b).
|
||||||
pub fn remove_lines(&mut self, line_a: uint, line_b: uint) {
|
pub fn remove_lines(&mut self, line_a: uint, line_b: uint) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
|
|
|
@ -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)
|
/// Removes lines in line number range [line_a, line_b)
|
||||||
pub fn remove_lines_recursive(&mut self, line_a: uint, line_b: uint) {
|
pub fn remove_lines_recursive(&mut self, line_a: uint, line_b: uint) {
|
||||||
let mut remove_left = false;
|
let mut remove_left = false;
|
||||||
|
|
29
src/files.rs
29
src/files.rs
|
@ -2,33 +2,40 @@ use std::io::{IoResult, BufferedReader, BufferedWriter};
|
||||||
use std::io::fs::File;
|
use std::io::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use buffer::line::{Line, LineEnding, line_ending_to_str};
|
||||||
use buffer::Buffer as TextBuffer;
|
use buffer::Buffer as TextBuffer;
|
||||||
|
|
||||||
pub fn load_file_to_buffer(path: &Path) -> IoResult<TextBuffer> {
|
pub fn load_file_to_buffer(path: &Path) -> IoResult<TextBuffer> {
|
||||||
let mut tb = TextBuffer::new();
|
let mut tb = TextBuffer::new();
|
||||||
let mut f = BufferedReader::new(try!(File::open(path)));
|
let mut f = BufferedReader::new(try!(File::open(path)));
|
||||||
|
let mut last_line_breaks = true;
|
||||||
|
|
||||||
loop {
|
for line in f.lines() {
|
||||||
let line = f.read_line();
|
let l = Line::new_from_string_unchecked(line.unwrap());
|
||||||
if let Ok(ref s) = line {
|
last_line_breaks = l.ending != LineEnding::None;
|
||||||
let tbl = tb.len();
|
tb.append_line_unchecked(l);
|
||||||
tb.insert_text(s.as_slice(), tbl);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
return Ok(tb);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> IoResult<()> {
|
pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> IoResult<()> {
|
||||||
// TODO: make save atomic
|
// 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)));
|
let mut f = BufferedWriter::new(try!(File::create(path)));
|
||||||
|
|
||||||
for g in iter {
|
for l in iter {
|
||||||
let _ = f.write_str(g);
|
let _ = f.write_str(l.as_str());
|
||||||
|
let _ = f.write_str(line_ending_to_str(l.ending));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
Loading…
Reference in New Issue
Block a user