CodeMirror入门教程
CodeMirror(下面简称为cm)是一款基于JavaScript、面向语言的前端代码编辑器。它支持开箱即用,自带了超过100种语言的库,同时还有很多附加功能,目前得到了jetbrains等公司的支持。在这个分类下,能够与cm并驾齐驱的另一个编辑器则是ACE。由于笔者并没有使用过ACE,因此就不对两者做对比了。接下来笔者会分几个章节来具体介绍介绍cm的基本使用方法和高级功能。
CodeMirror官网:https://codemirror.net/
这里有一个在线demo,大家可以自行调整学习:
See the Pen CodeMirror Learn by jiangmitiao (@jiangmitiao) on CodePen.
1. CodeMirror引入
在cm官网使用手册中,介绍了在项目中引入cm的方式。如果是传统项目,使用cdn或者将cm包放置在项目中即可引入;如果使用了npm等包管理工具,安装cm依赖然后就可以使用了。
有人针对vue框架,封装了cm,提供了一个面向vue的cm组件:vue-codemirror
,使用起来比较方便。
由于笔者使用的是vue框架,使用npm作为包管理工具,下面就拿vue-codemirror
来做具体介绍,但功能与原生的使用方式基本上无差别。
vue-codemirror的npm安装命令:npm install vue-codemirror --save
安装完成后,首先需要在全局或组件下引入vue-codemirror,笔者这里演示的是在组件下引入。
js/css文件引入:
// 全局引入vue-codemirror
import {codemirror} from 'vue-codemirror';
// 引入主题 可以从 codemirror/theme/ 下引入多个
import 'codemirror/theme/idea.css'
// 引入语言模式 可以从 codemirror/mode/ 下引入多个
import 'codemirror/mode/sql/sql.js';
在组件中引入:
export default {
components: {
codemirror
}
}
2. 使用Codemirror
在vue中引入cm后,接下来就可以使用了,下面是完整的例子:
<template>
<div>
<codemirror
ref="cm"
v-model="code"
:options="cmOptions"
@input="inputChange"
></codemirror>
</div>
</template>
<script>
// 全局引入vue-codemirror
import {codemirror} from 'vue-codemirror';
// 引入css文件
import 'codemirror/lib/codemirror.css'
// 引入主题 可以从 codemirror/theme/ 下引入多个
import 'codemirror/theme/idea.css'
// 引入语言模式 可以从 codemirror/mode/ 下引入多个
import 'codemirror/mode/sql/sql.js';
export default {
name: 'Simple',
components: {codemirror},
data() {
return {
code: 'select a from table1 where b = 1',
cmOptions: {
// 语言及语法模式
mode: 'text/x-sql',
// 主题
theme: 'idea',
// 显示函数
line: true,
lineNumbers: true,
// 软换行
lineWrapping: true,
// tab宽度
tabSize: 4,
}
}
},
methods: {
inputChange(content) {
this.$nextTick(() => {
console.log("code:" + this.code);
console.log("content:" + content)
});
},
},
}
</script>
在上边这个例子中,你已经能够在页面中展示编辑器,并且很容易地获取到用户输入。
在这里笔者做一个小提示,v-model
是vue的语法糖,vue将v-model的值设置到对应组件的value属性上,并在这个组件上设置一个input事件的监听,将input事件返回的数据绑定到v-model的值上。实际上cm设置数据是执行cmInstance.setValue(value)
这个方法,vue-codemirror在组件初始化时,从code/value/content属性中获取数据,并且绑定cm的change
方法,在cm的内容发生改变时,抛出一个input事件,附带的值通过cmInstance.getValue()
方法得到。
因此,你可以使用下面两种代码,得到的效果和上边的代码一致。
分离写入和读取,不使用vue-codemirror自带的value绑定特效:
<template>
<div>
<!-- 下面的:code可以用:value或:content代替 -->
<codemirror
ref="cm"
:code="code"
:options="cmOptions"
@input="inputChange"
></codemirror>
</div>
</template>
<script>
// 全局引入vue-codemirror
import {codemirror} from 'vue-codemirror';
// 引入css文件
import 'codemirror/lib/codemirror.css'
// 引入主题 可以从 codemirror/theme/ 下引入多个
import 'codemirror/theme/idea.css'
// 引入语言模式 可以从 codemirror/mode/ 下引入多个
import 'codemirror/mode/sql/sql.js';
export default {
name: 'Show',
components: {codemirror},
data() {
return {
code: 'select a from table1 where b = 1',
cmOptions: {
// 语言及语法模式
mode: 'text/x-sql',
// 主题
theme: 'idea',
// 显示函数
line: true,
lineNumbers: true,
// 软换行
lineWrapping: true,
// tab宽度
tabSize: 4,
}
}
},
methods: {
inputChange(content) {
this.code = content;
this.$nextTick(() => {
console.log("code:" + this.code);
console.log("content:" + content)
});
},
},
}
</script>
使用cm原生方式,在mounted阶段获得原生cm,绑定change效果:
<template>
<div>
<!-- 下面的:code可以用:value或:content代替 -->
<codemirror
ref="cm"
:code="code"
:options="cmOptions"
@input="inputChange"
></codemirror>
</div>
</template>
<script>
// 全局引入vue-codemirror
import {codemirror} from 'vue-codemirror';
// 引入css文件
import 'codemirror/lib/codemirror.css'
// 引入主题 可以从 codemirror/theme/ 下引入多个
import 'codemirror/theme/idea.css'
// 引入语言模式 可以从 codemirror/mode/ 下引入多个
import 'codemirror/mode/sql/sql.js';
export default {
name: 'Show',
components: {codemirror},
data() {
return {
code: 'select a from table1 where b = 1',
cmOptions: {
// 语言及语法模式
mode: 'text/x-sql',
// 主题
theme: 'idea',
// 显示函数
line: true,
lineNumbers: true,
// 软换行
lineWrapping: true,
// tab宽度
tabSize: 4,
}
}
},
methods: {
inputChange(content) {
this.$nextTick(() => {
console.log("code:" + this.code);
console.log("content:" + content)
});
},
},
mounted() {
this.$refs.cm.codemirror.on("change", (cm) => {
this.code = cm.getValue();
})
}
}
</script>
2. CodeMirror高级功能
在第一节中,笔者展示了codemirror的简单使用。一般来说,在项目初期上边的简单使用已经足够了,但随着项目的发展,一般会要求在编辑器中增加一些特殊功能,例如高亮正在编辑行、搜索和替换功能、自动提示功能、样式调整等等。
cm在其官网对大多数附加高级功能都有简单介绍,笔者梳理了cm官网上的文档,对其中常用的高级功能进行了尝试。下面是汇总好的使用示例,大家可以根据自己的需要进行调整。
<template>
<div>
<codemirror
ref="cm"
v-model="code"
:options="cmOptions"
@input="inputChange"
></codemirror>
</div>
</template>
<script>
// 全局引入vue-codemirror
import {codemirror} from 'vue-codemirror';
// 引入css文件
import 'codemirror/lib/codemirror.css'
// 引入主题 可以从 codemirror/theme/ 下引入多个
import 'codemirror/theme/idea.css'
// 引入语言模式 可以从 codemirror/mode/ 下引入多个
import 'codemirror/mode/sql/sql.js';
// 搜索功能
// find:Ctrl-F (PC), Cmd-F (Mac)
// findNext:Ctrl-G (PC), Cmd-G (Mac)
// findPrev:Shift-Ctrl-G (PC), Shift-Cmd-G (Mac)
// replace:Shift-Ctrl-F (PC), Cmd-Alt-F (Mac)
// replaceAll:Shift-Ctrl-R (PC), Shift-Cmd-Alt-F (Mac)
import 'codemirror/addon/dialog/dialog.css'
import 'codemirror/addon/dialog/dialog'
import 'codemirror/addon/search/searchcursor'
import 'codemirror/addon/search/search'
import 'codemirror/addon/search/jump-to-line'
import 'codemirror/addon/search/matchesonscrollbar'
import 'codemirror/addon/search/match-highlighter'
// 代码提示功能 具体语言可以从 codemirror/addon/hint/ 下引入多个
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/sql-hint';
// 高亮行功能
import 'codemirror/addon/selection/active-line'
import 'codemirror/addon/selection/selection-pointer'
// 调整scrollbar样式功能
import 'codemirror/addon/scroll/simplescrollbars.css'
import 'codemirror/addon/scroll/simplescrollbars'
// 自动括号匹配功能
import 'codemirror/addon/edit/matchbrackets'
// 全屏功能 由于项目复杂,自带的全屏功能一般不好使
import 'codemirror/addon/display/fullscreen.css'
import 'codemirror/addon/display/fullscreen'
// 显示自动刷新
import 'codemirror/addon/display/autorefresh'
// 多语言支持?
import 'codemirror/addon/mode/overlay'
import 'codemirror/addon/mode/multiplex'
// 代码段折叠功能
import 'codemirror/addon/fold/foldcode'
import 'codemirror/addon/fold/foldgutter'
import 'codemirror/addon/fold/foldgutter.css'
import 'codemirror/addon/fold/brace-fold'
import 'codemirror/addon/fold/comment-fold'
import 'codemirror/addon/fold/xml-fold.js';
import 'codemirror/addon/fold/indent-fold.js';
import 'codemirror/addon/fold/markdown-fold.js';
import 'codemirror/addon/fold/comment-fold.js';
// merge功能
import 'codemirror/addon/merge/merge.css'
import 'codemirror/addon/merge/merge'
// google DiffMatchPatch
import DiffMatchPatch from 'diff-match-patch'
// DiffMatchPatch config with global
window.diff_match_patch = DiffMatchPatch;
window.DIFF_DELETE = -1;
window.DIFF_INSERT = 1;
window.DIFF_EQUAL = 0;
export default {
name: 'Show',
components: {codemirror},
data() {
return {
code: 'select a from table1 where b = 1',
cmOptions: {
// 语言及语法模式
mode: 'text/x-sql',
// 主题
theme: 'idea',
// 显示函数
line: true,
lineNumbers: true,
// 软换行
lineWrapping: true,
// tab宽度
tabSize: 4,
// 代码提示功能
hintOptions: {
// 避免由于提示列表只有一个提示信息时,自动填充
completeSingle: false,
// 不同的语言支持从配置中读取自定义配置 sql语言允许配置表和字段信息,用于代码提示
tables: {
"table1": ["c1", "c2"],
},
},
// 高亮行功能
styleActiveLine: true,
// 调整scrollbar样式功能
scrollbarStyle: 'overlay',
// 自动括号匹配功能
matchBrackets: true
}
}
},
methods: {
inputChange(content) {
this.$nextTick(() => {
console.log("code:" + this.code);
console.log("content:" + content)
});
},
},
mounted() {
// 代码提示功能 当用户有输入时,显示提示信息
this.$refs.cm.codemirror.on('inputRead', cm => {
cm.showHint();
})
}
}
</script>
3. CodeMirror自定义代码提示
前两节介绍了cm的基本用法和高级功能,但随着项目的发展,有时候需要更进一步的定制才能满足需求。接下来介绍如何实现自定义代码提示。
3.1 自定义hint方法
在methods中自定义代码实现方法:
/**
使用自定义hint,网上没有详细的讲解,这里着重讲一下。
1. 第一个入参cmInstance指的是codeMirror实例,第二个是配置中的的hintOptions值。
2. 从cmInstance中getCursor指的是获取光标实例,光标实例里有行数、列数。
3. 可以从cmInstance的getLine方法里传入一个行数,从而获取行中的字符串。
4. token对象是cmInstance对光标所在字符串进行提取处理,从对应语言的类库中判断光标所在字符串的类型,方便hint提示。token中包含start、end、string、type等属性,start和end指的是光标所在字符串在这一行的起始位置和结束位置,string是提取的字符串,type表示该字符串是什么类型(keyword/operator/string等等不定)
5. 下面方法中返回的结果体意思是:下拉列表中展示hello和world两行提示,from和to表示当用户选择了提示内容后,这些提示内容要替换编辑区域的哪个字符串。方法中的代码含义是替换token全部字符串。
*/
handleShowHint(cmInstance, hintOptions) {
let cursor = cmInstance.getCursor();
let cursorLine = cmInstance.getLine(cursor.line);
let end = cursor.ch;
let start = end;
let token = cmInstance.getTokenAt(cursor)
console.log(cmInstance, cursor, cursorLine, end, token)
// console.log(hintOptions.tables)
// return hintOptions.tables;
return {
list: ["hello","world"],
from: {ch: token.start, line: cursor.line},
to: {ch: token.end, line: cursor.line}
};
}
接下来修改配置文件中的hintOptions
属性,增加hint属性,并指向实现方法:
{ // 省略其他配置项...
hintOptions: {
completeSingle: false,
hint: this.handleShowHint
}
}
3.2 自定义hint展示内容
自定义代码提示内容后,如果想让弹出的内容与实际插入内容不一样,则需要将返回结果进行调整。这里有一个示例,插入内容是英文,展示内容是中文。
在methods中新增方法:
handleShowHint2(cmInstance, hintOptions) {
let cursor = cmInstance.getCursor();
let cursorLine = cmInstance.getLine(cursor.line);
let end = cursor.ch;
let start = end;
let token = cmInstance.getTokenAt(cursor)
console.log(cmInstance, cursor, cursorLine, end, token)
return {
list: [{
text: "hello",
displayText: "你好呀",
displayInfo: "提示信息1",
render: this.hintRender
}, {
text: "world",
displayText: "世界",
displayInfo: "提示信息2",
render: this.hintRender
}],
from: {
ch: token.start, line: cursor.line
},
to: {
ch: token.end, line: cursor.line
}
}
},
hintRender(element, self, data) {
let div = document.createElement("div");
div.setAttribute("class", "autocomplete-div");
let divText = document.createElement("div");
divText.setAttribute("class", "autocomplete-name");
divText.innerText = data.displayText;
let divInfo = document.createElement("div");
divInfo.setAttribute("class", "autocomplete-hint");
divInfo.innerText = data.displayInfo;
div.appendChild(divText);
div.appendChild(divInfo);
element.appendChild(div);
}
增加样式调整:
<style>
.autocomplete-div {
display: inline-block;
width: 100%;
}
.autocomplete-name {
display: inline-block;
}
.autocomplete-hint {
display: inline-block;
float: right;
color: #0088ff;
margin-left: 1em;
}
</style>
最终的效果如下:
此段功能参考了phpmyadmin中的用法:
3.3 异步返回hint结果
cm提供了一种异步hint的功能,如果我们的数据来自后端,那这个功能就用的上了。具体使用方式如下:
设置hint配置:
{ // 省略其他配置项...
hintOptions: {
completeSingle: false,
hint: this.handleShowHint,
async: true
}
}
实现自定义hint:
handleShowHint3(cmInstance, hintOptions) {
let cursor = cmInstance.getCursor();
let cursorLine = cmInstance.getLine(cursor.line);
let end = cursor.ch;
let start = end;
let token = cmInstance.getTokenAt(cursor)
console.log(cmInstance, cursor, cursorLine, end, token)
// console.log(hintOptions.tables)
// return hintOptions.tables;
// 返回一个promise即可
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
list: ["hello", "world"],
from: {ch: token.start, line: cursor.line},
to: {ch: token.end, line: cursor.line}
})
}, 2000);
})
}
5. CodeMirror命令API
第二节中我们使用到了cm自带的搜索功能,它虽然默认指定了快捷键,如果你想要自行触发这些功能,cm提供了命令API可以帮助你实现想法。
具体命令见:https://codemirror.net/doc/manual.html#commands
实际调用方式:
methods:{
find(){
this.$refs.cm.comdemirror.execCommand("find")
}
}
4. 特殊用法和踩过的坑
4.1 自动高度
codemirror默认的高度是300px,如果想要调整默认高度,可以在mounted方法中增加下面一段代码,这段代码的含义是调整cm高度为(当前浏览器高度-200)px,并且在窗口发生变化时,重新再做出调整。
this.$refs.cm.codemirror.setSize("auto", (document.documentElement.clientHeight - 200) + "px")
this.$nextTick(() => {
window.addEventListener('resize', () => {
//监听浏览器窗口大小改变
//浏览器变化执行动作
this.$refs.cm.codemirror.setSize("auto", (document.documentElement.clientHeight - 200) + "px")
});
})
4.2 只读模式
在官方文档里提示调整options中的readOnly参数便可以设置为只读,但实际上如果设置值为true后,用户还能在浏览器中看到光标闪烁,如果希望页面上不能编辑,则将该值设置为'nocursor'即可。
但如果设置了'nocursor',那么任何人将无法选中代码,也无法右键复制。如果还想支持选择和复制,那么需要用到以下代码:
this.$refs.cm.codemirror.setOption("readOnly",true)
// 不设的话,默认是530
this.$refs.cm.codemirror.setOption("cursorBlinkRate",-1)
4.3 tab转空格(2021-01-09补充)
如果在新的一行直接使用tab键,大概率会输入一个制表符,但如果从上一行敲回车进入下一行,却默认是空格。这样的逻辑让使用者深恶痛绝,如何让tab键也变成空格呢?在配置json中增加下面配置,既可实现两者逻辑统一。
indentUnit:4,
extraKeys: {
Tab: (cm) => {
// 存在文本选择
if (cm.somethingSelected()) {
// 正向缩进文本
cm.indentSelection('add');
} else {
// 无文本选择
//cm.indentLine(cm.getCursor().line, "add"); // 整行缩进 不符合预期
// 光标处插入 indentUnit 个空格
//console.log(cm.getOption("tabSize"),cm.getOption("indentUnit"))
cm.replaceSelection(Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");
}
},
"Shift-Tab": (cm) => {
// 反向缩进
if (cm.somethingSelected()) {
// 反向缩进
cm.indentSelection('subtract');
} else {
// cm.indentLine(cm.getCursor().line, "subtract"); // 直接缩进整行
const cursor = cm.getCursor();
// 光标回退 indexUnit 字符
cm.setCursor({line: cursor.line, ch: cursor.ch - cm.getOption("indentUnit")});
}
return;
},
}
5. 小结
codemirror是业界使用很广泛的前端代码编辑器,它的功能很强大。也是因为它功能强大,导致了很多高级功能需要进行独特的配置,如果只看官方文档,一时半会也无法参透其中的含义。因此笔者将其中常用的内容整理出来,方便大家学习参考。
你好,请教下这个自定义提示功能定义完成之后,只会提示自己定义的,如果还想提示官方原有的,应该怎么做呢?
如果启用自定义提示功能,相当于替换掉了原有的官方提示。如果你还想提示官方内容,那就查找相关源码,把里面的提示内容复制到你的自定义提示里面。
请问search中 (Use /re/ syntax for regexp search)这个提示可以修改吗
可是codemirror是npm install的,如何提取到本地,我试了,下载包,但是找不到对应的js文件,感觉应该是src目录下面的codemirror.js,但是引入到项目中,会报错
安装时需要注意版本,version5和6目录结构是不一样的
你好,请问下在父组件中调用子组件codemirror,动态的添加了很多的编辑器页面,在实际取值的时候,数据会乱,请问有什么标识可以做吗,而不是通过循环的index
你好博主,文章中代码块的主题很棒!猜测用的CodeMirror编辑器的只读模式吧?是的话具体叫idea,还是Darcula吗?