【问题标题】:How are text editors generally implemented?文本编辑器一般是如何实现的?
【发布时间】:2011-05-02 01:37:42
【问题描述】:

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

我只是在想,如果我假设有兴趣设计我自己的文本编辑器 GUI 控件、小部件或任何你想称呼它的东西(我不是),我会怎么做呢?

对于像我这样的新手来说,将文本编辑器的内容以字符串的形式存储的诱惑,这似乎相当昂贵(并不是我太熟悉字符串实现在一种语言/平台之间的差异和下一个;但我知道,例如,在 .NET 中,它们是不可变的,因此频繁的操作(例如您需要在文本编辑器中支持的操作)将非常浪费,非常快速地构造一个又一个的字符串实例继承)。

大概是使用了一些包含文本的可变数据结构;但是弄清楚这个结构可能是什么样子让我觉得有点挑战。随机访问会很好(无论如何,我会认为 - 毕竟,您不希望用户能够跳转到文本中的任何地方吗?),但是我想知道成本例如,导航到一个巨大的文档中间的某个地方并立即开始输入。同样,新手方法(假设您将文本存储为可调整大小的字符数组)会导致性能非常差,我在想,因为用户输入的每个字符都会有大量数据需要“转移”结束了。

因此,如果我不得不猜测,我会假设文本编辑器采用某种结构将文本分解成更小的部分(行,也许?),它们单独包含具有随机访问的字符数组,并且本身是作为离散块随机访问的。然而,即使 看起来它一定是一个相当可怕的过度简化,如果它甚至从一开始就很接近的话。

当然,我也意识到可能没有文本编辑器实现的“标准”方式;也许它从一个编辑器到另一个编辑器差异很大。但我想,既然这显然是一个已经解决了很多次的问题,那么多年来可能已经出现了一种相对常见的方法。

无论如何,我只是想知道是否有人对此主题有所了解。就像我说的,我绝对不想编写自己的文本编辑器。我只是好奇。

【问题讨论】:

  • 虽然这无疑是一项艰巨的任务,但为什么不看看 vim 的源代码呢? vim.org/sources.php
  • 我觉得你和我一样是一个 C# 人,我也对此进行了一些调查。需要注意的一点是,对于繁重的文本编辑,Winforms 自带的 TextBox 和 RichTextBox 控件不是很好。如果您深入研究它们的实现,最深的部分只是围绕一点 Windows API 本地方法的包装器。您最好使用其他一些 GUI 工具包(不确定 WPF)或自己包装本机方法。
  • 我认为 Piece Table 是一种用于文本编辑器的数据结构,但不确定:)
  • Related: stackoverflow.com/questions/1520022/… stackoverflow.com/questions/3860423/… 还有其他的,虽然这是一个很难搜索的话题,因为所有要求编辑在某个平台上以某种方式对某些内容进行 frobnicate...

标签: user-interface language-agnostic string text-editor


【解决方案1】:

一种常见的技术(尤其是在旧版编辑器中)称为拆分缓冲区。基本上,您将文本“分解”为光标之前的所有内容和光标之后的所有内容。之前的一切都在缓冲区的开头。之后的所有内容都在缓冲区的末尾。

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

Charles Crowley 写了一个更完整的discussion of the topic。您可能还想查看The Craft of Text Editing,它更深入地介绍了拆分缓冲区(和其他可能性)。

【讨论】:

  • 关于这个主题的任何问题都应该没有指向 Crowley 的书的链接。
  • 花了我一秒钟才明白你在说什么(所以在一个大块的末端之间基本上有一个相当大的间隙,当你插入文本时它会在中间,被推到一个侧或其他取决于您单击下一步的位置)。这很聪明!不过,这种设计依赖于您不会在文本中的遥远点之间来回移动(和插入),这似乎是一个非常重要的限制,不是吗?再说一次,我想如果它涵盖了更常见的用例,那绝对是一种优势。
  • another in-depth guide(“说明手册”)介绍了如何实现一个简单但真实的文本编辑器 (kilo),其中包含语法突出显示和“184 步”搜索。我还没有读完,但写得很好,包括它的 C 示例代码。它有很多关于让终端玩得更好的有用信息。
【解决方案2】:

不久前,我用 Tcl 编写了自己的文本编辑器(实际上,我从某个地方窃取了代码并将其扩展得面目全非,啊开源的奇迹啊)。

正如您所提到的,对非常非常大的字符串进行字符串操作可能会很昂贵。因此,编辑器在每个换行符处将文本拆分为更小的字符串(“\n”或“\r”或“\r\n”)。所以我剩下的就是在行级别编辑小字符串并在行之间移动时执行列表操作。

