当前位置 : 主页 > 网络编程 > JavaScript >

Monaco Editor开发SQL代码提示编辑器实例详解

来源:互联网 收集:自由互联 发布时间:2023-02-08
目录 安装 简易 SQL 编辑器 相关功能 获取选中代码 替换选中代码 处理光标位置 自定义 SQL 库表提示,并保留原有 SQL 提示 编辑器 resize 编辑器设置主题 SQL 代码格式化 右键菜单汉化 记
目录
  • 安装
  • 简易 SQL 编辑器
  • 相关功能
    • 获取选中代码
    • 替换选中代码
    • 处理光标位置
    • 自定义 SQL 库表提示,并保留原有 SQL 提示
    • 编辑器 resize
    • 编辑器设置主题
    • SQL 代码格式化
    • 右键菜单汉化
    • 记得销毁编辑器对象哦
  • 踩坑
    • 如何快速去看懂 Monaco Editor

      安装

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

      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-pluginmonaco-editor7.*.*>= 0.31.06.*.*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 | 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>
      

      相关功能

      获取选中代码

          getSelectionVal() {
            const selection = this.monacoEditor.getSelection() // 获取光标选中的值
            const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
            const model = this.monacoEditor.getModel()
            return model.getValueInRange({
              startLineNumber,
              startColumn,
              endLineNumber,
              endColumn,
            })
          },
      

      替换选中代码

      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,
            })
          },
      

      处理光标位置

        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 } = 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

          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
      

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

      记得销毁编辑器对象哦

        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 就会发现容易理解多了。逐步发散理解更多关联功能。

      参考资料

      • 官方文档

      microsoft.github.io/monaco-edit…

      相关库

      Monaco Editor www.npmjs.com/package/mon…

      右键菜单汉化 www.npmjs.com/package/mon…

      webpack 插件 www.npmjs.com/package/mon…

      汉化 webpack 插件 www.npmjs.com/package/mon…

      SQL 代码格式化 www.npmjs.com/package/sql…

      博客

      https://www.jb51.net/article/258307.htm

      https://www.jb51.net/article/258269.htm

      以上就是Monaco Editor开发SQL编辑器实例详解的详细内容,更多关于Monaco Editor开发SQL编辑器的资料请关注易盾网络其它相关文章!

      上一篇:vue中data的基础汇总
      下一篇:没有了
      网友评论