monaco editor + vue 3

586 阅读2分钟

封装一下,创建一个编辑器

<template>
  <div ref="editor" class="editor" style="height: 100%;"></div>
</template>

<script>
import * as monaco from 'monaco-editor';

import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';

// 在初始化之前,先设置MonacoEnvironment
self.MonacoEnvironment = {
  getWorker(_, label) {
    if (label === 'json') {
      return new jsonWorker();
    }
    if (label === 'css' || label === 'scss' || label === 'less') {
      return new cssWorker();
    }
    if (label === 'html' || label === 'handlebars' || label === 'razor') {
      return new htmlWorker();
    }
    if (label === 'typescript' || label === 'javascript') {
      return new tsWorker();
    }
    return new editorWorker();
  }
};

export default {
  name: 'MonacoEditor',
  props:{
    lang:{
      type:String,
      default: 'sql'
    },
    val: {
      type: String,
      default: ''
    }
  },
  setup(){
    return{
      editor: null,
    };
  },
  mounted(){
    this.init();
  },
  methods:{
    init(){
      this.editor = monaco.editor.create(this.$refs.editor, {
        value: this.val,
        theme: 'vs',
        language: this.lang,
        minimap: {
          enabled: false
        },
        automaticLayout: true,
        unicodeHighlight: {
          invisibleCharacters: false,
          ambiguousCharacters: false,
        },
        readOnly: false,
        wordWrap: 'on', // 文本超出换行,不出现滚动条
      });
    }
  }
};
</script>

<style scoped lang="less">

</style>

用setup是因为用data的话,会加一层响应式的东西,最后导致 什么获取值等方法不能用。。。

获取编辑器的值

<MonacoEditor ref="monaco_editor" v-model:val="sql"></MonacoEditor>
this.$refs.monaco_editor.editor.getValue();

获取选中的区域的值

const monacoEditor = this.$refs.monaco_editor.editor;
let sqls = [];
const selections = monacoEditor.getSelections();
selections.map(selection=>{
  sqls.push(monacoEditor.getModel().getValueInRange(selection));
});

这样就得到了全部语句的数组

自定义语言

因为需要,所以要看一下怎么自定义语言

注册语言:

import * as monaco from 'monaco-editor';

const languageId = 'mylanguage';

monaco.languages.register({
  id: languageId
});

这样我们在使用编辑器的时候,对于language的参数填入 mylanguage 就可以使用自定义语言了

odc 的实现方法

高亮配置 setMonarchTokensProvider

Token 可以理解为标记器,那么我们就是通过标记给代码高亮染色的

monarch 不知道是啥意思,查翻译是啥帝王,君主的意思,所以是最高优先级的标记???

monaco.languages.setMonarchTokensProvider(languageId, language);

两个参数,第一个是语言id没什么好说的,第二个相当于一个配置对象,下面细讲

语法配置 setLanguageConfiguration

monaco.languages.setLanguageConfiguration(languageId, conf);

第二个参数也是相当于语法配置,比如:

export const conf = {
  comments: {
    lineComment: '--',
    blockComment: ['/*', '*/'],
  },
  brackets: [
    ['{', '}'],
    ['[', ']'],
    ['(', ')'],
  ],
  wordPattern: /[\w#$]+/i,
  autoClosingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '"', close: '"' },
    { open: '\'', close: '\'' },
    { open: '`', close: '`' },
  ],
  surroundingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '"', close: '"' },
    { open: '\'', close: '\'' },
    { open: '`', close: '`' },
  ],
};

自动补全 registerCompletionItemProvider

名字还是很贴切的

  • completion 完成
  • item 个、项
  • provider 提供
monaco.languages.registerCompletionItemProvider(languageId, new AutoComplete(dbInfo));

AutoComplete 是一个我们声明的变量,需要实现 provideCompletionItems 方法,这个方法提供了一些参数供我们分析应该提示什么

provideCompletionItems(model, position, context, token){
    const triggerCharacter = context.triggerCharacter;
    const delimiter = ';';
    const word = model.getWordUntilPosition(position);
    const range = {
      startLineNumber: position.lineNumber,
      endLineNumber: position.lineNumber,
      startColumn: word.startColumn,
      endColumn: word.endColumn
    };
    const offset = model.getOffsetAt(position);
    return this.getCompleteWordFromOffset(offset, model.getValue(), delimiter, range, model, triggerCharacter);
  }

这里 odc 又转到了 getCompleteWordFromOffset 方法

getCompleteWordFromOffset(offset, input, delimiter, range, model, triggerCharacter) {
    return __awaiter(this, void 0, void 0, function* (){
      const parser = worker.parser;
      const result = yield parser.getAutoCompletion(input, delimiter, offset);
      if(result){
        let suggestions = [];
        for(let item of result){
          if(typeof item === 'string') {
            suggestions.push(keywordItem(item, range));
          }else if(item.type === 'allTables') {
            suggestions = suggestions.concat(yield this.getTableList(model, item.schema, range));
          }else if (item.type === 'tableColumns') {
            suggestions = suggestions.concat(yield this.getColumnList(model, item, range));
          }else if(item.type === 'withTable') {
            suggestions.push(tableItem(item.tableName, 'CTE', false, range));
          }else if(item.type === 'allSchemas'){
            suggestions = suggestions.concat(yield this.getSchemaList(model, range));
          }else if (item.type === 'objectAccess') {
            console.log('objectAccess', item);
            const objectName = item.objectName;
            const schemaList = yield this.service.getSchemaList(model);
            const schema = schemaList === null || schemaList === void 0 ? void 0 : schemaList.find(s => s === objectName);
            if (schema) {
              suggestions = suggestions.concat(yield this.getTableList(model, item.objectName, range));
              continue;
            }
            const arr = objectName.split('.');
            let tableName = arr.length > 1 ? arr[1] : arr[0];
            let schemaName = arr.length > 1 ? arr[0] : undefined;
            suggestions = suggestions.concat(yield this.getColumnList(model, { tableName, schemaName }, range));
          }
          else if (item.type === 'fromTable') {
            suggestions.push(tableItem(item.tableName, item.schemaName, true, range));
          }
          else if (item.type === 'allFunction') {
            suggestions = suggestions.concat(yield this.getFunctions(model, range));
          }
        }
        return {
          suggestions,
          incomplete: false
        };
      }
      return {
        suggestions: [],
        incomplete: false
      };
    });
  }

可以看出来,这里调用了一个 worker.parser 来分析,并返回了建议的结果集,然后遍历结果集进行查询和包装。

格式化

注册格式化方法

// 格式化
  monaco.languages.registerDocumentFormattingEditProvider(languageId, new DocumentFormattingEditProvider());
  monaco.languages.registerDocumentRangeFormattingEditProvider(languageId, new DocumentRangeFormattingEditProvider());

使用

const monacoEditor = this.$refs.monaco_editor.editor;
monacoEditor?.trigger('editor', 'editor.action.formatDocument', null);