如何设计撤消&在文本编辑器中重做?

时间:2010-08-27 12:04:16

标签: java text-editor undo undo-redo redo

我的项目的一部分是写文本编辑器,用于键入某些规则,我的应用程序编译并运行它。编写编译器结束并发布测试版。在最终版本中,我们必须在文本编辑器中添加撤消和重做。我使用文件并定期保存文本编辑器。如何设计撤消和重做到我的文本编辑器?文件持久性结构的变化是什么?

8 个答案:

答案 0 :(得分:14)

您可以将操作建模为commands,并将其保存在两个堆栈中。一个用于撤销,另一个用于重做。您可以compose命令创建更多高级命令,例如,当您要撤消宏的操作时;或者如果你想在一个动作中对单个单词或短语的单个击键进行分组。

编辑器中的每个操作(或重做操作)都会生成一个新的撤消命令,该命令将进入撤消堆栈(并清除重做堆栈)。每个撤消操作都会生成相应的重做命令,该命令将进入重做堆栈。

derekerdmann的评论所述,您还可以将undo和redo命令组合成一种命令,知道如何撤消和重做其操作。

答案 1 :(得分:8)

基本上有两种很好的方法可以解决这个问题:

  • “命令”设计模式

  • 在不可变对象上只使用 OO,其中一切都是由不可变对象组成的不可变对象,这些对象由不可变对象组成(这种情况不太常见,但正确完成时非常优雅)

在天真命令或天真的撤销/重做上使用OO而不是永久对象的优点是你不需要太多考虑它:不需要“撤消”动作的效果而不需要“重播“所有命令。您只需要一个指向大量不可变对象的指针。

因为对象是不可变的,所以所有“状态”都可以非常轻量级,因为你可以在任何状态下缓存/重用大多数对象。

“OO over immutable objects”是一颗纯粹的宝石。可能不会在另外10年之前成为主流; )

P.S:在不可变对象上执行OO也令人惊讶地简化了并发编程。

答案 2 :(得分:6)

如果您不想要任何想象,可以添加UndoManager。每次添加或删除文字时,Document都会触发UndoableEdit。要撤消和重做每个更改,只需在UndoManager中调用这些方法。

这方面的缺点是每次用户输入内容时UndoManager都会添加一个新的编辑,因此输入“apple”将为您提供5次编辑,一次可撤消。对于我的文本编辑器,我编写了一个包含编辑器的包装器,用于存储除文本更改和偏移之外的编辑时间,以及一个UndoableEditListener,如果只有短暂的时间段,它会将新编辑连接到以前的编辑他们之间的时间(0.5秒适合我)。

这适用于一般编辑,但在进行大量替换时会导致问题。如果你有一个包含5000个“apple”实例的文档而你想用“orange”替换它,你最终会得到5000个编辑,所有编辑都存储“apple”,“orange”和一个偏移量。为了降低使用的内存量,我将此视为普通编辑的单独案例,而是存储“apple”,“orange”和5000个偏移的数组。我还没有应用这个,但我知道当多个字符串匹配搜索条件时会引起一些麻烦(例如,不区分大小写的搜索,正则表达式搜索)。

答案 3 :(得分:4)

您可以通过两种方式实现:

  • 在列表中保留编辑器状态列表和指针;撤消指针返回并恢复那里的状态,重做向前移动,做一些事情抛弃指针之外的所有东西,并将状态作为新的顶部元素插入;
  • 不要保持状态,而是保持行动,这要求每次行动都有抵消来消除行动的影响

在我的(图表)编辑器中,有四个级别的状态更改:

  • 动作片段:这些是较大动作的一部分,不能单独撤消或重做 (例如移动鼠标)
  • actions:一个或多个动作片段,形成一个可以撤消或重做的有意义的变化, 但是在磁盘上已更改的编辑文档中没有反映出来 (例如选择元素)
  • 文档更改:一个或多个更改已编辑文档的操作,因为它将保存到磁盘 (例如,更改,添加或删除元素)
  • 文档保存:文档的当前状态显式保存到磁盘 - 此时我的编辑器会丢弃撤消历史记录,因此您无法撤消保存

答案 4 :(得分:4)

哇,多么可疑 - 我在最后一小时内完全在我的WYSIWYG文本编辑器中实现了undo / redo:

基本思想是将文本编辑器的全部内容保存在数组中,或者是最后一次编辑之间的差异。

在重要位置更新此数组,即每隔几个字符(检查每个按键的内容长度,如果超过20个字符,则设置保存点)。同样在样式的变化(如果是富文本),添加图像(如果它允许这样),粘贴文本等。您还需要一个指针(只是一个int变量)来指向数组中的哪个项目是当前状态的编辑器)

使数组具有设定长度。每次添加保存点时,将其添加到数组的开头,并将所有其他数据点向下移动一个。 (一旦你有这么多的保存点,数组中的最后一项将被遗忘)

当用户按下撤销按钮时,检查编辑器的当前内容是否与最新的保存相同(如果不是,则用户自上次保存点以来进行了更改,因此保存当前编辑器的内容(因此可以重做),使编辑器等于最后一个保存点,并使指针变量= 1(数组中的第二项)。如果它们相同,则不进行任何更改从上一个保存点开始,所以你需要撤消到之前的那个点。为此,增加指针值+ 1,并使编辑器的内容=指针的值。

要重做,只需将指针值减1并加载数组的内容(确保检查是否已到达数组的末尾)。

如果用户在撤消后进行编辑,则将指向的数值单元格移动到单元格0,然后将其余部分向上移动相同的数量(一旦他们进行了不同的编辑,您就不想重做其他内容)。

另一个主要问题 - 如果文本编辑器的内容实际发生了变化,请确保只添加一个保存点(否则你会得到重复的保存点,似乎撤消对用户没有任何作用。

我无法帮助您了解java细节,但我很乐意回答您的任何其他问题,

尼科

答案 5 :(得分:3)

这是the command pattern的工作。

答案 6 :(得分:3)

以下是一个片段,展示了SWT如何支持撤消/重做操作。以实际例子为例(如果您的编辑器基于SWT,则直接使用它):

SWT Undo Redo

答案 7 :(得分:2)

阅读一本书Design Patterns: Elements of Reusable Object-Oriented Software。据我记忆,有一个很好的例子。