Fleet | "Backstage Quest" Issue 2: Editor Details

In our previous article on "Fleet Behind the Scenes" , we gave an overview of the Fleet architecture. ( click here to review )


In this second part, we'll cover the algorithms and data structures that the editor uses behind the scenes .


Aggregation of data structures


Check out the screenshot below showing the editor window in Fleet


The graph contains a line of text with syntax highlighting, and a widget that provides information about the usage of a specific variable. Now, people can display this information in a number of ways, but the problem with editors is that they are not read-only. In addition to data visualization, data can also be updated. Something as simple as changing a function name can have many effects, such as affecting syntax highlighting, usage, and of course any other functionality provided, such as static analysis or just-in-time compilation.


To provide a good experience, we need to make sure that editing text and the visualizations that come with it are as seamless as possible. To achieve this, we must store and process data in an efficient manner. However, there is not only one way to store data. In fact, the diagram above stores data in multiple ways, each using different data structures that together make up what we call an editor. In other words, think of the editor as an aggregator of data structures!


Let's go into details!


The rope runs through the whole line

For those familiar with working with large amounts of text, you probably already know that using strings (i.e. arrays of characters) for storage is not ideal. In general, performing any operation on an array means having to create a new array, larger or smaller, and copy the contents of the old array to the new array. It is difficult to guarantee efficiency in this way.


A better and more standardized way is to use rope structures (rope structures 1 ). The idea behind this abstract data type is to store strings in leaf nodes on a tree.


Each leaf node contains a string (see note below) and its length (called a weight). Each intermediate node also contains a weight, which is the sum of the weights of all leaf nodes in its left subtree.

Note: The text used on the leaf nodes is just an example and does not represent how the actual text is broken down in Fleet.


In the above example, if we take   the node that holds the character fun , the count of that node is 3 because the string length is 3. Moving up to the parent node, the count is also 3, because the sum of the weights of all nodes to its left is 3. Continuing up, the node's parent has a count of 19, which is the sum of its left leaf nodes 3 and 16.


Common operations such as searching, appending, removing, splitting strings have a time complexity of O(log N), where N is the length of the string. The operation starts by traversing the tree, which can speed up the operation given the node information. For example, if we want to find the character at position  i = 30, starting at the node, we subtract (see note below) the weight value from i, and if 30 is less than the node's weight (number of characters), we look left. On the other hand, if  i  is greater than the weight, we will look to the right. As we move down and  the value of i  continues to decrease, when we find the position where the string contained by the leaf node is  i  , the character at that position is the character we are looking for.

注:根据使用的指标,可能并不需要减法运算。重要的是,当我们沿着树向下查找时,我们会累积到该点的指标,并将其与我们正在搜寻的键进行比较。


在 Fleet 的绳索结构中插入或删除节点时,我们使用自平衡 B-Tree2我们首先读取 64 个字符的块,一旦达到 32 个块,我们就创建一个节点并开始为第二个节点收集块。每个节点包含两个数字 – 除了权重,我们还会存储行数(两者的组合就是我们所说的指标)。


通过存储行数,我们可以更快地导航到特定的偏移量。在 Fleet 中,树的另一个特征是我们希望使之更宽,而非更深。


将区间树用于微件等


正如我们之前所看到的,一段代码可能不仅包含实际文本,还包含诸如用法等其他元素。


我们将这些元素称为微件,它们可以是行间微件(例如 Find Usages(查找用法)或 Run(运行)微件)、行后微件(例如出现在代码行之后的调试信息)或嵌入微件(例如变量和 lambda 的类型提示)。


微件本身只是一个标记元素,容纳微件的数据结构为区间树(Interval Trees3)的变体,在某种程度上也是一种绳索结构。在区间树中,节点容纳一个区间,权重对应于子树中区间的最大值。


在 Fleet 中,每个节点都包含子节点的相对起点和终点。叶节点又包含一个实际微件。运行查询以查看是否需要根据某个特定坐标显示特定微件时,我们会遍历树,直至找到与我们所查询的区间存在交集的区间为止。


一个重要的方面是叶节点还包含微件 ID。这意味着除了查询与特定区间的交集,对于任何微件,我们还可以查询以确定其实际位置。


Fleet 中采用了一种标准区间树的变体,我们允许节点重叠。这有可能会导致搜索效率降低,但允许节点重叠可以创建平衡的树,并使树可以在我们输入时更新。


除了微件以外,Fleet 中的区间树还被用于跟踪文本光标、高亮显示文本以及文本中被我们称为定位标记的粘性位置。


将绳索用于词例和抽象语法树


处理源代码时,无论是编译器还是编辑器,您通常都会使用抽象语法树 (Abstract Syntax Tree, AST)。工作方式为解析器分析源代码并创建一系列词例。随后,这些词例将用于构建 AST。

以如下代码为例

