extern crate luthor;
pub use self::gap_buffer::GapBuffer;
pub use self::distance::Distance;
pub use self::position::Position;
pub use self::range::Range;
pub use self::line_range::LineRange;
pub use self::cursor::Cursor;
pub use self::token::{Lexeme, Token, TokenSet};
pub use syntect::parsing::{Scope, ScopeStack};
mod gap_buffer;
mod distance;
mod position;
mod range;
mod line_range;
mod cursor;
mod operation;
mod operations;
mod token;
use errors::*;
use std::rc::Rc;
use std::cell::RefCell;
use std::fs::File;
use std::io;
use std::io::{Read, Write};
use std::mem;
use std::ops::Fn;
use std::path::{Path, PathBuf};
use self::operation::{Operation, OperationGroup};
use self::operation::history::History;
use syntect::parsing::SyntaxDefinition;
pub struct Buffer {
pub id: Option<usize>,
data: Rc<RefCell<GapBuffer>>,
pub path: Option<PathBuf>,
pub cursor: Cursor,
history: History,
operation_group: Option<OperationGroup>,
pub syntax_definition: Option<SyntaxDefinition>,
pub change_callback: Option<Box<Fn(Position)>>,
}
impl Buffer {
pub fn new() -> Buffer {
let data = Rc::new(RefCell::new(GapBuffer::new(String::new())));
let cursor = Cursor::new(data.clone(), Position{ line: 0, offset: 0 });
let mut history = History::new();
history.mark();
Buffer{
id: None,
data: data.clone(),
path: None,
cursor: cursor,
history: History::new(),
operation_group: None,
syntax_definition: None,
change_callback: None,
}
}
pub fn from_file(path: &Path) -> io::Result<Buffer> {
let mut file = File::open(path.clone())?;
let mut data = String::new();
file.read_to_string(&mut data)?;
let data = Rc::new(RefCell::new(GapBuffer::new(data)));
let cursor = Cursor::new(data.clone(), Position{ line: 0, offset: 0 });
let mut buffer = Buffer{
id: None,
data: data.clone(),
path: Some(try!(path.canonicalize())),
cursor: cursor,
history: History::new(),
operation_group: None,
syntax_definition: None,
change_callback: None,
};
buffer.history.mark();
Ok(buffer)
}
pub fn data(&self) -> String {
self.data.borrow().to_string()
}
pub fn save(&mut self) -> io::Result<()> {
let mut file =
if let Some(ref path) = self.path {
File::create(&path)?
} else {
File::create(&PathBuf::new())?
};
file.write_all(self.data().to_string().as_bytes())?;
self.history.mark();
return Ok(())
}
pub fn tokens(&self) -> Result<TokenSet> {
if let Some(ref def) = self.syntax_definition {
Ok(TokenSet::new(self.data(), def))
} else {
Err(ErrorKind::MissingSyntaxDefinition)?
}
}
pub fn current_scope(&self) -> Result<ScopeStack> {
let mut scope = None;
let tokens = self.tokens()?;
for token in tokens.iter() {
if let Token::Lexeme(lexeme) = token {
if lexeme.position > *self.cursor {
break;
}
scope = Some(lexeme.scope);
}
}
scope.ok_or(ErrorKind::MissingScope.into())
}
pub fn file_name(&self) -> Option<String> {
match self.path {
Some(ref path) => {
match path.file_name() {
Some(file_name) => {
match file_name.to_str() {
Some(utf8_file_name) => Some(utf8_file_name.to_string()),
None => None,
}
},
None => None,
}
},
None => None,
}
}
pub fn undo(&mut self) {
let operation: Option<Box<Operation>> = match self.operation_group.take() {
Some(group) => {
if group.is_empty() {
self.history.previous()
} else {
Some(Box::new(group))
}
}
None => self.history.previous(),
};
if let Some(mut op) = operation {
op.reverse(self);
}
}
pub fn redo(&mut self) {
if let Some(mut op) = self.history.next() {
op.run(self);
}
}
pub fn read(&self, range: &Range) -> Option<String> {
self.data.borrow().read(range)
}
pub fn search(&self, needle: &str) -> Vec<Position> {
let mut results = Vec::new();
for (line, data) in self.data().lines().enumerate() {
for (offset, _) in data.char_indices() {
let haystack = &data[offset..];
if haystack.len() >= needle.len() && needle.as_bytes() == &haystack.as_bytes()[..needle.len()] {
results.push(
Position{
line: line,
offset: offset
}
);
}
}
}
results
}
pub fn modified(&self) -> bool {
!self.history.at_mark()
}
pub fn line_count(&self) -> usize {
self.data().chars().filter(|&c| c == '\n').count() + 1
}
pub fn reload(&mut self) -> io::Result<()> {
if let Some(ref path) = self.path.clone() {
match Buffer::from_file(path) {
Ok(mut buf) => {
mem::swap(self, &mut buf);
self.id = buf.id;
self.syntax_definition = buf.syntax_definition;
self.change_callback = buf.change_callback;
},
Err(e) => return Err(e),
}
}
self.change_callback
.as_ref()
.map(|callback| callback(Position::new()));
Ok(())
}
}
#[cfg(test)]
mod tests {
extern crate syntect;
use syntect::parsing::SyntaxSet;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use buffer::{Buffer, Position};
#[test]
fn reload_persists_id_and_syntax_definition() {
let file_path = Path::new("tests/sample/file");
let mut buffer = Buffer::from_file(file_path).unwrap();
let mut syntax_set = SyntaxSet::load_defaults_newlines();
syntax_set.link_syntaxes();
let syntax_definition = Some(syntax_set.find_syntax_plain_text().clone());
buffer.id = Some(1);
buffer.syntax_definition = syntax_definition;
buffer.reload().unwrap();
assert_eq!(buffer.id, Some(1));
assert!(buffer.syntax_definition.is_some());
}
#[test]
fn reload_calls_change_callback_with_zero_position() {
let file_path = Path::new("tests/sample/file");
let mut buffer = Buffer::from_file(file_path).unwrap();
buffer.insert("amp\neditor");
let tracked_position = Rc::new(RefCell::new(Position{ line: 1, offset: 1 }));
let callback_position = tracked_position.clone();
buffer.change_callback = Some(Box::new(move |change_position| {
*callback_position.borrow_mut() = change_position
}));
buffer.reload().unwrap();
assert_eq!(*tracked_position.borrow(), Position::new());
}
#[test]
fn delete_joins_lines_when_invoked_at_end_of_line() {
let mut buffer = Buffer::new();
buffer.insert("scribe\n library");
buffer.cursor.move_to_end_of_line();
buffer.delete();
assert_eq!(buffer.data(), "scribe library");
}
#[test]
fn delete_does_nothing_when_invoked_at_the_end_of_the_document() {
let mut buffer = Buffer::new();
buffer.insert("scribe\n library");
buffer.cursor.move_down();
buffer.cursor.move_to_end_of_line();
buffer.delete();
assert_eq!(buffer.data(), "scribe\n library");
}
#[test]
fn insert_is_undoable() {
let mut buffer = Buffer::new();
buffer.insert("scribe");
assert_eq!("scribe", buffer.data());
buffer.undo();
assert_eq!("", buffer.data());
}
#[test]
fn delete_is_undoable() {
let mut buffer = Buffer::new();
buffer.insert("scribe");
assert_eq!("scribe", buffer.data());
buffer.cursor.move_to(Position{ line: 0, offset: 0 });
buffer.delete();
assert_eq!("cribe", buffer.data());
buffer.undo();
assert_eq!("scribe", buffer.data());
}
#[test]
fn correctly_called_operation_groups_are_undone_correctly() {
let mut buffer = Buffer::new();
buffer.start_operation_group();
buffer.insert("scribe");
buffer.cursor.move_to(Position{ line: 0, offset: 6});
buffer.insert(" library");
buffer.end_operation_group();
buffer.cursor.move_to(Position{ line: 0, offset: 14});
buffer.insert(" test");
assert_eq!("scribe library test", buffer.data());
buffer.undo();
assert_eq!("scribe library", buffer.data());
buffer.undo();
assert_eq!("", buffer.data());
}
#[test]
fn non_terminated_operation_groups_are_undone_correctly() {
let mut buffer = Buffer::new();
buffer.insert("scribe");
buffer.start_operation_group();
buffer.cursor.move_to(Position{ line: 0, offset: 6});
buffer.insert(" library");
buffer.cursor.move_to(Position{ line: 0, offset: 14});
buffer.insert(" test");
assert_eq!("scribe library test", buffer.data());
buffer.undo();
assert_eq!("scribe", buffer.data());
buffer.undo();
assert_eq!("", buffer.data());
}
#[test]
fn non_terminated_empty_operation_groups_are_dropped() {
let mut buffer = Buffer::new();
buffer.insert("scribe");
buffer.start_operation_group();
buffer.undo();
assert_eq!(buffer.data(), "");
}
#[test]
fn search_returns_empty_set_when_there_are_no_matches() {
let mut buffer = Buffer::new();
buffer.insert("scribe");
assert!(buffer.search("library").is_empty());
}
#[test]
fn search_does_not_panic_with_non_ascii_data() {
let mut buffer = Buffer::new();
buffer.insert("scribé");
assert!(buffer.search("library").is_empty());
assert!(buffer.search("scribe").is_empty());
assert!(buffer.search("scribé").len() > 0);
}
}