title: react-ace代码编辑器自定义mode攻略
date: 2022-12-02 15:48:45
tags:

  • React

背景

最近使用了react-ace实现一个代码编辑器,由于都是封装完整的,想要自定义主题、mode比较困难,下面是本人整理的自定义mode、theme示例:

开发版本

  1. react: ^18.2.0
  2. react-dom: ^18.2.0
  3. react-ace: ^10.1.0
  4. 均采用函数式组件
$ npm install --save react-ace    # 网络上的内容比较乱,自测只需要这个即可

实现效果

github.com/dzzhyk/react-ace-custom-mode-theme-demo

核心代码

这里是部分核心代码,具体查看完整工程!

App.js

// react-ace editor
import AceEditor from "react-ace";

// 这两个插件用于编辑器内搜索框、代码联想提示
import "ace-builds/src-min-noconflict/ext-searchbox"
import "ace-builds/src-min-noconflict/ext-language_tools"

// 解决webpack打包问题
import "ace-builds/webpack-resolver"
import {useEffect, useRef, useState} from "react";

// 引入自定义mode
import "./mode-yankai"
import "./theme-yankai.css"

function App(props) {

    const [content, setContent] = useState("");
    const editorRef = useRef(null);

    // 直接onLoad增加complete存在bug,需要使用useEffect解决
    useEffect(() => {
        if (editorRef.current) {
            complete(editorRef.current.editor)
        }
    }, [editorRef])

    // 自定义编辑器的代码补全器
    const complete = editor => {

        const YankaiCompleter = [
            {
                name: "and",
                value: "and",
                score: 100, // 提示优先级
                meta: "[关键字] 逻辑与"
            }, {
                name: "println",
                value: "println(str)",
                score: 99,
                meta: "[内置函数] 标准输出"
            },
        ]

        editor.completers = [{
            getCompletions: function (editor, session, pos, prefix, callback) {
                callback(null, YankaiCompleter)
            }
        }]
    }

    return (
        <div>
            <AceEditor
                ref={editorRef}
                mode="yankai"
                placeholder="由此开始输入代码"
                setOptions={{
                    enableBasicAutocompletion: false,
                    enableLiveAutocompletion: true,
                    enableSnippets: false,
                    showLineNumbers: true,
                    tabSize: 4
                }}
                fontSize={13}
                style={{height: 500, width: '100%', border: '1px solid #d9d9d9'}}
                value={content}
                onChange={(value) => setContent(value)}
                onLoad={complete}
            />
            <div>其他内容</div>
        </div>
    );
}

export default App;

mode-yankai.js:自定义的mode

/*
load custom yankai_highlight_rules for me.
this mode is based on python_highlight_rules, you can find in node_modules/ace-builds/src/mode-python.js
 */

ace.define("ace/mode/yankai_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (ace_require, exports, module) {
    var oop = ace_require("../lib/oop");
    var TextHighlightRules = ace_require("./text_highlight_rules").TextHighlightRules;
    var YankaiHighlightRules = function () {
        var keywords = "and|in|not|or|any";
        var builtinConstants = "true|false|null";

        // we defined builtin function 'yankai' and 'cool' here, so u will find it highlighted in ace-editor which mode="yankai"
        var builtinFunctions = ("yankai|cool");

        var keywordMapper = this.createKeywordMapper({
            "support.function": builtinFunctions,
            "constant.language": builtinConstants,
            "keyword": keywords
        }, "identifier");

        var strPre = "[uU]?";
        var strRawPre = "[rR]";
        var strFormatPre = "[fF]";
        var strRawFormatPre = "(?:[rR][fF]|[fF][rR])";
        var decimalInteger = "(?:(?:[1-9]\\d*)|(?:0))";
        var octInteger = "(?:0[oO]?[0-7]+)";
        var hexInteger = "(?:0[xX][\\dA-Fa-f]+)";
        var binInteger = "(?:0[bB][01]+)";
        var integer = "(?:" + decimalInteger + "|" + octInteger + "|" + hexInteger + "|" + binInteger + ")";
        var exponent = "(?:[eE][+-]?\\d+)";
        var fraction = "(?:\\.\\d+)";
        var intPart = "(?:\\d+)";
        var pointFloat = "(?:(?:" + intPart + "?" + fraction + ")|(?:" + intPart + "\\.))";
        var exponentFloat = "(?:(?:" + pointFloat + "|" + intPart + ")" + exponent + ")";
        var floatNumber = "(?:" + exponentFloat + "|" + pointFloat + ")";
        var stringEscape = "\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv'\"]|U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})";

        this.$rules = {
            // ... 这部分是匹配各种语言元素规则,太长了已省略请查看github完整工程
        }
    };

    oop.inherits(YankaiHighlightRules, TextHighlightRules);
    exports.YankaiHighlightRules = YankaiHighlightRules;
});

// load custom mode-yankai for me, now I should use mode="yankai" in ace-editor
ace.define("ace/mode/yankai", function (acequire, exports, module) {
    const oop = acequire("../lib/oop");
    const TextMode = acequire("./text").Mode;
    const YankaiHighlightRules = acequire("./yankai_highlight_rules").YankaiHighlightRules;

    // create my custom mode
    const YankaiMode = function () {
        this.HighlightRules = YankaiHighlightRules;
    };
    oop.inherits(YankaiMode, TextMode);
    exports.Mode = YankaiMode;
});

theme-yankai.js

/* autocomplete width */
.ace_editor.ace_autocomplete {
    width: 40vw;
}


/* override highlight style for custom yankai mode */
.ace_function {
    color: orangered !important;
    font-weight: bold
}

.ace_constant {
    color: blue !important;
}

.ace_operator {
    color: red !important;
}

.ace_string {
    color: green !important;
}