- 开发工具
- 代码编辑器
- 桌面应用
【免费下载链接】oni2
Native, lightweight modal code editor
导读
本文以官方开发文档《Architecture Overview》为主线,深入剖析 Onivim 2(一个原生、轻量的模态代码编辑器)的总体架构:从 Revery 渲染框架、ReasonML/OCaml 函数式编程栈,到仿 Elm/Redux 的 Action–State–Reducer–Updater–Store 状态管理模型,再到 Vim 内核集成与测试体系。读完本文,你将理解 Onivim 2 如何把 Web 生态熟悉的架构思想编译成高性能原生代码,并能在src、test、integration_test等目录中快速定位各类核心模块。
总体架构:Revery + ReasonML 的原生应用
Onivim 2 是一个基于 Revery UI 框架、使用 ReasonML 编写的原生应用。ReasonML 的语法与 JavaScript 相似,核心思想也与 React、Redux、Elm Architecture 一脉相承——但这一切最终被编译为高性能原生代码,而非运行在浏览器或 Node 环境中。
项目刻意采用函数式取向的代码库,这正是 ReasonML/OCaml 的强项:OCaml 的编译器乃至垃圾回收器都为构建高性能函数式应用做了深度优化。因此,Onivim 2 得以在保持 Web 开发者熟悉的心智模型的同时,获得原生应用的启动速度与渲染性能。
从当前仓库结构看,源码主体位于src目录,其中:
- src/Core — 跨应用共享的核心类型与工具
- src/Model — 应用状态(state)的描述与归约逻辑
- src/Store — 将状态与外部副作用连接起来的"存储"层
- src/UI — 用户界面与渲染
- src/Feature — 按功能域拆分的模块(缓冲区、编辑器、终端、搜索等)
- src/Exthost — 扩展宿主相关模块
- src/bin_editor —
Oni2_editor.exe的入口
需要注意的是:官方架构文档写作时使用的是src/editor/Core、src/editor/Model这类带editor中间层的旧目录结构,而当前仓库已将其扁平化为上述布局,阅读源码时以当前结构为准。
核心概念:仿 Elm/Redux 的状态管理词汇表
架构文档给出了一套与 Web 生态一一对应的核心概念,这也是理解整个代码库的钥匙:
- Action:描述一次状态变更的"信息载荷",等同于 Redux 中的 action。在代码中它是 src/Model/Actions.re 里定义的一个带标签联合类型(variant)。
- State / Model:二者互换使用,均指应用状态。对应 src/Model/State.re 中的
type t。 - Reducer:形如
(state, action) => newState的纯函数,依据 action 计算新状态。对应 src/Store/Reducer.re 的reduce函数。 - Updater:形如
(state, action) => (state, effects)的函数,与 Reducer 类似,但额外处理副作用,灵感来自 Elm Architecture。 - Store Connector:大致相当于 Redux 中的 middleware,负责把外部世界(Vim、剪贴板、扩展宿主等)接入 store。
- Store:由 State、Updater 与一组 Store Connector 组成,等价于 Redux 的 store。
另一个重要思想是:UI 是 state 的纯函数——给定相同的 state,UI 渲染结果必然一致,这对从 React 背景迁移过来的开发者非常熟悉。
这些状态管理基础设施(actions、state、reducers、updaters、store)由一个 reason 原生库 isolinear 提供实现。
数据流:从按键到渲染的完整链路
结合源码可以还原一条完整的数据流:
- 输入事件:键盘事件(
KeyDown、TextInput)从 src/Input 进入,编码为 src/Model/Actions.re 中的KeyDown、TextInput等 action。 - Store 线程调度:src/Store/StoreThread.re 是整个状态管理枢纽。它的文件头注释明确写道:状态更新运行在与渲染并行的独立线程中,以榨取架构上的极致性能(
StoreThread.re#L1-L8)。 - Reducer 归约:action 被送入 src/Store/Reducer.re 的
reduce函数,通过switch模式匹配逐一分发,产出新的State.t(Reducer.re#L9-L31)。 - Connector 副作用:各 Store Connector 监听状态变化并触发外部副作用,同时把外部事件(如 Vim 回调)反向转成 action 重新分发。
- UI 纯函数渲染:新状态驱动 src/UI/Root.re(顶层 UI)与 src/UI/EditorSurface.re(缓冲区渲染)重绘。
从源码看,Onivim 2 已将功能按域拆分为大量Feature_*模块,如Feature_Buffers、Feature_Editor、Feature_Terminal、Feature_QuickOpen等;State 中的每个域都有对应字段(见 src/Model/State.re 的type t,State.re#L461-L500)。这也意味着 Action 联合类型中大量构造子直接包着某个 Feature 的消息类型,例如Buffers(Feature_Buffers.msg)、Configuration(Feature_Configuration.msg)(Actions.re#L11-L31),形成"顶层薄归约 + 功能域自归约"的树状结构。
Model 三巨头:State / Actions / Reducer
官方文档特别点名了 Model 层三个最重要的模块,它们正是这套架构的"心脏":
- src/Model/State.re:应用状态类型定义。
type t由数十个字段组成,覆盖缓冲区、剪贴板、主题、命令、诊断、终端、扩展宿主、语法高亮等几乎全部编辑器子系统的状态,是"单一事实来源"(single source of truth)。 - src/Model/Actions.re:所有 action 的类型定义。它以
[@deriving show]注解派生可读的调试输出,构造子覆盖Init、KeyDown、TextInput、CommandInvoked、Buffers、Configuration、Exthost、WindowTitleSet等(Actions.re#L10-L80)。 - src/Store/Reducer.re:顶层归约函数。
reduce: (State.t, Actions.t) => State.t先整体更新bufferRenderers与lifecycle两个子状态,再对SetGrammarRepository、WindowMaximized、ReallyQuitting等少数全局 action 直接改写顶层状态,其余交给各 Feature 的 reducer 处理(Reducer.re#L9-L31)。
Store 与 StoreThread:并行线程中的状态中枢
src/Store/Oni_Store.re 的模块注释概括了 Store 库的职责:负责管理 Model 随时间的变化以及跨集成的协调,任何影响编辑器状态的变化都应建模为 Action,并通过相应的 reducer/updater 汇聚处理。
src/Store/StoreThread.re 是 store 的入口,它承担以下关键工作:
- 启动各 Store Connector:例如
CommandStoreConnector.start()与VimStoreConnector.start(...),后者返回(vimUpdater, vimStream),通过 Isolinear 的Stream.create()建立流式连接(StoreThread.re#L117-L124)。 - 发现扩展:
discoverExtensions按Bundled(内置)、Development(开发目录)、用户扩展三类分别扫描,支持通过 CLI 开关禁用扩展加载(StoreThread.re#L20-L66)。 - 初始化扩展宿主与语法仓库:从扩展中提取语法信息构建
GrammarRepository,并创建扩展宿主客户端(StoreThread.re#L108-L150)。 - 桥接剪贴板与窗口回调:把剪贴板读写、窗口最大化/最小化/关闭等原生能力以回调参数注入 store。
Vim 内核:libvim 与 reason-libvim
Vim 是 Onivim 2 的核心。架构上分为两个组件:
- libvim:Vim 的一个 fork,与平台和终端解耦,作为核心缓冲区编辑引擎,通过简洁的 C API 暴露能力。
- reason-libvim:
libvim的 Reason 绑定,位于 src/reason-libvim,其.rei接口文件直观地呈现了可用 API。
Onivim 2 通过一个 Store Connector——src/Store/VimStoreConnector.re 与reason-libvim集成。从源码可以看到它如何把 Vim 事件反向接入 redux 式数据流:
- 注册剪贴板 provider,依据
vim.useSystemClipboard配置决定是否从系统剪贴板拉取值(VimStoreConnector.re#L36-L62); - 通过
Vim.onVersion在版本变化时分发OpenFileByPath("oni://Version", ...)action(VimStoreConnector.re#L64-L72); - 通过
Vim.Buffer.onLineEndingsChanged、Vim.Buffer.onFiletypeChanged等回调,把换行符变化、文件类型变化等转译为Feature_Buffers的 action 再分发(VimStoreConnector.re#L74-L84)。
这套机制保证了 Vim 的编辑语义(模式、寄存器、按键序列)与 Onivim 2 自身的 UI 状态始终保持同步。
Revery:UI 框架与生命周期管理
Revery 是伴随 Onivim 2 诞生的 UI 框架,目标是让开发者以 React/Redux 的体验构建快速、原生、跨平台的应用。它负责应用生命周期管理,提供App.start、App.createWindow、UI.start等 API,用于启动应用、创建窗口、启动 UI。
架构文档指出的入口文件为 src/bin_editor/Oni2_editor.re(即Oni2_editor.exe的 main 入口),它调用上述 Revery API 完成应用启动。UI 层关键文件包括:
- src/UI/Root.re:顶层 UI 组件;
- src/UI/EditorSurface.re:缓冲区渲染的核心位置。
Extension Host:从"Coming soon"到已成形的扩展体系
架构文档写作时,Extension Host 一栏还标注着"Coming soon!"。但从当前仓库结构看,扩展宿主已经从设计蓝图落地为完整实现:src/Exthost下包含扩展扫描、消息协议、语法与工作区数据等模块;src/Feature/Feature_Exthost.re等把扩展消息纳入顶层 action;Store 层则有 src/Store/ExtensionClientStoreConnector.re 负责把扩展宿主接入 store,StoreThread中通过ExtensionClient.create(...)创建客户端并订阅其事件流。此外仓库还内置了extensions(VS Code 生态的语法/语言扩展)与development_extensions(开发调试用扩展)两个目录,可作为扩展体系的实际样例。
测试体系:单元测试与集成测试
对于一个由小团队维护的跨平台项目,测试覆盖至关重要。Onivim 2 维护两类测试:
单元测试(Unit Tests)
- 位于
test目录,命令为esy '@test' run;首次运行前需执行esy '@test' install安装测试依赖。 - 测试与
src源码 1 对 1 组织:例如src/Core/LineNumber.re的测试对应 test/Core/LineNumberTests.re。 - 单元测试隔离验证某段代码,依赖尽量最小化。仓库中
test下按Core、Model、Service、Exthost、Feature、Syntax等子目录镜像源码结构。
集成测试(Integration Tests)
- 位于
integration_test目录。 - 每个集成测试都是独立的可执行文件,会像真实应用一样初始化一整套 Store——唯一区别是 headless(无 UI)。
- 运行方式有两种:全部运行用
esy '@integrationtest' run;单个运行用esy '@integrationtest' x Test-Name.exe。例如 integration_test/Regression1671Test.re、integration_test/SyntaxHighlightTreesitterTest.re。
学习资源
文档为希望进一步了解 ReasonML 与架构思想的读者给出了建议方向(均为外部站点,此处仅作名称列举,供自行检索):ReasonML 官方文档、sketch.sh 交互式 REPL、Revery Playground;架构理念方面则推荐 Elm Architecture 官方指南、Redux 文档与 React 文档。结合本文的源码指引,推荐的实际进阶路径是:先读 src/Model/State.re 与 src/Model/Actions.re 建立整体印象,再顺着 src/Store/StoreThread.re 的启动流程梳理各 Store Connector,最后挑选一个integration_test用例(如 integration_test/TypingBatchedTest.re)跟踪完整数据流,即可快速掌握这套 Elm/Redux 风格原生架构的精髓。
- 开发工具
- 代码编辑器
- 桌面应用
【免费下载链接】oni2
Native, lightweight modal code editor
相关推荐
Onivim 2架构深度解析:从Electron到原生ReasonML
Onivim 2架构深度解析:从Electron到原生ReasonML 本文深入分析了Onivim 2从基于Electron的v1架构演进到原生ReasonML
开发工具代码编辑器桌面应用Webamp 浏览器版 Winamp 2 架构解析:Redux 状态管理、Web Audio 音频引擎与原生皮肤渲染管线
Webamp 浏览器版 Winamp 2 架构解析:Redux 状态管理、Web Audio 音频引擎与原生皮肤渲染管线 Webamp 是一个用浏览器技术重新实
前端音视频CKEditor5编辑器状态管理:Redux集成与状态同步
CKEditor5编辑器状态管理:Redux集成与状态同步 痛点与挑战:编辑器状态管理的困境 你是否曾面临以下问题?多组件共享编辑器内容时出现数据不一致,复杂表
前端富文本UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考