尚腱埂 发表于 2025-6-3 00:34:02

从零开发Vim-like编辑器(01)起步

前言

Vim和Neovim因其独特的模态编辑和高度可定制化,被列为程序员常用的文本编辑器选项之一,与Sublime Text、VS Code、Emacs等编辑器共同丰富了开发者工具生态。就目前而言,网络上绝大多数的文章都在讲解如何为Vim、Neovim编写配置,更深入一点的文章会教大家如何开发相关的插件。但作为开发者的你是否有想过,Vim、Neovim这样amazing的编辑器是如何开发出来的,甚至更大胆一点,你是否有想过能否自己编写一款类Vim编辑器呢?
本系列文章笔者将带你从零开始,基于Vim现有的功能,拆解模态编辑的实现原理,使用Rust一步步开发构建一个具备Vim基本特性的轻量级编辑器。
前置准备

"工欲善其事,必先利其器"。
本系列文章所开发的Vim-like编辑器,将会运行在命令行中,以TUI的形式进行呈现。在代码编写的过程中,我们将会使用Rataui这个三方库来作为最基础的TUI渲染框架。

Ratatui是一个Rust库,用于烹饪美味的TUI(终端用户界面)。它是一个轻量级库,提供了一组小部件和实用程序来构建简单或复杂的Rust TUI。作为TUI构建库,Ratatui也内建了一些基础的 UI 组件,同时社区也有许多基于Ratatui开发的第三方 UI 组件,上手会比较快。
考虑到本文读者不一定都了解这个库,所以笔者在接下来将会简单介绍Ratatui的基本概念和基础使用,让读者对该库有一个直观的感受。同时,在后续文章介绍过程中页会根据实际的情况适当的介绍Ratatui的一些深入的内容,但考虑文章重点,不会过多讲解(不然就成了《从零开始使用Ratatui》了),因此还请笔者自行通过官方的教程学习Ratatui的使用。
ratatui的基本使用

我们首先通过官方的一个基础例子,来理解ratatui的使用思路:

这个例子中最核心部分的莫过于我们在一个loop循环迭代中,先调用终端(terminal)实例的draw方法,完成对 UI 组件的绘制,紧接着读取事件做出对应按键的逻辑响应:

其中的绘制具体流程为:

[*]执行终端实例的draw方法,传递一个方法回调,该方法入参是一个帧(Frame)实例,代表了每一次绘制的上下文
[*]调用帧实例的area方法获取终端为当前帧分配的的区域(Rect)
[*]调用帧实例的draw方法,将 UI 组件绘制到指定区域中
https://img2024.cnblogs.com/blog/2050266/202505/2050266-20250528102543312-1878059155.png
绘制环节完成后,我们调用ratatui::crossterm::event::read()来获取捕获到的事件消息,并针对这些事件的具体分类执行对应逻辑。
读者可能很疑惑,这里的ratatui::crossterm是什么?要解释这个问题,我们要理解一个事实:ratatui是一个TUI渲染引擎,它对实际运行的命令行环境进行了抽象,在内容绘制层面提供了统一的API,而绘制的具体细节逻辑在其内部,通过调用实际的后端(backend)实现,进而调用底层不同的绘制逻辑。当我们调用ratatui::init()时,得到的是一个默认终端实例(DefaultTerminal),该默认终端实例在ratatui内部使用的是crossterm这个底层后端,因此,我们相对应的,需要调用ratatui内置默认依赖的crossterm的相关API来读取事件消息。

ratatui的思维模型

值得介绍的是,ratatui的核心采取的是立即模式渲染(Immediate Mode Rendering),立即模式渲染是一种 UI 范例,其中每帧都会重新创建 UI。与之相对应的是保留模式渲染(Retained Mode Rendering),该模式下通常会创建一组固定的 UI 组件,并在后续的某些时刻更新其状态以达到预期的UI效果。简单来讲:

[*]保留模式(Retained Mode):程序只需进行一次的 UI 组件创建,在随后过程中修改这些组件的其属性或处理组件触发的事件。
[*]即时模式(Immediate Mode):程序根据应用程序状态每帧重新绘制 UI,UI只是对状态的渲染表达,是一个瞬时的对象。
关于这块更多的内容,请读者自行阅读相关的文章进行了解
现在,让我们再通过一个例子来理解立即模式渲染思维。假设我们要在屏幕上渲染一个光标(cursor),这个光标可以随着我们的上下左右按键移动:

对于保留模式渲染来说,我们通常会这样做:

[*]创建一个光标组件实例
[*]将光标实例添加到界面上
[*]响应按键事件,修改光标的位置
let cursor = 创建光标(); // <- 创建“光标”实例
application.屏幕.添加(cursor); // <- 将“光标”添加到屏幕实例上
// ...
// 注册全局按键事件:键盘右方向键按下时,光标位置右移一个单位
application.onRightKeyPress = () => cursor.position.x += 1

application.run(); // 运行程序上述代码中,调用的saturating_add、saturating_sub是Rust所提供的安全的加减值操作,避免数字因为其字节长度造成溢出。
在上述代码中,我们通过响应空格键(KeyCode::Char(' '))来切换光标的激活状态;通过响应q键退出循环;通过上下左右按键来控制光标的位置。
至此,我们完成了上述例子的代码编写,运行该应用,我们可以看到对应的效果:

该节实践代码已上传至 github gist:
https://gist.github.com/w4ngzhen/96e49b5054054cab2138c976adf8c98d#file-main-rs
写在最后

本文作为本系列的开篇,还没开始涉及到vim-like编辑器的设计,主要是介绍ratatui这个库的基本的思路概念和一些基本使用。当然,就上述的内容还远远无法胜任接下来的文章,因此笔者希望读者能够仔细阅读官方的文档,掌握ratatui的更多内容,这将有助于更好地理解后续文章内容。
在下一篇文章,笔者将会正式搭建项目,并将详细介绍想要实现一款Vim-like编辑器所必要的一些模型设计。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 从零开发Vim-like编辑器(01)起步