Skip to content

Commit

Permalink
new zed-rope
Browse files Browse the repository at this point in the history
  • Loading branch information
greathongtu committed Sep 6, 2024
1 parent 5c2da6a commit b65e922
Show file tree
Hide file tree
Showing 59 changed files with 1,416 additions and 1,528 deletions.
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "themes/ananke"]
path = themes/ananke
url = https://github.com/theNewDynamic/gohugo-theme-ananke.git
[submodule "themes/paper"]
path = themes/paper
url = https://github.com/nanxiaobei/hugo-paper
[submodule "themes/hugo-paper"]
path = themes/hugo-paper
url = https://github.com/nanxiaobei/hugo-paper.git
136 changes: 128 additions & 8 deletions content/posts/zed-rope.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ draft = true

# Zed 编辑器中的 SumTree 和 Rope 数据结构

代码编辑器最重要的东西就是如何表示文本了。朴素的想法是直接使用 string 数据结构。但作为使用连续内存的数据结构在面对中间位置的数据插入删除时代价过大。并且更复杂的跳转某行等功能也很难实现
代码编辑器最重要的组成部分之一就是如何高效地表示和操作文本。最朴素的想法是直接使用 string 数据结构。但作为使用连续内存的数据结构,string 在面对中间位置的数据插入删除时代价过大。并且更复杂的功能,如跳转到某行,也很难高效实现

合理的实现应该能将文本分成很多段,且段与段之间互不影响,这样对其中某些部分进行插入、修改、删除也不会有很大的影响。Zed 选择的是 Rope 这种数据结构。
一个合理的实现应该能将文本分成很多段,且段与段之间互不影响,这样对其中某些部分进行修改也不会有很大的影响。Zed 编辑器选择的是 Rope 这种数据结构。

具体实现里,Rope 是对底层的 SumTree 的一层包装。而 SumTree 可以理解为一种特殊的 B+ 树,显然数据都存在叶子节点,对某些叶子节点的修改并不会影响其他大多数的节点。
通过使用 SumTree,也可以很容易的实现并发访问等功能。
## Rope 和 SumTree 概述

在 Zed 的具体实现中,Rope 是对底层 SumTree 的一层包装。而 SumTree 可以理解为一种特殊的 B+ 树,其中数据都存储在叶子节点。这种结构使得对某些叶子节点的修改并不会影响其他大多数的节点。通过使用 SumTree,也可以很容易地实现并发访问等功能。

```rust
struct Rope {
Expand All @@ -22,6 +23,8 @@ struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
```

这里 SumTree 的范型参数为 Chunk,也就是叶子节点存放的数据是 Chunk 类型 (ArrayString 来自 arrayvec 库,是存放在栈里的固定大小的 string)。这里我们首先来看 SumTree 的结构。

## SumTree 的结构
```rust
struct SumTree<T: Item>(pub Arc<Node<T>>);

Expand All @@ -38,30 +41,45 @@ enum Node<T: Item> {
item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
},
}

```

SumTree<T> 是一棵 B+ 树,其中:
- 每个叶节点包含多个 Item 类型的数据和对应每个 Item 的 Summary
- Leaf 的 summary 字段存放着所有 Item 的总 Summary
- Internal 节点包含多个子树和对应每个子树的 Summary
- Internal 节点的 summary 字段存放着所有子树的总 Summary
- height 表示该 Internal 节点的高度(叶子节点高度为0)

## Item 和 Summary
Item 是什么呢?Item 是存放在叶子节点的数据的真正类型(比如整个文本的一小段子串 &str)。它需要实现 summary 方法返回一个 Summary 类型。

```rust
trait Item: Clone {
type Summary: Summary;

fn summary(&self) -> Self::Summary;
}

trait Summary: Default + Clone + fmt::Debug {
type Context;

fn add_summary(&mut self, summary: &Self, cx: &Self::Context);
}
```

