盘点使用 Monaco Editor 实现在线代码编辑的一些需求

本文正在参加「金石计划」

前言

在笔者参与的一个低码项目中,在线编辑代码可以说是非常重要的一个功能。使用低码工具开发页面时,通过可视化拖拽、配置组件的一些属性后,针对组件的事件,按钮的点击等逻辑代码需要在线编写,因此 在线编辑代码 是刚需,本文跟各位掘友详细聊聊我在项目中通过集成Monaco Editor实现的一些需求点,如果在后面各位用到能帮到你的话就更好了😃!

关于 Monaco Editor

如果你不认识它,但我想你一定认识 vscode,你把它当做 vscode的编辑器即可,并且是开源的。

The Monaco Editor is the fully featured code editor from VS Code

笔者在这里也不过多介绍,反正很牛批,感兴趣可以看官网的文档:
盘点使用 Monaco Editor 实现在线代码编辑的一些需求
好了,废话不多说,走进正文。

初始化编辑器

我们可以通过实现一个组件来稍微封装一下:

<template>
    <div id="editor-box" :style="width: width, height: height"></div>
</template>
<script>
import * as monaco from 'monaco-editor'
export default {
  name: 'VMonacoEditor',
  props: {
    width: String, // 宽度
    height: String, // 高度
    editorOptions: Object // 编辑器初始化属性配置
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      const editorOptions = {
        //编辑器初始显示代码
        value: this.code,
        language: 'javascript',
        // 语言支持自行查阅demo
        automaticLayout: true,//自动布局
        theme: 'vs-black',
        //官方白带三种主题vS, hc-btack, or vs-dark
        tabsize: 2,
        ...this.editorOptions
      }
      // 初始化编辑器,确保dom已经渲染
      this.editor = monaco.editor.create(document.getelementById('editor-box'), editorOptions)
    }
  }
}
</script>

完成的初始化属性配置

编辑器的校验、编译配置

// validation settings
monaco.languages.typescript.javascriptDefaults.setDiagnostics0ptions({
  noSemanticValidation: true,
  noSyntaValidation: true
})
// compiler options
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
  target: monaco.languages.typescript.ScriptTarget.ES2017,
  strict: true,
  allowNonTsExtensions: true
})

设置代码

this.editor.setValue(code)

获取编辑器代码

this.editor.getValue()

设置主题、语言

// 设置主题
function themeChange(val) {
  monaco.editor.setTheme(val || 'vs')
}

// // 设置语言
function languageChange(val) {
  monaco.editor.setModelLanguage(this.editor.getModel(), val || 'javascript')
}

使用 prettier 格式化代码

如何使用 prettier 来格式化代码呢?关键就在于 Monaco Editor 提供了你自定义格式化代码的能力,通过registerDocumentFormattingEditProvider这个 API 来注册你的格式化规则,当触发格式化时就会调用。
盘点使用 Monaco Editor 实现在线代码编辑的一些需求
详细步骤如下:

安装 prettier

yarn add prettier

注册 格式化规则

// ...
import prettier from 'prettier'
import parserBabel from 'prettier/parser-babel'
// ...

// 格式化逻辑
function spliceSemiAndDoubleQoute(val) {
  return prettier.format(val, {
    parser: 'babel',
    //不保留行尾分号去掉,开发规范统一
    semi: false,
    //字符串用单引号包,裹,开发规范统一
    singleQuote: true,
    // 代码每行宇符数
    printWidth: 500,
    //jsx中'>'保持在一行
    bracketSameLine: true,
    //对象空格
    bracketSpacing: true,
    //行尾逗号
    trailingComma: 'none',
    // (x) => {}
    arrowParens: 'avoid',
    //函数后不带空格
    spaceBeforeFunctionParen: false,
    plugins: [parserBabel],
  })
}

const formatProvider = {
  provideDocumentFormattingEdits(model, options, token) {
    return [{
      text: spliceSemiAndDoubleQoute(model.getValue()),
      range: model.getFullModelRange()
    }]
  }
}

monaco.languages.registerDocumentFormattingEditProvider('javascript', formatProvider)

由于很多需求都需要在 monaco 这个对象上去设置,并且要先设置,再去初始化编辑器,所以笔者将以上逻辑提取出来单独一个文件 decorateMonacoEditor.js,最后将 monaco export 出去,然后在初始化的文件中直接import monaco from 'decorateMonacoEditor.js' 即可。

触发格式化规则

编辑器右键是有 格式化文件 的选项的,但有些时候我们需要自动的触发格式化逻辑:

this.editor.getAction(['editor.action.formatDocument' ])._run()

收起、展开代码块

// 收起
this.editor.getAction(['editor.foldAll'])._run()
// 展开
this.editor.getAction(['editor.unfoldAll'])._run()

