codemirror6及公式编辑器 001 codemirror6

react codemirror6 及公式编辑器—001 codemirror6

### 1.背景介绍

001.web-editor三幻神
cm6:
  • 体积小、高模块化、移动端支持体验好、可扩展性非常好

  • 文档大示例少难懂全靠社区论坛、

  • 函数式编程、比较未来可期

Monaco:
  • 微软vscode核心、全面、文档示例多易读

  • 体积大(打完包5M)、性能消耗大、不能移动端

ACE

哪里都根均衡,但是太旧

Ace,CodeMirror 和 Monaco:Web 代码编辑器的对比
带你全面了解 Web Editor 架构设计、技术选型

002.react&cm
  • cm版本

    cm5

    cm6: 更新 虚拟DOM+1、模块化+1、插件系统+1=>更好定制化

  • react-cm

uiwjs/react-codemirrorcodemirror6+react16.8+新、文档多、但是新、缺少部分自定义
react-codemirrorcodemirror5+react16老、社区资源多、但是与新版本react匹配有问题

2.CM6

架构

在这里插入图片描述

核心包:

编辑器: view:@codemirror/view 管显示上的东西,操作交互(如:获取行信息、光标…

					`state`:@codemirror/state 编辑器逻辑状态(如:自动补全、按键事件、语言包...

预置配置: commands:@codemirror/commands (如:默认按键命令、 快捷字符操作函数…

EditorState=>EditorView=>parent: 绑定父元素 dom

原生初始化
const CodeMirror: React.FC = () =>
{
        const editorRef = useRef(null);
 useEffect(() =>
    {
        // 初始化CodeMirror编辑器
        const state = EditorState.create({
            doc: 'hello world!',
            extensions: [
                basicSetup,
            ],
        });
        const editor = new EditorView({
            state,
            parent: editorRef.current as any,
        });
        return () =>
        {
            editor.destroy(); // 注意:此后此处要随组件销毁
        };
    },[]);
    return <>  <div ref={editorRef}></div>
    </>;
}
@uiw/react-codemirror 版
...
    const editorRefUiw = useRef<ReactCodeMirrorRef>(null);
...
return <>
        <ReactCodeMirror
            extensions={[...]]} ref={editorRefUiw} height="200" placeholder="UIW" />
 </>;
...

3.核心功能


basicSetup]: extensions 默认配置项,包含了一些比较常用的配置自动补全、高亮、主题等

state

EditorState.create({…配置属性…})

  • doc:当前编辑器内容

    ]: 有一些内容处理函数,直接在该类下

  • selection:当前光标

    ]: 其余同上

  • extension(核心插件包)

    before :
    优先级:extension里面的配置是有优先级的,越后越优先,优先级函数:prec、high、lowest...
    

    优先级官方

    扁平化:你可以在扩展之间可以互用一些扩展对象
    
    添加-删除插件:删除=过滤出配置
    
    内置自定义
    语言cm6 把语言包都独立出来了,需要什么语言去官网单独下下来,导入,里面已经集成完了autocompletion(自动补全)、高亮、snippets(参数控制)、linter自己加autocompletion、高亮、snippets(参数)、linter
    主题独立主题包、社区有资源独立主题包、社区有资源
    import {javascript} from ‘@codemirror/lang-javascript’; // 引入语言包
    import {oneDark} from ‘@codemirror/theme-one-dark’;在这里插入图片描述


| 见后详解 |
| 其他 | 见后详解 | |

内置实现了什么?
  • 语言解析器

    codemirror中有对于不同使用对象的语言解释器和对应的构造解析器:内置语言包、LRLanguage基于 Lezer 的语言解析、StreamLanguage(基于文本流)、Tree-sitter 解析器(增量解析)

优点缺点
LRLanguage /lezer语法适用于复杂的编程语言解析,能够处理嵌套结构、多种语法规则等复杂场景。

codemirror6的语言包用的这个
1.使用:Lezer 语法文件=>LRLanguage 创建语言支持
2.Lezer JavaScript 解析器生成器 (Parser Generator) 解释器 LR 上下文无关
StreamLanguage /string+正则小 快 简单(cm5基础)1.使用

2.状态机机制 类express
Tree-sitter /Tree-sitter 代码解析树1.使用:导入编写的Tree-sitter文件=>使用
2.

cm6的三种语言解析器 “主要介绍了LRLanguag”
[语言解析器专题codemirror6]: ./语言解析器专题codemirror6.md

LRLanguage | 源码

初始化解析配置parser(定义要解析出来的类型和一些正则匹配规则)

=>定义parser逻辑

=>LRLanguage.define()声明一些要解析的类型

->new LanguageSupport(EXAMPLELanguage) 语言支持器导出

源码js语言包

这里匹配到关键字处理 缩进、回车、高亮

“Lezer解释器”
官方给的解释器demo
cm6-Lezer编写指南

