6个版本 (3个破坏性更新)
0.4.2 | 2024年1月22日 |
---|---|
0.4.1 | 2023年12月1日 |
0.4.0 | 2023年10月11日 |
0.3.0 | 2023年4月16日 |
0.1.0 | 2023年2月26日 |
#106 in 数据结构
每月3,083次下载
在 3 个crate中使用 (2 个直接使用)
415KB
9K SLoC
🌾 crop
crop 是文本绳的实现,一种设计用于处理频繁编辑任意大缓冲区的应用程序的数据结构,例如文本编辑器。
crop 的 Rope
由一个 B树 支持,确保插入、删除或替换文本片段的时间复杂度始终是 Rope
大小的对数。
crop 极端关注性能:查看 基准测试 了解其与其他类似项目的比较。
考虑并行构建
Rope
使用线程安全的引用计数在线程之间共享数据。克隆一个 Rope
只需额外 16 字节内存,其写时复制语义允许实际文本内容随着不同克隆因用户编辑而分叉时逐步克隆。
这允许便宜地快照一个 Rope
并将其发送到后台线程以执行任何IO或CPU密集型计算,同时保持主线程的响应性,并始终准备好处理下一批编辑。
示例使用
// A `Rope` can be created either directly from a string or incrementally
// using the `RopeBuilder`.
let mut builder = RopeBuilder::new();
builder
.append("I am a 🦀\n")
.append("Who walks the shore\n")
.append("And pinches toes all day.\n")
.append("\n")
.append("If I were you\n")
.append("I'd wear some 👟\n")
.append("And not get in my way.\n");
let mut rope: Rope = builder.build();
// `Rope`s can be sliced to obtain `RopeSlice`s.
//
// A `RopeSlice` is to a `Rope` as a `&str` is to a `String`: the former in
// each pair is a borrowed reference of the latter.
// A `Rope` can be sliced using either byte offsets:
let byte_slice: RopeSlice = rope.byte_slice(..32);
assert_eq!(byte_slice, "I am a 🦀\nWho walks the shore\n");
// or line offsets:
let line_slice: RopeSlice = rope.line_slice(..2);
assert_eq!(line_slice, byte_slice);
// We can also get a `RopeSlice` by asking the `Rope` for a specific line
// index:
assert_eq!(rope.line(5), "I'd wear some 👟");
// We can modify that line by getting its start/end byte offsets:
let start: usize = rope.byte_of_line(5);
let end: usize = rope.byte_of_line(6);
// and replacing that byte range with some other text:
rope.replace(start..end, "I'd rock some 👠\n");
assert_eq!(rope.line(5), "I'd rock some 👠");
// `Rope`s use `Arc`s to share data between threads, so cloning them is
// extremely cheap.
let snapshot: Rope = rope.clone();
// This allows to save a `Rope` to disk in a background thread while
// keeping the main thread responsive.
thread::spawn(move || {
let mut file =
BufWriter::new(File::create("my_little_poem.txt").unwrap());
// The text content is stored as separate chunks in the leaves of the
// B-tree.
//
// We can iterate over them using the `Chunks` iterator which yields the
// chunks of the `Rope` as string slices.
for chunk in snapshot.chunks() {
file.write_all(chunk.as_bytes()).unwrap();
}
})
.join()
.unwrap();
查看 文档 了解crate的更详细概述。
与其他绳的比较
截至2023年4月,据我所知,仍有3个绳crate仍在积极维护:crop、Jumprope 和 Ropey。以下是它们的功能和权衡的快速(但不完整)概述,以帮助您决定哪个最适合您的特定用例。
速度
以下结果是通过在2018款MacBook Pro上运行由crdt-benchmarks提供的现实世界,逐字符编辑跟踪获得的。
数据集 | 裁剪(毫秒) | 跳绳(毫秒) | 绳索(毫秒) | std::string::String (毫秒) |
---|---|---|---|---|
automerge-paper | 12.39 | 12.52 | 44.14 | 108.57 |
rustcode | 2.67 | 2.86 | 7.96 | 13.40 |
sveltecomponent | 0.95 | 1.08 | 3.65 | 1.22 |
seph-blog1 | 6.47 | 6.94 | 23.46 | 22.26 |
廉价克隆
裁剪和绳索都允许它们的Rope
在O(1)
的时间和空间内被克隆,通过在克隆之间共享数据,而克隆一个JumpRope
则需要重新分配实际文本内容,就像它会对常规的String
那样。
索引指标
跳绳和绳索都使用Unicode码点偏移量(Rust中的char
)作为它们的索引指标。裁剪使用UTF-8代码单元(即字节)偏移量,就像Rust的String
一样。
行中断
裁剪和绳索都跟踪行中断,允许您在行和字节偏移量之间进行转换,并在它们的Rope
和RopeSlice
的行上进行迭代。绳索可以配置为识别所有Unicode行中断,而裁剪只识别LF和CRLF作为行终止符。
跳绳目前还没有基于行的API。
致谢
-
裁剪的公共API的大部分灵感来自出色的Ropey crate(我从其中也借用了一些测试向量)。与裁剪不同,Ropey使用码点(Rust中的
char
)作为它的主要索引指标。如果您更喜欢使用char
偏移量而不是字节偏移量,Ropey可能是一个不错的选择; -
尽管实现方式有很大不同,但裁剪的
Metric
特质灵感来自xi_rope中的同名词质特质。请查看Raph Levien在“Rope科学”系列中的第二篇博客以获取更多信息。
依赖关系
~215KB