一个好的文本编辑器应该对程序员可能做的各种工作都有用,包括打开有时可能有几 GB 大小的文件。因此,我不建议将所有内容都缓存在 RAM 中的思维定势。
我建议设置一个表示文件的切片搜索树,其中单个切片可能是:
- 对磁盘上实际文件中字节范围的引用,或
- 对已编辑“页面”的引用。
当您打开一个文件时,您首先将单个项目插入到树中,这只是一个代表整个文件的范围,例如对于 10-MiB 文件:
std::map<size_t, slice_info> slices;
slices[0].size = 10*1024*1024;
当用户编辑文件时,在编辑点周围创建一个合理大小的“页面”,比如 4 KiB。树在那个点被拼接。在示例中,编辑点位于 5 MiB:
size_t const PAGE_SIZE = 4*1024;
slices[0].size = 5*1024*1024;
slices[5*1024*1024].size = PAGE_SIZE;
slices[5*1024*1024].buffer = create_buffer(file, 5*1024*1024, PAGE_SIZE);
slices[5*1024*1024 + PAGE_SIZE].size = 5*1024*1024 - PAGE_SIZE
您可以将内存映射文件用于只读缓冲区(源文件)和复制的可编辑缓冲区(后者将放置在临时目录中)。如果编辑器崩溃,这也允许恢复。
使用固定大小的页面将大大减少内存堆的碎片,因为所有块都具有相同的大小,并且插入文本永远不需要移动超过 4 KiB 的数据。
这是一个简化的描述,给出了一般的想法,而不涉及太多的细节。真正的实现很可能需要更复杂,例如允许页面中的可变数据量以应对溢出的页面,并将许多小切片合并在一起,以便在大文件中运行正则表达式替换不会创建太多许多小缓冲区。可能需要对树中应该同时拥有的切片数量进行限制,但关键是当您开始在某处插入时,您应该确保使用的切片不太大。
对于正则表达式,只要整个编辑器在运行时不挂起,我认为性能不会有太大问题。试试Boost.Regex,它很可能足够快满足您的需求,而且它也足够通用,可以插入您需要的任何缓冲策略。
这同样适用于语法高亮,如果你在后台运行它,它不会在用户打字时对他造成太大的干扰。您可以在这里使用切片方法为您带来好处:
- 每个切片都可以有一个互斥锁,可以在编辑操作期间锁定,从而允许语法突出显示或“智能感知”类型分析在后台线程中运行。
- 您可以存储语法高亮引擎的状态,这样每当您在切片中进行编辑时,您都可以从该切片的开头重新开始语法高亮,而不是从文件的开头。
我不知道有任何独立的语法高亮引擎,但它们通常基于正则表达式替换(参见例如 vim 中的语法高亮文件)。