use std::cmp;
use std::collections::HashMap;
use std::path::PathBuf;
use std::fs::File;
use std::io::{Stdin, Read};
use std::convert::From;
use gapbuffer::GapBuffer;
use log::{Log, Change, LogEntry};
use utils::is_alpha_or_;
use input::Input;
use iterators::{Lines, Chars};
use textobject::{TextObject, Kind, Offset, Anchor};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Mark {
Cursor(usize),
DisplayMark(usize),
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum WordEdgeMatch {
Alphabet,
Whitespace,
}
pub struct Buffer {
text: GapBuffer<u8>,
marks: HashMap<Mark, (usize, usize)>,
pub log: Log,
pub file_path: Option<PathBuf>,
}
impl Buffer {
pub fn new() -> Buffer {
Buffer {
file_path: None,
text: GapBuffer::new(),
marks: HashMap::new(),
log: Log::new(),
}
}
pub fn len(&self) -> usize {
self.text.len() + 1
}
pub fn get_mark_coords(&self, mark: Mark) -> Option<(usize, usize)> {
if let Some(idx) = self.get_mark_idx(mark) {
if let Some(line) = get_line(idx, &self.text) {
Some((idx - line, (0..idx).filter(|i| -> bool { self.text[*i] == b'\n' })
.count()))
} else { None }
} else { None }
}
pub fn get_mark_idx(&self, mark: Mark) -> Option<usize> {
if let Some(&(idx, _)) = self.marks.get(&mark) {
if idx < self.len() {
Some(idx)
} else { None }
} else { None }
}
pub fn chars(&self) -> Chars {
Chars {
buffer: &self.text,
idx: 0,
forward: true,
}
}
pub fn chars_from_idx(&self, idx: usize) -> Option<Chars> {
if idx < self.len() {
Some(Chars {
buffer: &self.text,
idx: idx,
forward: true,
})
} else { None }
}
pub fn chars_from(&self, mark: Mark) -> Option<Chars> {
if let Some(&(idx, _)) = self.marks.get(&mark) {
self.chars_from_idx(idx)
} else { None }
}
pub fn lines(&self) -> Lines {
Lines {
buffer: &self.text,
tail: 0,
head: self.len()
}
}
pub fn lines_from(&self, mark: Mark) -> Option<Lines> {
if let Some(&(idx, _)) = self.marks.get(&mark) {
if idx < self.len() {
Some(Lines {
buffer: &self.text,
tail: idx,
head: self.len(),
})
} else { None }
} else { None }
}
pub fn get_object_index(&self, obj: TextObject) -> Option<(usize, usize)> {
match obj.kind {
Kind::Char => self.get_char_index(obj.offset),
Kind::Line(anchor) => self.get_line_index(obj.offset, anchor),
Kind::Word(anchor) => self.get_word_index(obj.offset, anchor),
}
}
fn get_char_index(&self, offset: Offset) -> Option<(usize, usize)> {
let text = &self.text;
match offset {
Offset::Forward(offset, from_mark) => {
let last = self.len() - 1;
if let Some(tuple)= self.marks.get(&from_mark) {
let (index, _) = *tuple;
let absolute_index = index + offset;
if absolute_index < last {
Some((absolute_index, absolute_index - get_line(absolute_index, text).unwrap()))
} else {
Some((last, last - get_line(last, text).unwrap()))
}
} else {
None
}
}
Offset::Backward(offset, from_mark) => {
if let Some(tuple)= self.marks.get(&from_mark) {
let (index, _) = *tuple;
if index >= offset {
let absolute_index = index - offset;
Some((absolute_index, absolute_index - get_line(absolute_index, text).unwrap()))
} else {
None
}
} else {
None
}
}
Offset::Absolute(absolute_char_offset) => {
Some((absolute_char_offset, absolute_char_offset - get_line(absolute_char_offset, text).unwrap()))
},
}
}
fn get_line_index(&self, offset: Offset, anchor: Anchor) -> Option<(usize, usize)> {
match offset {
Offset::Forward(offset, from_mark) => { self.get_line_index_forward(anchor, offset, from_mark) }
Offset::Backward(offset, from_mark) => { self.get_line_index_backward(anchor, offset, from_mark) }
Offset::Absolute(line_number) => { self.get_line_index_absolute(anchor, line_number) }
}
}
fn get_line_index_absolute(&self, anchor: Anchor, line_number: usize) -> Option<(usize, usize)> {
let text = &self.text;
let nlines = (0..text.len()).filter(|i| text[*i] == b'\n')
.take(line_number + 1)
.collect::<Vec<usize>>();
match anchor {
Anchor::Start => {
let end_offset = nlines[line_number - 1];
let start = get_line(end_offset, text).unwrap();
Some((start, 0))
}
Anchor::End => {
let end_offset = nlines[line_number - 1];
Some((end_offset, end_offset))
}
_ => {
print!("Unhandled line anchor: {:?} ", anchor);
None
},
}
}
fn get_line_index_backward(&self, anchor: Anchor, offset: usize, from_mark: Mark) -> Option<(usize, usize)> {
let text = &self.text;
if let Some(tuple) = self.marks.get(&from_mark) {
let (index, line_index) = *tuple;
let nlines = (0..index).rev().filter(|i| text[*i] == b'\n')
.take(offset + 1)
.collect::<Vec<usize>>();
match anchor {
Anchor::Start => {
if nlines.len() == 0 {
return Some((0, 0))
}
let start_offset = cmp::min(line_index + nlines[offset] + 1, nlines[offset]);
Some((start_offset + 1, 0))
}
Anchor::Same => {
if offset == 0 {
Some((0, 0)) } else if offset == nlines.len() {
Some((cmp::min(line_index, nlines[0]), line_index))
} else if offset > nlines.len() {
Some((0, 0)) } else {
Some((cmp::min(line_index + nlines[offset] + 1, nlines[offset-1]), line_index))
}
}
_ => {
print!("Unhandled line anchor: {:?} ", anchor);
None
},
}
} else {
None
}
}
fn get_line_index_forward(&self, anchor: Anchor, offset: usize, from_mark: Mark) -> Option<(usize, usize)> {
let text = &self.text;
let last = self.len() - 1;
if let Some(tuple) = self.marks.get(&from_mark) {
let (index, line_index) = *tuple;
let nlines = (index..text.len()).filter(|i| text[*i] == b'\n')
.take(offset + 1)
.collect::<Vec<usize>>();
match anchor {
Anchor::Same => {
if offset == nlines.len() {
Some((cmp::min(line_index + nlines[offset-1] + 1, last), line_index))
} else {
if offset > nlines.len() {
Some((last, last - get_line(last, text).unwrap()))
} else {
Some((cmp::min(line_index + nlines[offset-1] + 1, nlines[offset]), line_index))
}
}
}
Anchor::End => {
if nlines.len() == 0 {
return Some((last, offset))
}
let end_offset = cmp::min(line_index + nlines[offset] + 1, nlines[offset]);
Some((end_offset, end_offset - get_line(end_offset, text).unwrap()))
}
_ => {
print!("Unhandled line anchor: {:?} ", anchor);
None
},
}
} else {
None
}
}
fn get_word_index(&self, offset: Offset, anchor: Anchor) -> Option<(usize, usize)> {
match offset {
Offset::Forward(nth_word, from_mark) => { self.get_word_index_forward(anchor, nth_word, from_mark) }
Offset::Backward(nth_word, from_mark) => { self.get_word_index_backward(anchor, nth_word, from_mark) }
Offset::Absolute(word_number) => { self.get_word_index_absolute(anchor, word_number) }
}
}
fn get_word_index_forward(&self, anchor: Anchor, nth_word: usize, from_mark: Mark) -> Option<(usize, usize)> {
let text = &self.text;
let last = self.len() - 1;
let edger = WordEdgeMatch::Whitespace;
if let Some(tuple) = self.marks.get(&from_mark) {
let (index, _) = *tuple;
match anchor {
Anchor::Start => {
if let Some(new_index) = get_words(index, nth_word, edger, text) {
Some((new_index, new_index - get_line(new_index, text).unwrap()))
} else {
Some((last, last - get_line(last, text).unwrap()))
}
}
_ => {
print!("Unhandled word anchor: {:?} ", anchor);
Some((last, last - get_line(last, text).unwrap()))
}
}
} else {
None
}
}
fn get_word_index_backward(&self, anchor: Anchor, nth_word: usize, from_mark: Mark) -> Option<(usize, usize)> {
let text = &self.text;
let edger = WordEdgeMatch::Whitespace;
if let Some(tuple) = self.marks.get(&from_mark) {
let (index, _) = *tuple;
match anchor {
Anchor::Start => {
if let Some(new_index) = get_words_rev(index, nth_word, edger, text) {
Some((new_index, new_index - get_line(new_index, text).unwrap()))
} else {
Some((0, 0))
}
}
_ => {
print!("Unhandled word anchor: {:?} ", anchor);
None
},
}
} else {
None
}
}
fn get_word_index_absolute(&self, anchor: Anchor, word_number: usize) -> Option<(usize, usize)> {
let text = &self.text;
let edger = WordEdgeMatch::Whitespace;
match anchor {
Anchor::Start => {
let new_index = get_words(0, word_number - 1, edger, text).unwrap();
Some((new_index, new_index - get_line(new_index, text).unwrap()))
}
_ => {
print!("Unhandled word anchor: {:?} ", anchor);
None
}
}
}
pub fn status_text(&self) -> String {
match self.file_path {
Some(ref path) => format!("[{}] ", path.display()),
None => format!("untitled "),
}
}
pub fn set_mark_to_object(&mut self, mark: Mark, obj: TextObject) {
if let Some(tuple) = self.get_object_index(obj) {
self.marks.insert(mark, tuple);
}
}
pub fn set_mark(&mut self, mark: Mark, idx: usize) {
if let Some(line) = get_line(idx, &self.text) {
if let Some(tuple) = self.marks.get_mut(&mark) {
*tuple = (idx, idx - line);
return;
}
self.marks.insert(mark, (idx, idx - line));
}
}
pub fn remove_range(&mut self, start: usize, end: usize) -> Option<Vec<u8>> {
let text = &mut self.text;
let mut transaction = self.log.start(start);
let mut vec = (start..end)
.rev()
.filter_map(|idx| text.remove(idx).map(|ch| (idx, ch)))
.inspect(|&(idx, ch)| transaction.log(Change::Remove(idx, ch), idx))
.map(|(_, ch)| ch)
.collect::<Vec<u8>>();
vec.reverse();
Some(vec)
}
pub fn remove_from_mark_to_object(&mut self, mark: Mark, object: TextObject) -> Option<Vec<u8>> {
if let Some(&(mark_idx, _)) = self.marks.get(&mark) {
let object_index = self.get_object_index(object);
if let Some((obj_idx, _)) = object_index {
if mark_idx != obj_idx {
let (start, end) = if mark_idx < obj_idx { (mark_idx, obj_idx) } else { (obj_idx, mark_idx) };
return self.remove_range(start, end);
}
}
}
None
}
pub fn remove_object(&mut self, object: TextObject) -> Option<Vec<u8>> {
let object_start = TextObject { kind: object.kind.with_anchor(Anchor::Start), offset: object.offset };
let object_end = TextObject { kind: object.kind.with_anchor(Anchor::End), offset: object.offset };
let start = self.get_object_index(object_start);
let end = self.get_object_index(object_end);
if let (Some((start_index, _)), Some((end_index, _))) = (start, end) {
return self.remove_range(start_index, end_index);
}
None
}
pub fn insert_char(&mut self, mark: Mark, ch: u8) {
if let Some(&(idx, _)) = self.marks.get(&mark) {
self.text.insert(idx, ch);
let mut transaction = self.log.start(idx);
transaction.log(Change::Insert(idx, ch), idx);
}
}
pub fn redo(&mut self) -> Option<&LogEntry> {
if let Some(transaction) = self.log.redo() {
commit(transaction, &mut self.text);
Some(transaction)
} else { None }
}
pub fn undo(&mut self) -> Option<&LogEntry> {
if let Some(transaction) = self.log.undo() {
commit(transaction, &mut self.text);
Some(transaction)
} else { None }
}
}
trait BufferFrom {}
impl BufferFrom for Stdin {}
impl BufferFrom for File {}
impl From<PathBuf> for Buffer {
fn from(path: PathBuf) -> Buffer {
if let Ok(file) = File::open(&path) {
let mut buff = Buffer::from(file);
buff.file_path = Some(path);
buff
} else { Buffer::new() }
}
}
impl<R: Read + BufferFrom> From<R> for Buffer {
fn from(mut reader: R) -> Buffer {
let mut buff = Buffer::new();
let mut contents = String::new();
if let Ok(_) = reader.read_to_string(&mut contents) {
buff.text.extend(contents.bytes());
}
buff
}
}
impl From<Input> for Buffer {
fn from(input: Input) -> Buffer {
match input {
Input::Filename(path) => {
match path {
Some(path) => Buffer::from(PathBuf::from(path)),
None => Buffer::new(),
}
},
Input::Stdin(reader) => {
Buffer::from(reader)
}
}
}
}
impl WordEdgeMatch {
fn is_word_edge(&self, c1: &u8, c2: &u8) -> bool {
match (self, *c1 as char, *c2 as char) {
(_, '\n', '\n') => true, (&WordEdgeMatch::Whitespace, c1, c2) => c1.is_whitespace() && !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, c1, c2) if c1.is_whitespace() => !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, c1, c2) if is_alpha_or_(c1) => !is_alpha_or_(c2) && !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, c1, c2) if !is_alpha_or_(c1) => is_alpha_or_(c2) && !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, _, _) => false,
}
}
}
fn get_words(mark: usize, n_words: usize, edger: WordEdgeMatch, text: &GapBuffer<u8>) -> Option<usize> {
(mark + 1..text.len() - 1)
.filter(|idx| edger.is_word_edge(&text[*idx - 1], &text[*idx]))
.take(n_words)
.last()
}
fn get_words_rev(mark: usize, n_words: usize, edger: WordEdgeMatch, text: &GapBuffer<u8>) -> Option<usize> {
(1..mark)
.rev()
.filter(|idx| edger.is_word_edge(&text[*idx - 1], &text[*idx]))
.take(n_words)
.last()
}
fn get_line(mark: usize, text: &GapBuffer<u8>) -> Option<usize> {
let val = cmp::min(mark, text.len());
(0..val + 1).rev().filter(|idx| *idx == 0 || text[*idx - 1] == b'\n')
.take(1)
.next()
}
fn get_line_end(mark: usize, text: &GapBuffer<u8>) -> Option<usize> {
let val = cmp::min(mark, text.len());
(val..text.len()+1).filter(|idx| *idx == text.len() || text[*idx] == b'\n')
.take(1)
.next()
}
fn commit(transaction: &LogEntry, text: &mut GapBuffer<u8>) {
for change in transaction.changes.iter() {
match change {
&Change::Insert(idx, ch) => {
text.insert(idx, ch);
}
&Change::Remove(idx, _) => {
text.remove(idx);
}
}
}
}
#[cfg(test)]
mod test {
use buffer::{Buffer, Mark};
use textobject::{TextObject, Offset, Kind, Anchor};
fn setup_buffer(testcase: &'static str) -> Buffer {
let mut buffer = Buffer::new();
buffer.text.extend(testcase.bytes());
buffer.set_mark(Mark::Cursor(0), 0);
buffer
}
#[test]
fn move_mark_char_right() {
let mut buffer = setup_buffer("Some test content");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Char,
offset: Offset::Forward(1, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(1, 1));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (1, 0));
}
#[test]
fn move_mark_char_left() {
let mut buffer = setup_buffer("Some test content");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Char,
offset: Offset::Backward(1, mark),
};
buffer.set_mark(mark, 3);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(2, 2));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (2, 0));
}
#[test]
fn move_mark_five_chars_right() {
let mut buffer = setup_buffer("Some test content");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Char,
offset: Offset::Forward(5, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(5, 5));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (5, 0));
}
#[test]
fn move_mark_line_down() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Forward(1, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(18, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 1));
}
#[test]
fn move_mark_line_up() {
let mut buffer = setup_buffer("Some test content\nnew lines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Backward(1, mark),
};
buffer.set_mark(mark, 18);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(0, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 0));
}
#[test]
fn move_mark_two_lines_down() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Forward(2, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(27, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 2));
}
#[test]
fn move_mark_line_down_to_shorter_line() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Forward(1, mark),
};
buffer.set_mark(mark, 15);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(26, 15));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (8, 1));
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Backward(1, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(15, 15));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (15, 0));
}
#[test]
fn move_mark_two_words_right() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Word(Anchor::Start),
offset: Offset::Forward(2, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(10, 10));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (10, 0));
}
#[test]
fn move_mark_two_words_left() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Word(Anchor::Start),
offset: Offset::Backward(2, mark),
};
buffer.set_mark(mark, 18);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(5, 5));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (5, 0));
}
#[test]
fn move_mark_move_word_left_at_start_of_buffer() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Word(Anchor::Start),
offset: Offset::Backward(1, mark),
};
buffer.set_mark(mark, 5);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(0, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 0));
}
#[test]
fn move_mark_move_word_right_past_end_of_buffer() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Word(Anchor::Start),
offset: Offset::Forward(8, mark),
};
buffer.set_mark(mark, 28);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(33, 6));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (6, 2));
}
#[test]
fn move_mark_second_word_in_buffer() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Word(Anchor::Start),
offset: Offset::Absolute(2),
};
buffer.set_mark(mark, 18);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(5, 5));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (5, 0));
}
#[test]
fn move_mark_fifth_word_in_buffer() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Word(Anchor::Start),
offset: Offset::Absolute(5),
};
buffer.set_mark(mark, 18);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(23, 5));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (5, 1));
}
#[test]
fn move_mark_second_line_in_buffer() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Start),
offset: Offset::Absolute(2),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(18, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 1));
}
#[test]
fn move_mark_second_char_in_buffer() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Char,
offset: Offset::Absolute(2),
};
buffer.set_mark(mark, 18);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(2, 2));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (2, 0));
}
#[test]
fn move_mark_end_of_line() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::End),
offset: Offset::Forward(0, mark),
};
buffer.set_mark(mark, 19);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(26, 8));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (8, 1));
}
#[test]
fn move_mark_start_of_line() {
let mut buffer = setup_buffer("Some test content\nwith new\nlines!");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Start),
offset: Offset::Backward(0, mark),
};
buffer.set_mark(mark, 19);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(18, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 1));
}
#[test]
fn move_mark_past_last_line() {
let mut buffer = setup_buffer("Some test content\n");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Forward(6, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(18, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 1));
}
#[test]
fn move_mark_line_up_middle_of_file() {
let mut buffer = setup_buffer("Some\ntest\ncontent");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Backward(1, mark),
};
buffer.set_mark(mark, 10);
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(5, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 1));
}
#[test]
fn move_mark_line_up_past_first_line() {
let mut buffer = setup_buffer("Some\ntest\ncontent");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Backward(1, mark),
};
buffer.set_mark_to_object(mark, obj);
assert_eq!(buffer.marks.get(&mark).unwrap(), &(0, 0));
assert_eq!(buffer.get_mark_coords(mark).unwrap(), (0, 0));
}
#[test]
fn test_insert() {
let mut buffer = setup_buffer("");
buffer.insert_char(Mark::Cursor(0), b'A');
assert_eq!(buffer.len(), 2);
assert_eq!(buffer.lines().next().unwrap(), [b'A']);
}
#[test]
fn test_remove() {
let mut buffer = setup_buffer("ABCD");
let mark = Mark::Cursor(0);
let obj = TextObject {
kind: Kind::Char,
offset: Offset::Forward(1, mark)
};
buffer.remove_from_mark_to_object(mark, obj);
assert_eq!(buffer.len(), 4);
assert_eq!(buffer.lines().next().unwrap(), [b'B', b'C', b'D']);
}
#[test]
fn test_set_mark() {
let mut buffer = setup_buffer("Test");
buffer.set_mark(Mark::Cursor(0), 2);
assert_eq!(buffer.get_mark_idx(Mark::Cursor(0)).unwrap(), 2);
}
#[test]
fn test_to_lines() {
let buffer = setup_buffer("Test\nA\nTest");
let mut lines = buffer.lines();
assert_eq!(lines.next().unwrap(), [b'T',b'e',b's',b't',b'\n']);
assert_eq!(lines.next().unwrap(), [b'A',b'\n']);
assert_eq!(lines.next().unwrap(), [b'T',b'e',b's',b't']);
}
#[test]
fn test_to_lines_from() {
let mut buffer = setup_buffer("Test\nA\nTest");
buffer.set_mark(Mark::Cursor(0), 6);
let mut lines = buffer.lines_from(Mark::Cursor(0)).unwrap();
assert_eq!(lines.next().unwrap(), [b'\n']);
assert_eq!(lines.next().unwrap(), [b'T',b'e',b's',b't']);
}
#[test]
fn test_to_chars() {
let mut buffer = setup_buffer("Test𐍈Test");
buffer.set_mark(Mark::Cursor(0), 0);
let mut chars = buffer.chars();
assert!(chars.next().unwrap() == 'T');
assert!(chars.next().unwrap() == 'e');
assert!(chars.next().unwrap() == 's');
assert!(chars.next().unwrap() == 't');
assert!(chars.next().unwrap() == '𐍈');
assert!(chars.next().unwrap() == 'T');
}
#[test]
fn test_to_chars_from() {
let mut buffer = setup_buffer("Test𐍈Test");
buffer.set_mark(Mark::Cursor(0), 2);
let mut chars = buffer.chars_from(Mark::Cursor(0)).unwrap();
assert!(chars.next().unwrap() == 's');
assert!(chars.next().unwrap() == 't');
assert!(chars.next().unwrap() == '𐍈');
}
#[test]
fn test_to_chars_rev() {
let mut buffer = setup_buffer("Test𐍈Test");
buffer.set_mark(Mark::Cursor(0), 8);
let mut chars = buffer.chars_from(Mark::Cursor(0)).unwrap().reverse();
assert!(chars.next().unwrap() == 'T');
assert!(chars.next().unwrap() == '𐍈');
assert!(chars.next().unwrap() == 't');
}
}