• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

vue3 使用 monaco-editor 自定义代码补全。

武飞扬头像
Y...................
帮助1

使用场景:

        数据编辑时需要支持sql语法高亮, 并且支持自定义代码提示补全。

monaco详细说明和使用可参考另一篇发文Monaco Editor (vite/webpack ts vue项目使用)

步骤一:安装依赖 

npm i monaco-editor

步骤二:组件功能封装

  1.  
    <template>
  2.  
    <div ref="cusEditor"></div>
  3.  
    </template>
  4.  
    <script setup lang="ts">
  5.  
    import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
  6.  
    import { withDefaults, defineProps, ref, defineEmits, onMounted, onUnmounted, watch } from 'vue'
  7.  
    import { OPTIONS_BASE } from './registerCompletion'
  8.  
    import './worker'
  9.  
    interface IProps {
  10.  
    modelValue: string
  11.  
    disabled?: boolean
  12.  
    editorConfig?: { language: string; theme: 'vs' | 'vs-dark' | 'hc-black' }
  13.  
    }
  14.  
    const props = withDefaults(defineProps<IProps>(), {
  15.  
    modelValue: '',
  16.  
    disabled: false,
  17.  
    editorConfig: () => ({ language: 'sql', theme: 'vs-dark' }),
  18.  
    })
  19.  
     
  20.  
    const cusEditor = ref<HTMLElement | null>(null)
  21.  
    let editor: Partial<monaco.editor.IStandaloneCodeEditor> = {}
  22.  
    const emit = defineEmits(['update:modelValue'])
  23.  
    /**初始化编辑器 */
  24.  
    onMounted(() => {
  25.  
    onDispose()
  26.  
    if (cusEditor.value) {
  27.  
    editor = monaco.editor.create(cusEditor.value, { ...OPTIONS_BASE, ...props.editorConfig, readOnly: props.disabled })
  28.  
    editor.onDidChangeModelContent &&
  29.  
    editor.onDidChangeModelContent(() => {
  30.  
    const value = editor.getValue && editor.getValue() // 给父组件实时返回最新文本
  31.  
    emit('update:modelValue', value)
  32.  
    })
  33.  
    }
  34.  
    })
  35.  
    /**销毁实例 */
  36.  
    const onDispose = () => {
  37.  
    editor && editor.dispose && editor.dispose()
  38.  
    }
  39.  
    onUnmounted(() => {
  40.  
    onDispose()
  41.  
    })
  42.  
    /**修改只读状态 */
  43.  
    watch(
  44.  
    () => props.disabled,
  45.  
    (val) => {
  46.  
    editor.updateOptions && editor.updateOptions({ readOnly: val })
  47.  
    }
  48.  
    )
  49.  
    /**修改配置 */
  50.  
    watch(
  51.  
    () => props.editorConfig,
  52.  
    (val) => {
  53.  
    const model = editor.getModel && editor.getModel()
  54.  
    if (model) {
  55.  
    monaco.editor.setModelLanguage(model, val.language)
  56.  
    monaco.editor.setTheme(val.theme)
  57.  
    }
  58.  
    },
  59.  
    { deep: true }
  60.  
    )
  61.  
    /**回显数据 */
  62.  
    watch(
  63.  
    () => props.modelValue,
  64.  
    (val) => {
  65.  
    if (editor) {
  66.  
    const value = editor.getValue && editor.getValue()
  67.  
    if (val !== value) {
  68.  
    editor.setValue && editor.setValue(val || '')
  69.  
    }
  70.  
    }
  71.  
    }
  72.  
    )
  73.  
    </script>
学新通
  • OPTIONS_BASE :  为基础配置,具体参数可参考官网
  • worker: 解决vite引入代码高亮和错误提示

参考git, discussions

OPTIONS_BASE:

  1.  
    export const OPTIONS_BASE: monaco.editor.IStandaloneEditorConstructionOptions = {
  2.  
    value: '', // 初始显示文字
  3.  
    lineNumbers: 'on', // 是否展示行号 'off' | 'on
  4.  
    automaticLayout: false, // 自适应布局 默认true
  5.  
    minimap: {
  6.  
    enabled: false,
  7.  
    },
  8.  
    tabSize: 2,
  9.  
    fontSize: 16
  10.  
    }

worker.ts

  1.  
    import * as monaco from 'monaco-editor';
  2.  
    import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
  3.  
    import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
  4.  
    import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
  5.  
    import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
  6.  
    import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
  7.  
     
  8.  
    self.MonacoEnvironment = {
  9.  
    getWorker(_, label) {
  10.  
    if (label === 'json') {
  11.  
    return new jsonWorker()
  12.  
    }
  13.  
    if (label === 'css' || label === 'scss' || label === 'less') {
  14.  
    return new cssWorker()
  15.  
    }
  16.  
    if (label === 'html' || label === 'handlebars' || label === 'razor') {
  17.  
    return new htmlWorker()
  18.  
    }
  19.  
    if (label === 'typescript' || label === 'javascript') {
  20.  
    return new tsWorker()
  21.  
    }
  22.  
    return new editorWorker()
  23.  
    }
  24.  
    }
  25.  
    monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
