CodeMirror 概述
CodeMirror 用于在 Web 平台构建代码编辑器,它支持扩展以实现丰富的功能(例如语法高亮、代码块折叠等)
注意
该系列文章主要介绍 CodeMirror 6
其他版本的 CodeMirror 可以参考相关官方文档
模块化
CodeMirror 采用模块化设计,需要导入多个库(推荐使用 bundler 进行打包)才能够构建一个完整的代码编辑器,其中核心库有 3 个:
- @codemirror/state 模块:定义数据结构,以表示代码编辑器的内容,以及描述修改内容的操作
- @codemirror/view 模块:将代码编辑器展示到页面上,并响应用户的操作以更新内容
- @codemirror/commands 模块:用于创建指令(一般与按键绑定),以编程的方式更改内容
不可变数据
指导 CodeMirror 架构的一个宗旨是采用函数式(纯)代码比命令式代码更易于处理,因为函数式代码创建新值而不是产生副作用。
编辑器状态 state(包含编辑器内容、选区等信息)是不可变的 immutable。CodeMirror 响应用户对编辑器的操作时,会生成新的状态值,而旧的状态值是保持不变的。
提示
同时拥有旧状态和新状态通常非常有用,方便进行历史版本的管理。
let state = EditorState.create({doc: "123"})
// ⚠️ 以下操作不可行,不能直接对原有状态赋值(因为这会修改旧状态)
state.doc = Text.of("abc") // <- DON'T DO THIS
需要通过分发事务 dispatch transaction 的方式来更新编辑器状态(而不能直接更改原有的状态值),这会同步通知视图进行更新以呈现新的状态
// 创建事务
// (Assume view is an EditorView instance holding the document "123".)
let transaction = view.state.update({changes: {from: 0, insert: "0"}})
console.log(transaction.state.doc.toString()) // "0123"
// At this point the view still shows the old state.
// 分发事务
view.dispatch(transaction)
// And now it shows the new state.
在 CodeMirror 中的数据流动方向:视图 view 监听用户操作所触发的 DOM 事件(或编辑器 dispatch 的指令) ➡️ 通过事务 transaction 对编辑器状态进行修改(生成新的编辑器状态 state) ➡️ 视图基于新的编辑器状态更新页面

位置系统
CodeMirror 使用数字来表示文档的每个位置
提示
除去装饰器、标记等额外的样式,CodeMirror 代码编辑器的文档内容实际上只包含纯文本,如果每一个字符表示一个单位,则使用数字就可以很方便地表示整个文档的各个位置
一般字符数量和文档位置的定位索引值都是一一对应的
实际而言是 UTF-16 编码的字符计作 1 个 unit,而 astral characters 超出 Unicode 标准 BMP 范围的字符(范围为 U+0000 到 U+FFFF)计作 2 个 unit,例如 🤩 表情符号(其 Unicode 编码为 U+1F929)
另外换行符也计作 1 个 unit(即使换行符包含多个字符,例如默认换行符包括 "\n"(换行符)、"\r"(回车符)、"\r\n"(回车符+换行符),CodeMirror 也支持设置自定义的换行符,在文档位置定位系统中它们都会被解析为 1 个 unit)
文档的每个位置都可以用数字表示,那么选区 selection 框选的范围、文档更改所发生 changes 的位置、装饰器 decorate 应用的位置等场景都可以用数字来描述
插件系统
CodeMirror 通过插件为编辑器添加丰富的功能,例如在编辑器的侧栏显示行号,撤销与重做功能等