最近接到一个项目中增加在线代码编辑器的功能需求,经过对比(其实算是半指定),选择了monaco-editor,这个库跟VSCode的源码高度重合,所以本身功能是比较强大的,包含自定义主题、自定义提示、设置代码对比、多文件编辑、代码信息提示等功能。但是目前项目中暂时不需要比较复杂的功能,主要功能需求点就是获取指定函数的函数体,添加代码注释及提示以及部分行禁用的功能,以下进行简单介绍以及遇到的踩坑点。
项目采用Vue + TS, 使用的monaco-editor及Vue的版本为:
"vue": "^3.4.19",
"monaco-editor": "0.30.1","typescript": "^5.3.3",
1. 安装
yarn 或者 npm 或者pnpm
2. 引入(使用前准备)
import * as monaco from 'monaco-editor';
集成到项目中的时候控制台报了一个错误,关于getWorker报错,在网上找的解决办法:
// 解决getWorker报错问题 Uncaught (in promise) Error: Unexpected usage at _EditorSimpleWorker.loadForeignModule
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';// @ts-ignoreself.MonacoEnvironment = {getWorker(_, label) {if (label === 'json') {return new jsonWorker();}if (label === 'css' || label === 'scss' || label === 'less') {return new cssWorker();}if (label === 'html' || label === 'handlebars' || label === 'razor') {return new htmlWorker();}if (label === 'typescript' || label === 'javascript') {return new tsWorker();}return new editorWorker();},};
3. 使用
1. 编辑器结构(可支持展示多个编辑器,进行分别输入、取值等)
// 如果仅需一个编辑器
<div id="container" class="app-monaco"></div>
// 如果项目中需要多个编辑器,可以传入对应的id(不可相同)
<div :id="containerId" class="app-monaco"></div>// monaco 实例
const editor = ref<monaco.editor.IStandaloneCodeEditor | null>(null);
// 因为会存在一个页面上有两个monaco-editor实例的问题,所以动态展示id
const containerId = ref(props.containerTitle ?? 'container');
2. 配置项(可根据项目需要进行选择)
// monaco editor配置const monacoEditorConfig = ref({automaticLayout: true, // 自动布局,编辑器自适应大小theme: 'vs-dark', // 官方自带三种主题vs, hc-black, or vs-darkminimap: {enabled: true, // 是否开启右侧代码小窗},folding: true, // 是否折叠foldingHighlight: true, // 折叠等高线foldingStrategy: 'auto', // 折叠方式showFoldingControls: 'always', // 是否一直显示折叠disableLayerHinting: true, // 等宽优化emptySelectionClipboard: false, // 空选择剪切板selectionClipboard: false, // 选择剪切板codeLens: true, // 代码镜头scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕colorDecorators: true, // 颜色装饰器accessibilitySupport: 'on', // 辅助功能支持"auto" | "off" | "on"selectOnLineNumbers: true, //显示行号lineNumbers: 'on', // 行号 取值: "on" | "off" | "relative" | "interval" | functionlineNumbersMinChars: 4, // 行号最小字符 numberenableSplitViewResizing: false,readOnly: false, //是否只读 取值 true | falsefontSize: 18,cursorStyle: 'line', //光标样式glyphMargin: true, //字形边缘useTabStops: false,autoIndent: true, //自动布局quickSuggestionsDelay: 100, //代码提示延时dropIntoEditor: {enabled: false, // 能否把文字拖拽进编辑器},});
3. 创建编辑器
// 初始化manoca编辑器const initEditor = () => {const config = Object.assign({}, monacoEditorConfig.value, {language: props.language,value: textValue.value,});// 创建 monaco 实例editor.value = monaco.editor.create(document.getElementById(containerId.value)!, config);};
4. 编辑器取值事件(通过onDidChangeCursorPosition方法,通过onChange函数暴露出去)
// 编辑器改变的回调,用来取方法内的值
// 编辑器内容change事件
editor.value.onDidChangeModelContent((event: any) => {// @ts-ignore// 触发父组件的 change 事件,通知编辑器内容变化(直接获取编辑器内容)// props.onChange?.(rawEditor.getValue());// 只取方法内的内容const content = getLineVal();props.onChange?.(content);
}
5. 获取值(getValue方法)
editor.value.getValue();
6.设置值(setValue方法)
editor.value.setValue('123');
7. 获取指定范围的内容(主要是通过确定对应的行号及列号,getValueInRange方法)
const getLineVal = () => {const model = toRaw(editor.value)?.getModel();// 获取行数const lineNum = model?.getLineCount();let value;if (lineNum) {// 去掉注释以及函数头的行,再去掉最后返回值以及大括号的行value = model?.getValueInRange({startLineNumber: 11,startColumn: 1,endLineNumber: lineNum - 1,endColumn: 1,});}return value;};
8. 如果父组件有禁用事件,动态修改编辑器是否可用(updateOptions方法)
// 监听disabled变化,设置禁用watchEffect(() => {if (props.disabled) {toRaw(editor.value)?.updateOptions({ readOnly: true });}
});
9. 编辑器重新布局(layout方法)
// 当容器尺寸发生变化的时候(例如:浏览器 resize),需要通过 layout 接口让 MonacoEditor 重新计算布局// 编辑器 resizeconst layout = () => {if (editor.value) {toRaw(editor.value)?.layout();}};// 可以加个防抖
const debounedLayout = debounce(layout, 500);// 重新计算尺寸
window.addEventListener('resize', debounedLayout);
10. 改变光标位置(setPosition方法)
const changePosition = () => {toRaw(editor.value)?.setPosition({ lineNumber: 1, column: 1 });toRaw(editor.value)?.focus();
};
11. 设置主题(setTheme方法)
// 编辑器设置主题 并非在实例上
const setEditorTheme = () => {monaco.editor.setTheme('vs');
};
12. 获取选中的代码(getSelection方法)
const getSelectionVal = () => {const selection: monaco.Selection | null | undefined = toRaw(editor.value)?.getSelection();console.log('===selection', selection);if (selection) {const { startLineNumber, endLineNumber, startColumn, endColumn } = selection;const model = toRaw(editor.value)?.getModel();return model?.getValueInRange({startLineNumber,startColumn,endLineNumber,endColumn,});}};
13. 向指定位置插入代码(executeEdits方法)
const insertCode = (code: string) => {const rawEditor = toRaw(editor.value);const model = rawEditor?.getModel();// 获取行数const lineNum = model?.getLineCount();if (lineNum) {// 计算范围,插入到第一行和最后一行中间const range = {startLineNumber: 2,startColumn: 1,endLineNumber: lineNum,endColumn: 1,};// 增加空格if (!code.startsWith(' ')) {code = ' ' + code;}// 增加三行空格,以免返回的数据没有空格,不好看// const textCode = [code, '', '', ''].join('\n');rawEditor?.executeEdits('', [{range: range, // 要修改的范围// text: textCode, // 要插入的内容text: code, // 要插入的内容forceMoveMarkers: true, // 是否强制移动光标},]);}};
14. 指定行设置禁用(就是改变光标的位置,只让它达到你的只读范围)
// 光标改变的回调,用来设置某些行不可用
editor.value.onDidChangeCursorPosition(function (e) {// 获取行数const lineNum = model?.getLineCount();// 前10行不可编辑if (e.position.lineNumber < 11) {rawEditor?.setPosition({lineNumber: 11,column: 1,});}// 后2行不可编辑if (e.position.lineNumber > lineNum - 2) {rawEditor?.setPosition({lineNumber: lineNum - 2,column: 1,});}});
15. 销毁编辑器实例
const destroyEditor = () => {if (editor.value == null) return;// 销毁编辑器toRaw(editor.value)?.dispose();editor.value = null;
};
4. 遇到的大坑
1. 可以看到,我的代码中有时会使用toRaw这个方法,是因为editor的实例在创建时被代理了,导致本身实例上的很多方法没有了,所以将其转为纯粹的变量,有些方法才可以使用,如果没遇到此问题的话,可以直接editor.(相关方法);
2. position或者range相关的,包含开始和结束的位置或者范围信息,包含开始,但是不包含结束,比如startLine是1,endLine是10,getValueRange时就是前9行,这个使用时注意下;
暂时就发现这些问题,以及基本的方法使用,后续需求更新时再加,希望对各位有点帮助~