fun compileBundles(ship: JpsModule, model: JpsModel, src: SrcBundles): DstBundles

它将被分解为以下词例

[fun][ ][compileBundles][(][ship][:][ ][JpsModule][,][ ][model][:][ ][JpsModel][,][ ][src][:][ ][SrcBundles][)][:][ ][DstBundles]

其中,每组方括号都表示一个词例(请注意,空格也是词例)。随后,这些词例将用于构建相应的 AST


随后,AST 将被用于各种运算,例如语法高亮显示、静态分析等。它是任何 IDE 的重要组成部分。


顺带一提,如果您有兴趣了解如何将某些代码转译成 AST,请了解一下这款超酷的在线 AST 浏览器4(它提供了对多种语言的支持)


我们在编辑器中输入时,文本会发生变化,这意味着词例会发生变化,这转而需要构建新的 AST 以便提供上述功能。


在 Fleet 中,为了避免直接更新 AST,我们使用绳索结构将词例存储在叶节点中(实际上仅存储长度)。举例来说,上文中的词例列表可以表示为以下树


当用户输入空格字符等内容时,树会更新(最左侧的叶节点的长度将增加 1,进而导致沿该路径的计数增加)


特定叶节点使新词例长度增大,转而会导致树的某些节点发生更新以调整权重。随后,解析器会收到通知,强制它更新和重新解析 AST。因此,AST 可能会有片刻不完全正确的情况,但在几乎不需要更新的编辑方面,用户体验要好得多。


将绳索用于渲染


下图是编辑器的另一个示例,但此次添加了一些附加元素,也就是将实际用法微件扩展为显示用法、自动换行以及诸如在滚动条中加入彩色竖线等其他元素。


要渲染上述内容,对于特定的 Y 坐标,我们不仅需要清楚显示哪行,还需要考虑到所有微件和自动换行的行。

有趣的事实:用法微件中渲染的编辑器使用与我们在本文中所探索的相同底层数据结构。对于同一文件中的用法,构建和渲染此叠加编辑器使用了相同的绳索。


微件和自动换行信息也会存储为绳索结构。虽然之前树的叶节点会容纳字符串及其长度,但在此例中,我们将使用叶节点来保存我们所谓的 SoftLine 对象.这些是带有高度的文本块,被视为视觉线。此例中节点的权重(我们称为指标)为 SoftLine 的高度和长度。存储高度是为了能够支持视口查询。此高度受位于它内部的中间行影响。此外,当启用自动换行时,SoftLine 不会与实际行一一对应,而是可以跨越多行。


关于不变性的说明

值得一提的是,我们在 Fleet 中拥护不变性。使用纯函数和不可变对象有很多好处。纯函数不仅使我们能够更好地思考代码,而且还可确保调用函数不会在我们不知情的情况下导致系统的其他部分发生变化(即出现副作用)。在数据方面,知道对象为不可变对象意味着它具备线程安全性,因此在尝试任何更新时不会出现竞争条件。对于多线程环境,这提供了巨大的好处。


这种不变性理念也是使用绳索结构的运算的核心。之前我们谈到了如何更新节点和树的叶节点。这些都是以不可变的方式完成的 – 树上的任何运算都会产生与旧树共享结构的树的新副本,差别仅限从根到需要更改的单个节点。鉴于树通常宽度较大而深度较小这一事实,这一路径将很短。如果运算导致出现任何未引用的节点,将对这些节点进行垃圾回收。


这与我们在 IntelliJ 平台上使用读写锁定机制执行变更的方法截然不同。



总结


正如我们在这个关于如何构建 Fleet 的第二部分中所见,可供输入和浏览代码的编辑器看似简单,实则为十分复杂的多种不同数据结构的底层聚合,其中多为绳索结构。如果您有兴趣详细了解绳索,请务必参阅绳索科学(Rope Science5)系列内容,它对我们在 Fleet 上所做的工作产生了重大影响。


参考链接

1. Rope Structures: 

    https://en.wikipedia.org/wiki/Rope_(data_structure)

2. B-Tree:

    https://en.wikipedia.org/wiki/B-tree

3. Interval Trees:

    https://www.geeksforgeeks.org/interval-tree/

4. 在线 AST 浏览器示例:

    https://astexplorer.net/

5. Rope Science:

   https://xi-editor.io/docs/rope_science_00.html


关于 Fleet 的最新进展

Fleet 正处于封闭预览阶段,目前试用申请通道已关闭,请关注 JetBrains 中国官方社交媒体第一时间获取最新进展。

  • 如果您没有注册封闭式预览,可以扫码订阅Fleet新闻,以确保在公开预览推出时收到通知»»»

  • 如果您提交了封闭式预览的请求,可能会收到邀请,具体时间取决于我们处理反馈的速度。


本文英文原作者:Hadi Hariri


本文分享自微信公众号 - JetBrains(JetBrainsChina)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/5494143/blog/5497081