当我们构建好语法高亮后,我们发现当编辑器上的内容并没有命中我们匹配的规则,或者其语义并不能被正确解析。我们需要将异常的地方标示出来,并且给予信息提示。基于此,我们可以通过自定义Lint的方式去实现这个功能。如果需要实现以下功能,我们需要先集成CodeMirror中提供的lint插件,然后基于插件通过registerHelper API去定义我们的lint功能。
CodeMirror Lint插件集成
关于Lint插件的集成,我们需要两个步骤。
- 引入codemirror/addon/lint/lint.js和lint.css文件。
- 实例化配置项新增lint: true的选项
为什么要集成lint.js 和lint.css文件
- lint.js和lint.css文件内部实现了自定义配置项lint的功能
- 通过接口方式定义了外部和lint.js的交互,主要是返回以{message, severity, from, to}对象数组格式的数据来实现侧边栏的异常描述和具体位置异常提示
- 通过不同配置满足同步或者异步的处理
为什么要配置lint:true这个选项
lint这个选项是lint.js中通过defineOption的方式去扩展CodeMirror的配置项,通过初始化或者setOption时调用回调去启用lint。
CodeMirror.defineOption("lint", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
clearMarks(cm);
if (cm.state.lint.options.lintOnChange !== false)
cm.off("change", onChange);
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
clearTimeout(cm.state.lint.timeout);
delete cm.state.lint;
}
if (val) {
var gutters = cm.getOption("gutters"), hasLintGutter = false;
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
if (state.options.lintOnChange)
cm.on("change", onChange);
if (state.options.tooltips != false && state.options.tooltips != "gutter")
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
startLinting(cm);
}
});
如何实现Lint
先上代码。
CodeMirror.registerHelper("lint", MODE_NAME, (text: string, options: CodeMirror.EditorConfiguration, cm:CodeMirror) => {
const lintList: CodeMirrorVerifyError[] = [];
try {
// 业务实现,存在异常通过Error抛出
valid(text);
} catch (e: unknown) {
lintList.push(e as CodeMirrorVerifyError);
}
return lintList;
});
这里,我们通过CodeMirror的registHelper方法去定义lint。其中这里包含了3个参数
- type。CodeMirror的命名空间,由于我们定义的是lint,所以赋值为
'lint'
- name。这里需要注意的是,我们的name必须和我们在实例化使用的mode name必须一致,否则lint内部在调用getHelper时,会找不到对于的lint方法
- 回调函数cb。其中参数为内容字符串、选项对象和编辑器实例。并且需要返回上述说的约定数组{message, severity, from, to}数组。其中message为异常信息、severity时异常级别,默认为error。from和to为异常的起始位置和结束位置,类型为CodeMirror.Pos。
我们可以通过getHelper源码可以看到getHelper的实现基于getHelper。而getHelpers首先会根据type获取列表,然后会获取当前mode,通过mode name去取出匹配的函数列表。
getHelper: function(pos, type) {
return this.getHelpers(pos, type)[0]
},
getHelpers: function(pos, type) {
let found = []
if (!helpers.hasOwnProperty(type)) return found
let help = helpers[type], mode = this.getModeAt(pos)
if (typeof mode[type] == "string") {
if (help[mode[type]]) found.push(help[mode[type]])
} else if (mode[type]) {
for (let i = 0; i < mode[type].length; i++) {
let val = help[mode[type][i]]
if (val) found.push(val)
}
} else if (mode.helperType && help[mode.helperType]) {
found.push(help[mode.helperType])
} else if (help[mode.name]) {
found.push(help[mode.name])
}
for (let i = 0; i < help._global.length; i++) {
let cur = help._global[i]
if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
found.push(cur.val)
}
return found
},
这样,我们就基本可以实现我们自定义的Lint校验了。当然,如果你想配置不同的图标和样式,可以通修改css文件覆盖CodeMirror-lint-markers
相关的样式
总结
总的来说,配置lint是比较简单的,复杂的主要时你对lint的校验规则,其中涉及了词法分析、语法分析、语义分析等实现。当然,你可以可以通过babel插件去解析对应的语法来实现lint自定义。
2022.08.02
木更