CodeMirror 6入門用法

magnesium發表於2023-02-23
Tip: WeChat public account search and follow '進二開物', more sharing JavaScript/TypeScript/React and so on.

你是否對編輯器感興趣過呢?

編輯器是前端專案中比較複雜的專案之一了。想熟悉編輯器,對自定義 Markdown 編輯器需求,雖然市面上有類似的,且比較成熟,但是有些功能需要自己的定義。所以找到 CodeMirror,同類產品還有 Monoca 以及 ace 等,來支撐編輯器區域,自己定義顯示區域。

CodeMirror 到現在已經有兩個版本,CodeMirror v5 版本在很多的專案中得到了使用。例如 bytemd 掘金社群的文件就是使用 CodeMirror 編輯器。

V6 版本的 CodeMirror 的其實中文教程和文件都很少,因為 CodeMirror 使用場景,所以還是嘗試寫文章,來關注 CodeMirror v6 的版本。

使用 Vite 初始化專案

pnpm create vite

選擇 TS + React + SWC(編譯) 作為專案模板

增加 CodeMirror 編輯器元件

import { useEffect, useRef } from "react";

function CMEditor() {
  const edContainer = useRef<any>();

  return <div ref={edContainer} className="container"></div>;
}

export default CMEditor;

注意:使用最為基礎的編輯器作為

安裝依賴包

npm i codemirror @codemirror/view

建立一個 Hello World 元件的 CodeMirror 元件

import { useEffect, useRef } from "react";

import { EditorView } from "@codemirror/view";

function CMEditor() {
  const edContainer = useRef<any>();

  useEffect(() => {
    const view = new EditorView({
      doc: "hello world!\nsdfsdf", // 帶有 `\n` <img src="會渲染成兩行" alt="" width="70%" />
      parent: edContainer.current,
    });

    return () => {
      view.destroy(); // 注意:此後此處要隨元件銷燬
    };
  }, []);
  return <div ref={edContainer} className="container"></div>;
}

export default CMEditor;

將元件加入到專案主程式中:

import CMEditor from '../src/components/CMEditor'
function App() {

  return (
    <div className="App">
      <CMEditor />
    </div>
  )
}

export default App

顯示效果如下:

當然可以透過state 來渲染 doc

在 state 裡面定義 doc

使用 EditorState.create 方法建立 state, 然後 options 傳入 doc 方法:

import { useEffect, useRef } from "react";

import { EditorView } from "@codemirror/view";
import { EditorState } from "@codemirror/state";

function CMEditor() {
  const edContainer = useRef<any>();

  useEffect(() => {
    const state = EditorState.create({
      doc: "hello world!\nsdfsdf",
    });
    const view = new EditorView({
      parent: edContainer.current,
      state,
    });

    return () => {
      view.destroy();
    };
  }, []);

  return <div ref={edContainer} className="container"></div>;
}

export default CMEditor;

以上定義一個一個基本的 CodeMirror 使用方法的兩種定義方法

擴充套件

上面的兩種是最為基本的使用方法,CodeMirror 的內部提供了兩種擴充套件:

  • basicSetup
  • minimalSetup

這兩個 setup 匯出在 basic-setup 包裡面, 安裝時使使用 npm install codemirror 因為一些歷史原因 CodeMirror 的命名似乎有些混亂。

basicSetup 擴充套件

const view = new EditorView({
  parent: edContainer.current,
  doc: "hello world!\nsdfsdf",
  extensions: [basicSetup]
});
  • 支援擴充套件
export const basicSetup: Extension = (() => [
  lineNumbers(), // 顯示行
  highlightActiveLineGutter(),
  highlightSpecialChars(),
  history(),
  foldGutter(), // 摺疊
  drawSelection(),
  dropCursor(),
  EditorState.allowMultipleSelections.of(true), // 多行選擇
  indentOnInput(), // 輸入是縮排
  syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
  bracketMatching(),
  closeBrackets(),
  autocompletion(), // 自動完成
  rectangularSelection(),
  crosshairCursor(),
  highlightActiveLine(),
  highlightSelectionMatches(),
  keymap.of([
    ...closeBracketsKeymap,
    ...defaultKeymap,
    ...searchKeymap,
    ...historyKeymap,
    ...foldKeymap,
    ...completionKeymap,
    ...lintKeymap
  ])
])()