SumTree<T> 是一棵 B+ 树,其中每个叶节点都包含多个 Item 类型的数据和对应每个 Item 的 Summary, Leaf 的 summary 字段存放着所有 Item 的总 Summary。
Internal 节点包含多个子树和对应每个子树的 Summary, summary 字段存放着所有子树的总 Summary。height 则是该 Internal 节点的高度(叶子节点显然高度为0)。
Summary 类型可以是一切可累加的(associative)东西。听起来很抽象,但只要知道 Summary 是用来统计整个文本或其中部分文本的一些统计信息的。

Item 是什么呢?Item 是存放在叶子节点的数据的真正类型(比如整个文本的一小段子串 &str)。它需要实现 summary 方法返回一个 Summary 类型。
这个 Summary 类型可以是一切能累加的东西。听起来很抽象,但只要知道 Summary 是用来统计整个文本或其中部分文本的一些统计信息的。
比如:如果我想知道整个文本的长度是多少,那我需要知道每个叶子节点的数据长度是多少,最后累加得到最终的结果。
我们可以将这个长度信息通过 Summary 一层一层地传上去不断累加,最终在根节点的 summary 字段即是我们需要的结果。
具体实现上,我们可以给 usize 实现 Summary trait,具体 add_summary 的方法便是直接对两个 usize 相加。

再比如我想知道一个 string 中最大的 char 是哪个?把 add_summary 的方法实现为返回更大的那个 char 即可。

实际中我们会有很多维度的信息需要统计,这时我们可以把这些维度放到一个 struct 中,在 add_summary 方法里对该 struct 的各个字段进行相应的修改即可。

## TextSummary 示例

Zed 的 Rope 中的 Summary 是这样的:
```rust
struct TextSummary {
Expand Down Expand Up @@ -133,3 +151,105 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
}
```
其中之所以需要 first_line_chars 和 last_line_chars 这两个字段,是为了统计 longest_row_chars,而同一行的内容有可能被存放在前后两个不同的节点中。


这里举例说明 SumTree 的结构,以下是一段文本:
```text
Hello World!
This is
your captain speaking.
Are you
ready for take-off?
```

根据刚才的文本构建的 SumTree 如下:
![SumTree](/images/sumtree_diagram.png)

## Dimension 和 SeekTarget
有了对 SumTree 大概的理解,我们就可以深入代码研究了。在代码中我们可以发现另两个关键的 trait:Demension 和 SeekTarget。
```rust
/// Each [`Summary`] type can have more than one [`Dimension`] type that it measures.
/// You can use dimensions to seek to a specific location in the [`SumTree`]
/// # Example:
/// Zed's rope has a `TextSummary` type that summarizes lines, characters, and bytes.
/// Each of these are different dimensions we may want to seek to
pub trait Dimension<'a, S: Summary>: Clone + fmt::Debug + Default {
fn add_summary(&mut self, _summary: &'a S, _: &S::Context);

fn from_summary(summary: &'a S, cx: &S::Context) -> Self {
let mut dimension = Self::default();
dimension.add_summary(summary, cx);
dimension
}
}

pub trait SeekTarget<'a, S: Summary, D: Dimension<'a, S>>: fmt::Debug {
fn cmp(&self, cursor_location: &D, cx: &S::Context) -> Ordering;
}
```

Dimension 是 Summary 的一个维度, SeekTarget 定义了 SumTree 的查找目标。
你可能会问为什么有了 Summary 还需要 Dimension, 这是因为 Summary 存放的是全量的统计信息,而有时候我们只想关注其中的某个维度。
比如在 TextSummary 中,我们可能只关心最长行的字符数而不关心总长度;或者相反。在这种情况下如果我们直接使用 Summary 的 Cursor 遍历会导致性能浪费,
因为在遍历的过程中做了多余的计算,并且过程中累加的 Summary(或者某 Dimension)会被存在 Cursor 的 stack 字段(记录了从根节点到当前叶节点的路径)中,而我们只需要关心其中的一部分信息。


