前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 Monaco Editor 开发 SQL 编辑器

使用 Monaco Editor 开发 SQL 编辑器

原创
作者头像
CRMEB商城源码
发布2022-08-05 18:04:59
3.3K0
发布2022-08-05 18:04:59
举报
文章被收录于专栏:crmeb

安装

安装依赖,这里请特别注意下版本

代码语言:javascript
复制
yarn add monaco-editor@0.29.1
yarn add monaco-editor-webpack-plugin@5.0.0
复制代码

配置 webpack 插件

代码语言:javascript
复制
// 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 的对应关系,否则可能会出现无法运行的情况。

f20e386aaf504a36bdca8cd453cbafd7.png
f20e386aaf504a36bdca8cd453cbafd7.png

简易 SQL 编辑器

先给大家上干货!

代码语言:javascript
复制
<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 | true
          automaticLayout: true, // 自动布局
          theme: this.theme, // 官方自带三种主题vs, hc-black, or vs-dark
          minimap: {
            // 关闭小地图
            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 } = selection
      const 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>
复制代码

相关功能

获取选中代码

代码语言:javascript
复制
getSelectionVal() {
      const selection = this.monacoEditor.getSelection() // 获取光标选中的值
      const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
      const model = this.monacoEditor.getModel()

      return model.getValueInRange({
        startLineNumber,
        startColumn,
        endLineNumber,
        endColumn,
      })
    },
复制代码

替换选中代码

代码语言:javascript
复制
insertStringInTemplate(str) {
      const selection = this.monacoEditor.getSelection() // 获取光标选中的值
      const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
      const 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,
      })
    },
复制代码

处理光标位置

代码语言:javascript
复制
 setPosition(column, lineNumber) {
      this.monacoEditor.setPosition({ column, lineNumber })
    },
    getPosition() {
      return this.monacoEditor.getPosition()
    },
复制代码

自定义 SQL 库表提示,并保留原有 SQL 提示

首先由后端提供具体的库表信息:

代码语言:javascript
复制
export const hintData = {
  adbs: ['dim_realtime_recharge_paycfg_range', 'dim_realtime_recharge_range'],
  dimi: ['ads_adid', 'ads_spec_adid_category'],
}
复制代码

然后根据已有库表信息进行自定义 AutoComplete

代码语言:javascript
复制
import * as monaco from 'monaco-editor'
import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'

const { keywords } = language

export default {
  ...
  mounted() {
    this.initEditor()
  },
  methods: {
    ...
    registerCompletion() {
      const _that = this
      monaco.languages.registerCompletionItemProvider('sql', {
        triggerCharacters: ['.', ...keywords],
        provideCompletionItems: (model, position) => {
          let suggestions = []

          const { lineNumber, column } = position

          const 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 | true
          automaticLayout: true, // 自动布局
          theme: this.theme, // 官方自带三种主题vs, hc-black, or vs-dark
          minimap: {
            // 关闭小地图
            enabled: false,
          },
          tabSize: 2, // tab缩进长度
        })
      }
      this.setValue(this.value)
    },
  }
}
复制代码

编辑器 resize

代码语言:javascript
复制
  resize() {
      this.monacoEditor.layout()
    },
复制代码

编辑器设置主题

注意!设置主题并非在编辑器实例上修改的哦!

代码语言:javascript
复制
 setTheme() {
      monaco.editor.setTheme(this.theme)
    },
复制代码

SQL 代码格式化

编辑器自身不支持 sql 格式化(试了下 JavaScript 是支持的),所以用到了 sql-formatter 这个库。

代码语言:javascript
复制
import { format } from 'sql-formatter'

...
    format() {
      this.monacoEditor.setValue(
        format(this.monacoEditor.getValue(), {
          indentStyle: 'tabularLeft',
        }),
      )
    },
...
复制代码

右键菜单汉化

需要安装以下两个库

代码语言:javascript
复制
npm install monaco-editor-nls --save
npm install monaco-editor-esm-webpack-plugin --save-dev
复制代码

具体用法可以直接去 www.npmjs.com/package/mon… 里面看,我就不搬运了~

记得销毁编辑器对象哦

代码语言:javascript
复制
 beforeDestroy() {
    if (this.monacoEditor) {
      this.monacoEditor.dispose()
    }
  },
复制代码

踩坑

下面是我遇到的几个坑。

  • 最新版本的 Monaco Editor 已经使用了 ES2022 的语法,所以老项目可能会出现编译不过的问题。所以我把版本调低了一些。
  • 在最初调试编辑器的时候出现了无法编辑的情况,后来发现是同事用到了 default-passive-events 这个库来关闭 chrome 的 Added non-passive event listener to a scroll-blocking event. Consider marking event handler as ‘passive’ to make the page more responsive 警告。结果拦截一些 event。 如何快速去看懂 Monaco Editor 一开始我看它的官方文档是非常懵的,各种接口、函数、对象的定义,完全不像是个前端库那么好理解。鼓捣了好久才慢慢找到门路。

先看示例

查看它的 playground,上面其实是有一些功能可以直接找到的。

查看它在 github 上的 /samples 目录,里面也有不少示例。

去掘金这类网站上找别人写的示例,能有不少启发。

再看 API

了解了自己所需要的功能相关的代码,再去看它文档的 API 就会发现容易理解多了。逐步发散理解更多关联功能。

源码附件已经打包好上传到百度云了,大家自行下载即可~

链接: https://pan.baidu.com/s/14G-bpVthImHD4eosZUNSFA?pwd=yu27

提取码: yu27

百度云链接不稳定,随时可能会失效,大家抓紧保存哈。

如果百度云链接失效了的话,请留言告诉我,我看到后会及时更新~

开源地址

码云地址:

http://github.crmeb.net/u/defu

Github 地址:

http://github.crmeb.net/u/defu

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 安装
  • 简易 SQL 编辑器
  • 相关功能
    • 获取选中代码
    • 替换选中代码
    • 处理光标位置
    • 自定义 SQL 库表提示,并保留原有 SQL 提示
    • 编辑器 resize
    • 编辑器设置主题
    • SQL 代码格式化
    • 右键菜单汉化
    • 记得销毁编辑器对象哦
    • 踩坑
    • 开源地址
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档