文本编辑器如何实现?

这个问题可能会让我听起来很无能。 那是因为我。

我只是在想,如果我假设有兴趣devise我自己的文本编辑器GUI控件,窗口小部件,或者任何你想要调用它的东西(我不知道),我怎么会这么做呢?

对像我这样的新手的诱惑将是以string的forms存储文本编辑器的内容,这似乎相当昂贵(不是我太熟悉string实现在一种语言/平台和下一个之间是如何不同的;但是我知道,例如在.NET中,它们是不可变的,如此频繁的操作(例如在文本编辑器中需要支持的东西)将会非常浪费,构build一个又一个string实例将会非常迅速。

据推测,一些含有文本的可变数据结构被用来代替; 但搞清楚这个结构可能会是什么样子,这是一个挑战。 随机访问会很好(毕竟,我想是不是要让用户能够跳到文本中的任何地方?),但是我想知道导航到某个地方的成本在一个巨大的文件的中间,并立即开始打字。 再次,新手的方法(比如将文本存储为可resize的字符数组)会导致非常糟糕的性能,我想,与用户键入的每个字符一样,会有大量的数据“转移”过度。

所以,如果我不得不猜测,我假设文本编辑器采用某种结构将文本分解成更小的片断(可能是?),它们分别包含随机访问的字符数组,它们本身是随机的作为离散块访问。 即使如此,它似乎也是一个相当可怕的过度简单化,但如果它甚至远远接近开始。

当然我也意识到,可能没有文字编辑实行的“标准”方式, 从一个编辑者到另一个编辑者,可能会有很大的变化 但我想,因为这显然是一个多次被解决的问题,多年来可能是一种比较常见的方法。

无论如何,我只是有兴趣知道有没有人有这方面的知识。 就像我说的,我绝对不是在写我自己的文本编辑器, 我只是好奇。

一种常见的技术(特别是在较老的编辑器中)被称为拆分缓冲器。 基本上,你可以将文本“分解”为光标之前的所有内容以及光标之后的所有内容。 之前的一切都在缓冲区的开始处。 之后的一切都在缓冲区的末尾。

当用户键入文本时,它将进入中间的空白区域而不移动任何数据。 当用户移动光标时,将适当数量的文本从“中断”的一侧移到另一侧。 通常情况下,在单个区域内移动的数量很大,因此您通常一次只能移动less量文本。 最大的例外是如果你有一个“去xxx行”的能力。

查尔斯·克劳利(Charles Crowley)就这个话题写了一个更完整的讨论 。 您可能还想看看“文本编辑工艺” ( The Craft of Text Editing) ,它涵盖了更深层次的拆分缓冲区(以及其他可能性)。

前一段时间,我在Tcl中编写了我自己的文本编辑器(实际上,我从某个地方偷了代码,并将其扩展到无法识别的地方,以及开源的奇迹)。

正如你所提到的,对非常非常大的string进行string操作可能会很昂贵。 所以编辑器会在每一个换行符(“\ n”或“\ r”或“\ r \ n”)中将文本分割成更小的string。 所以我只剩下在行级编辑小string,在行间移动时做列表操作。

另一个好处是,这是一个简单自然的概念。 我的思想已经认为,每一行文字都要经过多年的编程,而这些编程在文体上或语法上都很重要。

这也有助于我的文本编辑器的用例是作为程序员编辑。 例如,我实现了语法高亮而不是字/线换行。 所以在我的情况下,文本中的换行符和屏幕上画的线之间有1:1的映射。

如果你想看看,这里是我的编辑器的源代码: http : //wiki.tcl.tk/16056

这不是玩具BTW。 我每天都使用它作为我的标准控制台文本编辑器,除非文件太大而无法放入RAM(严重的是,什么文本文件是什么?甚至小说,通常是4到5 MB,适合RAM,我只看到日志文件增长到数百MB)。

根据编辑器中一次需要的文本数量,整个缓冲区方法的一个string可能会很好。 我认为记事本这样做 – 永远注意到在一个大文件中插入文本要慢多less?

在一个哈希表中每行有一个string似乎是一个很好的折中。 这将使导航到一个特定的行,删除/粘贴高效,没有太多的复杂性。

如果你想实现一个撤消function,你需要一个表示,允许你回到以前的版本,而不用为30次修改存储整个文件的30个拷贝,尽pipe如果文件足够小,这也许可以。

最简单的方法是使用某种由该语言提供的string缓冲类。 即使是一个简单的char对象数组也可以做到。

追加,replace和寻找文本相对较快。 其他操作可能更耗时,当然,在缓冲区开始插入一系列字符是更昂贵的操作之一。

但是,对于一个简单的用例来说,这可能是完全可以接受的性能。

如果插入和删除的成本特别重要,我会试图通过创build一个内部维护一个缓冲区对象列表的缓冲包装类来进行优化。 任何未在现有缓冲区尾部出现的动作(除了简单replace)都会导致有关缓冲区在相关点处被分割,所以缓冲区可以在其尾部进行修改。 但是,外层包装将保持与简单缓冲区相同的接口,所以我不必重写例如我的search操作。

当然,这种简单的方法很快就会导致一个非常分散的缓冲区,我会考虑采取某种规则来在合适的时候合并缓冲区,或者在插入单个字符的情况下推迟分割缓冲区。 也许规则是我至多只能拥有2个内部缓冲区,而且在创build一个新的缓冲区之前我会合并它们,或者当有事情要求我一次查看整个缓冲区时。 不确定。

关键是,我会开始简单的,但通过一个精心select的接口访问可变缓冲区,如果分析显示我需要玩内部实现。

不过,我绝对不会从不可变的String对象开始!