我们可以使用针对某个 Dimension 的 Cursor 来遍历 SumTree,这样就可以得到一个 Dimension 的统计信息而不会有多余的计算。
那么如果我想通过一个 Dimension 的遍历找到某个位置,同时又想得到另一个 Dimension 的信息怎么办呢?
```rust
// Summary is a Dimension
impl<'a, T: Summary> Dimension<'a, T> for T {
fn add_summary(&mut self, summary: &'a T, cx: &T::Context) {
Summary::add_summary(self, summary, cx);
}
}
// Demension is a Target
impl<'a, S: Summary, D: Dimension<'a, S> + Ord> SeekTarget<'a, S, D> for D {
fn cmp(&self, cursor_location: &Self, _: &S::Context) -> Ordering {
Ord::cmp(self, cursor_location)
}
}

impl<'a, T: Summary, D1: Dimension<'a, T>, D2: Dimension<'a, T>> Dimension<'a, T> for (D1, D2) {
fn add_summary(&mut self, summary: &'a T, cx: &T::Context) {
self.0.add_summary(summary, cx);
self.1.add_summary(summary, cx);
}
}
```

可以看到我们为 (D1, D2) 的 tuple 实现了 Dimension trait,在这个泛型参数为 (D1, D2) 的 Cursor 的遍历过程中我们同时统计了 D1 和 D2 的信息。


简单总结一下,对于一颗 SumTree,他有一种 Item 类型,这个 Item 类型对应一个 Summary 类型。
在使用 Cursor 遍历 SumTree 时,可以通过指定一个 Summary 的子集类型来遍历去聚集,这个子集就是 Dimension。
Cursor 可以通过 SeekTarget 来定位到某个位置,SeekTarget 也可以是一个 Dimension(至少是可以和 Dimension 进行比较)。


## Cursor: SumTree 的遍历器
上面我们提到了 Cursor,这里我们深入研究以下 Cursor 的结构。Cursor 用于遍历 SumTree,它的结构如下:
```rust
#[derive(Clone)]
pub struct Cursor<'a, T: Item, D> {
// root of SumTree
tree: &'a SumTree<T>,
// stack of nodes from root to current node
stack: ArrayVec<StackEntry<'a, T, D>, 16>,
// D is what dimension this Cursor is currently seeking
// position stores the total value of D from the beginning
position: D,
did_seek: bool,
at_end: bool,
}
```
Cursor 允许我们高效地在 SumTree 中导航和查找,同时只计算我们关心的维度的统计信息。

## TODO


## 总结

Zed 编辑器通过使用 Rope 和底层的 SumTree 数据结构,实现了高效的文本表示和操作。
这种设计不仅允许快速的插入、删除和修改操作,还支持复杂的统计和查找功能。
通过 Summary、Dimension 和 SeekTarget 等概念的巧妙运用,Zed 能够灵活地处理各种文本操作需求,
同时保持良好的性能。
2 changes: 1 addition & 1 deletion hugo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
baseURL = 'https://greathongtu.github.io/'
languageCode = 'en-us'
title = 'greathongtu 的 Blog'
theme = 'paper'
theme = 'hugo-paper'

[taxonomies]
tag = "tags"
Expand Down
41 changes: 25 additions & 16 deletions public/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@








<html
class="not-ready lg:text-base"
style="--bg: #faf8f1"
lang="en-us"
dir="ltr"
>
<head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;path=livereload" data-no-instant defer></script>
<meta charset="utf-8" />
Expand Down Expand Up @@ -96,34 +99,38 @@



<link rel="icon" href="http://localhost:1313/favicon.ico" />
<link rel="apple-touch-icon" href="http://localhost:1313/apple-touch-icon.png" />
<link
rel="icon"
href="http://localhost:1313/favicon.ico"
/>
<link
rel="apple-touch-icon"
href="http://localhost:1313/apple-touch-icon.png"
/>


<meta name="generator" content="Hugo 0.134.1">
<meta name="generator" content="Hugo 0.133.1">




