Vue2的SQL编辑器 - monaco的基本使用

2022-11-10  本文已影响0人  思跃喵

概述

本文在vue中实现了一个基本的SQL编辑器,包括语法高亮,关键字补全,表名,字段名补全等功能

细节功能等尚未进行完善,仅作学习使用

效果如下:

iShot_2022-11-11_00.02.50.gif

准备工作

本文在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 文件里面添加上面的固定版本依赖项

先处理 monaco-editor-webpack-plugin

在 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();
    }
  },
};

结果总结

以上代码未进行组件封装,只是实现了基本功能

还可以枚举更多的补全情况,让补全显得更加智能

当然本文只是简单使用,尚未进行深入,待后续使用到了再继续研究

上一篇下一篇

猜你喜欢

热点阅读