本文在vue中实现了一个基本的SQL编辑器,包括语法高亮,关键字补全,表名,字段名补全等功能
细节功能等尚未进行完善,仅作学习使用
效果如下:
本文在vue-cli创建的项目中,使用vue2
需要安装两个包
"monaco-editor": "0.30.0", // 编辑器主体
"monaco-editor-webpack-plugin": "6.0.1", // 帮我们处理语法高亮等问题
使用 yarn add 或者 npm install 等命令均可,但是版本会有很大的影响,monaco-editor 的版本与 monaco-editor-webpack-plugin 的版本有对应关系,如下:
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.* |
如果对应关系不正确会导致无法运行,各种莫名其妙的报错
建议直接在package.json 文件里面添加上面的固定版本依赖项
在 vue.config.js 中添加
// 把 monaco webpack plugin 搞进去
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports={
configureWebpack: config => {
config.plugins.push(
new MonacoWebpackPlugin({
languages:["sql"], // 目前只处理SQL语言
features:["coreCommands","find"] // 基本命令和搜索功能
})
)
},
}
SQLEditor.vue
<template>
<div id="app" style="height: 100vh">
<div id="code-editor" ref="code-editor" style="height: 100%; width: 100%">div>
div>
template>
script 中的代码,全注释版
import * as monaco from "monaco-editor";
import { language } from "monaco-editor/esm/vs/basic-languages/sql/sql";
// 从 monaco-editor 的 sql 里面拿到关键字
const { keywords } = language;
export default {
name: "App",
data() {
return {
// 编辑器实例
monacoEditor: null,
// 原本已经写入的数据
value: "SELECT * FROM users;\n\nSELECT * FROM roles;",
// 补全的数据,建议在编辑器初始化之间就请求回来放好
tables: {
users: ["name", "id","email","phone","password"],
roles:["id","name","order","created_at","updated_at","deleted_at"]
},
// 编辑器主题
theme: "vs-dark", // 默认是 "vs"
};
},
methods: {
/**
* @description: 获取编辑器中填写的值
*/
getValue() {
return this.monacoEditor.getValue();
},
/**
* @description: 初始化自动补全
*/
initAutoCompletion() {
monaco.languages.registerCompletionItemProvider("sql", {
// 触发提示的字符
triggerCharacters: [".", " ", ...keywords],
provideCompletionItems: (model, position) => {
let suggestions = [];
// 行号,列号
const { lineNumber, column } = position;
// 光标之前的所有字符,即从这一行的 0 到当前的字符
const textBeforePointer = model.getValueInRange({
startLineNumber: lineNumber,
startColumn: 0,
endLineNumber: lineNumber,
endColumn: column,
});
// trim() 取消两边空格,保证拆分出来前后都不是空值
// \s是指空白,包括空格、换行、tab缩进等所有的空白
const words = textBeforePointer.trim().split(/\s+/);
// 最后的一个有效词
const lastWord = words[words.length - 1];
if (lastWord.endsWith(".")) { // 如果这个词以 . 结尾,那么认为是希望补全表的字段
// 拿到真实的表名,把 . 去掉
const tableName = lastWord.slice(0, lastWord.length - 1);
if (Object.keys(this.tables).includes(tableName)) {
suggestions = [...this.getFieldsSuggest(tableName)];
}
} else if (lastWord === ".") {
// 如果这个词本身就是一个 . 即点前面是空的,那么什么都不用补全了
// 按理说这应该是个语法错误
suggestions = [];
} else {
// 其他时候都补全表名,以及关键字
suggestions = [...this.getTableSuggest(), ...this.getKeywordsSuggest()];
}
return {
suggestions,
};
},
});
},
/**
* @description: 获取关键字的补全列表
* @tips: CompletionItemKind 的所有枚举可以在monaco.d.ts 文件中找到,有二十多个,取需即可
*/
getKeywordsSuggest() {
return keywords.map((key) => ({
label: key,// 显示的名称
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: key,// 真实补全的值
}));
},
/**
* @description: 获取表名的补全列表
*/
getTableSuggest() {
return Object.keys(this.tables).map((key) => ({
label: key, // 显示的名称
kind: monaco.languages.CompletionItemKind.Variable,
insertText: key, // 真实补全的值
}));
},
/**
* @description: 根据表名获取字段补全列表
* @param {*} tableName
*/
getFieldsSuggest(tableName) {
const fields = this.tables[tableName];
if (!fields) {
return [];
}
return fields.map((name) => ({
label: name,
kind: monaco.languages.CompletionItemKind.Field,
insertText: name,
}));
},
},
mounted() {
// 建议在这里把表名和字段名先拿出来
// ....
// this.tables = res.data?.data
// 首先初始化
this.initAutoCompletion();
// 初始化编辑器
this.monacoEditor = monaco.editor.create(document.getElementById("code-editor"), {
value: this.value, // 初始文字
language: "sql", // 语言
readOnly: false, // 是否只读
automaticLayout: true, // 自动布局
theme: this.theme, // vs | hc-black | vs-dark
minimap: {
enabled: false,// 关闭小地图
},
tabSize: 2, // tab缩进长度
fontSize: 16, // 文字大小
});
},
beforeDestroy() {
// 销毁之前把monaco的实例也销毁了,不然会多次注册
if (this.monacoEditor) {
this.monacoEditor.dispose();
}
},
};
以上代码未进行组件封装,只是实现了基本功能
还可以枚举更多的补全情况,让补全显得更加智能
当然本文只是简单使用,尚未进行深入,待后续使用到了再继续研究