好吧,这是一个有点厚颜无耻的问题。我想构建一个简单的文本编辑器(使用我自己的文本模式屏幕处理)。我只想要一个可用于表示文本缓冲区的数据结构的好示例,以及一些简单的字符/文本插入/删除示例。我可以自己处理所有其余的代码(文件 i/o、控制台 i/o 等)。一个不错的简单编辑器源的链接会很棒(C 或 C++)。
11 回答
我曾经在一家主要产品是文本编辑器的公司工作。虽然我主要研究它的脚本语言,但编辑器本身的内部设计自然是讨论的主要话题。
似乎它分解成两个一般的思路。一种是您单独存储每一行,然后将它们链接到一个链表或其他您满意的整体数据结构中。优点是任何面向行的编辑操作(例如删除整行,或在文件中移动行块)都非常容易实现,因此速度极快。不利的一面是加载和保存文件需要更多的工作,因为您必须遍历整个文件并构建这些数据结构。
当时的另一种思路是在没有更改的情况下尝试将大块文本保持在一起,而不考虑换行符,仅在编辑需要时将它们分解。优点是可以很容易地将未经编辑的文件大块分解为文件。加载文件、更改一行和保存文件的简单编辑速度非常快。缺点是执行面向行或列块的操作非常耗时,因为您必须解析这些文本块并移动大量数据。
我们始终坚持以线为导向的设计,无论价值如何,我们的产品被认为是当时最快的编辑器之一。
这是 2008 年。不要编写文本编辑器;你正在重新发明火。
还在?我不确定这是否适用或您计划支持哪些平台,但Neatpad 系列教程是开始考虑编写文本编辑器的好地方。他们专注于将 Win32 作为基本平台,但学到的许多经验教训将适用于任何地方。
我最喜欢的解决方案是间隙缓冲区,因为它很容易实现并且具有良好的摊销效率。只需使用单个字符数组,并将区域指定为间隙。一旦你理解了这个概念,代码几乎就自然而然地遵循了。
您还需要一个辅助数组 [vector<int>] 来跟踪每行开头的索引 - 这样您就可以轻松地提取特定的文本行。仅当间隙移动或插入/删除换行符时才需要更新辅助数组。
这两个在线文档为文本编辑器提供了一个小而有用的“众所周知的”数据结构/技术的聚宝盆。
- 文本序列的数据结构描述并实验分析了一些数据结构,倾向于片表作为选择的数据结构。然而,Net.wisdom 似乎倾向于间隙缓冲区,因为它足以用于文本编辑,并且更易于实现/调试。
- 《文本编辑的工艺》(www.finseth.com/craft/)是较老的作品,不仅仅涉及数据结构,而且面向 Emacs 风格的编辑器;但这些概念通常很有用。
一种简单的方法是面向行的——将文件表示为 char/wchar_t 数组/向量的数组/向量,每行一个。插入和删除按您期望的方式工作,尽管行尾是一种特殊情况。
我会从那开始,并可能在其他所有工作都正常工作后,用更有效地支持长行插入/删除的东西替换行数据结构。
您几乎可以使用任何数据结构来编写文本编辑器。两百万个字符是相当厚的小说的打字价值,您可以在不到十分之一秒的时间内轻松地向上/向下移动它们(对于简单数组中的插入/删除)。不要听任何人告诉你不要建造一个,你会得到在所有小细节上都完全正确的东西。
我写了我的,在我浏览了太多网页之后,我已经习惯了向上/向下翻页,就像在滚动条拇指的上方/下方单击一样。当您在普通编辑器中键入字符时,跳回到开始滚动条导航之前,这对我来说太烦人了,所以我自己写了。
如果我要进行重写(我只是对当前版本中的每个文本缓冲区使用了 delphi ansistrings,并嵌入了换行符),我会为每个字符使用整数或 int64s,并对块开始/停止、光标位置和行进行编码高位标记,这样您在插入或删除内容时不必调整指针。
您的主要数据结构是包含文本的数据结构。您可能需要一个行数组,而不是使用长缓冲区来包含文本,因为将字符插入行中间比将字符插入大缓冲区中间更快。
您需要决定您的文本编辑器是否应该支持嵌入格式。例如,如果您需要使用字体、粗体、下划线等,那么您的数据结构将需要包含在文本中嵌入格式代码的方法。在过去 8 位字符的好日子里,我们可以使用整数的高 8 位来存储任何格式标志,使用低 8 位来存储字符本身。
实际代码将取决于您使用的语言。在 C# 或 C++ 中,您可能会为这些行使用一个字符串数组。在 C 中,您将拥有一组基于堆的字符数组。
尽可能将显示代码与文本处理代码分开。您的代码的中心将是一个紧密的循环,例如:
while (editing) {
GetCharacter();
ProcessCharacter();
UpdateDisplay();
}
更复杂的编辑器将使用单独的线程进行字符获取/处理和显示更新。
这真的取决于你的设计。几年前,我用curses写了一个小编辑器。我使用了双向链表,其中每个节点都是一个字符(相当浪费的设计......但它使格式化和屏幕刷新例程非常容易)。
我的朋友使用的其他数据结构是(这是一个家庭作业项目):1)数组的链表,每个数组代表一条线。2)一个二维链表(只是组成了那个名字)..它是一个字符的链表,但每个字符都链接到上面和下面的字符。3)链表数组
但是,我建议您通过一些简单的编辑器(例如 pico)的源代码来查看他们使用的 ds。
你检查过Scintilla的源代码吗?
查看 vim,它是开源的。在其中四处逛逛,看看它如何处理您想要的。