这篇文章用来记录使用 CodeMirror 时的一些心得。
Demo
CodeMirror 官方提供很多 Demo 供我们参考,通过阅读这些 Demo 的源码可以解决很多使用时遇到的问题。
Vue
配合 Vue 使用的时候,我们一般会在 mounted
里初始化 CodeMirror 实例,然后在别的什么地方加载数据,然后通过 cm.setValue(code)
的方式写入代码。
这么做大体上 OK,不过在用户不断 Ctrl + Z 的时候,会清空代码。因为 CodeMirror 初始化的时候里面没有代码,我们第一次 cm.setValue(code)
就会把这个动作放在 history 的第一位,所以后退的时候,清空所有代码也是合理的。
要解决这个问题,一方面,我们可以加载完数据再执行初始化,不过这样页面会跳动,体验不好;或者,第一次填充完代码,清空历史,也可以。
methods: {
async fetch() {
const code = await fetchCode();
this.editor.setValue(data.content);
this.editor.clearHistory();
}
},
beforeMount() {
this.fetch();
},
mounted() {
this.editor = new CodeMirror.fromTextArea(this.$refs.editor, {...});
this.editor.on('change', cm => {
cm.save();
this.code = cm.getValue();
});
},
keyMap
与 extraKeys
作为一个 IDE,CodeMirror 自然支持各种键位配置,keyMap
就是用来定义这些键位配置的。它可以是一个字符串或者数组,字符串对应一种配置,数组对应多种配置,CodeMirror 会自动降级适配。
除此之外,用户还可以通过 extraKeys
定义一些自己偏爱的个性化配置,比如我厂常用的:
- Tab 插入空格
- Shift+Tab 减少缩进
- 删除整个缩进
实现起来是这样的:
CodeMirror.fromTextArea(this.$refs.editor, {
// ...其它配置
extraKeys: {
Tab(cm) {
if (cm.somethingSelected()) {
cm.indentSelection('add');
return;
}
cm.replaceSelection(' ', 'end', '+input');
},
'Shift-Tab'(cm) {
cm.indentSelection('subtract');
},
Backspace(cm) {
// 取当前行光标前面的字符,如果都是空格,就减少有一个缩进
let {line, ch} = cm.getCursor();
line = cm.getLine(line).substr(0, ch);
if (line.length > 0 && line.trim() === '') {
cm.indentSelection('subtract');
return;
}
// 表示使用 CodeMirror 默认行为
return CodeMirror.Pass;
},
},
});
启用搜索,侦听全局 Ctrl + F
CodeMirror 自带搜索功能,不过默认没有启动,需要引入一些组件。
import 'codemirror/addon/dialog/dialog';
import 'codemirror/addon/scroll/annotatescrollbar';
import 'codemirror/addon/search/matchesonscrollbar';
import 'codemirror/addon/search/jump-to-line';
import 'codemirror/addon/search/search';
import 'codemirror/addon/search/searchcursor';
接下来,在 CodeMirror 里 Ctrl+F(Mac 下是 Cmd+F)就会打开搜索面板。不过很多时候我们的编辑器页面里就只有一个 CodeMirror,这个时候我们就要侦听全局的 Ctrl + F,并且启动搜索面板。
我们只需要让 CodeMirror 执行 find
命令即可:
methods: {
onKeyDown(event) {
const isMac = navigator.platform.startsWith('Mac');
const {key, code, keyCode, ctrlKey, metaKey} = event;
const isCmd = isMac && metaKey || !isMac && ctrlKey;
if (!isCmd) {
return;
}
const isF = key === 'f' || code === 'KeyF' || keyCode === 70;
if (isF && this.editor) {
this.editor.execCommand('find');
event.preventDefault();
}
},
},
beforeMount() {
this.onKeyDown = this.onKeyDown.bind(this);
document.addEventListener('keydown', this.onKeyDown);
}
常见用法
const {line, ch} = cm.getCursor();
cm.replaceRange('# ', {line, ch});
const {line} = cm.getCursor();
const tokens = cm.getLineTokens(line);
if (tokens.length === 1 && tokens[0].type === 'comment') {
// do something
}
欢迎吐槽,共同进步