使用 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)
相关推荐
煸橙干儿~~1 分钟前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常10 分钟前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n037 分钟前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。1 小时前
案例-任务清单
前端·javascript·css
zqx_72 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己2 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称3 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色3 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2343 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河3 小时前
CSS总结
前端·css