一、安装
基础的安装只需要两个:
npm install monaco-editor -S
npm install monaco-editor-webpack-plugin -D
作为扩展汉化,也可以继续安装另外三个:
npm install monaco-editor-nls -S
//或者使用npm install --save-dev monaco-editor-locales-plugin
npm install monaco-editor-esm-webpack-plugin -D
npm install js-beautify -S
monaco-editor:是网页编辑器的核心包,整体非常大,因为支持了很多的语言与很多的扩展功能。monaco-editor-webpack-plugin:因为monaco-editor直接单独引入的情况下所支持的基本使用不能满足我们的需求,我们还需要支持智能提示等功能,所以需要额外单独做一些配置操作,虽然官网文档说明已经很清晰,但是配置起来还是不免比较繁琐,因此提供了这个webpack插件来帮助我们自动处理这些事情,简化我们的操作。monaco-editor-nls:是对整个编辑器的汉化处理,如果需要进行汉化,那么需要安装此包。monaco-editor-locales-plugin: 也是用来汉化的插件。monaco-editor-esm-webpack-plugin:针对汉化包所做的webpack插件,需要和汉化包配合使用。js-beautify:是用来做代码美化的,主要是做一些格式化的工作。
安装完成之后,就可以配置到我们的项目中进行应用啦。
二、配置
首先要在vue.config.js这个文件中引入插件,并设置相应的配置项。
以下为在该文件内需要配置的内容:
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const MonacoLocalesPlugin = require('monaco-editor-locales-plugin');
module.exports = {
configureWebpack: {
entry: {
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js'
},
plugins: [
new MonacoWebpackPlugin({
languages: ["json", "xml", "yaml", "python", 'java'],
features: ["coreCommands", "find", "format", "folding", 'smartSelect', 'snippets', 'suggest', 'hover']
}),
new MonacoLocalesPlugin({
//设置支持的语言
languages: ["es", "zh-cn"],
//默认语言
defaultLanguage: "zh-cn",
//打印不匹配的文本
logUnmatched: false,
//自定义文本翻译
mapLanguages: { "zh-cn": { "Peek References": "查找引用", "Go to Symbol...": "跳到变量位置", "Command Palette": "命令面板" } }
})
]
}
}
在模板内定义编辑器的元素,且该元素内不能有子元素。
<template>
<div class="monaco-editor" ref="editor">
</div>
</template>
以上代码适用于注册新的语言以及增加已有语言的语法规则的高亮主题的方法。(未完待续)接着实例化编辑器,此时可以在页面中进行使用。第一个参数为要挂载的元素,第二个参数为配置选项。实例化代码及具体配置如下:
//const beautify = require('js-beautify')
//import { setLocaleData } from 'monaco-editor-nls'
//import zh_CN from 'monaco-editor-nls/locale/zh-hans'
//setLocaleData(zh_CN)
//const monaco = require('monaco-editor')
//const beautify_js = beautify.js
//const beautify_css = beautify.css
//const beautify_html = beautify.html
import * as monaco from "monaco-editor";
this.editor = monaco.editor.create(this.$refs.editor, {
acceptSuggestionOnCommitCharacter: true, // 接受关于提交字符的建议
acceptSuggestionOnEnter: 'on', // 接受输入建议 "on" | "off" | "smart"
accessibilityPageSize: 10, // 辅助功能页面大小 Number 说明:控制编辑器中可由屏幕阅读器读出的行数。警告:这对大于默认值的数字具有性能含义。
accessibilitySupport: 'on', // 辅助功能支持 控制编辑器是否应在为屏幕阅读器优化的模式下运行。
autoClosingBrackets: 'always', // 是否自动添加结束括号(包括中括号) "always" | "languageDefined" | "beforeWhitespace" | "never"
autoClosingDelete: 'always', // 是否自动删除结束括号(包括中括号) "always" | "never" | "auto"
autoClosingOvertype: 'always', // 是否关闭改写 即使用insert模式时是覆盖后面的文字还是不覆盖后面的文字 "always" | "never" | "auto"
autoClosingQuotes: 'always', // 是否自动添加结束的单引号 双引号 "always" | "languageDefined" | "beforeWhitespace" | "never"
autoIndent: 'None', // 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
automaticLayout: true, // 自动布局
codeLens: false, // 是否显示codeLens 通过 CodeLens,你可以在专注于工作的同时了解代码所发生的情况 – 而无需离开编辑器。 可以查找代码引用、代码更改、关联的 Bug、工作项、代码评审和单元测试。
codeLensFontFamily: '', // codeLens的字体样式
codeLensFontSize: 14, // codeLens的字体大小
colorDecorators: false, // 呈现内联色彩装饰器和颜色选择器
comments: {
ignoreEmptyLines: true, // 插入行注释时忽略空行。默认为真。
insertSpace: true // 在行注释标记之后和块注释标记内插入一个空格。默认为真。
}, // 注释配置
contextmenu: true, // 启用上下文菜单
columnSelection: false, // 启用列编辑 按下shift键位然后按↑↓键位可以实现列选择 然后实现列编辑
autoSurround: 'never', // 是否应自动环绕选择
copyWithSyntaxHighlighting: true, // 是否应将语法突出显示复制到剪贴板中 即 当你复制到word中是否保持文字高亮颜色
cursorBlinking: 'Solid', // 光标动画样式
cursorSmoothCaretAnimation: true, // 是否启用光标平滑插入动画 当你在快速输入文字的时候 光标是直接平滑的移动还是直接"闪现"到当前文字所处位置
cursorStyle: 'UnderlineThin', // "Block"|"BlockOutline"|"Line"|"LineThin"|"Underline"|"UnderlineThin" 光标样式
cursorSurroundingLines: 0, // 光标环绕行数 当文字输入超过屏幕时 可以看见右侧滚动条中光标所处位置是在滚动条中间还是顶部还是底部 即光标环绕行数 环绕行数越大 光标在滚动条中位置越居中
cursorSurroundingLinesStyle: 'all', // "default" | "all" 光标环绕样式
cursorWidth: 2, // <=25 光标宽度
minimap: {
enabled: false // 是否启用预览图
}, // 预览图设置
folding: true, // 是否启用代码折叠
links: true, // 是否点击链接
overviewRulerBorder: false, // 是否应围绕概览标尺绘制边框
renderLineHighlight: 'gutter', // 当前行突出显示方式
roundedSelection: false, // 选区是否有圆角
scrollBeyondLastLine: false, // 设置编辑器是否可以滚动到最后一行之后
readOnly: false, // 是否为只读模式
theme: 'vs'// vs, hc-black, or vs-dark
})
以上配置并不是全部配置,若有缺失,需要自己再补充。
三、高级功能
1. 自定义语言高亮主题
const monaco = require('monaco-editor')
monaco.languages.register({ id: 'properties', aliases: ['properties'] });
monaco.languages.register({ id: 'yaml1', aliases: ['YAML', 'yaml', 'YML', 'yml', "yaml1"], mimetypes: ["application/x-yaml", "text/x-yaml"]});
monaco.languages.setMonarchTokensProvider('properties', {
ignoreCase: true,
tokenizer: {
root: [
[/^\s*(#).*/, 'notes'],
//每对小括号代表一个分词
[/(.*\s*)(\s*=\s*)(\w*[^\s]*)(\s*[#]*\w*)/, ['key', 'sym', 'value', 'comment']]
]
}
});
monaco.languages.setMonarchTokensProvider('yaml1', {
ignoreCase: true,
tokenizer: {
root: [
[/^\s*(#).*/, 'comment'],
[/(\w*)(\s*:\s*)(\w*[^\s]*)(\s*[#]*\w*)/, ['key', 'sym', 'value', 'comment']]
]
}
});
monaco.editor.defineTheme("newTheme", {
base: 'vs',
inherit: false,
rules: [
{ token: 'key', foreground: '#0000FF', fontStyle: 'bold' },
{ token: 'sym', foreground: '#f5a623', },
{ token: 'value', foreground: '#4EC9B0' },
{ token: "notes", foreground: "#6A9955" },
{ token: "comment", foreground: "#6A9955" },
]
});
以上代码适用于注册新的语言以及增加已有语言的语法规则的高亮主题的方法。
首先需要通过monaco.languages.register注册新的语言,其主要参数为:
id:新增的语言的keyaliases:别名,注册的新语言的别名extensions:扩展,其他语言的id可以写在这里,会被识别为新的语言,可以用来覆盖原有语言的分词方式
然后通过monaco.languages.setMonarchTokensProvider设置Monarch分词器,其主要参数为:
ignoreCase:忽略分词器的大小写tokenizer:分词器,通过正则表达式可以划分monaco-editor每行的词条。其中属性root可以上面的代码方式进行分词。
最后通过monaco.editor.defineTheme设置语言的主题,相当于通过规则设置已分词汇的背景色,字体颜色等。其主要参数为:
base:要继承的基础主题,即内置的三个:vs、vs-dark、hc-blackinherit:是否继承rules:高亮规则,即给代码里不同token类型的代码设置不同的显示样式,常见的token有string(字符串)、comment(注释)、keyword(关键词)等等。查找样式可以通过按F1或鼠标右键点击Command Palette,然后再找到并点击Developer: Inspect Tokens,接下来鼠标点哪一块代码,就会显示对应的信息,包括token类型,当前应用的颜色等。colors:非代码部分的其他部分的颜色,比如背景、滚动条等
高阶些的自定义主题用法可以查看该链接文章:闲谈Monaco Editor-自定义语言之Monarch
2. 右键菜单自定义
addRMenu({ id, label, keybindings, MenuGroup, MenuOrder }, fn) {
let disposable = this.editor.addAction({
id,
label,
keybindings: keybindings || [],
contextMenuGroupId: MenuGroup === undefined ? 'navigation' : MenuGroup,
contextMenuOrder: MenuOrder || 1,
run: fn
})
this.collectDispose(disposable);
}
this.editor.addRMenu(
{
id: ActionsIds.javaOrganizeImports,
keybindings: [monaco.KeyMod.CtrlCmd|monaco.KeyMod.Alt|monaco.KeyCode.KeyF],
label: "重新组织导入",
},
this.organizeImports
);
可以在右键面板中增加自定义功能:
id:该选项的唯一标识。
label:选项显示的名称。
keybindings:绑定的快捷键,多个快捷键用竖线分割。每个按键要使用monaco的内置枚举类型来设定。
contextMenuGroupId:选项所属组的id,内置了三个组id(navigation:该组是右键的第一栏,1_modification:修改组,9_cutcopypaste:剪切复制粘贴组)。
run:选择该选项之后的回调函数,第一个参数为editor实例,第二个参数为一个剩余参数的数组,注意如果通过快捷键触发那么第二个参数不存在。
3. 代码自动补全
monaco.languages.registerCompletionItemProvider('xml',
{
triggerCharacters: ['>'],
provideCompletionItems: (model, position) => {
const codePre = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const tag = codePre.match(/.*<(\w+)>$/)?.[1];
if (!tag) {
return;
}
const word = model.getWordUntilPosition(position);
return {
suggestions: [
{
label: `</${tag}>`,
kind: monaco.languages.CompletionItemKind.EnumMember,
insertText: `$1</${tag}>`,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range: {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
},
},
],
//dispose() {
// const line = pos.lineNumber
// const column = pos.column
// if(model.getValueInRange(new monaco.Range(line, column - 1, line, column)) !== "/") {
// return
// }
// editor.executeEdits("", [
// {
// range: new monaco.Range(line, column - 1, line, column),
// text: null
// }
// ])
}
}
};
},
});
以上代码是在识别为xml语言时,在输入'>'后会开启自动补全功能。其中:
triggerCharacters: 表示触发补全操作的快捷键,编辑器会提示出suggestions中设置的内容。注意回车之后输入的快捷键仍然是存在的。
suggestions中的label表示提示的名称,insertText表示回车之后输入的内容,kind用来设定提示片段的类型,即最前面的图标标识,detail是提示的说明,位于提示行的末尾。
dispose回调函数: 有时候输入的快捷键并不是实际需要的代码,补全代码后需要删除掉,这时就需要这个回调函数来删除掉已输入的快捷键。可以通过editor.executeEdits在指定位置插入代码,替换成空字符串。
4.冲突代码合并功能
monaco.languages.registerCodeLensProvider('*', {
provideCodeLenses: function (model, token) {
let newCodeLens = [];
let editor = model.myEditor;
if(editor){
let content = model.getValue();
let contentSplit = content.split("\n");
let compareDatas = [];
//当前的所在的状态,start开始状态,split分层状态,end结束状态
let status = 'end';
//当前版本的内容
let versionContent = '';
//冲突块
let conflitBlock = {};
let versionContentCount = 0;
contentSplit.forEach((line, index) => {
let currentLine = index + 1;
if (line.startsWith('<<<<<<<')) {
conflitBlock = {
//信息提示的行号
startLine: currentLine,
//结束行
endLine: -1,
//本地版本内容
currentVersionContent: '',
//当前版本的总行数
currentVersionLineCount:-1,
//远程版本内容
incomingVersionContent: '',
//远程版本的总行数
incomingVersionLineCount: -1
}
status = 'start';
versionContent = '';
} else if (line.startsWith('>>>>>>>') && status === 'split') {
conflitBlock.incomingVersionContent = versionContent;
conflitBlock.endLine = currentLine;
conflitBlock.incomingVersionLineCount = versionContentCount;
versionContent = '';
versionContentCount = 0;
status = 'end';
compareDatas.push(conflitBlock);
} else if (line.startsWith('=======') && status === 'start') {
conflitBlock.currentVersionContent = versionContent;
conflitBlock.currentVersionLineCount = versionContentCount;
versionContent = '';
versionContentCount = 0;
status = 'split';
} else {
if (status === 'start' || status === 'split') {
versionContent = versionContent + line;
versionContentCount = versionContentCount + 1;
}
}
});
let codeDecorations = [];
if (compareDatas.length > 0) {
for (let compareData of compareDatas) {
let {startLine,endLine,currentVersionContent,currentVersionLineCount,incomingVersionContent,incomingVersionLineCount} = compareData;
//使用本地版本触发按钮
newCodeLens.push(createCodeLen('currentVersion','使用本地的版本',startLine,endLine,1,editor,currentVersionContent));
//使用远程版本按钮
newCodeLens.push(createCodeLen('incomingVersion','使用远程版本',startLine,endLine,2,editor,incomingVersionContent));
//合并两个版本按钮
newCodeLens.push(createCodeLen('bothVersion','同时使用两个版本',startLine,endLine,3,editor,currentVersionContent + "\r\n" + incomingVersionContent));
//当前版本的标签
codeDecorations.push(createMergeDecoration(startLine,startLine,'currentVersionTag'));
//当前版本的代码范围 开始行数 + 当前版本的总行数
codeDecorations.push(createMergeDecoration(startLine,startLine+currentVersionLineCount,'currentContentTag'));
//远程版本的代码范围 结束行数 - 远程行数的总行数
codeDecorations.push(createMergeDecoration(endLine-1,endLine-incomingVersionLineCount,'incomingContentTag'));
//远程版本的标签
codeDecorations.push(createMergeDecoration(endLine,endLine,'incomingVersionTag'));
}
}
let oldMergeDecorations = editor.oldMergeDecorations?editor.oldMergeDecorations:[];
// 返回的是一个decotation集,可以对期进行增删处理
var decorations = editor.deltaDecorations(
oldMergeDecorations,
codeDecorations
);
editor.oldMergeDecorations = decorations;
}
return {
lenses: newCodeLens,
dispose: () => { }
};
},
resolveCodeLens: function (model, codeLens, token) {
alert("condLens");
return codeLens;
}
});
四、常见API
1.editor.getValue()
获取编辑器中的所有文本,并生成一个字符串返回,会保留所有信息(换行、缩进、注释等等)。
2.editor.getSelection()
获取编辑器中被选中文案的 range ,返回一个对象,如下:
{
startLineNumber: 0,
startColumnNumber: 0,
endLineNumber: 0,
endColumnNumber: 0,
}
3.editor.getModel()
获取编辑器当前的 textmodel,一般不会直接使用,通过 textmodel 可以对文本各种操作。
4.editor.getModel().findMatches(str|regexp)
功能和 “⌘ + F” 一致,通过字符串或正则表达式查找编辑器内匹配的文本,并返回匹配文本 range 的集合。
5.editor.getModel().getValueInRange(range)
通过 range 获取范围内的文本,返回一个字符串。
6.editor.getModel().getLinesContent(lineNumber)
如果传入 lineNumber,则返回对应行的文本字符串,不传参则返回所有行的文本字符串的集合。
7.editor.executeEdits()
在指定位置插入代码,跟 editor.setValue() 不同的地方是,可以用 “⌘ + Z” 撤销输入。
editor.executeEdits('需要插入的代码/string', [
{
range: {
startLineNumber,
startColumn,
endLineNumber,
endColumn,
},
text,
},
])
8.editor.addAction()
在右键菜单里增加一栏自定义的操作。
this.editor.addAction({
id: '', // 菜单项 id
label: '', // 菜单项名称
keybindings: [this.monaco.KeyMod.CtrlCmd | this.monaco.KeyCode.KEY_J], // 绑定快捷键
contextMenuGroupId: '9_cutcopypaste', // 所属菜单的分组
run: () => {}, // 点击后执行的操作
})
9.monaco.editor.setModelMarkers()
在编辑器中用波浪线标出错误提示。
monaco.editor.setModelMarkers(editor.getModel(), 'owner', [
{
startLineNumber,
startColumn,
endLineNumber,
endColumn,
message, // 提示文案
severity: monaco.MarkerSeverity.Error, // 提示的类型
},
])
10.editor.getAction('actions.xxx').run()
手动调用monaco方法。
//实现查找效果
const model = editor.getModel();
const range = model.findMatches('匹配的字符串或者正则表达式')[0].range;
editor.setSelection(range);
editor.getAction('actions.find').run();
//实现拷贝效果
editor.getAction('editor.action.clipboardCopyAction').run();
//实现复制效果
var selection = editor.getSelection();
var content = localStorage.getItem('your-content');
editor.executeEdits("", [
{
range: new monaco.Range(selection.endLineNumber, selection.endColumn, selection.endLineNumber, selection.endColumn),
text: content
}
])