From e012eb6d072e63b3ec3ab4e7999c36b8600f4173 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 6 Jan 2025 19:19:36 +0800 Subject: [PATCH] history: Add `unique` mode to keep unique item in History. (#529) --- crates/ui/src/dock/tiles.rs | 2 +- crates/ui/src/history.rs | 64 ++++++++++++++++++++++++++++++++++- crates/ui/src/input/change.rs | 2 +- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/crates/ui/src/dock/tiles.rs b/crates/ui/src/dock/tiles.rs index 53008776..18f2ad3f 100644 --- a/crates/ui/src/dock/tiles.rs +++ b/crates/ui/src/dock/tiles.rs @@ -28,7 +28,7 @@ const MINIMUM_SIZE: Size = size(px(100.), px(100.)); const DRAG_BAR_HEIGHT: Pixels = px(30.); const HANDLE_SIZE: Pixels = px(20.0); -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] struct TileChange { tile_id: EntityId, old_bounds: Option>, diff --git a/crates/ui/src/history.rs b/crates/ui/src/history.rs index 05eb9d23..11c89a2a 100644 --- a/crates/ui/src/history.rs +++ b/crates/ui/src/history.rs @@ -3,7 +3,7 @@ use std::{ time::{Duration, Instant}, }; -pub trait HistoryItem: Clone { +pub trait HistoryItem: Clone + PartialEq { fn version(&self) -> usize; fn set_version(&mut self, version: usize); } @@ -22,6 +22,7 @@ pub struct History { pub(crate) ignore: bool, max_undo: usize, group_interval: Option, + unique: bool, } impl History @@ -37,6 +38,7 @@ where version: 0, max_undo: 1000, group_interval: None, + unique: false, } } @@ -46,6 +48,13 @@ where self } + /// Set the history to be unique, defaults to false. + /// If set to true, the history will only keep unique changes. + pub fn unique(mut self) -> Self { + self.unique = true; + self + } + /// Set the interval in milliseconds to group changes, defaults to None. pub fn group_interval(mut self, group_interval: Duration) -> Self { self.group_interval = Some(group_interval); @@ -75,6 +84,10 @@ where self.undos.remove(0); } + if self.unique { + self.undos.retain(|c| *c != item); + } + let mut item = item; item.set_version(version); self.undos.push(item); @@ -150,6 +163,12 @@ mod tests { version: usize, } + impl PartialEq for TabIndex { + fn eq(&self, other: &Self) -> bool { + self.tab_index == other.tab_index + } + } + impl From for TabIndex { fn from(value: usize) -> Self { TabIndex { @@ -210,4 +229,47 @@ mod tests { assert_eq!(history.undo().is_none(), true); } + + #[test] + fn test_unique_history() { + let mut history: History = History::new().max_undo(100).unique(); + + // Push some items + history.push(0.into()); + history.push(1.into()); + history.push(1.into()); // Duplicate, should be ignored + history.push(2.into()); + history.push(1.into()); // Duplicate, should be remove old, and add new + + // Check the version and undo stack + assert_eq!(history.version(), 5); + assert_eq!(history.undos().len(), 3); + assert_eq!(history.undos().last().unwrap().tab_index, 1); + + // Undo the last change + let changes = history.undo().unwrap(); + assert_eq!(changes.len(), 1); + assert_eq!(changes[0].tab_index, 1); + + // Redo the last undone change + let changes = history.redo().unwrap(); + assert_eq!(changes.len(), 1); + assert_eq!(changes[0].tab_index, 1); + + // Push another item + history.push(3.into()); + + // Check the version and undo stack + assert_eq!(history.version(), 6); + assert_eq!(history.undos().len(), 4); + + // Undo all changes + for _ in 0..4 { + history.undo(); + } + + // Check the undo stack is empty and redo stack has all changes + assert_eq!(history.undos().len(), 0); + assert_eq!(history.redos().len(), 4); + } } diff --git a/crates/ui/src/input/change.rs b/crates/ui/src/input/change.rs index 4cafb3f6..657bbae9 100644 --- a/crates/ui/src/input/change.rs +++ b/crates/ui/src/input/change.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, ops::Range}; use crate::history::HistoryItem; -#[derive(Debug, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct Change { pub(crate) old_range: Range, pub(crate) old_text: String,