当前位置:   article > 正文

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

vue3 monaco-editor

使用场景:

        数据编辑时需要支持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. const cusEditor = ref<HTMLElement | null>(null)
  20. let editor: Partial<monaco.editor.IStandaloneCodeEditor> = {}
  21. const emit = defineEmits(['update:modelValue'])
  22. /**初始化编辑器 */
  23. onMounted(() => {
  24. onDispose()
  25. if (cusEditor.value) {
  26. editor = monaco.editor.create(cusEditor.value, { ...OPTIONS_BASE, ...props.editorConfig, readOnly: props.disabled })
  27. editor.onDidChangeModelContent &&
  28. editor.onDidChangeModelContent(() => {
  29. const value = editor.getValue && editor.getValue() // 给父组件实时返回最新文本
  30. emit('update:modelValue', value)
  31. })
  32. }
  33. })
  34. /**销毁实例 */
  35. const onDispose = () => {
  36. editor && editor.dispose && editor.dispose()
  37. }
  38. onUnmounted(() => {
  39. onDispose()
  40. })
  41. /**修改只读状态 */
  42. watch(
  43. () => props.disabled,
  44. (val) => {
  45. editor.updateOptions && editor.updateOptions({ readOnly: val })
  46. }
  47. )
  48. /**修改配置 */
  49. watch(
  50. () => props.editorConfig,
  51. (val) => {
  52. const model = editor.getModel && editor.getModel()
  53. if (model) {
  54. monaco.editor.setModelLanguage(model, val.language)
  55. monaco.editor.setTheme(val.theme)
  56. }
  57. },
  58. { deep: true }
  59. )
  60. /**回显数据 */
  61. watch(
  62. () => props.modelValue,
  63. (val) => {
  64. if (editor) {
  65. const value = editor.getValue && editor.getValue()
  66. if (val !== value) {
  67. editor.setValue && editor.setValue(val || '')
  68. }
  69. }
  70. }
  71. )
  72. </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. self.MonacoEnvironment = {
  8. getWorker(_, label) {
  9. if (label === 'json') {
  10. return new jsonWorker()
  11. }
  12. if (label === 'css' || label === 'scss' || label === 'less') {
  13. return new cssWorker()
  14. }
  15. if (label === 'html' || label === 'handlebars' || label === 'razor') {
  16. return new htmlWorker()
  17. }
  18. if (label === 'typescript' || label === 'javascript') {
  19. return new tsWorker()
  20. }
  21. return new editorWorker()
  22. }
  23. }
  24. 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. })

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/1000092
推荐阅读
相关标签
  

闽ICP备14008679号