12

  最近做的是后端的低代码平台构建工作,功能界面主体是类Eclipse风格的,其中文件夹内创建内容可以通过可视化操作创建功能代码,也可以后期编辑已生成的代码。因为后者涉及到网页版编辑器,所以我最近就一直和monaco-editor打交道了。
  首先就是老生常谈的入门问题,那就是如何创建一个monaco编辑器。在此就以monaco-editor集成到vue项目中为例,为大家解决此类问题,以及期望能够帮助大家快速入手实践。
image.png
  如上图所示,是该项目的前端部分界面展示。右侧文本编辑器就是monaco-editor。接下来的内容并不是最基础的逐个js的分析教学,而是直接使用插件代替部分基础配置。如果需要学习更多细节,可以去阅读其他网友的文章,这里并不过多分析。

PS:贴个Monaco Editor官方文档地址:https://microsoft.github.io/monaco-editor/

一、安装

  基础的安装只需要两个:

npm install monaco-editor -S

npm install monaco-editor-webpack-plugin -D

  作为扩展汉化,也可以继续安装另外三个:

npm install monaco-editor-nls -S 
//或者使用npm install --save-dev monaco-editor-locales-plugin

npm install monaco-editor-esm-webpack-plugin -D

npm install js-beautify -S
  • monaco-editor:是网页编辑器的核心包,整体非常大,因为支持了很多的语言与很多的扩展功能。
  • monaco-editor-webpack-plugin:因为monaco-editor直接单独引入的情况下所支持的基本使用不能满足我们的需求,我们还需要支持智能提示等功能,所以需要额外单独做一些配置操作,虽然官网文档说明已经很清晰,但是配置起来还是不免比较繁琐,因此提供了这个webpack插件来帮助我们自动处理这些事情,简化我们的操作。
  • monaco-editor-nls:是对整个编辑器的汉化处理,如果需要进行汉化,那么需要安装此包。
  • monaco-editor-locales-plugin: 也是用来汉化的插件。
  • monaco-editor-esm-webpack-plugin:针对汉化包所做的webpack插件,需要和汉化包配合使用。
  • js-beautify:是用来做代码美化的,主要是做一些格式化的工作。

  安装完成之后,就可以配置到我们的项目中进行应用啦。

二、配置

  首先要在vue.config.js这个文件中引入插件,并设置相应的配置项。

  以下为在该文件内需要配置的内容:

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const MonacoLocalesPlugin = require('monaco-editor-locales-plugin');
module.exports = {
  configureWebpack: {
      entry: {
        'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js'
      },
      plugins: [
        new MonacoWebpackPlugin({
          languages: ["json", "xml", "yaml", "python", 'java'],
          features: ["coreCommands", "find", "format", "folding", 'smartSelect', 'snippets', 'suggest', 'hover']
        }),
        new MonacoLocalesPlugin({
          //设置支持的语言
          languages: ["es", "zh-cn"],
          //默认语言
          defaultLanguage: "zh-cn",
          //打印不匹配的文本
          logUnmatched: false,
          //自定义文本翻译
          mapLanguages: { "zh-cn": { "Peek References": "查找引用", "Go to Symbol...": "跳到变量位置", "Command Palette": "命令面板" } }
        })
      ]
    }
}

  在模板内定义编辑器的元素,且该元素内不能有子元素。

<template>
  <div class="monaco-editor" ref="editor">
  </div>
</template>

  以上代码适用于注册新的语言以及增加已有语言的语法规则的高亮主题的方法。(未完待续)接着实例化编辑器,此时可以在页面中进行使用。第一个参数为要挂载的元素,第二个参数为配置选项。实例化代码及具体配置如下:

//const beautify = require('js-beautify')
//import { setLocaleData } from 'monaco-editor-nls'
//import zh_CN from 'monaco-editor-nls/locale/zh-hans'
//setLocaleData(zh_CN)
//const monaco = require('monaco-editor')
//const beautify_js = beautify.js
//const beautify_css = beautify.css
//const beautify_html = beautify.html

