本文主要是介绍使用 Monaco Editor 开发 SQL 编辑器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
安装
安装依赖,这里请注意版本
yarn add monaco-editor@0.29.1
yarn add monaco-editor-webpack-plugin@5.0.0
配置 webpack 插件
// vue.config.js
...
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')module.export = {...configureWebpack: {name: name,resolve: {alias: {'@': resolve('src'),},},plugins: [new MonacoWebpackPlugin()],},...
}
请注意 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.* |
简易 SQL 编辑器
先上干货!
<template><div ref="codeContainer" class="editor-container" :style="{ height: height + 'px' }" />
</template><script>
import * as monaco from 'monaco-editor'/*** VS Code 编辑器** 通过 getEditorVal 函数向外传递编辑器即时内容* 通过 initValue 用于初始化编辑器内容。* 编辑器默认 sql 语言,支持的语言请参考 node_modules\monaco-editor\esm\vs\basic-languages 目录下~* 编辑器样式仅有 'vs', 'vs-dark', 'hc-black' 三种*/
export default {name: 'MonacoEditor',props: {initValue: {type: String,default: '',},readOnly: Boolean,language: {type: String,default: 'sql',},height: {type: Number,default: 300,},theme: {type: String,default: 'vs',},},data() {return {monacoEditor: null, // 语言编辑器}},computed: {inputVal() {return this.monacoEditor?.getValue()},},watch: {inputVal() {if (this.monacoEditor) {this.$emit('change', this.monacoEditor.getValue())}},theme() {this.setTheme(this.theme)},height() {this.layout()},},mounted() {this.initEditor()},beforeDestroy() {if (this.monacoEditor) {this.monacoEditor.dispose()}},methods: {initEditor() {if (this.$refs.codeContainer) {this.registerCompletion()// 初始化编辑器,确保dom已经渲染this.monacoEditor = monaco.editor.create(this.$refs.codeContainer, {value: '', // 编辑器初始显示文字language: 'sql', // 语言readOnly: this.readOnly, // 是否只读 Defaults to false | trueautomaticLayout: true, // 自动布局theme: this.theme, // 官方自带三种主题vs, hc-black, or vs-darkminimap: {// 关闭小地图enabled: false,},tabSize: 2, // tab缩进长度})}this.setInitValue()},focus() {this.monacoEditor.focus()},layout() {this.monacoEditor.layout()},getValue() {return this.monacoEditor.getValue()},// 将 initValue Property 同步到编辑器中setInitValue() {this.monacoEditor.setValue(this.initValue)},setTheme() {monaco.editor.setTheme(this.theme)},getSelectionVal() {const selection = this.monacoEditor.getSelection() // 获取光标选中的值const { startLineNumber, endLineNumber, startColumn, endColumn } = selectionconst model = this.monacoEditor.getModel()return model.getValueInRange({startLineNumber,startColumn,endLineNumber,endColumn,})},setPosition(column, lineNumber) {this.monacoEditor.setPosition({ column, lineNumber })},getPosition() {return this.monacoEditor.getPosition()},},
}
</script><style lang="scss" scoped></style>
相关功能
获取选中代码
getSelectionVal() {const selection = this.monacoEditor.getSelection() // 获取光标选中的值const { startLineNumber, endLineNumber, startColumn, endColumn } = selectionconst model = this.monacoEditor.getModel()return model.getValueInRange({startLineNumber,startColumn,endLineNumber,endColumn,})},
替换选中代码
insertStringInTemplate(str) {const selection = this.monacoEditor.getSelection() // 获取光标选中的值const { startLineNumber, endLineNumber, startColumn, endColumn } = selectionconst model = this.monacoEditor.getModel()const textBeforeSelection = model.getValueInRange({startLineNumber: 1,startColumn: 0,endLineNumber: startLineNumber,endColumn: startColumn,})const textAfterSelection = model.getValueInRange({startLineNumber: endLineNumber,startColumn: endColumn,endLineNumber: model.getLineCount(),endColumn: model.getLineMaxColumn(model.getLineCount()),})this.monacoEditor.setValue(textBeforeSelection + str + textAfterSelection)this.monacoEditor.focus()this.monacoEditor.setPosition({lineNumber: startLineNumber,column: startColumn + str.length,})},
处理光标位置
setPosition(column, lineNumber) {this.monacoEditor.setPosition({ column, lineNumber })},getPosition() {return this.monacoEditor.getPosition()},
自定义 SQL 库表提示,并保留原有 SQL 提示
首先由后端提供具体的库表信息:
export const hintData = {adbs: ['dim_realtime_recharge_paycfg_range', 'dim_realtime_recharge_range'],dimi: ['ads_adid', 'ads_spec_adid_category'],
}
然后根据已有库表信息进行自定义 AutoComplete
import * as monaco from 'monaco-editor'
import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'const { keywords } = languageexport default {...mounted() {this.initEditor()},methods: {...registerCompletion() {const _that = thismonaco.languages.registerCompletionItemProvider('sql', {triggerCharacters: ['.', ...keywords],provideCompletionItems: (model, position) => {let suggestions = []const { lineNumber, column } = positionconst textBeforePointer = model.getValueInRange({startLineNumber: lineNumber,startColumn: 0,endLineNumber: lineNumber,endColumn: column,})const tokens = textBeforePointer.trim().split(/\s+/)const lastToken = tokens[tokens.length - 1] // 获取最后一段非空字符串if (lastToken.endsWith('.')) {const tokenNoDot = lastToken.slice(0, lastToken.length - 1)if (Object.keys(_that.hintData).includes(tokenNoDot)) {suggestions = [..._that.getTableSuggest(tokenNoDot)]}} else if (lastToken === '.') {suggestions = []} else {suggestions = [..._that.getDBSuggest(), ..._that.getSQLSuggest()]}return {suggestions,}},})},// 获取 SQL 语法提示getSQLSuggest() {return keywords.map((key) => ({label: key,kind: monaco.languages.CompletionItemKind.Enum,insertText: key,}))},getDBSuggest() {return Object.keys(this.hintData).map((key) => ({label: key,kind: monaco.languages.CompletionItemKind.Constant,insertText: key,}))},getTableSuggest(dbName) {const tableNames = this.hintData[dbName]if (!tableNames) {return []}return tableNames.map((name) => ({label: name,kind: monaco.languages.CompletionItemKind.Constant,insertText: name,}))},initEditor() {if (this.$refs.codeContainer) {this.registerCompletion()// 初始化编辑器,确保dom已经渲染this.monacoEditor = monaco.editor.create(this.$refs.codeContainer, {value: '', // 编辑器初始显示文字language: 'sql', // 语言readOnly: this.readOnly, // 是否只读 Defaults to false | trueautomaticLayout: true, // 自动布局theme: this.theme, // 官方自带三种主题vs, hc-black, or vs-darkminimap: {// 关闭小地图enabled: false,},tabSize: 2, // tab缩进长度})}this.setValue(this.value)},}
}
编辑器 resize
resize() {this.monacoEditor.layout()},
编辑器设置主题
注意!设置主题并非在编辑器实例上修改的哦!
setTheme() {monaco.editor.setTheme(this.theme)},
SQL 代码格式化
编辑器自身不支持 sql 格式化(试了下 JavaScript 是支持的),所以用到了 sql-formatter 这个库。
import { format } from 'sql-formatter'...format() {this.monacoEditor.setValue(format(this.monacoEditor.getValue(), {indentStyle: 'tabularLeft',}),)},
...
右键菜单汉化
需要安装以下两个库
npm install monaco-editor-nls --save
npm install monaco-editor-esm-webpack-plugin --save-dev
具体用法可以直接去 https://www.npmjs.com/package/monaco-editor-esm-webpack-plugin 里面看,我就不搬运了~
记得销毁编辑器对象哦
beforeDestroy() {if (this.monacoEditor) {this.monacoEditor.dispose()}},
踩坑
下面是我遇到的几个坑。
- 最新版本的 Monaco Editor 已经使用了 ES2022 的语法,所以老项目可能会出现编译不过的问题。所以我把版本调低了一些。
- 在最初调试编辑器的时候出现了无法编辑的情况,后来发现是同事用到了
default-passive-events
这个库来关闭 chrome 的Added non-passive event listener to a scroll-blocking <some> event. Consider marking event handler as 'passive' to make the page more responsive
警告。结果拦截一些 event。
如何快速去看懂 Monaco Editor
一开始我看它的官方文档是非常懵的,各种接口、函数、对象的定义,完全不像是个前端库那么好理解。鼓捣了好久才慢慢找到门路。
- 先看示例
- 查看它的 playground,上面其实是有一些功能可以直接找到的。
- 查看它在 github 上的 /samples 目录,里面也有不少示例。
- 去掘金这类网站上找别人写的示例,能有不少启发。
- 再看 API
- 了解了自己所需要的功能相关的代码,再去看它文档的 API 就会发现容易理解多了。逐步发散理解更多关联功能。
参考资料
- 官方文档
- https://microsoft.github.io/monaco-editor/index.html
- 相关库
- Monaco Editor https://www.npmjs.com/package/monaco-editor
- 右键菜单汉化 https://www.npmjs.com/package/monaco-editor-nls
- webpack 插件 https://www.npmjs.com/package/monaco-editor-webpack-plugin
- 汉化 webpack 插件 https://www.npmjs.com/package/monaco-editor-esm-webpack-plugin
- SQL 代码格式化 https://www.npmjs.com/package/sql-formatter
- 博客
- https://blog.csdn.net/m0_37986789/article/details/121135519
- https://juejin.cn/post/6986907628937379871
这篇关于使用 Monaco Editor 开发 SQL 编辑器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!