blob: 7f7c9abddee56902daee306e2cf9ccd193094f5b [file] [log] [blame]
//! Undo API
use std::fmt::Debug;
use keymap::RepeatCount;
use line_buffer::{ChangeListener, DeleteListener, Direction, LineBuffer};
use unicode_segmentation::UnicodeSegmentation;
enum Change {
Begin,
End,
Insert {
idx: usize,
text: String,
}, // QuotedInsert, SelfInsert, Yank
Delete {
idx: usize,
text: String,
}, /* BackwardDeleteChar, BackwardKillWord, DeleteChar,
* KillLine, KillWholeLine, KillWord,
* UnixLikeDiscard, ViDeleteTo */
Replace {
idx: usize,
old: String,
new: String,
}, /* CapitalizeWord, Complete, DowncaseWord, Replace, TransposeChars, TransposeWords,
* UpcaseWord, YankPop */
}
impl Change {
fn undo(&self, line: &mut LineBuffer) {
match *self {
Change::Begin | Change::End => {
unreachable!();
}
Change::Insert { idx, ref text } => {
line.delete_range(idx..idx + text.len());
}
Change::Delete { idx, ref text } => {
line.insert_str(idx, text);
line.set_pos(idx + text.len());
}
Change::Replace {
idx,
ref old,
ref new,
} => {
line.replace(idx..idx + new.len(), old);
}
}
}
#[cfg(test)]
fn redo(&self, line: &mut LineBuffer) {
match *self {
Change::Begin | Change::End => {
unreachable!();
}
Change::Insert { idx, ref text } => {
line.insert_str(idx, text);
}
Change::Delete { idx, ref text } => {
line.delete_range(idx..idx + text.len());
}
Change::Replace {
idx,
ref old,
ref new,
} => {
line.replace(idx..idx + old.len(), new);
}
}
}
fn insert_seq(&self, indx: usize) -> bool {
if let Change::Insert { idx, ref text } = *self {
idx + text.len() == indx
} else {
false
}
}
fn delete_seq(&self, indx: usize, len: usize) -> bool {
if let Change::Delete { idx, .. } = *self {
// delete or backspace
idx == indx || idx == indx + len
} else {
false
}
}
fn replace_seq(&self, indx: usize) -> bool {
if let Change::Replace { idx, ref new, .. } = *self {
idx + new.len() == indx
} else {
false
}
}
}
pub struct Changeset {
undo_group_level: u32,
undos: Vec<Change>, // undoable changes
redos: Vec<Change>, // undone changes, redoable
}
impl Changeset {
pub fn new() -> Changeset {
Changeset {
undo_group_level: 0,
undos: Vec::new(),
redos: Vec::new(),
}
}
pub fn begin(&mut self) -> usize {
debug!(target: "rustyline", "Changeset::begin");
self.redos.clear();
let mark = self.undos.len();
self.undos.push(Change::Begin);
self.undo_group_level += 1;
mark
}
pub fn end(&mut self) {
debug!(target: "rustyline", "Changeset::end");
self.redos.clear();
while self.undo_group_level > 0 {
self.undo_group_level -= 1;
if let Some(&Change::Begin) = self.undos.last() {
// emtpy Begin..End
self.undos.pop();
} else {
self.undos.push(Change::End);
}
}
}
fn insert_char(idx: usize, c: char) -> Change {
let mut text = String::new();
text.push(c);
Change::Insert { idx, text }
}
pub fn insert(&mut self, idx: usize, c: char) {
debug!(target: "rustyline", "Changeset::insert({}, {:?})", idx, c);
self.redos.clear();
if !c.is_alphanumeric() || !self.undos.last().map_or(false, |lc| lc.insert_seq(idx)) {
self.undos.push(Self::insert_char(idx, c));
return;
}
// merge consecutive char insertions when char is alphanumeric
let mut last_change = self.undos.pop().unwrap();
if let Change::Insert { ref mut text, .. } = last_change {
text.push(c);
} else {
unreachable!();
}
self.undos.push(last_change);
}
pub fn insert_str<S: AsRef<str> + Into<String> + Debug>(&mut self, idx: usize, string: S) {
debug!(target: "rustyline", "Changeset::insert_str({}, {:?})", idx, string);
self.redos.clear();
if string.as_ref().is_empty() {
return;
}
self.undos.push(Change::Insert {
idx,
text: string.into(),
});
}
pub fn delete<S: AsRef<str> + Into<String> + Debug>(&mut self, indx: usize, string: S) {
debug!(target: "rustyline", "Changeset::delete({}, {:?})", indx, string);
self.redos.clear();
if string.as_ref().is_empty() {
return;
}
if !Self::single_char(string.as_ref()) || !self
.undos
.last()
.map_or(false, |lc| lc.delete_seq(indx, string.as_ref().len()))
{
self.undos.push(Change::Delete {
idx: indx,
text: string.into(),
});
return;
}
// merge consecutive char deletions when char is alphanumeric
let mut last_change = self.undos.pop().unwrap();
if let Change::Delete {
ref mut idx,
ref mut text,
} = last_change
{
if *idx == indx {
text.push_str(string.as_ref());
} else {
text.insert_str(0, string.as_ref());
*idx = indx;
}
} else {
unreachable!();
}
self.undos.push(last_change);
}
fn single_char(s: &str) -> bool {
let mut graphemes = s.graphemes(true);
graphemes.next().map_or(false, |grapheme| {
grapheme.chars().all(|c| c.is_alphanumeric())
}) && graphemes.next().is_none()
}
pub fn replace<S: AsRef<str> + Into<String> + Debug>(&mut self, indx: usize, old_: S, new_: S) {
debug!(target: "rustyline", "Changeset::replace({}, {:?}, {:?})", indx, old_, new_);
self.redos.clear();
if !self.undos.last().map_or(false, |lc| lc.replace_seq(indx)) {
self.undos.push(Change::Replace {
idx: indx,
old: old_.into(),
new: new_.into(),
});
return;
}
// merge consecutive char replacements
let mut last_change = self.undos.pop().unwrap();
if let Change::Replace {
ref mut old,
ref mut new,
..
} = last_change
{
old.push_str(old_.as_ref());
new.push_str(new_.as_ref());
} else {
unreachable!();
}
self.undos.push(last_change);
}
pub fn undo(&mut self, line: &mut LineBuffer, n: RepeatCount) -> bool {
debug!(target: "rustyline", "Changeset::undo");
let mut count = 0;
let mut waiting_for_begin = 0;
let mut undone = false;
loop {
if let Some(change) = self.undos.pop() {
match change {
Change::Begin => {
waiting_for_begin -= 1;
}
Change::End => {
waiting_for_begin += 1;
}
_ => {
change.undo(line);
undone = true;
}
};
self.redos.push(change);
} else {
break;
}
if waiting_for_begin <= 0 {
count += 1;
if count >= n {
break;
}
}
}
undone
}
pub fn truncate(&mut self, len: usize) {
debug!(target: "rustyline", "Changeset::truncate({})", len);
self.undos.truncate(len);
}
#[cfg(test)]
pub fn redo(&mut self, line: &mut LineBuffer) -> bool {
let mut waiting_for_end = 0;
let mut redone = false;
loop {
if let Some(change) = self.redos.pop() {
match change {
Change::Begin => {
waiting_for_end += 1;
}
Change::End => {
waiting_for_end -= 1;
}
_ => {
change.redo(line);
redone = true;
}
};
self.undos.push(change);
} else {
break;
}
if waiting_for_end <= 0 {
break;
}
}
redone
}
pub fn last_insert(&self) -> Option<String> {
for change in self.undos.iter().rev() {
match change {
Change::Insert { ref text, .. } => return Some(text.to_owned()),
Change::Replace { ref new, .. } => return Some(new.to_owned()),
Change::End => {
continue;
}
_ => {
return None;
}
}
}
None
}
}
impl DeleteListener for Changeset {
fn start_killing(&mut self) {}
fn delete(&mut self, idx: usize, string: &str, _: Direction) {
self.delete(idx, string);
}
fn stop_killing(&mut self) {}
}
impl ChangeListener for Changeset {
fn insert_char(&mut self, idx: usize, c: char) {
self.insert(idx, c);
}
fn insert_str(&mut self, idx: usize, string: &str) {
self.insert_str(idx, string);
}
fn replace(&mut self, idx: usize, old: &str, new: &str) {
self.replace(idx, old, new);
}
}
#[cfg(test)]
mod tests {
use super::Changeset;
use line_buffer::LineBuffer;
#[test]
fn test_insert_chars() {
let mut cs = Changeset::new();
cs.insert(0, 'H');
cs.insert(1, 'i');
assert_eq!(1, cs.undos.len());
assert_eq!(0, cs.redos.len());
cs.insert(0, ' ');
assert_eq!(2, cs.undos.len());
}
#[test]
fn test_insert_strings() {
let mut cs = Changeset::new();
cs.insert_str(0, "Hello");
cs.insert_str(5, ", ");
assert_eq!(2, cs.undos.len());
assert_eq!(0, cs.redos.len());
}
#[test]
fn test_undo_insert() {
let mut buf = LineBuffer::init("", 0, None);
buf.insert_str(0, "Hello");
buf.insert_str(5, ", world!");
let mut cs = Changeset::new();
assert_eq!(buf.as_str(), "Hello, world!");
cs.insert_str(5, ", world!");
cs.undo(&mut buf, 1);
assert_eq!(0, cs.undos.len());
assert_eq!(1, cs.redos.len());
assert_eq!(buf.as_str(), "Hello");
cs.redo(&mut buf);
assert_eq!(1, cs.undos.len());
assert_eq!(0, cs.redos.len());
assert_eq!(buf.as_str(), "Hello, world!");
}
#[test]
fn test_undo_delete() {
let mut buf = LineBuffer::init("", 0, None);
buf.insert_str(0, "Hello");
let mut cs = Changeset::new();
assert_eq!(buf.as_str(), "Hello");
cs.delete(5, ", world!");
cs.undo(&mut buf, 1);
assert_eq!(buf.as_str(), "Hello, world!");
cs.redo(&mut buf);
assert_eq!(buf.as_str(), "Hello");
}
#[test]
fn test_delete_chars() {
let mut buf = LineBuffer::init("", 0, None);
buf.insert_str(0, "Hlo");
let mut cs = Changeset::new();
cs.delete(1, "e");
cs.delete(1, "l");
assert_eq!(1, cs.undos.len());
cs.undo(&mut buf, 1);
assert_eq!(buf.as_str(), "Hello");
}
#[test]
fn test_backspace_chars() {
let mut buf = LineBuffer::init("", 0, None);
buf.insert_str(0, "Hlo");
let mut cs = Changeset::new();
cs.delete(2, "l");
cs.delete(1, "e");
assert_eq!(1, cs.undos.len());
cs.undo(&mut buf, 1);
assert_eq!(buf.as_str(), "Hello");
}
#[test]
fn test_undo_replace() {
let mut buf = LineBuffer::init("", 0, None);
buf.insert_str(0, "Hello, world!");
let mut cs = Changeset::new();
assert_eq!(buf.as_str(), "Hello, world!");
buf.replace(1..5, "i");
assert_eq!(buf.as_str(), "Hi, world!");
cs.replace(1, "ello", "i");
cs.undo(&mut buf, 1);
assert_eq!(buf.as_str(), "Hello, world!");
cs.redo(&mut buf);
assert_eq!(buf.as_str(), "Hi, world!");
}
#[test]
fn test_last_insert() {
let mut cs = Changeset::new();
cs.begin();
cs.delete(0, "Hello");
cs.insert_str(0, "Bye");
cs.end();
let insert = cs.last_insert();
assert_eq!(Some("Bye".to_owned()), insert);
}
}