</head>

<body class="text-black duration-200 ease-out dark:text-white">
<header class="mx-auto flex h-[4.5rem] max-w-3xl px-8 lg:justify-center">
<div class="relative z-50 mr-auto flex items-center">
<a
class="-translate-x-[1px] -translate-y-[1px] text-2xl font-semibold"
href="http://localhost:1313/"
<header class="mx-auto flex h-[4.5rem] max-w-[--w] px-8 lg:justify-center">
<div class="relative z-50 ltr:mr-auto rtl:ml-auto flex items-center">
<a class="-translate-y-[1px] text-2xl font-medium" href="http://localhost:1313/"
>greathongtu 的 Blog</a
>
<div
class="btn-dark text-[0] ml-4 h-6 w-6 shrink-0 cursor-pointer [background:url(./theme.png)_left_center/_auto_theme('spacing.6')_no-repeat] [transition:_background-position_0.4s_steps(5)] dark:[background-position:right]"
class="btn-dark text-[0] ltr:ml-4 rtl:mr-4 h-6 w-6 shrink-0 cursor-pointer [background:url(./theme.png)_left_center/_auto_theme('spacing.6')_no-repeat] [transition:_background-position_0.4s_steps(5)] dark:[background-position:right]"
role="button"
aria-label="Dark"
></div>
</div>

<div
class="btn-menu relative z-50 -mr-8 flex h-[4.5rem] w-[5rem] shrink-0 cursor-pointer flex-col items-center justify-center gap-2.5 lg:hidden"
class="btn-menu relative z-50 ltr:-mr-8 rtl:-ml-8 flex h-[4.5rem] w-[5rem] shrink-0 cursor-pointer flex-col items-center justify-center gap-2.5 lg:hidden"
role="button"
aria-label="Menu"
></div>
Expand Down Expand Up @@ -181,11 +188,11 @@


<nav
class="mt-12 flex justify-center space-x-10 dark:invert lg:ml-12 lg:mt-0 lg:items-center lg:space-x-6"
class="mt-12 flex justify-center space-x-10 rtl:space-x-reverse dark:invert ltr:lg:ml-14 rtl:lg:mr-14 lg:mt-0 lg:items-center"
>

<a
class="h-8 w-8 text-[0] [background:var(--url)_center_center/cover_no-repeat] lg:h-6 lg:w-6"
class="h-7 w-7 text-[0] [background:var(--url)_center_center/cover_no-repeat] lg:h-6 lg:w-6"
style="--url: url(./github.svg)"
href="https://github.com/greathongtu"
target="_blank"
Expand All @@ -201,7 +208,7 @@


<main
class="prose prose-neutral relative mx-auto min-h-[calc(100%-9rem)] max-w-3xl px-8 pb-16 pt-12 dark:prose-invert"
class="prose prose-neutral relative mx-auto min-h-[calc(100%-9rem)] max-w-[--w] px-8 pb-16 pt-14 dark:prose-invert"
>

<h1
Expand All @@ -213,21 +220,23 @@
</main>

<footer
class="opaco mx-auto flex h-[4.5rem] max-w-3xl items-center px-8 text-[0.9em] opacity-60"
class="mx-auto flex h-[4.5rem] max-w-[--w] items-center px-8 text-xs uppercase tracking-wider opacity-60"
>
<div class="mr-auto">

&copy; 2024
<a class="link" href="http://localhost:1313/">greathongtu 的 Blog</a>

</div>
<a class="link mx-6" href="https://gohugo.io/" rel="noopener" target="_blank"
>Powered by Hugo️️</a
>powered by hugo️️</a
>
<a
class="link"
href="https://github.com/nanxiaobei/hugo-paper"
rel="noopener"
target="_blank"
>✎ Paper</a
>hugo-paper</a
>
</footer>

Expand Down
2 changes: 1 addition & 1 deletion public/github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/sumtree_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b65e922

Please sign in to comment.