xi_core_lib/
recorder.rs

1// Copyright 2018 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Manages recording and enables playback for client sent events.
16//!
17//! Clients can store multiple, named recordings.
18
19use xi_trace::trace_block;
20
21use std::collections::HashMap;
22use std::mem;
23
24use crate::edit_types::{BufferEvent, EventDomain};
25
26/// A container that manages and holds all recordings for the current editing session
27pub(crate) struct Recorder {
28    active_recording: Option<String>,
29    recording_buffer: Vec<EventDomain>,
30    recordings: HashMap<String, Recording>,
31}
32
33impl Recorder {
34    pub(crate) fn new() -> Recorder {
35        Recorder {
36            active_recording: None,
37            recording_buffer: Vec::new(),
38            recordings: HashMap::new(),
39        }
40    }
41
42    pub(crate) fn is_recording(&self) -> bool {
43        self.active_recording.is_some()
44    }
45
46    /// Starts or stops the specified recording.
47    ///
48    ///
49    /// There are three outcome behaviors:
50    /// - If the current recording name is specified, the active recording is saved
51    /// - If no recording name is specified, the currently active recording is saved
52    /// - If a recording name other than the active recording is specified,
53    /// the current recording will be thrown out and will be switched to the new name
54    ///
55    /// In addition to the above:
56    /// - If the recording was saved, there is no active recording
57    /// - If the recording was switched, there will be a new active recording
58    pub(crate) fn toggle_recording(&mut self, recording_name: Option<String>) {
59        let is_recording = self.is_recording();
60        let last_recording = self.active_recording.take();
61
62        match (is_recording, &last_recording, &recording_name) {
63            (true, Some(last_recording), None) => {
64                self.save_recording_buffer(last_recording.clone())
65            }
66            (true, Some(last_recording), Some(recording_name)) => {
67                if last_recording != recording_name {
68                    self.recording_buffer.clear();
69                } else {
70                    self.save_recording_buffer(last_recording.clone());
71                    return;
72                }
73            }
74            _ => {}
75        }
76
77        mem::replace(&mut self.active_recording, recording_name);
78    }
79
80    /// Saves an event into the currently active recording.
81    ///
82    /// Every sequential `BufferEvent::Insert` event will be merged together to cut down the number of
83    /// `Editor::commit_delta` calls we need to make when playing back.
84    pub(crate) fn record(&mut self, current_event: EventDomain) {
85        assert!(self.is_recording());
86
87        let recording_buffer = &mut self.recording_buffer;
88
89        if recording_buffer.last().is_none() {
90            recording_buffer.push(current_event);
91            return;
92        }
93
94        {
95            let last_event = recording_buffer.last_mut().unwrap();
96            if let (
97                EventDomain::Buffer(BufferEvent::Insert(old_characters)),
98                EventDomain::Buffer(BufferEvent::Insert(new_characters)),
99            ) = (last_event, &current_event)
100            {
101                old_characters.push_str(new_characters);
102                return;
103            }
104        }
105
106        recording_buffer.push(current_event);
107    }
108
109    /// Iterates over a specified recording's buffer and runs the specified action
110    /// on each event.
111    pub(crate) fn play<F>(&self, recording_name: &str, action: F)
112    where
113        F: FnMut(&EventDomain) -> (),
114    {
115        let is_current_recording: bool = self
116            .active_recording
117            .as_ref()
118            .map_or(false, |current_recording| current_recording == recording_name);
119
120        if is_current_recording {
121            warn!("Cannot play recording while it's currently active!");
122            return;
123        }
124
125        self.recordings.get(recording_name).and_then(|recording| {
126            recording.play(action);
127            Some(())
128        });
129    }
130
131    /// Completely removes the specified recording from the Recorder
132    pub(crate) fn clear(&mut self, recording_name: &str) {
133        self.recordings.remove(recording_name);
134    }
135
136    /// Cleans the recording buffer by filtering out any undo or redo events and then saving it
137    /// with the specified name.
138    ///
139    /// A recording should not store any undos or redos--
140    /// call this once a recording is 'finalized.'
141    fn save_recording_buffer(&mut self, recording_name: String) {
142        let mut saw_undo = false;
143        let mut saw_redo = false;
144
145        // Walk the recording backwards and remove any undo / redo events
146        let filtered: Vec<EventDomain> = self
147            .recording_buffer
148            .clone()
149            .into_iter()
150            .rev()
151            .filter(|event| {
152                if let EventDomain::Buffer(event) = event {
153                    return match event {
154                        BufferEvent::Undo => {
155                            saw_undo = !saw_redo;
156                            saw_redo = false;
157                            false
158                        }
159                        BufferEvent::Redo => {
160                            saw_redo = !saw_undo;
161                            saw_undo = false;
162                            false
163                        }
164                        _ => {
165                            let ret = !saw_undo;
166                            saw_undo = false;
167                            saw_redo = false;
168                            ret
169                        }
170                    };
171                }
172
173                true
174            })
175            .collect::<Vec<EventDomain>>()
176            .into_iter()
177            .rev()
178            .collect();
179
180        let current_recording = Recording::new(filtered);
181        self.recordings.insert(recording_name, current_recording);
182        self.recording_buffer.clear();
183    }
184}
185
186struct Recording {
187    events: Vec<EventDomain>,
188}
189
190impl Recording {
191    fn new(events: Vec<EventDomain>) -> Recording {
192        Recording { events }
193    }
194
195    /// Iterates over the recording buffer and runs the specified action
196    /// on each event.
197    fn play<F>(&self, action: F)
198    where
199        F: FnMut(&EventDomain) -> (),
200    {
201        let _guard = trace_block("Recording::play", &["core", "recording"]);
202        self.events.iter().for_each(action)
203    }
204}
205
206// Tests for filtering undo / redo from the recording buffer
207// A = Event
208// B = Event
209// U = Undo
210// R = Redo
211#[cfg(test)]
212mod tests {
213    use crate::edit_types::{BufferEvent, EventDomain};
214    use crate::recorder::Recorder;
215
216    #[test]
217    fn play_recording() {
218        let mut recorder = Recorder::new();
219
220        let recording_name = String::new();
221        let mut expected_events: Vec<EventDomain> = vec![
222            BufferEvent::Indent.into(),
223            BufferEvent::Outdent.into(),
224            BufferEvent::DuplicateLine.into(),
225            BufferEvent::Transpose.into(),
226        ];
227
228        recorder.toggle_recording(Some(recording_name.clone()));
229        for event in expected_events.iter().rev() {
230            recorder.record(event.clone());
231        }
232        recorder.toggle_recording(Some(recording_name.clone()));
233
234        recorder.play(&recording_name, |event| {
235            // We shouldn't iterate more times than we added items!
236            let expected_event = expected_events.pop();
237            assert!(expected_event.is_some());
238
239            // Should be the event we expect
240            assert_eq!(*event, expected_event.unwrap());
241        });
242
243        // We should have iterated over everything we inserted
244        assert_eq!(expected_events.len(), 0);
245    }
246
247    #[test]
248    fn play_only_after_saved() {
249        let mut recorder = Recorder::new();
250
251        let recording_name = String::new();
252        let expected_events: Vec<EventDomain> = vec![
253            BufferEvent::Indent.into(),
254            BufferEvent::Outdent.into(),
255            BufferEvent::DuplicateLine.into(),
256            BufferEvent::Transpose.into(),
257        ];
258
259        recorder.toggle_recording(Some(recording_name.clone()));
260        for event in expected_events.iter().rev() {
261            recorder.record(event.clone());
262        }
263
264        recorder.play(&recording_name, |_| {
265            // We shouldn't have any events to play since nothing was saved!
266            assert!(false);
267        });
268    }
269
270    #[test]
271    fn prevent_same_playback() {
272        let mut recorder = Recorder::new();
273
274        let recording_name = String::new();
275        let expected_events: Vec<EventDomain> = vec![
276            BufferEvent::Indent.into(),
277            BufferEvent::Outdent.into(),
278            BufferEvent::DuplicateLine.into(),
279            BufferEvent::Transpose.into(),
280        ];
281
282        recorder.toggle_recording(Some(recording_name.clone()));
283        for event in expected_events.iter().rev() {
284            recorder.record(event.clone());
285        }
286        recorder.toggle_recording(Some(recording_name.clone()));
287
288        recorder.toggle_recording(Some(recording_name.clone()));
289        recorder.play(&recording_name, |_| {
290            // We shouldn't be able to play a recording while recording with the same name
291            assert!(false);
292        });
293    }
294
295    #[test]
296    fn clear_recording() {
297        let mut recorder = Recorder::new();
298
299        let recording_name = String::new();
300
301        recorder.toggle_recording(Some(recording_name.clone()));
302        recorder.record(BufferEvent::Transpose.into());
303        recorder.record(BufferEvent::DuplicateLine.into());
304        recorder.record(BufferEvent::Outdent.into());
305        recorder.record(BufferEvent::Indent.into());
306        recorder.toggle_recording(Some(recording_name.clone()));
307
308        assert_eq!(recorder.recordings.get(&recording_name).unwrap().events.len(), 4);
309
310        recorder.clear(&recording_name);
311
312        assert!(recorder.recordings.get(&recording_name).is_none());
313    }
314
315    #[test]
316    fn multiple_recordings() {
317        let mut recorder = Recorder::new();
318
319        let recording_a = "a".to_string();
320        let recording_b = "b".to_string();
321
322        recorder.toggle_recording(Some(recording_a.clone()));
323        recorder.record(BufferEvent::Transpose.into());
324        recorder.record(BufferEvent::DuplicateLine.into());
325        recorder.toggle_recording(Some(recording_a.clone()));
326
327        recorder.toggle_recording(Some(recording_b.clone()));
328        recorder.record(BufferEvent::Outdent.into());
329        recorder.record(BufferEvent::Indent.into());
330        recorder.toggle_recording(Some(recording_b.clone()));
331
332        assert_eq!(
333            recorder.recordings.get(&recording_a).unwrap().events,
334            vec![BufferEvent::Transpose.into(), BufferEvent::DuplicateLine.into()]
335        );
336        assert_eq!(
337            recorder.recordings.get(&recording_b).unwrap().events,
338            vec![BufferEvent::Outdent.into(), BufferEvent::Indent.into()]
339        );
340
341        recorder.clear(&recording_a);
342
343        assert!(recorder.recordings.get(&recording_a).is_none());
344        assert!(recorder.recordings.get(&recording_b).is_some());
345    }
346
347    #[test]
348    fn text_playback() {
349        let mut recorder = Recorder::new();
350
351        let recording_name = String::new();
352
353        recorder.toggle_recording(Some(recording_name.clone()));
354        recorder.record(BufferEvent::Insert("Foo".to_owned()).into());
355        recorder.record(BufferEvent::Insert("B".to_owned()).into());
356        recorder.record(BufferEvent::Insert("A".to_owned()).into());
357        recorder.record(BufferEvent::Insert("R".to_owned()).into());
358
359        recorder.toggle_recording(Some(recording_name.clone()));
360        assert_eq!(
361            recorder.recordings.get(&recording_name).unwrap().events,
362            vec![BufferEvent::Insert("FooBAR".to_owned()).into()]
363        );
364    }
365
366    #[test]
367    fn basic_undo() {
368        let mut recorder = Recorder::new();
369
370        let recording_name = String::new();
371
372        // Undo removes last item, redo only affects undo
373        // A U B R => B
374        recorder.toggle_recording(Some(recording_name.clone()));
375        recorder.record(BufferEvent::Transpose.into());
376        recorder.record(BufferEvent::Undo.into());
377        recorder.record(BufferEvent::DuplicateLine.into());
378        recorder.record(BufferEvent::Redo.into());
379        recorder.toggle_recording(Some(recording_name.clone()));
380        assert_eq!(
381            recorder.recordings.get(&recording_name).unwrap().events,
382            vec![BufferEvent::DuplicateLine.into()]
383        );
384    }
385
386    #[test]
387    fn basic_undo_swapped() {
388        let mut recorder = Recorder::new();
389
390        let recording_name = String::new();
391
392        // Swapping order of undo and redo from the basic test should give us a different leftover item
393        // A R B U => A
394        recorder.toggle_recording(Some(recording_name.clone()));
395        recorder.record(BufferEvent::Transpose.into());
396        recorder.record(BufferEvent::Redo.into());
397        recorder.record(BufferEvent::DuplicateLine.into());
398        recorder.record(BufferEvent::Undo.into());
399        recorder.toggle_recording(Some(recording_name.clone()));
400        assert_eq!(
401            recorder.recordings.get(&recording_name).unwrap().events,
402            vec![BufferEvent::Transpose.into()]
403        );
404    }
405
406    #[test]
407    fn redo_cancels_undo() {
408        let mut recorder = Recorder::new();
409
410        let recording_name = String::new();
411
412        // Redo cancels out an undo
413        // A U R B => A B
414        recorder.toggle_recording(Some(recording_name.clone()));
415        recorder.record(BufferEvent::Transpose.into());
416        recorder.record(BufferEvent::Undo.into());
417        recorder.record(BufferEvent::Redo.into());
418        recorder.record(BufferEvent::DuplicateLine.into());
419        recorder.toggle_recording(Some(recording_name.clone()));
420        assert_eq!(
421            recorder.recordings.get(&recording_name).unwrap().events,
422            vec![BufferEvent::Transpose.into(), BufferEvent::DuplicateLine.into()]
423        );
424    }
425
426    #[test]
427    fn undo_cancels_redo() {
428        let mut recorder = Recorder::new();
429
430        let recording_name = String::new();
431
432        // Undo should cancel a redo, preventing it from canceling another undo
433        // A U R U => _
434        recorder.toggle_recording(Some(recording_name.clone()));
435        recorder.record(BufferEvent::Transpose.into());
436        recorder.record(BufferEvent::Undo.into());
437        recorder.record(BufferEvent::Redo.into());
438        recorder.record(BufferEvent::Undo.into());
439        recorder.toggle_recording(Some(recording_name.clone()));
440        assert_eq!(recorder.recordings.get(&recording_name).unwrap().events, vec![]);
441    }
442
443    #[test]
444    fn undo_as_first_item() {
445        let mut recorder = Recorder::new();
446
447        let recording_name = String::new();
448
449        // Undo shouldn't do anything as the first item
450        // U A B R => A B
451        recorder.toggle_recording(Some(recording_name.clone()));
452        recorder.record(BufferEvent::Undo.into());
453        recorder.record(BufferEvent::Transpose.into());
454        recorder.record(BufferEvent::DuplicateLine.into());
455        recorder.record(BufferEvent::Redo.into());
456        recorder.toggle_recording(Some(recording_name.clone()));
457        assert_eq!(
458            recorder.recordings.get(&recording_name).unwrap().events,
459            vec![BufferEvent::Transpose.into(), BufferEvent::DuplicateLine.into()]
460        );
461    }
462
463    #[test]
464    fn redo_as_first_item() {
465        let mut recorder = Recorder::new();
466
467        let recording_name = String::new();
468
469        // Redo shouldn't do anything as the first item
470        // R A B U => A
471        recorder.toggle_recording(Some(recording_name.clone()));
472        recorder.record(BufferEvent::Redo.into());
473        recorder.record(BufferEvent::Transpose.into());
474        recorder.record(BufferEvent::DuplicateLine.into());
475        recorder.record(BufferEvent::Undo.into());
476        recorder.toggle_recording(Some(recording_name.clone()));
477        assert_eq!(
478            recorder.recordings.get(&recording_name).unwrap().events,
479            vec![BufferEvent::Transpose.into()]
480        );
481    }
482}