我們看到支援可眾多的外掛:高亮相關,顯示行號等等...

minimal

const state = EditorState.create({
  doc: "hello world!\nsdfsdf",
  extensions: [basicSetup]
});
const view = new EditorView({
  parent: edContainer.current,
  state
});
  • 支援擴充套件
export const minimalSetup: Extension = (() => [
  highlightSpecialChars(),
  history(),
  drawSelection(),
  syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
  keymap.of([
    ...defaultKeymap,
    ...historyKeymap,
  ])
])()

內建擴充套件命令支援

CodeMirror 命令擴充套件的方式支援縮排,以及一些自定義的快捷鍵

import { defaultKeymap, indentWithTab } from '@codemirror/commands';

const startState = EditorState.create({
  doc: 'Hello World',
  extensions: [keymap.of([defaultKeymap, indentWithTab])], // 支援 tab 縮排
});

擴充套件語言/型別主題支援

CodeMirror 多服務於程式碼相關的編輯器,要高亮一些語法等等,安裝支援包:

npm install @codemirror/lang-javascript @codemirror/theme-one-dark

改寫元件:

import { useEffect, useRef } from "react";
import { EditorView } from "@codemirror/view";
import { basicSetup } from 'codemirror';
import { javascript } from '@codemirror/lang-javascript'
import { oneDark } from '@codemirror/theme-one-dark'

function CMEditor() {
  const edContainer = useRef<any>();

  useEffect(() => {
    const view = new EditorView({
      parent: edContainer.current,
      doc: "console.log('this is javascript code')\nfunction red(){retun'this is red function'}",
      extensions: [basicSetup, javascript(), oneDark]
    });

    return () => {
      view.destroy();
    };
  }, []);

  return <div ref={edContainer} className="container"></div>;
}

export default CMEditor;

顯示效果:

更新 doc

使用 state + editor.dispatch(tr)更新資料

import { useEffect, useRef, useState } from "react";
import { EditorView, ViewUpdate } from "@codemirror/view";
import { basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { oneDark } from "@codemirror/theme-one-dark";

function CMEditor() {
  const viewRef = useRef();
  const edContainer = useRef<any>();

  useEffect(() => {
    let onUpdateExt = EditorView.updateListener.of((v: ViewUpdate) => {
      if (v.docChanged) {
        viewRef.current!.dispatch({
          state: v.state,
        });
      }
    });
    const viewRef = new EditorView({
      parent: edContainer.current,
      doc: 'if(true) {return false}',
      extensions: [basicSetup, javascript(), oneDark, onUpdateExt],
    });

    return () => {
      viewRef?.current?.destroy();
    };
  }, []);

  return <div ref={edContainer} className="container"></div>;
}

export default CMEditor;

自定義主題和語法高亮

import {EditorView} from "@codemirror/view"

let baseTheme = EditorView.baseTheme({
  ".cm-o-replacement": {
    display: "inline-block",
    width: ".5em",
    height: ".5em",
    borderRadius: ".25em"
  },
  "&light .cm-o-replacement": {
    backgroundColor: "#04c"
  },
  "&dark .cm-o-replacement": {
    backgroundColor: "#5bf"
  }
})

改變基礎 theme 的樣式 EditorView.baseTheme 樣式。


const viewRef = new EditorView({
  parent: edContainer.current,
  doc: 'if(1) {return false}',
  extensions: [baseTheme],
});

語法高亮

import {tags} from "@lezer/highlight"
import {HighlightStyle} from "@codemirror/language"

const myHighlightStyle = HighlightStyle.define([
  {tag: tags.keyword, color: "#fc6"},
  {tag: tags.comment, color: "#f5d", fontStyle: "italic"}
])

小結

codemirror 的入門使用方法,這裡講解使用 React + vite 建立一個專案,並建立編輯器元件的兩種方式。codemirror 內建外掛 basicSetup/minimal 兩種不同的擴充套件集合。官方提供的命令擴充套件如: tab 命令等。然後提供了擴充套件語言:如使用 JavaScript 語言為例,講解了擴充套件語言外掛和語言的語法高亮等

參考

相關文章