use std::fmt::{Debug, Formatter, Result};
use super::{cur_index::{CurIndex, CurIndexLoc},
EditorContent};
use crate::{format_as_kilobytes_with_commas, idx, Length, RingBuffer};
#[derive(Clone, PartialEq, Default)]
pub struct EditorHistory {
pub versions: super::sizing::HistoryBuffer,
pub current_index: CurIndex,
}
impl EditorHistory {
#[must_use]
pub fn is_empty(&self) -> bool { self.versions.is_empty() }
pub fn clear(&mut self) {
self.versions.clear();
self.current_index.clear();
}
#[must_use]
pub fn current_index(&self) -> Option<CurIndex> {
if self.is_empty() {
None
} else {
Some(self.current_index)
}
}
pub fn add(&mut self, content: EditorContent) {
match self.locate_current_index() {
CurIndexLoc::End(current_index) | CurIndexLoc::Middle(current_index) => {
self.versions.truncate((current_index + idx(1)).as_usize());
}
CurIndexLoc::Start => {
self.versions.truncate(0);
}
CurIndexLoc::EmptyHistory => {}
}
self.versions.add(content);
CurIndexLoc::inc(&mut self.current_index, Length::from(self.versions.len().as_usize()));
}
pub fn undo(&mut self) -> Option<EditorContent> {
match self.locate_current_index() {
CurIndexLoc::EmptyHistory => {
None
}
CurIndexLoc::Start => {
CurIndexLoc::dec(&mut self.current_index, self.versions.len());
None
}
CurIndexLoc::End(_) | CurIndexLoc::Middle(_) => {
CurIndexLoc::dec(&mut self.current_index, self.versions.len());
self.versions.get(self.current_index.as_index()).cloned()
}
}
}
pub fn redo(&mut self) -> Option<EditorContent> {
match self.locate_current_index() {
CurIndexLoc::EmptyHistory => {
None
}
CurIndexLoc::End(_) => {
None
}
CurIndexLoc::Start | CurIndexLoc::Middle(_) => {
CurIndexLoc::inc(&mut self.current_index, self.versions.len());
self.versions.get(self.current_index.as_index()).cloned()
}
}
}
#[must_use]
pub fn locate_current_index(&self) -> CurIndexLoc {
CurIndexLoc::locate(&self.current_index, Length::from(self.versions.len().as_usize()))
}
}
mod impl_debug_format {
use super::{format_as_kilobytes_with_commas, Debug, EditorHistory, Formatter,
Result, RingBuffer};
impl Debug for EditorHistory {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
use crate::GetMemSize;
let self_mem_size = self.get_mem_size();
let size_fmt = format_as_kilobytes_with_commas(self_mem_size);
write!(
f,
"EditorHistory [index: {index:?} | versions.len(): {len} | size: {size}]",
len = self.versions.len().as_usize(),
size = size_fmt,
index = self.current_index.0
)
}
}
}
#[cfg(test)]
mod tests_editor_history_struct {
use super::*;
use crate::assert_eq2;
#[test]
fn test_editor_history_struct_one_item() {
let mut history = EditorHistory::default();
assert_eq2!(history.versions.len(), 0.into());
assert_eq2!(history.current_index, CurIndex(None));
assert_eq2!(history.locate_current_index(), CurIndexLoc::EmptyHistory);
assert!(history.is_empty());
history.add(EditorContent::default());
assert_eq!(history.versions.len(), 1.into());
assert_eq!(history.current_index, 0.into());
assert!(!history.is_empty());
assert_eq!(history.current_index(), Some(0.into()));
assert_eq!(history.locate_current_index(), CurIndexLoc::End(0.into()));
assert!(history.redo().is_none());
assert_eq!(history.current_index, 0.into());
assert_eq!(history.locate_current_index(), CurIndexLoc::End(0.into()));
assert!(history.undo().is_some());
assert_eq!(history.current_index, CurIndex(None));
assert_eq!(history.locate_current_index(), CurIndexLoc::Start);
assert!(history.redo().is_some());
assert_eq!(history.current_index, 0.into());
assert_eq!(history.locate_current_index(), CurIndexLoc::End(0.into()));
}
#[test]
fn test_editor_history_struct_multiple_items() {
let mut history = EditorHistory::default();
history.add(EditorContent::default());
history.add(EditorContent::default());
history.add(EditorContent::default());
assert_eq!(history.versions.len(), 3.into());
assert_eq!(history.current_index, 2.into());
assert!(!history.is_empty());
assert_eq!(history.current_index(), Some(2.into()));
assert!(history.undo().is_some());
assert_eq!(history.current_index, 1.into());
assert!(history.undo().is_some());
assert_eq!(history.current_index, 0.into());
assert!(history.undo().is_some());
assert_eq!(history.current_index, CurIndex(None));
assert!(history.undo().is_none());
assert!(history.redo().is_some());
assert_eq!(history.current_index, 0.into());
assert!(history.redo().is_some());
assert_eq!(history.current_index, 1.into());
assert!(history.redo().is_some());
assert_eq!(history.current_index, 2.into());
assert!(history.redo().is_none());
}
#[test]
fn test_editor_history_struct_truncate_dangling_redos() {
let mut history = EditorHistory::default();
history.add(EditorContent::default());
history.add(EditorContent::default());
history.add(EditorContent::default());
history.add(EditorContent::default());
assert_eq!(history.versions.len(), 4.into());
assert_eq!(history.current_index, 3.into());
assert!(!history.is_empty());
assert_eq!(history.current_index(), Some(3.into()));
assert!(history.undo().is_some());
assert!(history.undo().is_some());
assert_eq!(history.current_index, 1.into());
assert_eq!(history.versions.len(), 4.into());
history.add(EditorContent::default());
assert_eq!(history.versions.len(), 3.into());
assert_eq!(history.current_index, 2.into());
assert!(!history.is_empty());
assert_eq!(history.current_index(), Some(2.into()));
}
}
#[cfg(test)]
mod tests_history_functions {
use smallvec::smallvec;
use crate::{assert_eq2, cur_index::CurIndex, EditorBuffer, GCStringExt,
Length, RingBuffer};
#[test]
fn test_push_default() {
let mut buffer = EditorBuffer::default();
let content = buffer.content.clone();
buffer.add();
assert_eq2!(buffer.history.current_index, 0.into());
let history_stack = buffer.history.versions;
assert_eq2!(history_stack.len(), Length::from(1));
assert_eq2!(history_stack.get(0).unwrap(), &content);
}
#[test]
fn test_push_with_contents() {
let mut buffer = EditorBuffer::default();
buffer.content.lines = smallvec!["abc".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 0.into());
let history_stack = buffer.history.versions;
assert_eq2!(history_stack.len(), Length::from(1));
assert_eq2!(history_stack.get(0).unwrap().lines.len(), 1);
assert_eq2!(
history_stack.get(0).unwrap().lines[0],
"abc".grapheme_string()
);
}
#[test]
fn test_push_and_drop_future_redos() {
let mut buffer = EditorBuffer::default();
buffer.content.lines = smallvec!["abc".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 0.into());
buffer.content.lines = smallvec!["def".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 1.into());
buffer.content.lines = smallvec!["ghi".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 2.into());
buffer.undo();
buffer.undo();
assert_eq!(buffer.history.current_index, 0.into());
assert_eq!(buffer.history.versions.len(), Length::from(3));
buffer.content.lines = smallvec!["xyz".grapheme_string()];
buffer.add();
assert_eq!(buffer.history.current_index, 1.into());
assert_eq!(buffer.history.versions.len(), Length::from(2));
let history = buffer.history;
assert_eq2!(history.current_index, 1.into());
let history_stack = history.versions;
assert_eq2!(history_stack.len(), Length::from(2));
for (index, content) in history_stack.iter().enumerate() {
match index {
0 => {
assert_eq2!(content.lines.len(), 1);
assert_eq2!(content.lines[0], "abc".grapheme_string());
}
1 => {
assert_eq2!(content.lines.len(), 1);
assert_eq2!(content.lines[0], "xyz".grapheme_string());
}
_ => unreachable!(),
}
}
}
#[test]
fn test_single_undo() {
let mut buffer = EditorBuffer::default();
buffer.content.lines = smallvec!["abc".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 0.into());
buffer.undo();
assert_eq2!(buffer.history.current_index, CurIndex(None));
}
#[test]
fn test_many_undo() {
let mut buffer = EditorBuffer::default();
buffer.content.lines = smallvec!["abc".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 0.into());
buffer.content.lines = smallvec!["def".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 1.into());
let copy_of_editor_content = buffer.content.clone();
buffer.content.lines = smallvec!["ghi".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 2.into());
buffer.undo();
assert_eq2!(buffer.history.current_index, 1.into());
assert_eq2!(buffer.content, copy_of_editor_content);
let history_stack = buffer.history.versions;
assert_eq2!(history_stack.len(), Length::from(3));
for (index, content) in history_stack.iter().enumerate() {
match index {
0 => {
assert_eq2!(content.lines.len(), 1);
assert_eq2!(content.lines[0], "abc".grapheme_string());
}
1 => {
assert_eq2!(content.lines.len(), 1);
assert_eq2!(content.lines[0], "def".grapheme_string());
}
2 => {
assert_eq2!(content.lines.len(), 1);
assert_eq2!(content.lines[0], "ghi".grapheme_string());
}
_ => unreachable!(),
}
}
}
#[test]
fn test_multiple_undos() {
let mut buffer = EditorBuffer::default();
buffer.content.lines = smallvec!["abc".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 0.into());
buffer.content.lines = smallvec!["def".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 1.into());
buffer.undo();
buffer.undo();
buffer.undo();
assert_eq2!(buffer.history.current_index, CurIndex(None));
}
#[test]
fn test_undo_and_multiple_redos() {
let mut buffer = EditorBuffer::default();
buffer.content.lines = smallvec!["abc".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 0.into());
buffer.content.lines = smallvec!["def".grapheme_string()];
buffer.add();
assert_eq2!(buffer.history.current_index, 1.into());
let snapshot_content = buffer.content.clone();
buffer.undo();
assert_eq2!(buffer.history.current_index, 0.into());
buffer.redo();
assert_eq2!(buffer.history.current_index, 1.into());
assert_eq2!(buffer.content, snapshot_content);
buffer.redo();
let history_stack = buffer.history.versions;
assert_eq2!(history_stack.len(), Length::from(2));
for (index, content) in history_stack.iter().enumerate() {
match index {
0 => {
assert_eq2!(content.lines.len(), 1);
assert_eq2!(content.lines[0], "abc".grapheme_string());
}
1 => {
assert_eq2!(content.lines.len(), 1);
assert_eq2!(content.lines[0], "def".grapheme_string());
}
_ => unreachable!(),
}
}
}
}