设置编辑区域滚动高度

this.editor.setScrollTop(srollTop)

代码区域滚动事件监听

在什么时候会用到这个呢?举一个🌰:当我们离开编辑区,如果做了自动格式化代码或其他一些操作,滚动条就会回到顶部,我们需要优化体验,希望我在点击回到代码编辑区还是上次滚动的位置,此时就可以使用这个事件记录一下滚动位置,当点击回来时自动的滚动到上次离开的位置。

this.editor.onDidScrollChange (() = {
    // 你的逻辑
})

编辑区域失去焦点

this.editor.onDidBlurEditorWidget(() => {
   // 你的逻辑
})

自定代码块

可以新建一个文件用于定义自定义代码块,比如:

// my-snippets.js

export default {
  log: `console.log()`,
  for: `for (let index = 0; index < array.length; index++) {
  const element = array[index];
}`,
  // 更多自定义代码块......
}

然后通过以下方式将自定义代码块注册到编辑器中:

import snippets from './my-snippets'
monaco.languages.registerCompletionItemProvider('javascript', {
  provideCompletionItems: () => {
    const suggestions = Object.keys(snippets).map(key => ({
      label: key,
      kind: monaco.languages.CompletionItemKind.Enum,
      insertText: snippets[key],
      detail: snippets[key]
    }))
    return {
      incomplete: true,
      suggestions: [...suggestions]
    }
  }
})

大概就是这么个效果:
盘点使用 Monaco Editor 实现在线代码编辑的一些需求

代码提示

如下图所示,在 vscode 的 JS 文件编辑器中定义一个数组,当我们使用这个变量的时候,会提示这个数组对象上可用的属性、方法,这就是根据类型推断的提示。
盘点使用 Monaco Editor 实现在线代码编辑的一些需求
当然 Monaco Editor 已经内置了 JS 对象的代码提示,我在这里要说的是假如我们封装了一些对象,如何让编辑器提示。如果你的项目是 TS 开发的,那就非常简单,只需要将类型文件加载到编辑器应该就 OK,由于笔者项目不是 TS 写的,本文主要讲一下 JS 开发的 对象 如何让 MonacoEditor 自动提示。下面是一个例子:

export default {
  /**
   * @param {*} code 
   * @returns {Instance} 返回
   */
  getInstance() {
    return new Instance()
  }
}

class Instance {
  constructor() {
    this.id = Date.now()
    this.logTime = 0
  }

  logId() {
    console.log()
    this.logTime++
  }

  getLogTime() {
    return this.logTime
  }

}


// 上面的是我们封装的,当我们在编辑器编写以下代码,希望编辑器能够提示 Instance 的实例属性,方法
function test(context) { // 假如该函数运行时,context是上面的 { getInstance }
  const inst = context.getInstance()
  // inst. 
}

可以通过以下代码来实现:

const apiType = `const context = {
  /**
   * @param {*} code 
   * @returns {Instance} 返回Instance实例
   */
  getInstance() {
  }
}

const Instance = {
  id,
  logTime,
  logId(),
  getLogTime()
}`
monaco.Languages.typescript.javascriptDefaults.addExtralib(apiType, 'myapi.js')

看看效果:
盘点使用 Monaco Editor 实现在线代码编辑的一些需求
盘点使用 Monaco Editor 实现在线代码编辑的一些需求

可能你想为你的属性、方法添加一些描述信息:

const apiType = `const context = {
  /**
   * @param {*} code 
   * @returns {Instance} 返回Instance实例
   */
  getInstance() {
  }
}

const Instance = {
  /** 实例 ID*/
  id,
  /** 实例打印次数 */
  logTime,
  /** 输出实例 ID */
  logId(),
  /** 获取打印次数 */
  getLogTime()
}`

按照 jsdoc 的规范来写即可,结果如下:
盘点使用 Monaco Editor 实现在线代码编辑的一些需求

当然你肯定还会有疑问,难道我定义了一堆 API,我要一个一个手敲在这里才行吗?
答案肯定不是,我们从构造函数的原型上也可以获取到属性和方法,然后写个转换逻辑生成我们需要的格式即可。比如笔者就是这么玩的:

盘点使用 Monaco Editor 实现在线代码编辑的一些需求

总结

以上就是笔者在项目中遇到的一些需求,都是通过阅读官方文档得出的方案,其余笔者未实现的需求请查阅文档哦😁!觉得对您有帮助的话,可以点个赞支持下,谢谢!

原文链接:https://juejin.cn/post/7218419207129415738 作者:Lvzl

(0)
上一篇 2023年4月5日 上午11:13
下一篇 2023年4月5日 下午4:05

相关推荐

发表回复

登录后才能评论