如何设计撤消和撤消在文本编辑器中重做?
我的项目的一部分是编写一个文本编辑器,用于输入一些规则、编译我的应用程序并运行它。编写编译器已结束并发布测试版。在最终版本中,我们必须向文本编辑器添加撤消和重做。我使用一个文件并定期保存它以供文本编辑器使用。如何为我的文本编辑器设计撤消和重做?文件的持久结构发生了什么变化?
Part of my project is to write a text editor that is used for typing some rules, compiling my application and running it. Writing compiler was end and release beta version. In the final version we must add undo and redo to the text editor. I use a file and save it periodically for the text editor. How to design undo and redo to my text editor? What is changed in the structure of persistent of file?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
您可以将您的操作建模为 命令,并将其保存在两个堆栈中。一个用于撤消,另一个用于重做。您可以组合您的命令来创建更多高级命令,例如当您想要撤消例如,宏的操作;或者如果您想在一个操作中对单个单词或短语的各个击键进行分组。
编辑器中的每个操作(或重做操作)都会生成一个新的撤消命令,该命令进入撤消堆栈(并且还会清除重做堆栈)。每个撤消操作都会生成相应的重做命令,该命令会进入重做堆栈。
您还可以,正如 derekerdmann 的评论中提到的,将撤消和重做命令组合成一种类型的命令,该命令知道如何撤消和重做其操作。
You can model your actions as commands, that you keep in two stacks. One for undo, another for redo. You can compose your commands to create more high-level commands, like when you want to undo the actions of a macro, for example; or if you want to group individual keystrokes of a single word, or phrase, in one action.
Each action in your editor (or a redo action) generates a new undo command that goes into the undo stack (and also clears the redo stack). Each undo action generates the corresponding redo command that goes into the redo stack.
You can also, as mentioned in the comments by derekerdmann, combine both undo and redo commands into one type of command, that knows how to undo and redo its action.
基本上有两种好方法可以实现:
“命令”设计模式
仅在不可变对象上使用面向对象,其中一切都是只是由不可变对象组成的不可变对象本身也是由不可变对象组成的(这种情况不太常见,但如果正确完成,会非常优雅)
与不可变对象相比,使用 OO 相对于简单命令或简单撤消/重做的优点是,您不需要考虑太多:无需“撤消”操作的效果,也无需“重播”所有命令。您所需要的只是一个指向大量不可变对象的指针。
因为对象是不可变的,所以所有“状态”都可以非常轻量,因为您可以缓存/重用任何状态下的大多数对象。
“面向不可变对象的面向对象”是一颗纯粹的宝石。再过 10 年,可能不会成为主流; )
PS:对不可变对象进行面向对象也惊人地简化了并发编程。
There are basically two good ways to go about it:
the "Command" design pattern
using only OO over immutable objects, where everything is just immutable objects made of immutable objects made themselves of immutable objects (this is less common but wonderfully elegant when done correctly)
The advantage of using OO over immutable objects over the naive command or the naive undo/redo is that you don't need to think much about it: no need to "undo" the effect of an action and no need to "replay" all the commands. All you need is a pointer to a huge list of immutable objects.
Because objects are immutable all the "states" can be incredibly lightweight because you can cache/reuse most objects in any state.
"OO over immutable objects" is a pure jewel. Probably not gonna become mainstream before another 10 years that said ; )
P.S: doing OO over immutable objects also amazingly simplifies concurrent programming.
如果你不想要任何花哨的东西,你可以添加一个 撤消管理器。每次添加或删除文本时,您的
Document
都会触发UndoableEdit
。要撤消和重做每个更改,只需调用 UndoManager 中的这些方法即可。这样做的缺点是,每次用户输入内容时,UndoManager 都会添加一个新的编辑,因此输入“apple”会给您留下 5 个编辑,一次不可撤消一个。对于我的文本编辑器,我编写了一个用于编辑的包装器,除了文本更改和偏移量之外,还存储编辑的时间,以及一个
UndoableEditListener
,如果只有一个编辑,则将新的编辑连接到以前的编辑。它们之间的时间很短(0.5 秒对我来说比较合适)。这对于一般编辑来说效果很好,但在进行大规模替换时会出现问题。如果您的文档包含 5000 个“apple”实例,并且您想将其替换为“orange”,则最终会进行 5000 次编辑,所有编辑都存储“apple”、“orange”和偏移量。为了减少内存使用量,我将其视为普通编辑的单独情况,而是存储“apple”、“orange”和 5000 个偏移量的数组。我还没有开始应用这个,但我知道当多个字符串匹配搜索条件时(例如,不区分大小写的搜索、正则表达式搜索),它会引起一些头痛。
If you don't want anything fancy, you can just add an UndoManager. Your
Document
will fire anUndoableEdit
every time you add or remove text. To undo and redo each change, simply call those methods in UndoManager.The downside of this is UndoManager adds a new edit each time the user types something in, so typing "apple" will leave you with 5 edits, undoable one at a time. For my text editor, I wrote a wrapper for edits that stores the time it was made in addition to text change and offset, as well as an
UndoableEditListener
that concatenates new edits to previous ones if there is only a short period of time between them (0.5 seconds works well for me).This works well for general editting, but causes problems when a massive replace is done. If you had a document with 5000 instances of "apple" and you wanted to replace this with "orange", you'd end up with 5000 edits all storing "apple", "orange" and an offset. To lower the amount of memory used, I've treated this as a separate case to ordinary edits and am instead storing "apple", "orange" and an array of 5000 offsets. I haven't gotten around to applying this yet, but I know that it'll cause some headaches when multiple strings match the search condition (eg. case insensitive search, regex search).
哇,真是巧合 - 我在最后一个小时内确实在我的所见即所得文本编辑器中实现了撤消/重做:
基本思想是将文本编辑器的全部内容保存在数组中,或者保存上次编辑之间的差异。
在重要点更新此数组,即每隔几个字符(检查每次按键的内容长度,如果超过 20 个字符不同,则创建一个保存点)。此外,还可以更改样式(如果是富文本)、添加图像(如果允许)、粘贴文本等。您还需要一个指针(只是一个 int 变量)来指向数组中的哪个项目是当前状态编辑器)
使数组具有设定的长度。每次添加保存点时,将其添加到数组的开头,并将所有其他数据点向下移动 1。 (一旦保存点太多,数组中的最后一项就会被忘记)
当用户按下撤消按钮时,检查编辑器当前内容是否与最新保存的内容相同(如果不同,则用户自上次保存点以来已进行更改,因此保存编辑器的当前内容(以便可以重做),使编辑器等于上次保存点,并使指针变量= 1(中的第二项)如果它们相同,则自上次保存点以来没有进行任何更改,因此您需要撤消到之前的点,将指针值增加 + 1,并制作编辑器的内容。 = 指针的值。
要重做,只需将指针值减 1 并加载数组的内容(确保检查是否已到达数组的末尾),
如果用户在撤消后进行了编辑,则移动指针。将值数组单元格向上移动到单元格 0,并将其余单元格向上移动相同的量(一旦其他内容进行了不同的编辑,您就不想重做它们)。
另一个主要要点 - 确保仅在文本编辑器的内容实际发生更改时添加保存点(否则您会得到重复的保存点,并且看起来撤消不会对用户执行任何操作。
我无能为力你有java细节,但我很乐意回答你的任何其他问题,
Nico
Wow, what a conicidence - I have literally in the last hour implemented undo/redo in my WYSIWYG text editor:
The basic idea is to either save the entire contents of the text editor in an array, or the difference between the last edit.
Update this array at significant points, i.e. every few character (check the length of the content each keypress, if its more than say 20 characters different then make a save point). Also at changes in styling (if rich text), adding images (if it allows this), pasting text, etc. You also need a pointer(just an int variable) to point at which item in the array is the current state of the editor)
Make the array have a set length. Each time you add a save point, add it to the start of the array, and move all of the other data points down by one. (the last item in the array will be forgotten once you have so many save points)
When the user presses the undo button, check to see if the current contents of the editor are the same as the latest save (if they are not, then the user has made changes since the last save point, so save the current contents of the editor (so it can be redo-ed), make the editor equal to the last save point, and make the pointer variable = 1 (2nd item in array ). If they are they same, then no changes have been made since the last save point, so you need to undo to the point before that. To do this, increment the pointer value + 1, and make the contents of the editor = the value of pointer.
To redo simply decrease the pointer value by 1 and load the contents of the array (make sure to check if you have reached the end of the array).
If the user makes edits after undoing, then move the pointed value array cell up to cell 0, and move the rest up by the same amount (you dont want to redo to other stuff once they've made different edits).
One other major catch point - make sure you only add a save point if the contents of the text editor have actually changed (otherwise you get duplicate save points and it will seem like undo is not doing anything to the user.
I can't help you with java specifics, but I'm happy to answer any other questions you have,
Nico
您可以通过两种方式完成此操作:
在我的(图)编辑器中,有四个级别的状态更改:
(例如移动鼠标)
但这些内容并未反映在磁盘上更改的已编辑文档中
(例如选择元素)
(例如更改、添加或删除元素)
You can do it in two ways:
In my (diagram) editor, there are four levels of state changes:
(e.g. moving the mouse)
but which are not reflected in the edited document as changed on disk
(e.g. selecting elements)
(e.g. changing, adding or deleting elements)
这是命令模式的工作。
This is a job for the command pattern.
下面的代码片段显示了 SWT 如何支持撤消/重做操作。将其作为实际示例(或者直接使用它,如果您的编辑器基于 SWT):
SWT 撤消重做
Here is a snippet that shows how SWT supports Undo/Redo operations. Take it as practical example (or use it directly, if your editor is based on SWT):
SWT Undo Redo
阅读一本书设计模式:可重用面向对象软件的元素。据我记得,有一个很好的例子。
Read a book Design Patterns: Elements of Reusable Object-Oriented Software. As far as I remember, there is a pretty good example.