#undo-redo #undo #redo

undo_2

正确实现撤销和重做

3个不稳定版本

0.2.0 2022年11月17日
0.1.2 2022年11月16日
0.1.1 2022年11月16日
0.1.0 2022年11月15日

数据结构中排名第753

Download history 98/week @ 2024-03-13 75/week @ 2024-03-20 52/week @ 2024-03-27 76/week @ 2024-04-03 117/week @ 2024-04-10 79/week @ 2024-04-17 64/week @ 2024-04-24 96/week @ 2024-05-01 33/week @ 2024-05-08 8/week @ 2024-05-15 6/week @ 2024-05-22 39/week @ 2024-05-29 65/week @ 2024-06-05 73/week @ 2024-06-12 86/week @ 2024-06-19 30/week @ 2024-06-26

每月下载量263

MIT/Apache

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的撤销在历史中回溯,而经典撤销通过构建状态的命令序列回溯。

特性

  1. 历史撤销序列,不会丢失任何命令。
  2. 与复杂的撤销树相比,用户友好。
  3. 优化实现:永远不会复制任何命令。
  4. 非常轻量,简单。
  5. 可以合并和拼接命令。

如何使用

将依赖项添加到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");

更多信息

  1. 在连续撤销后,如果添加新命令,则撤销序列会合并。这通过避免状态重复来使撤销序列的遍历更简洁。
命令 状态 注释
初始状态
执行A A
执行B A,B
执行C A, B, C
撤销 A, B 合并
撤销 A 合并
执行D A, D
撤销 A 重做2次合并的撤销
撤销 A, B, C
撤销 A, B
撤销 A
撤销
  1. 每次执行撤销或重做可能会执行一系列以以下形式的操作: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