目标
- Monaco Editor基础用法
- 配置自定义内容补全
概述
Monaco Editor 是 VS Code 底层的代码编辑器,开源协议是MIT,支持Edge、 Chrome、Firefox、Safari和Opera浏览器,但是不支持移动端浏览器或移动端框架
本文中涉及到的Monaco Editor版本号是0.27.0
安装
npm install monaco-editor@0.27.0
获取语言列表
monaco.languages.getLanguages()
返回一个语言列表数组,需要拿到的是其中的id字段
浏览器端创建指定语言编辑环境
<html>
<head>
<style>
#editor {
width: 700px;
height: 700px;
}
</style>
</head>
<body>
<div id="editor"></div>
<script>
var require = {
paths: {
'vs': '../min/vs'
}
};
</script>
<script src="../min/vs/loader.js"></script>
<script src="../min/vs/editor/editor.main.nls.js"></script>
<script src="../min/vs/editor/editor.main.js"></script>
<script>
require(['vs/editor/editor.main'], function () {
// "javascript"
const editor = monaco.editor.create(document.getElementById('editor'), {
model: null,
minimap: {
enabled: false
}
});
const newModel = monaco.editor.createModel(
`alert('hello');\nalert('hello');\nalert('hello');\nalert('hello');\nalert('hello')`,
"javascript"
);
editor.setModel(newModel);
})
</script>
</body>
</html>
monaco.editor.createModel方法的第二个参数就是指定语言,这个语言具体取值可以通过monaco.languages.getLanguages()获取到
ESM端创建
import React from 'react';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';
import 'monaco-editor/esm/vs/editor/contrib/find/findController.js';
import { defaultScript } from '../config/config';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
monaco.editor.create(document.getElementById('monaco'), {
value: defaultScript,
language: 'javascript'
});
}
render() {
return (
<div id='monaco' />
);
}
}
export default App;
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';用于引入语言包,import 'monaco-editor/esm/vs/editor/contrib/find/findController.js';用于引入搜索控件。当然上述功能可以用import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';替换,但缺点是打包体积过大
但是该方式存在问题,鼠标右键功能缺失,需要导入相应的web worker,但该部分目前根据官网的示例还未走通
vite环境下的使用
import React, { useEffect } from 'react';
import * as monaco from 'monaco-editor';
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';
self.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();
},
};
export default props => {
const [css] = useStyletron();
let instance;
useEffect(() => {
instance = monaco.editor.create(document.getElementById(`monaco-editor-ser-${props.id}`), {
value: 'var a=100;',
language: 'javascript',
});
}, []);
const showContent = () => {
alert(instance.getValue());
};
return (
<div>
<div id={`monaco-editor-ser-${props.id}`} />
<button onClick={showContent}>获取内容</button>
</div>
);
};
需要格外留意的是,还需要在vite的启动文件中用define字段配置process.env,否则会页面报错白屏
// 启动Vite
async function startVite() {
const vite = await createServer({
define: {
'process.env': {}, // 重要
},
plugins: [
...
],
server: {
...
},
});
await vite.listen();
}
自动补全
vite版
import React, { useEffect } from 'react';
import { useStyletron } from 'styletron-react';
import * as monaco from 'monaco-editor';
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';
self.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();
},
};
let instance;
export default props => {
const [css] = useStyletron();
const className = css({
height: '400px',
width: '400px',
margin: '20px',
border: '2px solid #aaa',
});
useEffect(() => {
instance = monaco.editor.create(document.getElementById(`monaco-editor-ser-${props.id}`), {
value: 'var a=100;',
language: 'javascript',
});
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems(model, position) {
const textUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const createComplete = _textUntilPosition => {
// 切割成数组
const words = _textUntilPosition.split(' ');
// 取当前输入值
const activeStr = words[words.length - 1];
return [{
label: 'wq',
kind: monaco.languages.CompletionItemKind.Keyword,
documentation: 'wq',
insertText: 'wq',
}];
};
const suggestions = createComplete(textUntilPosition);
return {
suggestions: [{
label: 'jack',
kind: monaco.languages.CompletionItemKind.Keyword,
documentation: 'jack',
insertText: 'jack',
}, {
label: 'tom',
kind: monaco.languages.CompletionItemKind.Keyword,
documentation: 'tom',
insertText: 'tom',
}],
};
},
});
}, []);
const showContent = () => {
alert(instance.getValue());
};
return (
<div>
<div id={`monaco-editor-ser-${props.id}`} className={className} />
<button onClick={showContent}>获取内容</button>
</div>
);
};
重点关注provideCompletionItems,返回一个对象,suggestions是个数组
获取当前编辑内容
/* eslint-disable no-restricted-globals,@typescript-eslint/no-unused-vars */
import React, { useEffect } from 'react';
import { useStyletron } from 'styletron-react';
import * as monaco from 'monaco-editor';
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';
self.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();
},
};
let instance;
export default ({
id, currentCode, readOnly, onChange
}) => {
const [css] = useStyletron();
const className = css({
height: '500px',
margin: '20px',
border: '2px solid #fff',
});
useEffect(() => {
instance = monaco.editor.create(document.getElementById(`monaco-editor-ser-${id}`), {
value: currentCode,
language: 'javascript',
});
instance.onDidChangeModelContent(() => {
// 获取到当前编辑内容
onChange(instance.getValue())
})
}, []);
return <div id={`monaco-editor-ser-${id}`} className={className} />;
};
参考文章
- 闲谈Monaco Editor-基本使用:介绍了ESM版的基本用法,有一定的出入,例如右键不起作用
- monaco-editor-samples:monaco官方搭建环境的示例
- monaco-editor初体验:介绍了自动补全方面的内容