3个不稳定版本
0.2.0 | 2022年11月17日 |
---|---|
0.1.2 | 2022年11月16日 |
0.1.1 |
|
0.1.0 | 2022年11月15日 |
在数据结构中排名第753
每月下载量263次
56KB
677 行
正确实现撤销!
简介
这是一个撤销功能库,当你编辑已经被撤销的内容时,而不是截断撤销/重做历史,它会将回滚操作作为新变更的前置操作添加到撤销历史中。这个想法起源于(并且所有功劳归功于)zaboople/klonk。这个库是这个想法的一个实现,下面会进行一些轻微的修改。
例如,考虑以下命令序列
命令 | 状态 |
---|---|
初始状态 | |
执行A | A |
执行B | A, B |
撤销 | A |
执行C | A, C |
在经典撤销中,重复撤销会导致以下序列
命令 | 状态 |
---|---|
A, C | |
撤销 | A |
撤销 |
从5开始,在undo_2
中,重复撤销会导致以下序列
命令 | 状态 |
---|---|
A, C | |
撤销 | A |
撤销 | A,B |
撤销 | A |
撤销 |
undo_2
的撤销在历史中回溯,而经典撤销通过构建状态的命令序列回溯。
特性
- 历史撤销序列,不会丢失任何命令。
- 与复杂的撤销树相比,用户友好。
- 优化实现:永远不会复制任何命令。
- 非常轻量,简单。
- 可以合并和拼接命令。
如何使用
将依赖项添加到Cargo文件中
[dependencies]
undo_2 = "0.1"
然后将其添加到源文件中
use undo_2::Commands;
下面的示例实现了一个简单的文本编辑器。 undo_2不会执行“撤销”和“重做”,而是返回必须由应用程序解释的命令序列。这种设计模式使得实现更加容易,因为不需要在存储的命令列表中借用数据。
use undo_2::{Commands,Action};
enum Command {
Add(char),
Delete(char),
}
struct TextEditor {
text: String,
command: Commands<Command>,
}
impl TextEditor {
pub fn new() -> Self {
Self {
text: String::new(),
command: Commands::new(),
}
}
pub fn add_char(&mut self, c: char) {
self.text.push(c);
self.command.push(Command::Add(c));
}
pub fn delete_char(&mut self) {
if let Some(c) = self.text.pop() {
self.command.push(Command::Delete(c));
}
}
pub fn undo(&mut self) {
for action in self.command.undo() {
interpret_action(&mut self.text, action)
}
}
pub fn redo(&mut self) {
for action in self.command.redo() {
interpret_action(&mut self.text, action)
}
}
}
fn interpret_action(data: &mut String, action: Action<&Command>) {
use Command::*;
match action {
Action::Do(Add(c)) | Action::Undo(Delete(c)) => {
data.push(*c);
}
Action::Undo(Add(_)) | Action::Do(Delete(_)) => {
data.pop();
}
}
}
let mut editor = TextEditor::new();
editor.add_char('a'); // :[1]
editor.add_char('b'); // :[2]
editor.add_char('d'); // :[3]
assert_eq!(editor.text, "abd");
editor.undo(); // first undo :[4]
assert_eq!(editor.text, "ab");
editor.add_char('c'); // :[5]
assert_eq!(editor.text, "abc");
editor.undo(); // Undo [5] :[6]
assert_eq!(editor.text, "ab");
editor.undo(); // Undo the undo [4] :[7]
assert_eq!(editor.text, "abd");
editor.undo(); // Undo [3] :[8]
assert_eq!(editor.text, "ab");
editor.undo(); // Undo [2] :[9]
assert_eq!(editor.text, "a");
更多信息
- 在连续撤销后,如果添加新命令,则撤销序列会合并。这通过避免状态重复来使撤销序列的遍历更简洁。
命令 | 状态 | 注释 |
---|---|---|
初始状态 | ||
执行A | A | |
执行B | A,B | |
执行C | A, B, C | |
撤销 | A, B | 合并 |
撤销 | A | 合并 |
执行D | A, D | |
撤销 | A | 重做2次合并的撤销 |
撤销 | A, B, C | |
撤销 | A, B | |
撤销 | A | |
撤销 |
- 每次执行撤销或重做可能会执行一系列以以下形式的操作:
Undo(a)+Do(b)+Do(c)
。基本的算术实现假设Do(a)+Undo(a)
等价于什么也不做(这里的2个a
指代同一实体,而不是相等的对象)。
下面的代码片段是上述代码的较长版本,说明了第1点和第2点。
let mut editor = TextEditor::new();
editor.add_char('a'); // :[1]
editor.add_char('b'); // :[2]
editor.add_char('d'); // :[3]
assert_eq!(editor.text, "abd");
editor.undo(); // first undo :[4]
assert_eq!(editor.text, "ab");
editor.add_char('c'); // :[5]
assert_eq!(editor.text, "abc");
editor.undo(); // Undo [5] :[6]
assert_eq!(editor.text, "ab");
editor.undo(); // Undo the undo [4] :[7]
assert_eq!(editor.text, "abd");
editor.undo(); // Undo [3] :[8]
assert_eq!(editor.text, "ab");
editor.undo(); // Undo [2] :[9]
assert_eq!(editor.text, "a");
editor.add_char('z'); // :[10]
assert_eq!(editor.text, "az");
// when an action is performed after a sequence
// of undo, the undos are merged: undos [6] to [9] are merged now
editor.undo(); // back to [10]
assert_eq!(editor.text, "a");
editor.undo(); // back to [5]: reverses the consecutive sequence of undos in batch
assert_eq!(editor.text, "abc");
editor.undo(); // back to [4]
assert_eq!(editor.text, "ab");
editor.undo(); // back to [3]
assert_eq!(editor.text, "abd");
editor.undo(); // back to [2]
assert_eq!(editor.text, "ab");
editor.undo(); // back to [1]
assert_eq!(editor.text, "a");
editor.undo(); // back to [0]
assert_eq!(editor.text, "");
editor.redo(); // back to [1]
assert_eq!(editor.text, "a");
editor.redo(); // back to [2]
assert_eq!(editor.text, "ab");
editor.redo(); // back to [3]
assert_eq!(editor.text, "abd");
editor.redo(); // back to [4]
assert_eq!(editor.text, "ab");
editor.redo(); // back to [5]
assert_eq!(editor.text, "abc");
editor.redo(); // back to [9]: redo inner consecutive sequence of undos in batch
// (undo are merged only when they are not the last action)
assert_eq!(editor.text, "a");
editor.redo(); // back to [10]
assert_eq!(editor.text, "az");
editor.add_char('1');
editor.add_char('2');
assert_eq!(editor.text, "az12");
editor.undo();
editor.undo();
assert_eq!(editor.text, "az");
editor.redo(); // undo is the last action, undo the undo only once
assert_eq!(editor.text, "az1");
editor.redo();
assert_eq!(editor.text, "az12");
箱子功能
serde
:默认启用
依赖关系
~1.3–1.7MB
~41K SLoC