学新通

步骤三:组件的使用

  1.  
    <template>
  2.  
    <el-form-item label="表名:" prop="table">
  3.  
    <MonacoEditor class="w-full h-40" v-model="formData.table" :disabled="disabled" :constModelData="constModelData"></MonacoEditor>
  4.  
    </el-form-item>
  5.  
    </template>
  6.  
    <script lang="ts" setup>
  7.  
    import MonacoEditor from '@/components/monaco/index.vue'
  8.  
    </script>

到此,就可以实现基础的编辑功能

*扩展

由于我项目中使用的时sql语法,并且还需要支持自定义代码补全, 接下来就以sql语言为例:

步骤一:自定义补全语法方法封装

  1.  
    import * as monaco from 'monaco-editor';
  2.  
    /**
  3.  
    * 注册:自定义语法补全
  4.  
    * @param language 语言类型
  5.  
    * @param constValues 常量提示
  6.  
    */
  7.  
    export const registerProvider = (language: string, constValues: string[]) => {
  8.  
    const monacoProvider = monaco.languages.registerCompletionItemProvider(language, {
  9.  
    provideCompletionItems: function (model, position) {
  10.  
    // 获取当前行数
  11.  
    const line = position.lineNumber
  12.  
    // 获取当前列数
  13.  
    const column = position.column
  14.  
    // 获取当前输入行的所有内容
  15.  
    const content = model.getLineContent(line)
  16.  
    // 通过下标来获取当前光标后一个内容,即为刚输入的内容
  17.  
    const sym = content[column - 2]
  18.  
    const word = model.getWordUntilPosition(position)
  19.  
    const range = {
  20.  
    startLineNumber: position.lineNumber,
  21.  
    endLineNumber: position.lineNumber,
  22.  
    startColumn: word.startColumn,
  23.  
    endColumn: word.endColumn,
  24.  
    }
  25.  
    let suggestions: any[] = []
  26.  
    if (sym === '$') {
  27.  
    suggestions = constValues.map((e) => ({
  28.  
    label: e,
  29.  
    kind: monaco.languages.CompletionItemKind.Keyword,
  30.  
    insertText: '{' e '}',
  31.  
    detail: '常量配置',
  32.  
    }))
  33.  
    //拦截到用户输入$,开始设置提示内容,同else中代码一致,自行拓展
  34.  
    } else if(language === 'sql'){
  35.  
    // 直接提示,以下为sql语句关键词提示
  36.  
    var sqlStr = ['SELECT', 'FROM', 'WHERE', 'AND', 'OR', 'LIMIT', 'ORDER BY', 'GROUP BY', 'LEFT', 'ON', 'if(){}', 'for(){}', 'size', 'get()', 'substring', 'return']
  37.  
    suggestions = sqlStr.map((e) => ({
  38.  
    label: e, // 显示的提示内容
  39.  
    kind: monaco.languages.CompletionItemKind['Function'], // 用来显示提示内容后的不同的图标
  40.  
    insertText: e, // 选择后粘贴到编辑器中的文字
  41.  
    detail: '', // 提示内容后的说明
  42.  
    range: range,
  43.  
    }))
  44.  
    }
  45.  
    return {
  46.  
    suggestions: suggestions,
  47.  
    }
  48.  
    },
  49.  
    triggerCharacters: ['$', ''],
  50.  
    })
  51.  
    return monacoProvider
  52.  
    }
学新通

步骤二: 组件中使用

  1.  
    import { registerProvider } from '@/components/monaco/registerCompletion'
  2.  
    const registerPro = registerProvider('sql', props.constModelData)

效果展示:

1.sql关键字提示 

学新通

2.常量提示:

学新通

问题:每打开一次编辑弹框, 常量的提示就多一条重复数据。如图:

      学新通

        原因: registerCompletionItemProvider多次注册。由于自定义补全注册的代码写在了编辑弹框中,所以每打开一次弹框就执行一次注册自定义补全的方法,。

解决方案一:将注册方法移动到最外层组件中可解决

但是 由于功能需求,常量的提示内容要根据每次打开的弹框数据改变, 所以注册方法必须写在内部,(编辑弹框打开后,需根据弹框绑定数据的id去请求常量接口, 然后注册)

解决方案:参考官方:https://github.com/microsoft/monaco-editor/issues/2084

解决方案二:dispose(), 页面卸载时,销毁之前注册的实例

  1.  
    const registerPro = registerProvider('sql', props.constModelData)
  2.  
    onUnmounted(() => {
  3.  
    registerPro && registerPro.dispose()
  4.  
    })

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhigkabb
系列文章
更多 icon
同类精品
更多 icon
继续加载