这样做的另一个优点是它是一个简单而自然的概念。我的想法已经考虑到每一行文本都是分开的,因为多年来的编程在文体或句法上都具有重要意义。

我的文本编辑器的用例作为程序员编辑器也很有帮助。例如,我实现了语法高亮,但没有实现单词/换行。所以在我的例子中,文本中的换行符和屏幕上绘制的线条之间存在 1:1 的映射。

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

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

【讨论】:

  • 不错。我会说,我遇到了许多文本文件不适合 ram 的情况。想到 Rinex 文件、csv 数据库等。经常发生的情况是,客户向我发送数据,迫使我使用晦涩难懂的编辑方法。我有兴趣了解更多关于缓解这种情况的策略。
  • @ChrisMauer 对于不适合 RAM 的内容的经典策略是在磁盘上进行编辑。对于文本文件,这通常是不可能的,但是像旧版本的 Word 和 Wordstar 这样的字处理器具有相当复杂的磁盘数据结构,可以通过就地更改标记的值来进行磁盘编辑。这要求标记在磁盘上具有固定大小(这意味着它们不能像 HTML 那样,<b> 是 3 个字节,而 <em> 是 4 个字节)
  • ...文件编辑不会导致磁盘上的实际内容被更改,只是标记表明该行或区域现已过时,请进一步查看文件以获取实际更新的内容这一行 - 有点像今天的 git 工作方式
  • 对于像 csv 这样的实际纯文本文件,这当然不是一个合适的策略。但是你可以通过创建一个代表编辑结构的临时文件来实现一种暂存盘机制——有点像穷人的数据库。实际上,现在我认为这是 SQLite 之类的完美用途 - 一个简单的无服务器磁盘数据库
【解决方案3】:

根据一次需要在编辑器中的文本量,整个缓冲区方法的一个字符串可能会很好。我认为记事本可以做到这一点——有没有注意到在大文件中插入文本的速度有多慢?

在哈希表中每行一个字符串似乎是一个很好的折衷方案。它将使导航到特定行和删除/粘贴高效而不复杂。

如果您想实现撤消功能,您将需要一种表示形式,允许您返回到以前的版本,而无需为 30 次更改存储整个文件的 30 个副本,尽管如果文件是足够小。

【讨论】:

  • 我怀疑每行字符串方法是否可行。这更像是一个渲染问题而不是数据建模问题。如果您想要更高级的功能,如文档宽度格式、字体大小等,那将非常麻烦。
  • 是的,如果有很多布局和格式要求,您会想要一个比线条更复杂的布局元素。不过,这听起来像是一个基本的纯文本编辑器。
  • @willcodejavaforfood:我自己的文本编辑器每行使用一个字符串。我所说的行是指换行符(“\n”或“\r”或“\r\n”)而不是屏幕上的物理行。这利用了几乎所有已知的人类可读文本都有频繁的换行符。这样,我只需要处理一个大数组(或者在我的例子中是一个列表)中的小字符串。
【解决方案4】:

最简单的方法是使用该语言提供的某种字符串缓冲区类。即使是一个简单的 char 对象数组也可以在紧要关头完成。

添加、替换和查找文本的速度相对较快。当然,其他操作可能更耗时,在缓冲区的开头插入一系列字符是更昂贵的操作之一。

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

如果插入和删除的成本特别高,我会很想通过创建一个内部维护缓冲区对象列表的缓冲区包装类来进行优化。任何未发生在现有缓冲区尾部的操作(除了简单替换)都会导致相关缓冲区在相关点被拆分,因此可以在其尾部修改缓冲区。但是,外部包装器将保持与简单缓冲区相同的接口,因此我不必重写例如我的搜索操作。

当然,这种简单的方法很快就会导致缓冲区非常碎片化,我会考虑制定某种规则来在适当的时候合并缓冲区,或者在以下情况下推迟拆分缓冲区。单个字符插入。也许规则是我最多只能有 2 个内部缓冲区,我会在创建新缓冲区之前合并它们 - 或者当有东西要求我立即查看整个缓冲区时。不确定。

重点是,我会从简单开始,但通过精心选择的接口访问可变缓冲区,并在分析显示我需要时使用内部实现。

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

【讨论】:

  • 是的,我同意像这样的简单方法(嗯,你在前几段中描述的)对于小文件可能是可以接受的,其优点是非常容易实现。在我看来,如果你构建了一个这样的文本编辑器,你有责任让它拒绝打开超过一定大小的文件。
猜你喜欢
  • 2017-07-21
  • 2014-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多