use core::iter::Iterator;
use std::rc::Rc;
use super::*;
use crate::error_consts::*;
#[cfg(test)]
mod test {
use super::*;
#[test]
fn api_validation() {
let mut buf = VecBuffer::new();
super::super::test::api_validation(&mut buf);
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct Line {
tag: char,
matched: bool,
text: Rc<String>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct VecBuffer {
saved: bool,
history: Vec<Vec<Line>>,
buffer_i: usize, clipboard: Vec<Line>,
}
impl Default for VecBuffer {
fn default() -> Self { Self::new() }
}
impl VecBuffer {
pub fn new() -> Self
{
Self{
saved: true,
history: vec![vec![]],
buffer_i: 0,
clipboard: Vec::new(),
}
}
}
impl Buffer for VecBuffer {
fn len(&self) -> usize { self.history[self.buffer_i].len() }
fn is_empty(&self) -> bool { self.history[self.buffer_i].is_empty() }
fn get_tag(&self, tag: char)
-> Result<usize, &'static str>
{
for (index, line) in self.history[self.buffer_i][..].iter().enumerate() {
if tag == line.tag { return Ok(index + 1); } }
Err(NO_MATCH)
}
fn get_matching(&self, pattern: &str, curr_line: usize, backwards: bool)
-> Result<usize, &'static str>
{
verify_index(self, curr_line)?;
use regex::RegexBuilder;
let regex = RegexBuilder::new(pattern)
.multi_line(true)
.build()
.map_err(|_| INVALID_REGEX)
?;
let length = if ! backwards {
self.len().saturating_sub(curr_line)
} else {
curr_line.saturating_sub(1)
};
for index in 0 .. length {
if backwards {
if regex.is_match(&(self.history[self.buffer_i][curr_line - 2 - index].text)) {
return Ok(curr_line - 1 - index) }
} else {
if regex.is_match(&(self.history[self.buffer_i][curr_line + index].text)) {
return Ok(curr_line + 1 + index) }
}
}
Err(NO_MATCH)
}
fn mark_matching(&mut self, pattern: &str, selection: (usize, usize), inverse: bool)
-> Result<(), &'static str>
{
use regex::RegexBuilder;
verify_selection(self, selection)?;
let regex = RegexBuilder::new(pattern)
.multi_line(true)
.build()
.map_err(|_| INVALID_REGEX)
?;
for index in 0 .. self.len() {
if index >= selection.0.saturating_sub(1) && index <= selection.1.saturating_sub(1) {
self.history[self.buffer_i][index].matched = regex.is_match(&(self.history[self.buffer_i][index].text)) ^ inverse;
}
else {
self.history[self.buffer_i][index].matched = false;
}
}
Ok(())
}
fn get_marked(&mut self)
-> Result<Option<usize>, &'static str>
{
for index in 0 .. self.len() {
if self.history[self.buffer_i][index].matched {
self.history[self.buffer_i][index].matched = false;
return Ok(Some(index + 1));
}
}
Ok(None)
}
fn tag_line(&mut self, index: usize, tag: char)
-> Result<(), &'static str>
{
verify_line(self, index)?;
self.history[self.buffer_i][index.saturating_sub(1)].tag = tag;
Ok(())
}
fn insert<'a>(&mut self, data: &mut dyn Iterator<Item = &'a str>, index: usize)
-> Result<(), &'static str>
{
verify_index(self, index)?;
self.saved = false;
let mut tail = self.history[self.buffer_i].split_off(index);
for line in data {
self.history[self.buffer_i].push(
Line{tag: '\0', matched: false, text: line.to_owned().into()}
);
}
self.history[self.buffer_i].append(&mut tail);
Ok(())
}
fn cut(&mut self, selection: (usize, usize)) -> Result<(), &'static str>
{
verify_selection(self, selection)?;
self.saved = false;
let mut tail = self.history[self.buffer_i].split_off(selection.1);
self.clipboard = self.history[self.buffer_i].split_off(selection.0.saturating_sub(1));
self.history[self.buffer_i].append(&mut tail);
Ok(())
}
fn change<'a>(&mut self, data: &mut dyn Iterator<Item = &'a str>, selection: (usize, usize))
-> Result<(), &'static str>
{
verify_selection(self, selection)?;
self.saved = false;
let mut tail = self.history[self.buffer_i].split_off(selection.1);
self.clipboard = self.history[self.buffer_i].split_off(selection.0.saturating_sub(1));
for line in data {
self.history[self.buffer_i].push(
Line{tag: '\0', matched: false, text: line.to_owned().into()}
);
}
self.history[self.buffer_i].append(&mut tail);
Ok(())
}
fn mov(&mut self, selection: (usize, usize), index: usize) -> Result<(), &'static str> {
verify_selection(self, selection)?;
verify_index(self, index)?;
self.saved = false;
if index < selection.0 {
let mut tail = self.history[self.buffer_i].split_off(selection.1);
let mut data = self.history[self.buffer_i].split_off(selection.0.saturating_sub(1));
let mut middle = self.history[self.buffer_i].split_off(index);
self.history[self.buffer_i].append(&mut data);
self.history[self.buffer_i].append(&mut middle);
self.history[self.buffer_i].append(&mut tail);
Ok(())
}
else if index >= selection.1 {
let mut tail = self.history[self.buffer_i].split_off(index);
let mut middle = self.history[self.buffer_i].split_off(selection.1);
let mut data = self.history[self.buffer_i].split_off(selection.0.saturating_sub(1));
self.history[self.buffer_i].append(&mut middle);
self.history[self.buffer_i].append(&mut data);
self.history[self.buffer_i].append(&mut tail);
Ok(())
}
else {
Err(MOVE_INTO_SELF)
}
}
fn mov_copy(&mut self, selection: (usize, usize), index: usize) -> Result<(), &'static str> {
verify_selection(self, selection)?;
verify_index(self, index)?;
self.saved = false;
let mut data = Vec::new();
for line in &self.history[self.buffer_i][selection.0.saturating_sub(1) .. selection.1] {
data.push(line.clone());
}
let mut tail = self.history[self.buffer_i].split_off(index);
self.history[self.buffer_i].append(&mut data);
self.history[self.buffer_i].append(&mut tail);
Ok(())
}
fn join(&mut self, selection: (usize, usize)) -> Result<(), &'static str> {
verify_selection(self, selection)?;
self.saved = false;
let mut tail = self.history[self.buffer_i].split_off(selection.1);
let data = self.history[self.buffer_i].split_off(selection.0);
self.history[self.buffer_i].append(&mut tail);
let mut text = String::from(
&self.history[self.buffer_i][selection.0.saturating_sub(1)].text[..]
);
for line in data {
text.pop(); text.push_str(&line.text[..]);
}
self.history[self.buffer_i][selection.0.saturating_sub(1)].text = Rc::new(text);
Ok(())
}
fn reflow(&mut self,
selection: (usize, usize),
width: usize,
) -> Result<usize, &'static str> {
verify_selection(self, selection)?;
self.saved = false;
let mut tail = self.history[self.buffer_i].split_off(selection.1);
let data = self.history[self.buffer_i].split_off(selection.0.saturating_sub(1));
let mut joined = String::new();
for line in data {
for ch in line.text.chars() {
joined.push(match ch {
'\n' => ' ',
c => c,
})
}
}
joined.pop();
let mut w = 0; let mut ch_w = 0; let mut latest_space = None;
for i in 0 .. joined.len() {
if !joined.is_char_boundary(i) {
ch_w += 1;
continue;
}
w += 1;
if &joined[i - ch_w..=i] == " " {
latest_space = Some(i);
}
if w > width {
if let Some(s) = latest_space {
joined.replace_range(s..=s, "\n");
w = i - s;
latest_space = None;
}
}
ch_w = 0; }
for line in joined.lines() {
self.history[self.buffer_i].push(
Line{tag: '\0', matched: false, text: format!("{}\n", line).into()}
);
}
let end = self.history[self.buffer_i].len();
self.history[self.buffer_i].append(&mut tail);
Ok(end)
}
fn copy(&mut self, selection: (usize, usize)) -> Result<(), &'static str> {
verify_selection(self, selection)?;
self.clipboard = Vec::new();
for line in &self.history[self.buffer_i][selection.0.saturating_sub(1) .. selection.1] {
self.clipboard.push(line.clone());
}
Ok(())
}
fn paste(&mut self, index: usize) -> Result<usize, &'static str> {
verify_index(self, index)?;
self.saved = false;
let mut tmp = self.history[self.buffer_i].split_off(index);
for line in &self.clipboard {
self.history[self.buffer_i].push(line.clone());
}
self.history[self.buffer_i].append(&mut tmp);
Ok(self.clipboard.len())
}
fn search_replace(&mut self, pattern: (&str, &str), selection: (usize, usize), global: bool) -> Result<usize, &'static str>
{
use regex::RegexBuilder;
verify_selection(self, selection)?;
self.saved = false; let regex = RegexBuilder::new(pattern.0)
.multi_line(true)
.build()
.map_err(|_| INVALID_REGEX)
?;
let mut tail = self.history[self.buffer_i].split_off(selection.1);
let mut before = self.history[self.buffer_i]
.split_off(selection.0.saturating_sub(1))
;
let mut tmp = String::new();
for line in &before {
tmp.push_str(&line.text);
}
tmp.pop();
if ! regex.is_match(&tmp) {
self.history[self.buffer_i].append(&mut before);
self.history[self.buffer_i].append(&mut tail);
return Err(NO_MATCH);
}
let replace = super::substitute::substitute(pattern.1);
let after = if global {
regex.replace_all(&tmp, replace).to_string()
}
else {
regex.replace(&tmp, replace).to_string()
};
tmp.push('\n');
for line in after.lines() {
self.history[self.buffer_i].push(
Line{tag: '\0', matched: false, text: format!("{}\n", line).into()}
);
}
let end = self.history[self.buffer_i].len();
self.history[self.buffer_i].append(&mut tail);
Ok(end)
}
fn read_from(&mut self, path: &str, index: Option<usize>, must_exist: bool)
-> Result<usize, &'static str>
{
if let Some(i) = index { verify_index(self, i)?; }
let data = file::read_file(path, must_exist)?;
let len = data.len();
let mut iter = data.iter().map(| string | &string[..]);
let mut consider_saved = false; let i = match index {
Some(i) => i,
None => {
self.history = vec![vec![]];
consider_saved = true;
0
},
};
self.insert(&mut iter, i)?;
self.saved = consider_saved;
Ok(len)
}
fn write_to(&mut self, selection: Option<(usize, usize)>, path: &str, append: bool)
-> Result<(), &'static str>
{
let data = match selection {
Some(sel) => self.get_selection(sel)?,
None => Box::new(self.history[self.buffer_i][..].iter().map(
|line| (line.tag, &line.text[..])
)),
};
file::write_file(path, data.map(|(_,x)| x), append)?;
if selection == Some((1, self.len())) || selection.is_none() {
self.saved = true;
}
Ok(())
}
fn saved(&self) -> bool {
self.saved
}
fn undo(&mut self, steps: isize)
-> Result<(), &'static str>
{
if !self.undo_range()?.contains(&steps) { return Err(INVALID_UNDO_STEPS); }
self.saved = false;
if steps.is_negative() {
self.buffer_i += (-steps) as usize;
} else {
self.buffer_i -= steps as usize;
}
Ok(())
}
fn undo_range(&self)
-> Result<std::ops::Range<isize>, &'static str>
{
if self.history.len() < isize::MAX as usize && self.buffer_i < self.history.len() {
Ok(self.buffer_i as isize - self.history.len() as isize +1 .. self.buffer_i as isize + 1)
} else {
Err(UNDO_HISTORY_TOO_LARGE)
}
}
fn snapshot(&mut self) -> Result<(), &'static str> {
self.history.truncate(self.buffer_i + 1);
if self.history.len() < isize::MAX as usize {
self.history.push(self.history[self.buffer_i].clone());
self.buffer_i += 1;
Ok(())
} else {
Err(UNDO_HISTORY_TOO_LARGE)
}
}
fn get_selection<'a>(&'a self, selection: (usize, usize))
-> Result<Box<dyn Iterator<Item = (char, &'a str)> + 'a>, &'static str>
{
verify_selection(self, selection)?;
let tmp = self.history[self.buffer_i][selection.0 - 1 .. selection.1]
.iter()
.map(|line| (line.tag, &line.text[..]))
;
Ok(Box::new(tmp))
}
}