use ropey::Rope;
#[derive(Clone)]
pub enum HistoryChange {
InsertChar {
idx: usize,
len: usize,
ch: char,
},
InsertText {
idx: usize,
len: usize,
text: String,
},
Remove {
idx: usize,
len: usize,
text: String,
},
}
#[derive(Default, Clone)]
pub struct EditorHistory {
pub changes: Vec<HistoryChange>,
pub current_change: usize,
pub version: usize,
}
impl EditorHistory {
pub fn new() -> Self {
Self::default()
}
pub fn push_change(&mut self, change: HistoryChange) {
if self.can_redo() {
self.changes.drain(self.current_change..);
}
self.changes.push(change);
self.current_change = self.changes.len();
self.version += 1;
}
pub fn current_change(&self) -> usize {
self.current_change
}
pub fn any_pending_changes(&self) -> usize {
self.changes.len() - self.current_change
}
pub fn can_undo(&self) -> bool {
self.current_change > 0
}
pub fn can_redo(&self) -> bool {
self.current_change < self.changes.len()
}
pub fn undo(&mut self, rope: &mut Rope) -> Option<usize> {
if !self.can_undo() {
return None;
}
let last_change = self.changes.get(self.current_change - 1);
if let Some(last_change) = last_change {
let idx_end = match last_change {
HistoryChange::Remove { idx, text, len } => {
let start = rope.utf16_cu_to_char(*idx);
rope.insert(start, text);
*idx + len
}
HistoryChange::InsertChar { idx, len, .. } => {
let start = rope.utf16_cu_to_char(*idx);
let end = rope.utf16_cu_to_char(*idx + len);
rope.remove(start..end);
*idx
}
HistoryChange::InsertText { idx, len, .. } => {
let start = rope.utf16_cu_to_char(*idx);
let end = rope.utf16_cu_to_char(*idx + len);
rope.remove(start..end);
*idx
}
};
self.current_change -= 1;
self.version += 1;
Some(idx_end)
} else {
None
}
}
pub fn redo(&mut self, rope: &mut Rope) -> Option<usize> {
if !self.can_redo() {
return None;
}
let next_change = self.changes.get(self.current_change);
if let Some(next_change) = next_change {
let idx_end = match next_change {
HistoryChange::Remove { idx, len, .. } => {
let start = rope.utf16_cu_to_char(*idx);
let end = rope.utf16_cu_to_char(*idx + len);
rope.remove(start..end);
*idx
}
HistoryChange::InsertChar { idx, ch, len } => {
let start = rope.utf16_cu_to_char(*idx);
rope.insert_char(start, *ch);
*idx + len
}
HistoryChange::InsertText { idx, text, len } => {
let start = rope.utf16_cu_to_char(*idx);
rope.insert(start, text);
*idx + len
}
};
self.current_change += 1;
self.version += 1;
Some(idx_end)
} else {
None
}
}
}
#[cfg(test)]
mod test {
use ropey::Rope;
use super::{
EditorHistory,
HistoryChange,
};
#[test]
fn test() {
let mut rope = Rope::new();
let mut history = EditorHistory::new();
rope.insert(0, "Hello World");
assert!(!history.can_undo());
assert!(!history.can_redo());
rope.insert(11, "\n!!!!");
history.push_change(HistoryChange::InsertText {
idx: 11,
text: "\n!!!!".to_owned(),
len: "\n!!!!".len(),
});
assert!(history.can_undo());
assert!(!history.can_redo());
assert_eq!(rope.to_string(), "Hello World\n!!!!");
history.undo(&mut rope);
assert!(!history.can_undo());
assert!(history.can_redo());
assert_eq!(rope.to_string(), "Hello World");
rope.insert(11, "\n!!!!");
history.push_change(HistoryChange::InsertText {
idx: 11,
text: "\n!!!!".to_owned(),
len: "\n!!!!".len(),
});
rope.insert(16, "\n!!!!");
history.push_change(HistoryChange::InsertText {
idx: 16,
text: "\n!!!!".to_owned(),
len: "\n!!!!".len(),
});
rope.insert(21, "\n!!!!");
history.push_change(HistoryChange::InsertText {
idx: 21,
text: "\n!!!!".to_owned(),
len: "\n!!!!".len(),
});
assert_eq!(history.any_pending_changes(), 0);
assert!(history.can_undo());
assert!(!history.can_redo());
assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!\n!!!!");
history.undo(&mut rope);
assert_eq!(history.any_pending_changes(), 1);
assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
history.undo(&mut rope);
assert_eq!(history.any_pending_changes(), 2);
assert_eq!(rope.to_string(), "Hello World\n!!!!");
history.undo(&mut rope);
assert_eq!(history.any_pending_changes(), 3);
assert_eq!(rope.to_string(), "Hello World");
assert!(!history.can_undo());
assert!(history.can_redo());
history.redo(&mut rope);
assert_eq!(rope.to_string(), "Hello World\n!!!!");
history.redo(&mut rope);
assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
history.redo(&mut rope);
assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!\n!!!!");
assert_eq!(history.any_pending_changes(), 0);
assert!(history.can_undo());
assert!(!history.can_redo());
history.undo(&mut rope);
assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
assert_eq!(history.any_pending_changes(), 1);
history.undo(&mut rope);
assert_eq!(rope.to_string(), "Hello World\n!!!!");
assert_eq!(history.any_pending_changes(), 2);
rope.insert_char(0, '.');
history.push_change(HistoryChange::InsertChar {
idx: 0,
ch: '.',
len: 1,
});
assert_eq!(history.any_pending_changes(), 0);
}
}