import * as monaco from "monaco-editor";

this.editor = monaco.editor.create(this.$refs.editor, {
      acceptSuggestionOnCommitCharacter: true, // 接受关于提交字符的建议
      acceptSuggestionOnEnter: 'on', // 接受输入建议 "on" | "off" | "smart" 
      accessibilityPageSize: 10, // 辅助功能页面大小 Number 说明:控制编辑器中可由屏幕阅读器读出的行数。警告:这对大于默认值的数字具有性能含义。
      accessibilitySupport: 'on', // 辅助功能支持 控制编辑器是否应在为屏幕阅读器优化的模式下运行。
      autoClosingBrackets: 'always', // 是否自动添加结束括号(包括中括号) "always" | "languageDefined" | "beforeWhitespace" | "never"
      autoClosingDelete: 'always', // 是否自动删除结束括号(包括中括号) "always" | "never" | "auto"
      autoClosingOvertype: 'always', // 是否关闭改写 即使用insert模式时是覆盖后面的文字还是不覆盖后面的文字 "always" | "never" | "auto"
      autoClosingQuotes: 'always', // 是否自动添加结束的单引号 双引号 "always" | "languageDefined" | "beforeWhitespace" | "never"
      autoIndent: 'None', // 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
      automaticLayout: true, // 自动布局
      codeLens: false, // 是否显示codeLens 通过 CodeLens,你可以在专注于工作的同时了解代码所发生的情况 – 而无需离开编辑器。 可以查找代码引用、代码更改、关联的 Bug、工作项、代码评审和单元测试。
      codeLensFontFamily: '', // codeLens的字体样式
      codeLensFontSize: 14, // codeLens的字体大小
      colorDecorators: false, // 呈现内联色彩装饰器和颜色选择器
      comments: {
        ignoreEmptyLines: true, // 插入行注释时忽略空行。默认为真。
        insertSpace: true // 在行注释标记之后和块注释标记内插入一个空格。默认为真。
      }, // 注释配置
      contextmenu: true, // 启用上下文菜单
      columnSelection: false, // 启用列编辑 按下shift键位然后按↑↓键位可以实现列选择 然后实现列编辑
      autoSurround: 'never', // 是否应自动环绕选择
      copyWithSyntaxHighlighting: true, // 是否应将语法突出显示复制到剪贴板中 即 当你复制到word中是否保持文字高亮颜色
      cursorBlinking: 'Solid', // 光标动画样式
      cursorSmoothCaretAnimation: true, // 是否启用光标平滑插入动画  当你在快速输入文字的时候 光标是直接平滑的移动还是直接"闪现"到当前文字所处位置
      cursorStyle: 'UnderlineThin', // "Block"|"BlockOutline"|"Line"|"LineThin"|"Underline"|"UnderlineThin" 光标样式
      cursorSurroundingLines: 0, // 光标环绕行数 当文字输入超过屏幕时 可以看见右侧滚动条中光标所处位置是在滚动条中间还是顶部还是底部 即光标环绕行数 环绕行数越大 光标在滚动条中位置越居中
      cursorSurroundingLinesStyle: 'all', // "default" | "all" 光标环绕样式
      cursorWidth: 2, // <=25 光标宽度
      minimap: {
        enabled: false // 是否启用预览图
      }, // 预览图设置
      folding: true, // 是否启用代码折叠
      links: true, // 是否点击链接
      overviewRulerBorder: false, // 是否应围绕概览标尺绘制边框
      renderLineHighlight: 'gutter', // 当前行突出显示方式
      roundedSelection: false, // 选区是否有圆角
      scrollBeyondLastLine: false, // 设置编辑器是否可以滚动到最后一行之后
      readOnly: false, // 是否为只读模式
      theme: 'vs'// vs, hc-black, or vs-dark
    })

以上配置并不是全部配置,若有缺失,需要自己再补充。

三、高级功能

1. 自定义语言高亮主题