StreamLanguage | 如何实现一个简单的语言解析器

  import {StreamLanguage} from "@codemirror/language";

// startState:初始化解析状态。
  // token:定义如何解析每个文本流的片段。
解析器将处理每一行并应用相应的 token 类型

const mySimpleLanguage = StreamLanguage.define({
    startState() {return {};},
    token(stream,state)
    {
        if (stream.match("//"))
        {
            stream.skipToEnd();
            return "comment";
        }
        if (stream.match("error"))
        {
            return "error";
        }
        if (stream.eatWhile(/\w/))
        {
            return "variable";
        }
        stream.next();
        return null;
    }
});
  • 解析类型及配置项
const snippets = [
    /*@__PURE__*/snippetCompletion("function ${name}(${params}) {\n\t${}\n}", {
        label: "function",
        detail: "definition",
        type: "keyword"
    }),
    /*@__PURE__*/snippetCompletion("for (let ${index} = 0; ${index} < ${bound}; ${index}++) {\n\t${}\n}", {
        label: "for",
        detail: "loop",
        type: "keyword"
    }),
    /*@__PURE__*/snippetCompletion("for (let ${name} of ${collection}) {\n\t${}\n}", {
        label: "for",
        detail: "of loop",
      type: "keyword"
    }),...]

cm6文档流解析器

其他常见功能及模块

这里可以参考basicSetup,大部分会用到的功能都可以通过这里面给出的默认配置找到

基础配置

  • keymap
extensions: [
                basicSetup,
              javascript(), // 在extensions中配置语言
                oneDark,
              keymap.of([{
                    key: 'Tab',
                  run: (state: any) =>
                    {
                      console.log("state------------",state);
                        return true;// 表示事件处理完了
                  }
                },
              ...closeBracketsKeymap,
    		   ...defaultKeymap,
              ...searchKeymap,
                ...historyKeymap,
              ...foldKeymap,
    		   ...completionKeymap,
  		   ...lintKeymap

                ])
            ],

其他各种监听器和状态更新

   doc :   **EditorView**.updateListener.**of**
