WIP refactor to switch over to Rope from BufferNode.

This commit is contained in:
Nathan Vegdahl 2015-02-22 11:45:57 -08:00
parent 0ee364f6ce
commit 02f24e4e6f
6 changed files with 311 additions and 186 deletions

View File

@ -5,15 +5,12 @@ use std::old_path::Path;
use std::old_io::fs::File; use std::old_io::fs::File;
use std::old_io::{IoResult, BufferedReader, BufferedWriter}; use std::old_io::{IoResult, BufferedReader, BufferedWriter};
use self::line::Line; pub use self::rope::{Rope, RopeSlice, RopeGraphemeIter, RopeLineIter};
use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
use self::undo_stack::{UndoStack}; use self::undo_stack::{UndoStack};
use self::undo_stack::Operation::*; use self::undo_stack::Operation::*;
use string_utils::{is_line_ending, grapheme_count}; use string_utils::grapheme_count;
pub mod line;
mod rope; mod rope;
mod node;
mod undo_stack; mod undo_stack;
@ -23,7 +20,7 @@ mod undo_stack;
/// A text buffer /// A text buffer
pub struct Buffer { pub struct Buffer {
text: BufferNode, text: Rope,
file_path: Option<Path>, file_path: Option<Path>,
undo_stack: UndoStack, undo_stack: UndoStack,
} }
@ -33,7 +30,7 @@ pub struct Buffer {
impl Buffer { impl Buffer {
pub fn new() -> Buffer { pub fn new() -> Buffer {
Buffer { Buffer {
text: BufferNode::new(), text: Rope::new(),
file_path: None, file_path: None,
undo_stack: UndoStack::new(), undo_stack: UndoStack::new(),
} }
@ -42,58 +39,23 @@ impl Buffer {
pub fn new_from_file(path: &Path) -> IoResult<Buffer> { pub fn new_from_file(path: &Path) -> IoResult<Buffer> {
let mut f = BufferedReader::new(try!(File::open(path))); let mut f = BufferedReader::new(try!(File::open(path)));
let string = f.read_to_string().unwrap();
let mut buf = Buffer { let buf = Buffer {
text: BufferNode::new(), text: Rope::new_from_str(string.as_slice()),
file_path: Some(path.clone()), file_path: Some(path.clone()),
undo_stack: UndoStack::new(), undo_stack: UndoStack::new(),
}; };
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_with_count_unchecked(substr, count);
let node = BufferNode::new_from_line_with_count_unchecked(line, count);
buf.append_leaf_node_unchecked(node);
}
a = b;
}
// Remove initial blank line
buf.remove_lines(0, 1);
return Ok(buf); return Ok(buf);
} }
pub fn save_to_file(&self, path: &Path) -> IoResult<()> { pub fn save_to_file(&self, path: &Path) -> IoResult<()> {
// TODO: make more efficient
let mut f = BufferedWriter::new(try!(File::create(path))); let mut f = BufferedWriter::new(try!(File::create(path)));
for g in self.grapheme_iter() { for c in self.text.chunk_iter() {
let _ = f.write_str(g); let _ = f.write_str(c);
} }
return Ok(()); return Ok(());
@ -107,12 +69,12 @@ impl Buffer {
//------------------------------------------------------------------------ //------------------------------------------------------------------------
pub fn grapheme_count(&self) -> usize { pub fn grapheme_count(&self) -> usize {
self.text.grapheme_count self.text.grapheme_count()
} }
pub fn line_count(&self) -> usize { pub fn line_count(&self) -> usize {
self.text.line_count self.text.line_count()
} }
@ -130,7 +92,7 @@ impl Buffer {
} }
fn _insert_text(&mut self, text: &str, pos: usize) { fn _insert_text(&mut self, text: &str, pos: usize) {
self.text.insert_text(text, pos); self.text.insert_text_at_grapheme_index(text, pos);
} }
@ -173,16 +135,13 @@ impl Buffer {
panic!("Buffer::_remove_text(): attempt to remove text past the end of buffer."); panic!("Buffer::_remove_text(): attempt to remove text past the end of buffer.");
} }
// Complete removal of all text // Complete removal of all text
else if pos_a == 0 && pos_b == self.text.grapheme_count { else if pos_a == 0 && pos_b == self.text.grapheme_count() {
let mut temp_node = BufferNode::new(); let mut temp_node = Rope::new();
mem::swap(&mut (self.text), &mut temp_node); mem::swap(&mut (self.text), &mut temp_node);
} }
// All other cases // All other cases
else { else {
if self.text.remove_text_recursive(pos_a, pos_b, true) { self.text.remove_text_between_grapheme_indices(pos_a, pos_b);
panic!("Buffer::_remove_text(): dangling left side remains. This should never happen!");
}
self.text.set_last_line_ending_recursive();
} }
} }
@ -232,6 +191,7 @@ impl Buffer {
/// Removes the lines in line indices [line_a, line_b). /// Removes the lines in line indices [line_a, line_b).
/// TODO: undo
pub fn remove_lines(&mut self, line_a: usize, line_b: usize) { pub fn remove_lines(&mut self, line_a: usize, line_b: usize) {
// Nothing to do // Nothing to do
if line_a == line_b { if line_a == line_b {
@ -246,32 +206,25 @@ impl Buffer {
panic!("Buffer::remove_lines(): attempt to remove lines past the last line of text."); panic!("Buffer::remove_lines(): attempt to remove lines past the last line of text.");
} }
// Complete removal of all lines // Complete removal of all lines
else if line_a == 0 && line_b == self.text.line_count { else if line_a == 0 && line_b == self.text.line_count() {
let mut temp_node = BufferNode::new(); let mut temp_node = Rope::new();
mem::swap(&mut (self.text), &mut temp_node); mem::swap(&mut (self.text), &mut temp_node);
} }
// All other cases // All other cases
else { else {
self.text.remove_lines_recursive(line_a, line_b); let a = self.text.line_index_to_grapheme_index(line_a);
self.text.set_last_line_ending_recursive(); let b = if line_b < self.line_count() {
self.text.line_index_to_grapheme_index(line_b)
}
else {
self.text.grapheme_count()
};
self.text.remove_text_between_grapheme_indices(a, b);
} }
} }
/// 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.text.append_line_unchecked_recursive(line);
}
fn append_leaf_node_unchecked(&mut self, node: BufferNode) {
self.text.append_leaf_node_unchecked_recursive(node);
}
//------------------------------------------------------------------------ //------------------------------------------------------------------------
@ -359,7 +312,7 @@ impl Buffer {
/// If the index is off the end of the text, returns the line and column /// If the index is off the end of the text, returns the line and column
/// number of the last valid text position. /// number of the last valid text position.
pub fn index_to_line_col(&self, pos: usize) -> (usize, usize) { pub fn index_to_line_col(&self, pos: usize) -> (usize, usize) {
return self.text.index_to_line_col_recursive(pos); return self.text.grapheme_index_to_line_col(pos);
} }
@ -371,7 +324,7 @@ impl Buffer {
/// beyond the end of the buffer, returns the index of the buffer's last /// beyond the end of the buffer, returns the index of the buffer's last
/// valid position. /// valid position.
pub fn line_col_to_index(&self, pos: (usize, usize)) -> usize { pub fn line_col_to_index(&self, pos: (usize, usize)) -> usize {
return self.text.line_col_to_index_recursive(pos); return self.text.line_col_to_grapheme_index(pos);
} }
@ -384,20 +337,25 @@ impl Buffer {
panic!("Buffer::get_grapheme(): index past last grapheme."); panic!("Buffer::get_grapheme(): index past last grapheme.");
} }
else { else {
return self.text.get_grapheme_recursive(index); return &self.text[index];
} }
} }
pub fn get_line<'a>(&'a self, index: usize) -> &'a Line { pub fn get_line<'a>(&'a self, index: usize) -> RopeSlice<'a> {
if index >= self.line_count() { if index >= self.line_count() {
panic!("get_line(): index out of bounds."); panic!("get_line(): index out of bounds.");
} }
// NOTE: this can be done non-recursively, which would be more let a = self.text.line_index_to_grapheme_index(index);
// efficient. However, it seems likely to require unsafe code let b = if index+1 < self.line_count() {
// if done that way. self.text.line_index_to_grapheme_index(index+1)
return self.text.get_line_recursive(index); }
else {
self.text.grapheme_count()
};
return self.text.slice(a, b);
} }
@ -416,7 +374,7 @@ impl Buffer {
let mut i = 0; let mut i = 0;
let i_end = pos_b - pos_a; let i_end = pos_b - pos_a;
for g in self.grapheme_iter_at_index(pos_a) { for g in self.text.grapheme_iter_at_index(pos_a) {
if i == i_end { if i == i_end {
break; break;
} }
@ -436,34 +394,26 @@ impl Buffer {
//------------------------------------------------------------------------ //------------------------------------------------------------------------
/// Creates an iterator at the first character /// Creates an iterator at the first character
pub fn grapheme_iter<'a>(&'a self) -> BufferGraphemeIter<'a> { pub fn grapheme_iter<'a>(&'a self) -> RopeGraphemeIter<'a> {
BufferGraphemeIter { self.text.grapheme_iter()
gi: self.text.grapheme_iter()
}
} }
/// Creates an iterator starting at the specified grapheme index. /// Creates an iterator starting at the specified grapheme index.
/// If the index is past the end of the text, then the iterator will /// If the index is past the end of the text, then the iterator will
/// return None on next(). /// return None on next().
pub fn grapheme_iter_at_index<'a>(&'a self, index: usize) -> BufferGraphemeIter<'a> { pub fn grapheme_iter_at_index<'a>(&'a self, index: usize) -> RopeGraphemeIter<'a> {
BufferGraphemeIter { self.text.grapheme_iter_at_index(index)
gi: self.text.grapheme_iter_at_index(index)
}
} }
pub fn line_iter<'a>(&'a self) -> BufferLineIter<'a> { pub fn line_iter<'a>(&'a self) -> RopeLineIter<'a> {
BufferLineIter { self.text.line_iter()
li: self.text.line_iter()
}
} }
pub fn line_iter_at_index<'a>(&'a self, index: usize) -> BufferLineIter<'a> { pub fn line_iter_at_index<'a>(&'a self, index: usize) -> RopeLineIter<'a> {
BufferLineIter { self.text.line_iter_at_index(index)
li: self.text.line_iter_at_index(index)
}
} }
@ -471,77 +421,6 @@ impl Buffer {
//=============================================================
// Buffer iterators
//=============================================================
/// An iterator over a text buffer's graphemes
pub struct BufferGraphemeIter<'a> {
gi: BufferNodeGraphemeIter<'a>,
}
impl<'a> BufferGraphemeIter<'a> {
// Puts the iterator on the next line.
// Returns true if there was a next line,
// false if there wasn't.
pub fn next_line(&mut self) -> bool {
self.gi.next_line()
}
// Skips the iterator n graphemes ahead.
// If it runs out of graphemes before reaching the desired skip count,
// returns false. Otherwise returns true.
pub fn skip_graphemes(&mut self, n: usize) -> bool {
self.gi.skip_graphemes(n)
}
//pub fn skip_non_newline_graphemes(&mut self, n: usize) -> bool {
// let mut i: usize = 0;
//
// for g in self.gi {
// if is_line_ending(g) {
// return true;
// }
//
// i += 1;
// if i >= n {
// break;
// }
// }
//
// return false;
//}
}
impl<'a> Iterator for BufferGraphemeIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
self.gi.next()
}
}
pub struct BufferLineIter<'a> {
li: BufferNodeLineIter<'a>,
}
impl<'a> Iterator for BufferLineIter<'a> {
type Item = &'a Line;
fn next(&mut self) -> Option<&'a Line> {
self.li.next()
}
}
//================================================================ //================================================================
// TESTS // TESTS
//================================================================ //================================================================
@ -549,7 +428,7 @@ impl<'a> Iterator for BufferLineIter<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(unused_imports)] #![allow(unused_imports)]
use super::{Buffer, BufferGraphemeIter, BufferLineIter}; use super::{Buffer, BufferLineIter};
#[test] #[test]
fn insert_text() { fn insert_text() {

View File

@ -8,7 +8,9 @@ use string_utils::{
insert_text_at_grapheme_index, insert_text_at_grapheme_index,
remove_text_between_grapheme_indices, remove_text_between_grapheme_indices,
split_string_at_grapheme_index, split_string_at_grapheme_index,
is_line_ending is_line_ending,
LineEnding,
str_to_line_ending,
}; };
pub const MIN_NODE_SIZE: usize = 64; pub const MIN_NODE_SIZE: usize = 64;
@ -259,6 +261,11 @@ impl Rope {
} }
pub fn grapheme_at_index<'a>(&'a self, index: usize) -> &'a str {
&self[index]
}
/// Inserts the given text at the given grapheme index. /// Inserts the given text at the given grapheme index.
/// For small lengths of 'text' runs in O(log N) time. /// For small lengths of 'text' runs in O(log N) time.
/// For large lengths of 'text', dunno. But it seems to perform /// For large lengths of 'text', dunno. But it seems to perform
@ -478,10 +485,50 @@ impl Rope {
return RopeGraphemeIter { return RopeGraphemeIter {
chunk_iter: chunk_iter, chunk_iter: chunk_iter,
cur_chunk: giter, cur_chunk: giter,
length: None,
}; };
} }
/// Creates an iterator that starts a pos_a and stops just before pos_b.
pub fn grapheme_iter_between_indices<'a>(&'a self, pos_a: usize, pos_b: usize) -> RopeGraphemeIter<'a> {
let mut iter = self.grapheme_iter_at_index(pos_a);
iter.length = Some(pos_b - pos_a);
return iter;
}
/// Creates an iterator over the lines in the rope.
pub fn line_iter<'a>(&'a self) -> RopeLineIter<'a> {
RopeLineIter {
rope: self,
li: 0,
}
}
/// Creates an iterator over the lines in the rope, starting at the given
/// line index.
pub fn line_iter_at_index<'a>(&'a self, index: usize) -> RopeLineIter<'a> {
RopeLineIter {
rope: self,
li: index,
}
}
pub fn slice<'a>(&'a self, pos_a: usize, pos_b: usize) -> RopeSlice<'a> {
let a = pos_a;
let b = min(self.grapheme_count_, pos_b);
RopeSlice {
rope: self,
start: a,
end: b,
}
}
// Creates a graphviz document of the Rope's structure, and returns // Creates a graphviz document of the Rope's structure, and returns
// it as a string. For debugging purposes. // it as a string. For debugging purposes.
pub fn to_graphviz(&self) -> String { pub fn to_graphviz(&self) -> String {
@ -944,6 +991,7 @@ impl<'a> Iterator for RopeChunkIter<'a> {
pub struct RopeGraphemeIter<'a> { pub struct RopeGraphemeIter<'a> {
chunk_iter: RopeChunkIter<'a>, chunk_iter: RopeChunkIter<'a>,
cur_chunk: Graphemes<'a>, cur_chunk: Graphemes<'a>,
length: Option<usize>,
} }
@ -951,8 +999,17 @@ impl<'a> Iterator for RopeGraphemeIter<'a> {
type Item = &'a str; type Item = &'a str;
fn next(&mut self) -> Option<&'a str> { fn next(&mut self) -> Option<&'a str> {
if let Some(ref mut l) = self.length {
if *l == 0 {
return None;
}
}
loop { loop {
if let Some(g) = self.cur_chunk.next() { if let Some(g) = self.cur_chunk.next() {
if let Some(ref mut l) = self.length {
*l -= 1;
}
return Some(g); return Some(g);
} }
else { else {
@ -970,6 +1027,106 @@ impl<'a> Iterator for RopeGraphemeIter<'a> {
/// An iterator over a rope's lines, returned as RopeSlice's
pub struct RopeLineIter<'a> {
rope: &'a Rope,
li: usize,
}
impl<'a> Iterator for RopeLineIter<'a> {
type Item = RopeSlice<'a>;
fn next(&mut self) -> Option<RopeSlice<'a>> {
if self.li >= self.rope.line_count() {
return None;
}
else {
let a = self.rope.line_index_to_grapheme_index(self.li);
let b = if self.li+1 < self.rope.line_count() {
self.rope.line_index_to_grapheme_index(self.li+1)
}
else {
self.rope.grapheme_count()
};
self.li += 1;
return Some(self.rope.slice(a, b));
}
}
}
//=============================================================
// Rope slice
//=============================================================
/// An immutable slice into a Rope
pub struct RopeSlice<'a> {
rope: &'a Rope,
start: usize,
end: usize,
}
impl<'a> RopeSlice<'a> {
pub fn grapheme_count(&self) -> usize {
self.end - self.start
}
pub fn grapheme_iter(&self) -> RopeGraphemeIter<'a> {
self.rope.grapheme_iter_between_indices(self.start, self.end)
}
pub fn grapheme_iter_at_index(&self, pos: usize) -> RopeGraphemeIter<'a> {
let a = min(self.end, self.start + pos);
self.rope.grapheme_iter_between_indices(a, self.end)
}
pub fn grapheme_iter_between_indices(&self, pos_a: usize, pos_b: usize) -> RopeGraphemeIter<'a> {
let a = min(self.end, self.start + pos_a);
let b = min(self.end, self.start + pos_b);
self.rope.grapheme_iter_between_indices(a, b)
}
pub fn grapheme_at_index(&self, index: usize) -> &'a str {
&self.rope[self.start+index]
}
/// Convenience function for when the slice represents a line
pub fn ending(&self) -> LineEnding {
if self.grapheme_count() > 0 {
let g = self.grapheme_at_index(self.grapheme_count() - 1);
return str_to_line_ending(g);
}
else {
return LineEnding::None;
}
}
pub fn slice(&self, pos_a: usize, pos_b: usize) -> RopeSlice<'a> {
let a = min(self.end, self.start + pos_a);
let b = min(self.end, self.start + pos_b);
RopeSlice {
rope: self.rope,
start: a,
end: b,
}
}
}
//=================================================================== //===================================================================
// Unit test // Unit test

View File

@ -1,12 +1,11 @@
#![allow(dead_code)] #![allow(dead_code)]
use buffer::Buffer; use buffer::Buffer;
use buffer::line::LineEnding;
use formatter::LineFormatter; use formatter::LineFormatter;
use formatter::RoundingBehavior::*; use formatter::RoundingBehavior::*;
use std::old_path::Path; use std::old_path::Path;
use std::cmp::{min, max}; use std::cmp::{min, max};
use string_utils::grapheme_count; use string_utils::{grapheme_count, LineEnding};
use utils::digit_count; use utils::digit_count;
use self::cursor::CursorSet; use self::cursor::CursorSet;
@ -103,7 +102,7 @@ impl<T: LineFormatter> Editor<T> {
// Collect statistics // Collect statistics
let mut line_i: usize = 0; let mut line_i: usize = 0;
for line in self.buffer.line_iter() { for line in self.buffer.line_iter() {
match line.ending { match line.ending() {
LineEnding::None => { LineEnding::None => {
}, },
LineEnding::CRLF => { LineEnding::CRLF => {

View File

@ -47,7 +47,7 @@ pub trait LineFormatter {
let (line_block, col_i_adjusted) = block_index_and_offset(col_i); let (line_block, col_i_adjusted) = block_index_and_offset(col_i);
// Get an iter into the right block // Get an iter into the right block
let g_iter = line.grapheme_iter_at_index_with_max_length(line_block * LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH); let g_iter = line.grapheme_iter_between_indices(line_block * LINE_BLOCK_LENGTH, line_block * (LINE_BLOCK_LENGTH+1));
return self.index_to_v2d(g_iter, col_i_adjusted).1; return self.index_to_v2d(g_iter, col_i_adjusted).1;
} }
@ -64,7 +64,7 @@ pub trait LineFormatter {
// Find the right block in the line, and the index within that block // Find the right block in the line, and the index within that block
let (line_block, col_i_adjusted) = block_index_and_offset(col_i); let (line_block, col_i_adjusted) = block_index_and_offset(col_i);
let (mut y, x) = self.index_to_v2d(buf.get_line(line_i).grapheme_iter_at_index_with_max_length(line_block*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH), col_i_adjusted); let (mut y, x) = self.index_to_v2d(buf.get_line(line_i).grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1)), col_i_adjusted);
let mut new_y = y as isize + offset; let mut new_y = y as isize + offset;
// First, find the right line while keeping track of the vertical offset // First, find the right line while keeping track of the vertical offset
@ -72,7 +72,7 @@ pub trait LineFormatter {
let mut block_index: usize = line_block; let mut block_index: usize = line_block;
loop { loop {
line = buf.get_line(line_i); line = buf.get_line(line_i);
let (h, _) = self.dimensions(line.grapheme_iter_at_index_with_max_length(line_block*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH)); let (h, _) = self.dimensions(line.grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1)));
if new_y >= 0 && new_y < h as isize { if new_y >= 0 && new_y < h as isize {
y = new_y as usize; y = new_y as usize;
@ -110,7 +110,7 @@ pub trait LineFormatter {
else { else {
block_index -= 1; block_index -= 1;
} }
let (h, _) = self.dimensions(line.grapheme_iter_at_index_with_max_length(line_block*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH)); let (h, _) = self.dimensions(line.grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1)));
new_y += h as isize; new_y += h as isize;
} }
else { else {
@ -121,7 +121,7 @@ pub trait LineFormatter {
// Next, convert the resulting coordinates back into buffer-wide // Next, convert the resulting coordinates back into buffer-wide
// coordinates. // coordinates.
col_i = (block_index * LINE_BLOCK_LENGTH) + self.v2d_to_index(line.grapheme_iter_at_index_with_max_length(block_index*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH), (y, x), rounding); col_i = (block_index * LINE_BLOCK_LENGTH) + self.v2d_to_index(line.grapheme_iter_between_indices(line_block*LINE_BLOCK_LENGTH, line_block*(LINE_BLOCK_LENGTH+1)), (y, x), rounding);
return buf.line_col_to_index((line_i, col_i)); return buf.line_col_to_index((line_i, col_i));
} }
@ -136,8 +136,8 @@ pub trait LineFormatter {
let start_index = line_block * LINE_BLOCK_LENGTH; let start_index = line_block * LINE_BLOCK_LENGTH;
// Calculate the horizontal position // Calculate the horizontal position
let (v, _) = self.index_to_v2d(line.grapheme_iter_at_index_with_max_length(start_index, LINE_BLOCK_LENGTH), col_i_adjusted); let (v, _) = self.index_to_v2d(line.grapheme_iter_between_indices(start_index, start_index+LINE_BLOCK_LENGTH), col_i_adjusted);
let mut new_col_i = start_index + self.v2d_to_index(line.grapheme_iter_at_index_with_max_length(start_index, LINE_BLOCK_LENGTH), (v, horizontal), (RoundingBehavior::Floor, rounding)); let mut new_col_i = start_index + self.v2d_to_index(line.grapheme_iter_between_indices(start_index, start_index+LINE_BLOCK_LENGTH), (v, horizontal), (RoundingBehavior::Floor, rounding));
// Make sure we're not pushing the index off the end of the line // Make sure we're not pushing the index off the end of the line
if (line_i + 1) < buf.line_count() if (line_i + 1) < buf.line_count()

View File

@ -190,3 +190,94 @@ pub fn split_string_at_grapheme_index(s1: &mut String, pos: usize) -> String {
return s2; return s2;
} }
/// Represents one of the valid Unicode line endings.
/// Also acts as an index into `LINE_ENDINGS`.
#[derive(PartialEq, Copy)]
pub enum LineEnding {
None = 0, // No line ending
CRLF = 1, // CarriageReturn followed by LineFeed
LF = 2, // U+000A -- LineFeed
VT = 3, // U+000B -- VerticalTab
FF = 4, // U+000C -- FormFeed
CR = 5, // U+000D -- CarriageReturn
NEL = 6, // U+0085 -- NextLine
LS = 7, // U+2028 -- Line Separator
PS = 8, // U+2029 -- ParagraphSeparator
}
pub fn str_to_line_ending(g: &str) -> LineEnding {
match g {
//==============
// Line endings
//==============
// CRLF
"\u{000D}\u{000A}" => {
return LineEnding::CRLF;
},
// LF
"\u{000A}" => {
return LineEnding::LF;
},
// VT
"\u{000B}" => {
return LineEnding::VT;
},
// FF
"\u{000C}" => {
return LineEnding::FF;
},
// CR
"\u{000D}" => {
return LineEnding::CR;
},
// NEL
"\u{0085}" => {
return LineEnding::NEL;
},
// LS
"\u{2028}" => {
return LineEnding::LS;
},
// PS
"\u{2029}" => {
return LineEnding::PS;
},
// Not a line ending
_ => {
return LineEnding::None;
}
}
}
pub fn line_ending_to_str(ending: LineEnding) -> &'static str {
LINE_ENDINGS[ending as usize]
}
/// An array of string literals corresponding to the possible
/// unicode line endings.
pub const LINE_ENDINGS: [&'static str; 9] = ["",
"\u{000D}\u{000A}",
"\u{000A}",
"\u{000B}",
"\u{000C}",
"\u{000D}",
"\u{0085}",
"\u{2028}",
"\u{2029}"
];

View File

@ -6,9 +6,8 @@ use editor::Editor;
use formatter::{LineFormatter, LINE_BLOCK_LENGTH, block_index_and_offset}; use formatter::{LineFormatter, LINE_BLOCK_LENGTH, block_index_and_offset};
use std::char; use std::char;
use std::time::duration::Duration; use std::time::duration::Duration;
use string_utils::{is_line_ending}; use string_utils::{is_line_ending, line_ending_to_str, LineEnding};
use utils::digit_count; use utils::digit_count;
use buffer::line::{line_ending_to_str, LineEnding};
use self::formatter::ConsoleLineFormatter; use self::formatter::ConsoleLineFormatter;
pub mod formatter; pub mod formatter;
@ -360,7 +359,7 @@ impl TermUI {
let (line_index, col_i) = editor.buffer.index_to_line_col(editor.view_pos.0); let (line_index, col_i) = editor.buffer.index_to_line_col(editor.view_pos.0);
let (mut line_block_index, _) = block_index_and_offset(col_i); let (mut line_block_index, _) = block_index_and_offset(col_i);
let mut grapheme_index = editor.buffer.line_col_to_index((line_index, line_block_index * LINE_BLOCK_LENGTH)); let mut grapheme_index = editor.buffer.line_col_to_index((line_index, line_block_index * LINE_BLOCK_LENGTH));
let (vis_line_offset, _) = editor.formatter.index_to_v2d(editor.buffer.get_line(line_index).grapheme_iter_at_index_with_max_length(line_block_index*LINE_BLOCK_LENGTH, LINE_BLOCK_LENGTH), editor.view_pos.0 - grapheme_index); let (vis_line_offset, _) = editor.formatter.index_to_v2d(editor.buffer.get_line(line_index).grapheme_iter_between_indices(line_block_index*LINE_BLOCK_LENGTH, (line_block_index+1)*LINE_BLOCK_LENGTH), editor.view_pos.0 - grapheme_index);
let mut screen_line = c1.0 as isize - vis_line_offset as isize; let mut screen_line = c1.0 as isize - vis_line_offset as isize;
let screen_col = c1.1 as isize + gutter_width as isize; let screen_col = c1.1 as isize + gutter_width as isize;