MONACO EDITOR基本使用指南
Monaco Editor 是由 Microsoft 开发并开源的一款代码编辑器,它是 Visual Studio Code 的核心编辑器部分。同时还提供了丰富的 API,使得开发者可以根据自己的需求进行定制。Monaco Editor 有着强大的功能且可以满足各种奇葩产品的癖好。
但是由于官方文档过于精简,demo 看不懂的情况下,简单介绍一下 Monaco Editor 的基本使用。由于项目框架是vue2 + webpack,所以基础封装以 vue2 为主。
官网
官网的 Playground 可以在线跑 demo,以及提供的了很多功能点的 demo,比较容易理解。
官网的 Documentation 是 api 文档,可以通过快速搜索找到 api 的传参格式样例。
安装
安装monaco-editor
shell
npm install monaco-editor
yarn add monaco-editor
安装 monaco-editor-webpack-plugin,是一个用于简化 Monaco Editor 在 Webpack 构建系统中的集成的插件。Monaco Editor 由许多模块和语言特性组成,如果手动管理这些资源可能会非常复杂。使用这个插件,你可以更容易地将 Monaco Editor 集成到你的 Webpack 项目中。
shell
npm install monaco-editor-webpack-plugin
yarn add monaco-editor-webpack-plugin
- webpack.config.js
js
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
module.exports = {
// ...其他配置
plugins: [
new MonacoWebpackPlugin({
languages: ["javascript", "sql"], // 还有很多不同的语言
}),
],
};
monaco-editor-webpack-plugin 可以配置语言,只对需要的语言进行打包。
monaco-editor-webpack-plugin 的版本要和 monaco editor 匹配。
monaco-editor-webpack-plugin |
monaco-editor |
---|---|
7.*.* |
>= 0.31.0 |
6.*.* |
0.30.* |
5.*.* |
0.29.* |
4.*.* |
0.25.* , 0.26.* , 0.27.* , 0.28.* |
3.*.* |
0.22.* , 0.23.* , 0.24.* |
2.*.* |
0.21.* |
1.9.* |
0.20.* |
1.8.* |
0.19.* |
1.7.* |
0.18.* |
组件封装
- 引入monaco
js
import * as monaco from "monaco-editor";
- 创建Monaco Editor实例
js
this.editor = monaco.editor.create(this.$el, options);
- 监听内容变化更新 (v-model双向绑定)
js
this.editor.onDidChangeModelContent((event) => {
const value = this.editor.getValue();
if (this.value !== value) {
this.$emit("change", value, event);
}
});
常用的option配置
这里是所有的option配置项,但用上的不是很多而且没有翻译,这里列举一些我使用过的配置项。官方配置项文档
js
{
fontSize: 16, // 字体大小
automaticLayout: true, // 编辑器自适应页面大小
dropIntoEditor: {
enabled: false // 能否把文字拖拽进编辑器
},
value: '初始内容', // 编辑器内容
theme: 'vs' // 主题 'vs-dark', 'hc-black', 'hc-light',
language: 'javascript', // 语言
readOnly: true, // 只读
minimap: {
enabled: false, // 是否开启右侧代码小窗
}
}
自定义主题
Monaco Editor 支持自定义主题,官方案例
js
// 根据语言设置token
monaco.languages.setMonarchTokensProvider("自定义语言", {
tokenizer: {
root: [
[/[error.*/, "custom-error"],
[/[notice.*/, "custom-notice"],
[/[info.*/, "custom-info"],
[/[[a-zA-Z 0-9:]+]/, "custom-date"],
],
},
});
monaco.editor.defineTheme("自定义主题", {
base: "vs",
inherit: false,
rules: [
{ token: "custom-info", foreground: "808080" },
{ token: "custom-error", foreground: "ff0000", fontStyle: "bold" },
{ token: "custom-notice", foreground: "FFA500" },
{ token: "custom-date", foreground: "008800" },
],
colors: {
"editor.foreground": "#000000",
},
});
自定义快捷键,右键菜单
js
editor.addAction({
// action唯一的id.
id: "my-unique-id",
// 右键菜单栏显示的名字.
label: "My Label!!!",
// 绑定的按键
keybindings: [
monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10,
// 组合键
monaco.KeyMod.chord(
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK,
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyM
),
],
// 在右键菜单中的哪个分组
contextMenuGroupId: "navigation",
contextMenuOrder: 1.5,
// 触发的方法
run: function (ed) {
alert("i'm running => " + ed.getPosition());
},
});
代码补全
在初始化的时候注册语言的补全提示 registerCompletionItemProvider
。语言包关键词可以在node_modules/monaco-editor/esm/vs/basic_languages
中找到。
js
monaco.languages.registerCompletionItemProvider("sql", {
provideCompletionItems: async (model, position) => {
let suggestions = [];
const { lineNumber, column } = position;
const textBeforePointer = model.getValueInRange({
startLineNumber: lineNumber,
startColumn: 0,
endLineNumber: lineNumber,
endColumn: column,
});
/**
* 修改suggestion,suggestion就是代码提示
*
* 这里可以调用接口,拉去后端提供的提示
*/
return {
suggestions,
};
},
});
插入代码
可以通过 executeEdits
方法插入代码,可以直接替换range内的代码。
range
是代表光标位置的一个对象,可以通过 editor.getPosition()
来获得。也可以通过 editor.getSelection()
来获取选中部分的位置。
js
editor.executeEdits("", [
{
range: range, // 要修改的范围
text: code, // 要插入的内容
forceMoveMarkers: true, // 是否强制移动光标
},
]);
高亮部分代码
通过 createDecorationsCollection
方法来设置高亮代码,这里也需要一个range对象,来代表高亮的位置。注意,这里高亮是会跟随文字输入移动的,比如说你在高亮范围内换行了,新的一行也会继续高亮。
js
let decorations = editor.createDecorationsCollection([
{
range: new monaco.Range(startLine, 1, endLine, 1),
options: {
isWholeLine: true,
className: className,
},
},
]);
// 移除高亮
decorations.clear()
// 获得高亮的范围
decorations.getRanges()
插入空行
插入空行是指在两行中间增加一行不属于编辑器的一行。copilot
就是在输入的过程中,在后面多加几行,实现换行的效果。插入空行并不影响代码内容,一般是用来加个按钮啥的。
js
let viewZoneId = null;
editor.changeViewZones(function (changeAccessor) {
let domNode = document.createElement("div");
domNode.style.background = "lightgreen";
viewZoneId = changeAccessor.addZone({
afterLineNumber: 3, // 在哪一行后面增加空行
heightInLines: 3, // 空多少行
domNode: domNode,
});
});
changeViewZones
还有 removeZone(viewZoneId)
方法,可以删除空行。
插入额外元素
插入额外元素的demo和上面插入空行的是同一个,先创建一个 contentWidget
,然后通过 addContentWidget
方法插入到编辑器中。可以配合插入空行的方法,在编辑器任意位置插入一段话/一个按钮。
js
var contentWidget = {
domNode: (function () {
var domNode = document.createElement("div");
domNode.innerHTML = "My content widget";
return domNode;
})(),
getId: function () {
return "my.content.widget";
},
getDomNode: function () {
return this.domNode;
},
getPosition: function () {
return {
position: {
lineNumber: 7,
column: 8,
},
preference: [
monaco.editor.ContentWidgetPositionPreference.ABOVE,
monaco.editor.ContentWidgetPositionPreference.BELOW, // 这里也可以用EXECT, 这样元素会准确的放置在指定行。
],
};
},
};
editor.addContentWidget(contentWidget);
如果要移除元素,可以通过 editor.removeContentWidget(id)
来将元素移除。
注意,如果元素不在额外空行,而在编辑器内,这个元素是可以选中的,可以调节他的z-index,让他不影响用户使用。
实现一个copilot
掌握上述技巧后,实现一个copilot就非常简单了。
- 先通过
editor.getPosition()
获取光标位置。 editor.getValueInRange()
来获得光标前的内容,然后发送给gpt,获得建议的内容。- 然后在光标位置用
editor.changeViewZones()
后面插入空行,空行数就是gpt的行数。 - 在光标位置后面用
editor.addContentWidget()
插入元素,内容是gpt的建议内容。 - 增加action,按tab的时候把gpt的建议内容用
executeEdits
替换光标位置的内容。且同时把新增的空行和新增的元素移除。 - 完事
还有一种简单点的方法 基于Monaco Editor实现在线版Copilot
踩坑
- 在组件外更改了内容,不能 ctrl + z 回退咋办?
js
editor.pushUndoStop();
// 监听value,通过这个方法更改内容
editor.executeEdits("code", [
{
range: editor.getModel().getFullModelRange(), // full range
text: newValue, // target value here
},
]);
editor.pushUndoStop();
- 同一个页面内,不能存在两个 monaco editor, 只能设置同一种主题
Can not create two editor with different theme · Issue #1713 · microsoft/monaco-editor