CodeMirror
中定制Mode
实践
背景介绍
几个月以来“亦非书”一直使用HTML
中的textarea
元素实现文章编辑功能,用户体验比较差,纯粹的textarea
不能对编辑内容自定义样式,并且编辑过程中的用户交互比较差。因此我需要优化编辑体验,对比Manaco
和CodeMirror
两款业内知名的开源项目之后,我选择基于CodeMirror
打造一个针对Kude
语法规则的插件。
CodeMirror
的官网链接是https://codemirror.net/,Github
地址是https://github.com/codemirror/codemirror。
我需要的是定制CodeMirror
中的mode
,因此其它功能在此不做详述。
Mode
我尝试使用一句话解释Mode
,即Mode
是以CodeMirror
编辑器为载体,针对用户输入的文本内容,按特定语法规则解析之后,实现关键字符高亮的插件。
Mode
概念
上图描述的是Mode
对象可以包含的钩子函数,其中token
函数是必须存在的。
CodeMirror
拿到当前编辑框中的所有字符之后,按行拆分,然后逐行调用token
函数,token
函数返回值为type
,CodeMirror
自动拼接type
到完整的CSS className
以渲染HTML
,展现给用户。(type
最好选用codemirror.css
中内置的一些CSS
高亮规则,因为如果Mode
使用了自定义CSS className
,开发者使用第三方theme.css
主题时不能完整覆盖)。
Mode
中的token
函数钩子
token
有两个参数,第一个是stream
,包含当前解析到的字符位置信息(stream.pos
)、当前行内容(stream.string
),以及CodeMirror
提供的一些方便操作stream
对象的函数等。
token
中stream
的使用
token
的参数stream
中提供了很多的api
,最常用的可能是:
sol
判断当前位置是否行首。eol
判断当前位置是否行尾。peek
取下一个字符。next
取下一个字符并使位置+1。skipToEnd
使位置跳到当前行尾。column
取token
开始的位置。current
取token
开始的位置到当前位置之间的子字符串。indentation
取当前行首的空格数(缩进)。- 其它函数方法。。。
实际上,在定制Mode
的时候,我并没有借助这些函数。我只是不断手动更新stream.pos
,即当前解析的字符在当前行的位置,然后返回对应的type
,最终实现目的。
举例分析
用上图举例:
用户输入第一行abcdef
,这里只是一段普通文本,不需要高亮展示,所以token
返回undefined
,并更新位置到行尾。
function token(stream, state) { // stream.pos ==> 当前位置0 // stream.string ==> 当前行内容'abcdef' if(stream.string === 'abcdef') { // stream.pos = stream.string.length; stream.skipToEnd(); return undefined; } }
用户输入第二行abc_def_ghi
,假设_
是特殊控制字符,即_def_
需要高亮显示,当前行最少需要使CodeMirror
调用3次token
函数钩子。
function token(stream, state) { if(stream.string.substring(stream.pos).startWith('abc')) { stream.pos = 3; return undefined; } if(stream.string.substring(stream.pos).startWith('_def_')) { stream.pos = 3 + 5; return 'strong'; } stream.skipToEnd(); return undefined; }
CodeMirror
在行字符位置为3 + 5
时,拿到type
为strong
,自动拼接为cm-strong
,这在codemirror.css
中的定义是.cm-strong {font-weight: bold;}
,最终渲染的HTML
节点是<span class="cm-strong">_def_</span>
。
Mode
中的startState
函数钩子
startState
仅会在整篇文本最开始出执行一次,它需要返回一个对象,包含的一些key value
由开发者自定义。主要用途是Mode
解析文本过程中暂存一些状态(比如解析一块表格内容,涉及跨行解析,需要借助state
)。
token
被调用时,state
作为第二个参数传递,token
函数可以修改state
中的值,以传递到之后的行解析。
Mode
中的blankLine
函数钩子
由于只包含空白字符的行会被忽略,不经过token
解析,所以如果需要实现编辑的文本内容遇到空白行修改state
,可以在blankLine
函数钩子中直接修改state
。
注意要点
CodeMirror
会暂存每行生成的state
快照,以便用户在某一行编辑时,自动从当前行开始至文末之间的所有行重新解析。
如果想借助copyState
实现每行重置state
是不行的。虽然它在每行开始解析时都会被调用,但是CodeMirror
传递到token
函数解析时依然是上一行解析完生成的state
快照。
总结
实际开发一个Mode
的复杂度当然比上文所述高很多,但是万变不离其宗,原理就是如此。实际开发一个CodeMirror Mode
可以翻阅官方文档。