Watch Print
Author: hu@yifeishu.com Last modified: 0000-00-00 00:00 Helpful: 0 Visits: 1112

CodeMirror中定制Mode实践

背景介绍

几个月以来“亦非书”一直使用HTML中的textarea元素实现文章编辑功能,用户体验比较差,纯粹的textarea不能对编辑内容自定义样式,并且编辑过程中的用户交互比较差。因此我需要优化编辑体验,对比ManacoCodeMirror两款业内知名的开源项目之后,我选择基于CodeMirror打造一个针对Kude语法规则的插件。

CodeMirror的官网链接是https://codemirror.net/Github地址是https://github.com/codemirror/codemirror

我需要的是定制CodeMirror中的mode,因此其它功能在此不做详述。

Mode

我尝试使用一句话解释Mode,即Mode是以CodeMirror编辑器为载体,针对用户输入的文本内容,按特定语法规则解析之后,实现关键字符高亮的插件。

Mode概念

上图描述的是Mode对象可以包含的钩子函数,其中token函数是必须存在的。

CodeMirror拿到当前编辑框中的所有字符之后,按行拆分,然后逐行调用token函数,token函数返回值为typeCodeMirror自动拼接type到完整的CSS className以渲染HTML,展现给用户。(type最好选用codemirror.css中内置的一些CSS高亮规则,因为如果Mode使用了自定义CSS className,开发者使用第三方theme.css主题时不能完整覆盖)。

Mode中的token函数钩子

token有两个参数,第一个是stream,包含当前解析到的字符位置信息(stream.pos)、当前行内容(stream.string),以及CodeMirror提供的一些方便操作stream对象的函数等。

tokenstream的使用

token的参数stream中提供了很多的api,最常用的可能是:

实际上,在定制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时,拿到typestrong,自动拼接为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可以翻阅官方文档

×
Is this page helpful?
Yes No