const monaco = require('monaco-editor')
monaco.languages.register({ id: 'properties', aliases: ['properties'] });
monaco.languages.register({ id: 'yaml1', aliases: ['YAML', 'yaml', 'YML', 'yml', "yaml1"],  mimetypes: ["application/x-yaml", "text/x-yaml"]});
monaco.languages.setMonarchTokensProvider('properties', {
      ignoreCase: true,
      tokenizer: {
            root: [
                  [/^\s*(\#).*/, 'notes'],
                  //每对小括号代表一个分词
                  [/(.*\s*)(\s*=\s*)(\w*[^\s]*)(\s*[\#]*\w*)/, ['key', 'sym', 'value', 'comment']]
            ]
      }
});
monaco.languages.setMonarchTokensProvider('yaml1', {
      ignoreCase: true,
      tokenizer: {
            root: [
                  [/^\s*(\#).*/, 'comment'],
                  [/(\w*)(\s*:\s*)(\w*[^\s]*)(\s*[\#]*\w*)/, ['key', 'sym', 'value', 'comment']]
            ]
      }
});

monaco.editor.defineTheme("newTheme", {
      base: 'vs',
      inherit: false,
      rules: [
            { token: 'key', foreground: '#0000FF', fontStyle: 'bold' },
            { token: 'sym', foreground: '#f5a623', },
            { token: 'value', foreground: '#4EC9B0' },
            { token: "notes", foreground: "#6A9955" },
            { token: "comment", foreground: "#6A9955" },
      ]
});

  以上代码适用于注册新的语言以及增加已有语言的语法规则的高亮主题的方法。
首先需要通过monaco.languages.register注册新的语言,其主要参数为:

  • id:新增的语言的key
  • aliases:别名,注册的新语言的别名
  • extensions:扩展,其他语言的id可以写在这里,会被识别为新的语言,可以用来覆盖原有语言的分词方式

然后通过monaco.languages.setMonarchTokensProvider设置Monarch分词器,其主要参数为:

  • ignoreCase:忽略分词器的大小写
  • tokenizer:分词器,通过正则表达式可以划分monaco-editor每行的词条。其中属性root可以上面的代码方式进行分词。

最后通过monaco.editor.defineTheme设置语言的主题,相当于通过规则设置已分词汇的背景色,字体颜色等。其主要参数为:

  • base:要继承的基础主题,即内置的三个:vs、vs-dark、hc-black
  • inherit:是否继承
  • rules:高亮规则,即给代码里不同token类型的代码设置不同的显示样式,常见的token有string(字符串)、comment(注释)、keyword(关键词)等等。查找样式可以通过按F1或鼠标右键点击Command Palette,然后再找到并点击Developer: Inspect Tokens,接下来鼠标点哪一块代码,就会显示对应的信息,包括token类型,当前应用的颜色等。
  • colors:非代码部分的其他部分的颜色,比如背景、滚动条等

高阶些的自定义主题用法可以查看该链接文章:闲谈Monaco Editor-自定义语言之Monarch

2. 右键菜单自定义

addRMenu({ id, label, keybindings, MenuGroup, MenuOrder }, fn) {
        let disposable = this.editor.addAction({
            id,
            label,
            keybindings: keybindings || [],
            contextMenuGroupId: MenuGroup === undefined ? 'navigation' : MenuGroup,
            contextMenuOrder: MenuOrder || 1,
            run: fn
        })
        this.collectDispose(disposable);
    }
this.editor.addRMenu(
        {
          id: ActionsIds.javaOrganizeImports,
          keybindings: [monaco.KeyMod.CtrlCmd|monaco.KeyMod.Alt|monaco.KeyCode.KeyF],
          label: "重新组织导入",
        },
        this.organizeImports
      );

可以在右键面板中增加自定义功能:
id:该选项的唯一标识。
label:选项显示的名称。
keybindings:绑定的快捷键,多个快捷键用竖线分割。每个按键要使用monaco的内置枚举类型来设定。
contextMenuGroupId:选项所属组的id,内置了三个组idnavigation:该组是右键的第一栏,1_modification:修改组,9_cutcopypaste:剪切复制粘贴组)。
run:选择该选项之后的回调函数,第一个参数为editor实例,第二个参数为一个剩余参数的数组,注意如果通过快捷键触发那么第二个参数不存在。

3. 代码自动补全

1.XML文件自动补全

monaco.languages.registerCompletionItemProvider('xml',
  {
        triggerCharacters: ['>'],
        provideCompletionItems: (model, position) => {
              const codePre = model.getValueInRange({
                    startLineNumber: position.lineNumber,
                    startColumn: 1,
                    endLineNumber: position.lineNumber,
                    endColumn: position.column,
              });

              const tag = codePre.match(/.*<(\w+)>$/)?.[1];

              if (!tag) {
                    return;
              }

              const word = model.getWordUntilPosition(position);

              return {
                    suggestions: [
                          {
                                label: `</${tag}>`,
                                kind: monaco.languages.CompletionItemKind.EnumMember,
                                insertText: `$1</${tag}>`,
                                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                                range: {
                                      startLineNumber: position.lineNumber,
                                      endLineNumber: position.lineNumber,
                                      startColumn: word.startColumn,
                                      endColumn: word.endColumn,
                                },
                          },
                    ],
                    //dispose() {
                    //  const line = pos.lineNumber
                    //  const column = pos.column
                    //  if(model.getValueInRange(new monaco.Range(line, column - 1, line, column)) !== "/") {
                    //    return
                    //  }
                    //  editor.executeEdits("", [
                    //    {
                    //      range: new monaco.Range(line, column - 1, line, column),
                    //      text: null
                    //    }
                    //  ])
                    }
                  }
              };
        },
  });

以上代码是在识别为xml语言时,在输入'>'后会开启自动补全功能。其中:
triggerCharacters: 表示触发补全操作的快捷键,编辑器会提示出suggestions中设置的内容。注意回车之后输入的快捷键仍然是存在的。
suggestions中的label表示提示的名称,insertText表示回车之后输入的内容,kind用来设定提示片段的类型,即最前面的图标标识,detail是提示的说明,位于提示行的末尾。
dispose回调函数: 有时候输入的快捷键并不是实际需要的代码,补全代码后需要删除掉,这时就需要这个回调函数来删除掉已输入的快捷键。可以通过editor.executeEdits在指定位置插入代码,替换成空字符串。

2023年3月13日更新:

2.properties文件自动补全
  和xml一样,使用 monaco.languages.registerCompletionItemProviderprovideCompletionItems来实现。

registerPropertiesCompletion(language = "properties") {
            let self = this;
            monaco.languages.registerCompletionItemProvider(language, {
                  provideCompletionItems: (model, position, context) => {
                        //获取当前光标行内容
                        // let line = model.getValueInRange({
                        //       startLineNumber: position.lineNumber, 
                        //       startColumn: 1, 
                        //       endLineNumber: position.lineNumber, 
                        //       endColumn: position.column
                        // });
                        if(self.suggestionData.length > 0){//如果存在能拿来补全的信息数据
                              let word = model.getWordUntilPosition(position);//获取当前填写的字段内容,位置
                              let range = {
                                    startLineNumber: position.lineNumber,
                                    endLineNumber: position.lineNumber,
                                    startColumn: word.startColumn,
                                    endColumn: word.endColumn,
                              };
                              let suggestions = [];
                              let content = model.getValue();//获取editor的全部内容
                              let contentSplit = content.split("\n");//每行内容的数组
                              let contentSplitMap = {};
                              contentSplit.forEach(item => {//用来判断是否存在相同key值的Map
                                    let key = item.split("=")[0];
                                    if(!contentSplitMap[key]){
                                          contentSplitMap[key] = key;   
                                    }
                              });
                              self.suggestionData.forEach(item => {
                                    if(item.name.indexOf(word.word) > -1){//如果输入的字段匹配到对应的补全信息
                                          if(!contentSplitMap[item.name]){//如果该补全信息不在已有的editor内,则push进补全信息的数组
                                                suggestions.push({
                                                      label: `${item.name}${item.defaultValue ? '='+ item.defaultValue : ''}`,
                                                      // label: `${item.name}${item.defaultValue ? '='+ item.defaultValue : ''} ${item.desc ? '('+item.desc+')': ""} ${item.shortType}`,
                                                      detail: item.shortType,
                                                      kind: monaco.languages.CompletionItemKind.Value,
                                                      documentation: item.desc,
                                                      insertText: `${item.name}=${item.defaultValue ? item.defaultValue : ''}`,
                                                      insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                                                      range: range
                                                });
                                          }     
                                    }
                              });
                        }
                        return {
                              suggestions: suggestions
                        }
                  }
            })
      }

2023年5月29日更新:

3.yaml文件自动补全

registerYamlCompletion(language = "yaml"){
            let self = this;
            monaco.languages.registerCompletionItemProvider(language, {
                  provideCompletionItems: (model, position, context) => {
                        let suggestions = [];
                        if(self.suggestionData.length > 0){//如果存在能拿来补全的信息数据
                              let content = model.getValue();//获取editor的全部内容
                              let json = require("js-yaml").load(content);//将文档内容转为json格式
                              let jsonContent = JSON.parse(JSON.stringify(json, null, 4));
                              let word = model.getWordUntilPosition(position);//获取当前填写的字段内容,位置
                              let parentsKey = self.getParent(jsonContent, word.word);//获取该字段的所有父级对象key值的数组
                              if(parentsKey.length > 1){//通过上面的数组定位到字段上一级父级的对象内容
                                    for(let i = 0; i < parentsKey.length - 1; i++){
                                          jsonContent = jsonContent[parentsKey[i]];
                                    }
                              }
                              let brothersKey = [];
                              if(typeof jsonContent === "object"){//获取字段同级的兄弟key值
                                    brothersKey = Object.keys(jsonContent).filter((key)=> {
                                          return key !== word.word;
                                    });
                              }
                              let regExp = self.getRegExp(parentsKey, brothersKey, word.word);//生成匹配父级元素和排除同级元素的正则表达式
                              let range = {
                                    startLineNumber: position.lineNumber,
                                    endLineNumber: position.lineNumber,
                                    startColumn: word.startColumn,
                                    endColumn: word.endColumn,
                              };
                              self.suggestionData.forEach(item => {
                                    if(new RegExp(regExp).test(item.name)){//如果存在能被匹配的字符串,则继续生成补全信息
                                          //排除掉前面已存在的父级,获得需要被输出的补全信息,注意父级key数组为空的情况
                                          let value = parentsKey.length > 0 ? item.name.slice(parentsKey.join(".").length + 1) : item.name;
                                          let keys = value.split(".");
                                          let insertText = self.getInsertText(keys, item);
                                          suggestions.push({
                                                label: `${item.name}${item.defaultValue ? '='+ item.defaultValue : ''}`,
                                                //label: `${item.name}${item.defaultValue ? '='+ item.defaultValue : ''} ${item.desc ? '('+item.desc+')': ""} ${item.shortType}`
                                                kind: monaco.languages.CompletionItemKind.Value,
                                                detail: item.shortType,
                                                documentation: item.desc,
                                                insertText: insertText,
                                                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                                                range: range
                                          });    
                                    }
                              });
                        }
                        return {
                              suggestions: suggestions
                        }
                  }
            })  
      }
      getGroupValueInsertText(keys, item){//获得对应层级的json对象,转化为yaml格式
            let json = {};
            for(let i = keys.length - 1; i >= 0; i--){
                  if(i === keys.length - 1){
                        json = item.defaultValue;
                  }
                  else{
                        json = {
                              [keys[i]]: json
                        };
                  }
            }
            return require("js-yaml").safeDump(json);
      }
      getInsertText(keys, item){
            //生成yaml格式的需要被输出的补全信息。
            //例子:"spring.datasource.druid"  -> 
                  //"spring:
                  //  datasource:
                  //   druid: ${0: }" 
            let insertText = "";
            if(item.group && item.defaultValue !== null){
                  insertText = this.getGroupValueInsertText(keys, item);
            }
            else{
                  for(let j = 0; j < keys.length; j++){
                        if(j === 0){
                              insertText = insertText + `${keys[j]}:\n`;   
                        }
                        else{
                              let empty = "";
                              let k = j;
                              while( k > 0){
                                    empty = empty + "  ";//选择空几格,现在是两格
                                    k--;
                              }
                              if(j !== keys.length - 1){
                                    insertText = insertText + empty + `${keys[j]}:\n`;
                              }
                              else{
                                    if(item.defaultValue !== null){
                                          insertText = insertText + empty + `${keys[j]}: ${item.defaultValue}`
                                    }
                                    else{
                                          if(item.group){
                                                insertText = insertText + empty + `${keys[j]}:\n` + empty + "- ${0: }" 
                                          }
                                          else{
                                                insertText = insertText + empty + `${keys[j]}: ` + "${0: }"//"${0: }"光标定位的输入位置,若有默认值则直接输入默认值     
                                          }
                                    }
                              }
                        }      
                  }
            }
            return insertText;
            
      }
      getParent(obj, key, parent = []){//通过递归方式获取json对象中某个属性名的所有父对象属性名的数组
            if(typeof obj !== "object"){
                  if(obj === key){
                        return parent;
                  }
                  else{
                        return undefined;
                  }     
            }
            if(obj[key]){
                  return parent;          
            }
            else{
                  for (const keys of Object.keys(obj)) {
                        let parents = JSON.parse(JSON.stringify(parent));
                        parents.push(keys);
                        let result = this.getParent(obj[keys], key, parents);
                        if(result){
                              return result;
                        }   
                  }     
            }
      }
      getRegExp(parentsKey = [], brothersKey = [], key){
            //根据父对象属性和兄弟对象属性,生成同一父属性并排除掉存在兄弟属性的补全信息内容
            //例:spring.data.(?!(r2dbc)|(redis)\.).*[d].*[.].*[r].*
            //其中spring.data是父属性,(r2dbc)|(redis)是兄弟属性
            if(key.trim() === ""){
                  return;
            }
            let regExp = "";
            parentsKey.forEach(item => {
                  regExp = `${regExp}${item}.`;
            })
            regExp = regExp + "(?!";
            brothersKey.forEach(item => {
                  regExp = `${regExp}(${item})|`; 
            });
            brothersKey.length > 0 && regExp.slice(0, regExp.length - 1);
            regExp = regExp + "\\.).*";
            for(let i = 0; i < key.length; i++){
                  regExp = `${regExp}[${key.charAt(i)}].*`;    
            }
            return regExp;
      }

4.冲突代码合并功能

//注册一个代码合并的命令
    monaco.editor.registerCommand("mergeVersion", (ctx, args) => {

        let { editor, replaceValue, startLine, endLine } = args;
        let content = editor.getValue();
        let contentSplit = content.split("\n");

        let newContent = '';
        let start = false;
        contentSplit.forEach((line, index) => {
            let currentLine = index + 1;
            if (currentLine === startLine) {
                start = true;
            } else if (currentLine === endLine) {
                start = false;
                newContent = newContent + replaceValue;
                return;
            }
            if (start === false) {
                newContent = newContent + "\n" + line;
            }
        });
        /**
             * 如果需要处理断点的问题,使用以下代码修改内容前先保存一下装饰点
             * oldDecorations = activeEditor.deltaDecorations(oldDecorations, []);
             * 
             * 修改内容xxxxx
             * 
             * 或者如果需要重设当前鼠标点,可以使用以下内容
             * activeEditor.setSelection(new monaco.Range(0, 0, 0, 0));
             * activeEditor.setPosition(currentPosition);
             * 
             * 修改完后重新设置断点
             * oldDecorations = activeEditor.deltaDecorations(oldDecorations, breakPoints);
             * 
             * 以下是一个参考的反馈单
             * https://github.com/Microsoft/monaco-editor/issues/299
             */

        //替换某行某列,因为这里是>>>>>>所以这里是默认column为8
        editor.executeEdits('replace', [{
            identifier: 'insert all', range: new monaco.Range(startLine, 1, endLine, 1000), text: replaceValue, forceMoveMarkers: true
        }])
        // this.replaceContent(args.incomingVersionContent,args.startLine,args.endLine);

    });
//注册一个合并工具提供者
monaco.languages.registerCodeLensProvider('*', {
        provideCodeLenses: function (model, token) {

            let newCodeLens = [];
            let editor = model.myEditor;
            if(editor){
                let content = model.getValue();
                let contentSplit = content.split("\n");
                let compareDatas = [];
    
                //当前的所在的状态,start开始状态,split分层状态,end结束状态
                let status = 'end';
                //当前版本的内容
                let versionContent = '';
                //冲突块
                let conflitBlock = {};
                let versionContentCount = 0;
                contentSplit.forEach((line, index) => {
                    let currentLine = index + 1;
                    if (line.startsWith('<<<<<<<')) {
                        conflitBlock = {
                            //信息提示的行号
                            startLine: currentLine,
                            //结束行
                            endLine: -1,
                            //本地版本内容
                            currentVersionContent: '',
                            //当前版本的总行数
                            currentVersionLineCount:-1,
                            //远程版本内容
                            incomingVersionContent: '',
                            //远程版本的总行数
                            incomingVersionLineCount: -1
                        }
                        status = 'start';
                        versionContent = '';
    
                    } else if (line.startsWith('>>>>>>>') && status === 'split') {
                        conflitBlock.incomingVersionContent = versionContent;
                        conflitBlock.endLine = currentLine;
                        conflitBlock.incomingVersionLineCount = versionContentCount;
                        versionContent = '';
                        versionContentCount = 0;
                        status = 'end';
                        compareDatas.push(conflitBlock);
                    } else if (line.startsWith('=======') && status === 'start') {
                        conflitBlock.currentVersionContent = versionContent;
                        conflitBlock.currentVersionLineCount = versionContentCount;
                        versionContent = '';
                        versionContentCount = 0;
                        status = 'split';
                    } else {
                        if (status === 'start' || status === 'split') {
                            versionContent = versionContent + line;
                            versionContentCount = versionContentCount + 1;
                        }
                    }
                });
    
    
                
                
                let codeDecorations = [];
                if (compareDatas.length > 0) {
                    
                    for (let compareData of compareDatas) {
                        let {startLine,endLine,currentVersionContent,currentVersionLineCount,incomingVersionContent,incomingVersionLineCount} = compareData;
    
                        //使用本地版本触发按钮
                        newCodeLens.push(createCodeLen('currentVersion','使用本地的版本',startLine,endLine,1,editor,currentVersionContent));
                        
                        //使用远程版本按钮
                        newCodeLens.push(createCodeLen('incomingVersion','使用远程版本',startLine,endLine,2,editor,incomingVersionContent));
    
                        //合并两个版本按钮
                        newCodeLens.push(createCodeLen('bothVersion','同时使用两个版本',startLine,endLine,3,editor,currentVersionContent + "\r\n" + incomingVersionContent));
                        
                        //当前版本的标签
                        codeDecorations.push(createMergeDecoration(startLine,startLine,'currentVersionTag'));
                        //当前版本的代码范围 开始行数 + 当前版本的总行数
                        codeDecorations.push(createMergeDecoration(startLine,startLine+currentVersionLineCount,'currentContentTag'));
    
                        //远程版本的代码范围 结束行数 - 远程行数的总行数
                        codeDecorations.push(createMergeDecoration(endLine-1,endLine-incomingVersionLineCount,'incomingContentTag'));
                        //远程版本的标签
                        codeDecorations.push(createMergeDecoration(endLine,endLine,'incomingVersionTag'));
    
                    }
    
                    
                }
    
                let oldMergeDecorations = editor.oldMergeDecorations?editor.oldMergeDecorations:[];
                // 返回的是一个decotation集,可以对期进行增删处理
                var decorations = editor.deltaDecorations(
                    oldMergeDecorations,
                    codeDecorations
                );
    
                editor.oldMergeDecorations = decorations;
            }
            

            return {
                lenses: newCodeLens,
                dispose: () => { }
            };
        },
        resolveCodeLens: function (model, codeLens, token) {
            alert("condLens");
            return codeLens;
        }
    });
//创建对比按钮
var createCodeLen = (id,title,startLine,endLine,column,editor,replaceValue)=>{

    return {
        range: {
            startLineNumber: startLine,
            startColumn: column,
            endLineNumber: endLine,
            endColumn: column
        },
        id,
        command: {
            id: 'mergeVersion',
            title,
            arguments: [{
                startLine,
                endLine,
                editor,
                replaceValue
            }]
        }
    };

}

//创建
var createMergeDecoration = (startLine,endLine,classStyleName) =>{
    return {
        range: new monaco.Range(startLine, 1, endLine, 1),
        options: {
            isWholeLine: true,
            className: classStyleName
        }
    };
}

四、常见API

1.editor.getValue()
  获取编辑器中的所有文本,并生成一个字符串返回,会保留所有信息(换行、缩进、注释等等)。

2.editor.getSelection()
  获取编辑器中被选中文案的 range ,返回一个对象,如下:

{
    startLineNumber: 0,
    startColumnNumber: 0,
    endLineNumber: 0,
    endColumnNumber: 0,
}

3.editor.getModel()
  获取编辑器当前的 textmodel,一般不会直接使用,通过 textmodel 可以对文本各种操作。

4.editor.getModel().findMatches(str|regexp)
  功能和 “⌘ + F” 一致,通过字符串或正则表达式查找编辑器内匹配的文本,并返回匹配文本 range 的集合。

5.editor.getModel().getValueInRange(range)
  通过 range 获取范围内的文本,返回一个字符串。

6.editor.getModel().getLinesContent(lineNumber)
  如果传入 lineNumber,则返回对应行的文本字符串,不传参则返回所有行的文本字符串的集合。

7.editor.executeEdits()
  在指定位置插入代码,跟 editor.setValue() 不同的地方是,可以用 “⌘ + Z” 撤销输入。

editor.executeEdits('需要插入的代码/string', [
  {
    range: {
      startLineNumber,
      startColumn,
      endLineNumber,
      endColumn,
    },
    text,
  },
])

8.editor.addAction()
  在右键菜单里增加一栏自定义的操作。

this.editor.addAction({
  id: '', // 菜单项 id
  label: '', // 菜单项名称
  keybindings: [this.monaco.KeyMod.CtrlCmd | this.monaco.KeyCode.KEY_J], // 绑定快捷键
  contextMenuGroupId: '9_cutcopypaste', // 所属菜单的分组
  run: () => {}, // 点击后执行的操作
})

9.monaco.editor.setModelMarkers()
  在编辑器中用波浪线标出错误提示。

monaco.editor.setModelMarkers(editor.getModel(), 'owner', [
  {
    startLineNumber,
    startColumn,
    endLineNumber,
    endColumn,
    message, // 提示文案
    severity: monaco.MarkerSeverity.Error, // 提示的类型
  },
])

10.editor.getAction('actions.xxx').run()
  手动调用monaco方法。

//实现查找效果
const model = editor.getModel();
const range = model.findMatches('匹配的字符串或者正则表达式')[0].range;

editor.setSelection(range);
editor.getAction('actions.find').run();
//实现拷贝效果
editor.getAction('editor.action.clipboardCopyAction').run();
//实现复制效果
var selection = editor.getSelection();
var content = localStorage.getItem('your-content');
editor.executeEdits("", [
    {
      range: new monaco.Range(selection.endLineNumber, selection.endColumn, selection.endLineNumber, selection.endColumn),
      text: content
    }
  ])

11.editor.trigger("xxx", "操作");
  手动调用monaco方法。

editor.trigger("myapp", "undo");//触发撤销
editor.trigger("myapp", "redo");//触发重做

爱吃鸡蛋饼
55 声望8 粉丝