use xi_trace::trace_block;
use std::collections::HashMap;
use std::mem;
use crate::edit_types::{BufferEvent, EventDomain};
pub(crate) struct Recorder {
active_recording: Option<String>,
recording_buffer: Vec<EventDomain>,
recordings: HashMap<String, Recording>,
}
impl Recorder {
pub(crate) fn new() -> Recorder {
Recorder {
active_recording: None,
recording_buffer: Vec::new(),
recordings: HashMap::new(),
}
}
pub(crate) fn is_recording(&self) -> bool {
self.active_recording.is_some()
}
pub(crate) fn toggle_recording(&mut self, recording_name: Option<String>) {
let is_recording = self.is_recording();
let last_recording = self.active_recording.take();
match (is_recording, &last_recording, &recording_name) {
(true, Some(last_recording), None) => {
self.save_recording_buffer(last_recording.clone())
}
(true, Some(last_recording), Some(recording_name)) => {
if last_recording != recording_name {
self.recording_buffer.clear();
} else {
self.save_recording_buffer(last_recording.clone());
return;
}
}
_ => {}
}
mem::replace(&mut self.active_recording, recording_name);
}
pub(crate) fn record(&mut self, current_event: EventDomain) {
assert!(self.is_recording());
let recording_buffer = &mut self.recording_buffer;
if recording_buffer.last().is_none() {
recording_buffer.push(current_event);
return;
}
{
let last_event = recording_buffer.last_mut().unwrap();
if let (
EventDomain::Buffer(BufferEvent::Insert(old_characters)),
EventDomain::Buffer(BufferEvent::Insert(new_characters)),
) = (last_event, ¤t_event)
{
old_characters.push_str(new_characters);
return;
}
}
recording_buffer.push(current_event);
}
pub(crate) fn play<F>(&self, recording_name: &str, action: F)
where
F: FnMut(&EventDomain) -> (),
{
let is_current_recording: bool = self
.active_recording
.as_ref()
.map_or(false, |current_recording| current_recording == recording_name);
if is_current_recording {
warn!("Cannot play recording while it's currently active!");
return;
}
self.recordings.get(recording_name).and_then(|recording| {
recording.play(action);
Some(())
});
}
pub(crate) fn clear(&mut self, recording_name: &str) {
self.recordings.remove(recording_name);
}
fn save_recording_buffer(&mut self, recording_name: String) {
let mut saw_undo = false;
let mut saw_redo = false;
let filtered: Vec<EventDomain> = self
.recording_buffer
.clone()
.into_iter()
.rev()
.filter(|event| {
if let EventDomain::Buffer(event) = event {
return match event {
BufferEvent::Undo => {
saw_undo = !saw_redo;
saw_redo = false;
false
}
BufferEvent::Redo => {
saw_redo = !saw_undo;
saw_undo = false;
false
}
_ => {
let ret = !saw_undo;
saw_undo = false;
saw_redo = false;
ret
}
};
}
true
})
.collect::<Vec<EventDomain>>()
.into_iter()
.rev()
.collect();
let current_recording = Recording::new(filtered);
self.recordings.insert(recording_name, current_recording);
self.recording_buffer.clear();
}
}
struct Recording {
events: Vec<EventDomain>,
}
impl Recording {
fn new(events: Vec<EventDomain>) -> Recording {
Recording { events }
}
fn play<F>(&self, action: F)
where
F: FnMut(&EventDomain) -> (),
{
let _guard = trace_block("Recording::play", &["core", "recording"]);
self.events.iter().for_each(action)
}
}
#[cfg(test)]
mod tests {
use crate::edit_types::{BufferEvent, EventDomain};
use crate::recorder::Recorder;
#[test]
fn play_recording() {
let mut recorder = Recorder::new();
let recording_name = String::new();
let mut expected_events: Vec<EventDomain> = vec![
BufferEvent::Indent.into(),
BufferEvent::Outdent.into(),
BufferEvent::DuplicateLine.into(),
BufferEvent::Transpose.into(),
];
recorder.toggle_recording(Some(recording_name.clone()));
for event in expected_events.iter().rev() {
recorder.record(event.clone());
}
recorder.toggle_recording(Some(recording_name.clone()));
recorder.play(&recording_name, |event| {
let expected_event = expected_events.pop();
assert!(expected_event.is_some());
assert_eq!(*event, expected_event.unwrap());
});
assert_eq!(expected_events.len(), 0);
}
#[test]
fn play_only_after_saved() {
let mut recorder = Recorder::new();
let recording_name = String::new();
let expected_events: Vec<EventDomain> = vec![
BufferEvent::Indent.into(),
BufferEvent::Outdent.into(),
BufferEvent::DuplicateLine.into(),
BufferEvent::Transpose.into(),
];
recorder.toggle_recording(Some(recording_name.clone()));
for event in expected_events.iter().rev() {
recorder.record(event.clone());
}
recorder.play(&recording_name, |_| {
assert!(false);
});
}
#[test]
fn prevent_same_playback() {
let mut recorder = Recorder::new();
let recording_name = String::new();
let expected_events: Vec<EventDomain> = vec![
BufferEvent::Indent.into(),
BufferEvent::Outdent.into(),
BufferEvent::DuplicateLine.into(),
BufferEvent::Transpose.into(),
];
recorder.toggle_recording(Some(recording_name.clone()));
for event in expected_events.iter().rev() {
recorder.record(event.clone());
}
recorder.toggle_recording(Some(recording_name.clone()));
recorder.toggle_recording(Some(recording_name.clone()));
recorder.play(&recording_name, |_| {
assert!(false);
});
}
#[test]
fn clear_recording() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::DuplicateLine.into());
recorder.record(BufferEvent::Outdent.into());
recorder.record(BufferEvent::Indent.into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(recorder.recordings.get(&recording_name).unwrap().events.len(), 4);
recorder.clear(&recording_name);
assert!(recorder.recordings.get(&recording_name).is_none());
}
#[test]
fn multiple_recordings() {
let mut recorder = Recorder::new();
let recording_a = "a".to_string();
let recording_b = "b".to_string();
recorder.toggle_recording(Some(recording_a.clone()));
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::DuplicateLine.into());
recorder.toggle_recording(Some(recording_a.clone()));
recorder.toggle_recording(Some(recording_b.clone()));
recorder.record(BufferEvent::Outdent.into());
recorder.record(BufferEvent::Indent.into());
recorder.toggle_recording(Some(recording_b.clone()));
assert_eq!(
recorder.recordings.get(&recording_a).unwrap().events,
vec![BufferEvent::Transpose.into(), BufferEvent::DuplicateLine.into()]
);
assert_eq!(
recorder.recordings.get(&recording_b).unwrap().events,
vec![BufferEvent::Outdent.into(), BufferEvent::Indent.into()]
);
recorder.clear(&recording_a);
assert!(recorder.recordings.get(&recording_a).is_none());
assert!(recorder.recordings.get(&recording_b).is_some());
}
#[test]
fn text_playback() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Insert("Foo".to_owned()).into());
recorder.record(BufferEvent::Insert("B".to_owned()).into());
recorder.record(BufferEvent::Insert("A".to_owned()).into());
recorder.record(BufferEvent::Insert("R".to_owned()).into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(
recorder.recordings.get(&recording_name).unwrap().events,
vec![BufferEvent::Insert("FooBAR".to_owned()).into()]
);
}
#[test]
fn basic_undo() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::Undo.into());
recorder.record(BufferEvent::DuplicateLine.into());
recorder.record(BufferEvent::Redo.into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(
recorder.recordings.get(&recording_name).unwrap().events,
vec![BufferEvent::DuplicateLine.into()]
);
}
#[test]
fn basic_undo_swapped() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::Redo.into());
recorder.record(BufferEvent::DuplicateLine.into());
recorder.record(BufferEvent::Undo.into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(
recorder.recordings.get(&recording_name).unwrap().events,
vec![BufferEvent::Transpose.into()]
);
}
#[test]
fn redo_cancels_undo() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::Undo.into());
recorder.record(BufferEvent::Redo.into());
recorder.record(BufferEvent::DuplicateLine.into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(
recorder.recordings.get(&recording_name).unwrap().events,
vec![BufferEvent::Transpose.into(), BufferEvent::DuplicateLine.into()]
);
}
#[test]
fn undo_cancels_redo() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::Undo.into());
recorder.record(BufferEvent::Redo.into());
recorder.record(BufferEvent::Undo.into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(recorder.recordings.get(&recording_name).unwrap().events, vec![]);
}
#[test]
fn undo_as_first_item() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Undo.into());
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::DuplicateLine.into());
recorder.record(BufferEvent::Redo.into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(
recorder.recordings.get(&recording_name).unwrap().events,
vec![BufferEvent::Transpose.into(), BufferEvent::DuplicateLine.into()]
);
}
#[test]
fn redo_as_first_item() {
let mut recorder = Recorder::new();
let recording_name = String::new();
recorder.toggle_recording(Some(recording_name.clone()));
recorder.record(BufferEvent::Redo.into());
recorder.record(BufferEvent::Transpose.into());
recorder.record(BufferEvent::DuplicateLine.into());
recorder.record(BufferEvent::Undo.into());
recorder.toggle_recording(Some(recording_name.clone()));
assert_eq!(
recorder.recordings.get(&recording_name).unwrap().events,
vec![BufferEvent::Transpose.into()]
);
}
}