使用 Monaco Editor 的一些总结

最近因为需要实现一个在线代码的需求,所以研究了相关的内容,其中最主要的就是编辑器相关的内容

使用

对于 monaco 编辑器的选择,我使用了 @monaco-editor/react

typescript 复制代码
import type { EditorProps, OnChange, OnMount } from '@monaco-editor/react'
import { Editor as MonacoEditor } from '@monaco-editor/react'
import { useDebounceFn } from 'ahooks'
import { Spin } from 'antd'
import { FC, useState } from 'react'

export const CodeEditor: FC<EditorProps> = props => {
  const [theme, setTheme] = useState('vs')

  // 处理代码修改, args需要做一层透传来完善防抖,避免触发重复构建
  const { run: handleChange } = useDebounceFn<OnChange>(
    (...args) => {
      if (props.onChange) {
        props.onChange(...args)
      }
    },
    {
      wait: 400,
    },
  )

  return (
    <MonacoEditor
      loading={<Spin />}
      theme={theme}
      {...props}
      onChange={handleChange}
    />
  )

简单的对齐进行了封装使用

本地使用

在使用 @monaco-editor/react 库,会通过CDN去加载编辑器的资源

但有时候,我们所需要的环境,并不能直接去访问外网,故我们需要将这些资源改变成本地加载 首先我们需要一个公共位置去存放这些资源,如 vite 的 public 文件夹等等 我们需要把 monaco-editor 库下载下来,主要是需要 min/vs 内容,提供给编辑器

其实还有另一种方式,是安装 monaco-editor 库,并引入即可,但之前测试的时候是有问题的,包括 webpack 等等,不太稳定,所以没有选择这种方式

typescript 复制代码
import { loader } from '@monaco-editor/react'

loader.config({
  paths: {
    // public 下存放 monaco-editor 的路径
    vs: '/npm/monaco-editor/min/vs',
  },
})

通过这种方式,就可以避免编辑器去加载远程资源了

类型提示

在在线代码里,我们还需要自定义一些专属的东西,如全局上下文等内容


通过 Suggestion 提供类型提示

Monaco Editor (microsoft.github.io)

基本的操作方法可以查看一下官网的案例 主要就是通过 monaco.languages.registerCompletionItemProvider 方法去注册,然后返回规定格式的 suggestion 即可,最后再选择一个触发方式,即 triggerCharacters

javascript 复制代码
monaco.languages.registerCompletionItemProvider('javascript', {
  provideCompletionItems: (model, position) => {
    const range = {
      startLineNumber: position.lineNumber,
      endLineNumber: position.lineNumber,
      startColumn: model.getWordUntilPosition(position).startColumn,
      endColumn: model.getWordUntilPosition(position).endColumn,
    }

    return {
      suggestions: [
        {
          label: 'test', // 显示的提示内容
          kind: monaco.languages.CompletionItemKind.Function, // 用来显示提示内容后的不同的图标
          insertText: 'test', // 选择后粘贴到编辑器中的文字
          detail: '', // 提示内容后的说明
          range: range,
        },
      ],
    }
  },
  triggerCharacters: ['.'],
})

上面代码实现的就是按下 . 时触发提示,并提示出 test 方法

接下来,我们来根据对象结果信息,动态的去触发提示,并封装几个方法

typescript 复制代码
import type { Monaco } from '@monaco-editor/react'

/**
 * 将对象转化为数组形式
 * @param input 待转换对象
 * @param path 递归路径
 * @param pathList 转换后的路径数组
 */
const convertObject2Array = (input: Record<any, any>, path: string[], pathList: string[][]) => {
  const arr = Object.keys(input)
  for (const k in arr) {
    const res = [path, arr[k]].flat()

    if (input[arr[k]].constructor !== Object) {
      // Object 递归处理
      // 说明找到了一条完整的路径
      pathList.push(res)

      if (k === arr.length - 1) return
    } else {
      convertObject2Array(input[arr[k]], res, pathList)
    }
  }
}

const getLastData = (content: string, index: number) => {
  content = content.substring(0, index - 1)
  const sql = content.trim().replace(/\s+/g, ' ').replace(/,/g, ', ').replace(/=/g, '= ').replace(/\(/g, '( ')
  const splitRes = sql.split(' ')
  const res = splitRes[splitRes.length - 1]
  return res.substring(0, res.length - 1)
}

/**
 * 将对象注入到monaco编辑器中进行提示
 * @param monaco 编辑器实例
 * @param obj 注入对象
 * @param triggerCharacters 触发字符
 */
export const injectSuggestionsByObject = (monaco: Monaco, obj: Record<any, any>, triggerCharacters = ['.']) => {
  const suggestionData = [] // 二维数组
  convertObject2Array(obj, [], suggestionData)

  monaco.languages.registerCompletionItemProvider('javascript', {
    provideCompletionItems: (model, position) => {
      const range = {
        startLineNumber: position.lineNumber,
        endLineNumber: position.lineNumber,
        startColumn: model.getWordUntilPosition(position).startColumn,
        endColumn: model.getWordUntilPosition(position).endColumn,
      }
      const line = position.lineNumber
      const content = model.getLineContent(line)
      const point = content[position.column - 2]
      const data = getLastData(content, position.column)
      const property = data.split(triggerCharacters[0]) // [ 'basic' , 'Info' ]  -----> 期望 .出 name
      const suggestions: any[] = []

      // 横向遍历二维数组 找到满足条件的所有行
      if (property.length !== 0 && triggerCharacters.indexOf(point) !== -1) {
        for (let r = 0; r < suggestionData.length; r++) {
          for (let c = 0; c < property.length; c++) {
            if (property[c] !== suggestionData[r][c]) {
              continue
            }
            if (c === property.length - 1) {
              // 避免重复添加相同的提示
              if (suggestions.findIndex(suggestion => suggestion.label === suggestionData[r][c + 1]) === -1) {
                // 转成provider需要的格式
                suggestions.push({
                  label: suggestionData[r][c + 1], // 显示的提示内容
                  kind: monaco.languages.CompletionItemKind.Function, // 用来显示提示内容后的不同的图标
                  insertText: suggestionData[r][c + 1], // 选择后粘贴到编辑器中的文字
                  detail: '', // 提示内容后的说明
                  range: range,
                })
              }
            }
          }
        }
      }

      return { suggestions: suggestions }
    },
    triggerCharacters: triggerCharacters,
  })
}

使用

typescript 复制代码
injectSuggestionsByObject(_monaco, {
  ctx: {
    state: '',
    setState: '',
  },
})

效果如下

但后面发现,这种方式只对对象有效,而且无法去判别方法和属性等等,而且实现起来很麻烦,所以就有了下面这种方式了

通过 Typescript 提供类型提示

这种方式的话,只需要通过 typescriptdeclare 去声明类型即可,方便很多

还是上面那个例子,现在换一种实现方式

首先先定义所需要的类型声明

typescript 复制代码
/**
 * 全局上下文
 */
declare const ctx: {
  /**
   * 全局状态
   */
  state: Record<string, any>

  /**
   * 更新全局状态
   */
  setState: (newState: Record<string, any>) => void
}

注意,使用的时候要变为字符串形式,传入给 monaco 编辑器

typescript 复制代码
export const ctxType = `
/**
 * 全局上下文
 */
declare const ctx: {
  /**
   * 全局状态
   */
  state: Record<string, any>

  /**
   * 更新全局状态
   */
  setState: (newState: Record<string, any>) => void
}
`

然后,在 monaco 编辑器中引入该类型提示即可

typescript 复制代码
monaco.languages.typescript.javascriptDefaults.addExtraLib(ctxType, 'ctx.d.ts')

参考链接

  1. MonacoEditor 对象代码提示 - 知乎 (zhihu.com)
  2. TypeScript 为 monaco 编辑器提供类型提示|极客教程 (geek-docs.com)
  3. Monaco Editor (microsoft.github.io)
相关推荐
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink4 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-6 小时前
验证码机制
前端·后端
燃先生._.7 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖8 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235248 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240259 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar9 小时前
纯前端实现更新检测
开发语言·前端·javascript