use super::{Cursor, View};
#[derive(Debug, PartialEq, Eq)]
pub enum Edit {
Insert(char),
Delete,
Paste(String),
Left,
Right,
ToStart,
ToEnd,
}
#[derive(Debug, PartialEq, Eq)]
enum Jump {
Left(usize),
Right(usize),
ToStart,
ToEnd,
}
#[derive(Debug)]
pub struct EditableString {
contents: Vec<char>,
cursor: Cursor,
_scratch: Vec<char>,
}
impl EditableString {
pub fn full_contents(&self) -> String {
self.contents.iter().collect()
}
pub fn view(&self) -> View<'_, char> {
self.cursor.view(&self.contents)
}
pub fn view_padded(&self, left: usize, right: usize) -> View<'_, char> {
self.cursor.view_padded(left, right, &self.contents)
}
pub fn new(width: usize) -> Self {
Self {
contents: Vec::default(),
cursor: Cursor::new(width),
_scratch: Vec::new(),
}
}
pub fn resize(&mut self, width: usize) {
self.cursor.set_width(width)
}
pub fn cursor_at_end(&self) -> bool {
self.cursor.idx() == self.contents.len()
}
#[inline(always)]
fn shift_to(&mut self, pos: usize) -> bool {
if pos <= self.contents.len() && self.cursor.idx() != pos {
self.cursor.set_index(pos);
true
} else {
false
}
}
#[inline(always)]
fn jump(&mut self, jm: Jump) -> bool {
match jm {
Jump::Left(dist) => self.shift_to(self.cursor.idx().saturating_sub(dist)),
Jump::Right(dist) => self.shift_to(self.cursor.idx().saturating_add(dist)),
Jump::ToStart => self.shift_to(0),
Jump::ToEnd => self.shift_to(self.contents.len()),
}
}
pub fn edit(&mut self, e: Edit) -> bool {
match e {
Edit::Left => self.jump(Jump::Left(1)),
Edit::Right => self.jump(Jump::Right(1)),
Edit::ToStart => self.jump(Jump::ToStart),
Edit::ToEnd => self.jump(Jump::ToEnd),
Edit::Insert(ch) => {
self.contents.insert(self.cursor.idx(), ch);
self.jump(Jump::Right(1))
}
Edit::Paste(s) => {
if !s.is_empty() {
if self.cursor_at_end() {
self.contents.extend(s.chars());
self.jump(Jump::ToEnd);
} else {
self._scratch.clear();
self._scratch
.extend(self.contents.drain(self.cursor.idx()..));
self.contents.extend(s.chars());
self.jump(Jump::Right(s.len()));
self.contents.append(&mut self._scratch);
}
true
} else {
false
}
}
Edit::Delete => {
let changed = self.jump(Jump::Left(1));
if changed {
self.contents.remove(self.cursor.idx());
}
changed
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edit() {
let mut editable = EditableString::new(usize::MAX);
for e in [
Edit::Insert('a'),
Edit::Insert('b'),
Edit::Insert('c'),
Edit::Insert('d'),
Edit::Left,
Edit::Insert('1'),
Edit::Insert('2'),
Edit::Insert('3'),
Edit::ToStart,
Edit::Delete,
Edit::Insert('4'),
Edit::ToEnd,
Edit::Delete,
] {
editable.edit(e);
}
assert_eq!(&editable.view().to_string(), "4abc123");
}
#[test]
fn window() {
let mut editable = EditableString::new(2);
for e in [
Edit::Insert('1'),
Edit::Insert('2'),
Edit::Insert('3'),
Edit::Insert('4'),
Edit::Left,
] {
editable.edit(e);
}
assert_eq!(editable.view().index(), 1);
editable.edit(Edit::Left);
editable.edit(Edit::Left);
assert_eq!(editable.view().index(), 0);
editable.edit(Edit::Insert('a'));
editable.edit(Edit::ToEnd);
assert_eq!(editable.view().index(), 2);
}
}