const state = EditorState.create({
  doc: 'select * from table',
  extensions: [
    // ...
    EditorView.updateListener.of((v) => {
      console.log(v.state.doc.toString()) //监测得到的最新代码 
   }),
  ],
});

  • 装饰

    cm6 不加建议我们硬操作cm编辑器生成的dom,建议走cm6生成,让我们用各种包装器包装]:您的代码不应尝试直接更改 CodeMirror 为其内容创建的 DOM 结构,否则将不起作用。相反,影响事物绘制方式的方法是提供装饰,这可以添加样式或用替代表示替换内容。

    MatchDecorator 匹配装饰器

    初始化需要配置的属性:

    regexp: 匹配正则表达式 要/g

    The regular expression to match against the content. Will only be matched inside lines (not across them). Should have its 'g' flag set.]: 这里实际中应该是只有匹配上的那一行会装饰器生效

    decoration:应用的装饰,返回空或者一个装饰Decoration

    fn(match: RegExpExecArray, view: EditorView, pos: number) → Decoration | [null

    cm官方装饰专题

    装饰有4种,可以生成不同的装饰品 标记-mark、组件DOM-widget、线条 line、替换对象 replace(将匹配内容替换而不是额外添加)

      #Decoration
    replace({...args}) 根据参数替换给定范围进行装饰
      参数:
    1.widget:组件装饰(返回一个类dom的类,类型为cm6给定类型WidgetType)|line 线装饰
      2.WidgetType的一些属性: constructor(构造器)、eq(额外比较器,何时生效)、toDOM(要装饰成的dom),详情见链接
    
      const placeholderMatcher = new MatchDecorator({
        regexp: /\[\[(.+?)\]\]/g,
          decoration: (match, view, pos) => {
          return Decoration.replace({
                   widget: new PlaceholderWidget(match[1]),
          });
          },
      });
      
      
    
    • 行提示器(侧面显示装订线)

      config 自定义行号显示

      gutterLineClass 行号类

    • 高亮控制、缩进、搜索匹配、撤消历史记录、lint(诊断)、matchbrackets括号匹配…

      高亮:


    自定义配置问题

    先明确一个思路

    自定义语言包(简略版)=自动补全+高

    • 自动补全 拓展 autocompletion

      form&to

      form 匹配区域起始位置

      to 匹配区域结束位置

      pos

      是从开头-起始位置,开头-结束位置,

      替换语言包

      语言规则及相关

      用户输入参数字段:${} 或者 #{}
      自定义参数顺序:默认先选中第一个${},${1} 或 ${1:defaultText} 改顺序
      激活:参数被选中 ---要字段处于活动状态,用户就可以在具有 Tab 和 Shift-Tab 的字段之间移动
      e.g:
      "function ${name}(${params}) {\n\t${}\n}"
      

      完全覆盖 override

      function myCompletions(context: CompletionContext) {
      let word = context.matchBefore(/\w*/)
        if (word.from === word.to && !context.explicit)
        return null
        return {
        from: word.from,
          options: [
       		 // label 选取器中显示的标签
             	 //  label:在自动补全弹窗中显示的选项标签。
      		// type:指示该选项的类型【 class constant enum function interface keyword method namespace property text type variable 】   
              // info:提供有关选项的附加信息,通常用于显示在选项旁边或下方的更详细的信息。
      		//apply:在用户选择此选项后应用的文本。可以是一个字符串,也可以是一个函数,用于生成要应用的文本。
      		//detail:提供关于选项的更详细描述,通常用于提供有关选项的更多上下文信息。
            {label: "match", type: "keyword"},
            {label: "hello", type: "variable", info: "(World)"},
            {label: "magic", type: "text", apply: "⠁⭒*.✩.*⭒⠁", detail: "macro"}
          ]
        }
      }
      const state = EditorState.create({
        doc,
        extensions: [
          // ...
          autocompletion({ override: [myCompletions]})
        ],
      });
      

      Completion

    部分覆盖 改包

    snippetCompletion 函数补全带snippet参数

    node_modules@codemirror\lang-javascript\dist\index.cjs

       ...
     autocomplete.snippetCompletion("function ${name}(${params}) {\n\t${}\n}", {
              label: "function",
            detail: "definition",
              type: "keyword"
          }),
       ...
    

    覆盖语言源override
    autocompletion
    语言模版规则
    ^keymap

    • 高亮

      syntaxHighlighting 高亮主题配置

      基础用法

      import {HighlightStyle,syntaxHighlighting} from "@codemirror/language"
      import {tags as t} from "@lezer/highlight";
      ...
      const myHighlightStyle = HighlightStyle.define([
        { tag: t.keyword, color: "#ff0000", fontWeight: "bold" },
      { tag: t.string, color: "#00ff00" },
        { tag: t.comment, color: "#888888", fontStyle: "italic" },
      { tag: t.variableName, color: "#0000ff" },
        { tag: t.number, color: "#ff00ff" },
      ]);
      // tag codemirror对关键字定义了一个类管理tag
      // HighlightStyle.define可以进行的配置class、style行内样式(style-mod版,就是react-dom的style属性)、高亮事件控制(函数,根据条件返回不同高亮样式)
      ...
      
      const state = EditorState.create({
      doc: 'function example() {\n  console.log("hello, world");\n}',
        extensions: [basicSetup, ...syntaxHighlighting(myHighlightStyle)]
      });
      
      
    ...
      
      [tag文档](https://lezer.codemirror.net/docs/ref/#highlight.Tag)
    [HighlightStyle.define配置项](https://codemirror.net/docs/ref/#language.TagStyle)
      
    
      
    `自定义关键词高亮`
      
    语法识别+样式控制
      
    识别=原生(修改关键词配置)|编写解释器 (编写新的解释器,改变匹配逻辑)*
      
    样式=自定义主题|修改部分原生样式
      
      
      `1.改关键词表`
      `2.新语言包`
      import { StreamLanguage } from "@codemirror/stream-parser";
      import { javascript } from "@codemirror/lang-javascript";
      const customKeywords = ["specialFunc", "magicNumber"];// 自定义关键词
      const myHighlightStyle = HighlightStyle.define([
        { tag: customKeywords, color: "#0077aa", fontWeight: "bold" }
      ]);// 自定义高亮样式
      //实现一个简单的语言解释器
      const customJavaScript = StreamLanguage.define({
        startState() {
          return { inString: false };
        },
        token(stream, state) {
          if (stream.match("specialFunc") || stream.match("magicNumber")) {
            return "keyword";
          }
          return javascript.token(stream, state);
        }
    });
      
    

    问题与解决

    001.语言包、语言解析器之间存在互相覆盖、冲突关系。

    codemirror6会尝试协同工作。然而,如果它们之间存在冲突,通常后声明的或更具体的配置会有更高的优先级(参考优先级部分)。

    语言包语言解析器自定义autocompletion
    语言包后面覆盖前面的会互相覆盖关键词=>建立公共关键词表,在语言解析器完全覆盖
    语言解析器会互相覆盖关键词=>建立公共关键词表,在语言解析器Lezer合并,类似中间件冲突,完全被覆盖=>建立公共关键词表,在语言解析器中提高优先级

    在这里插入图片描述

如何编写自定义语言解释器]( 见 内置实现了什么? 语言解释器部分)

官方高亮说明

view

new EditorView({state,parent: editorRef.current as any, });

  • *tate:EditorState
  • *parent:编辑器所在父元素
  • 一些相关的dom或者dom信息: dom scrollDOM contentDOM | documentTop scaleX scaleY contentHeight | 样式 lineWrapping是否换行
  • 事件及监听:光标事件(moveByxxxx)、setTabFocusMode、

在这里插入图片描述

  • 状态控制:update setstate
command

自定义linter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值