react-monaco-editor編輯器安裝使用&自定義提示


2021/02/28更
此次更新主要增加了給編輯器鼠標(biāo)焦點處設(shè)置值方法

  /**
     * 給編輯器設(shè)置值
     * @param value 需要設(shè)置的值
     */
    const handleSetEditorVal = (value: string): void => {
      if (!value) return;
      // 為所選取的值賦值到編輯器中
      if (editorInstance.current && value) {
        const selection = editorInstance?.current?.getSelection?.();
        const range = new _monaco.Range(
          selection.startLineNumber,
          selection.startColumn,
          selection.endLineNumber,
          selection.endColumn
        );
        const id = { major: 1, minor: 1 };
        const op = { identifier: id, range, text: value, forceMoveMarkers: true };
        editorInstance.current.executeEdits('', [op]);
        editorInstance.current.focus();
      }
    };

開始

最近產(chǎn)品上用到了代碼編輯器,因為技術(shù)棧使用的是react, 找了許久經(jīng)過甄選最后決定使用monaco-editor的react封裝版本:react-monaco-edotor案站,下面是他的簡單介紹:


image.png

經(jīng)過很多前輩大佬的努力砂缩,react-monaco-edotor橫空出世归敬,對于使用react技術(shù)棧的人是一個再好不過的好消息了惨寿,介紹完畢我們講講如何使用吧香璃。

使用方法:

yarn add monaco-editor -D 
yarn add react-monaco-editor -D 
yarn add monaco-editor-webpack-plugin -D 

安裝好依賴包再去webpack-config中配置相關(guān)依賴:

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
...
 plugins: [
      new MonacoWebpackPlugin(['apex', 'azcli', 'bat', 'clojure', 'coffee', 'cpp', 'csharp', 'csp', 'css', 'dockerfile', 'fsharp', 'go', 'handlebars', 'html', 'ini', 'java', 'javascript', 'json', 'less', 'lua', 'markdown', 'msdax', 'mysql', 'objective', 'perl', 'pgsql', 'php', 'postiats', 'powerquery', 'powershell', 'pug', 'python', 'r', 'razor', 'redis', 'redshift', 'ruby', 'rust', 'sb', 'scheme', 'scss', 'shell', 
'solidity', 'sql', 'st', 'swift', 'typescript', 'vb', 'xml', 'yaml']),
...
]

注意:自定義提示時一定需設(shè)置 language: 'plaintext' 自定義文本, 并且在monaco.languages.registerCompletionItemProvider中使用深拷貝
經(jīng)過以上步驟 編輯器就能正常應(yīng)用到項目中去了和橙,使用及自定義提示的使用方法:

import React, { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Icon, Tooltip } from 'choerodon-ui/pro';
import MonacoEditor from 'react-monaco-editor';
import * as _monaco from 'monaco-editor';

import styles from './index.less';

let provider = {
  dispose: () => {},
};
interface IRightContent {
  currentRecord: any; // fixme
  handleCheck: () => Promise<boolean>;
  secondRightData: (model.dataSource.BaseDataSourceField | model.dataSource.BaseDataSourceHeader)[];
}
export default forwardRef(
  ({ currentRecord, handleCheck, secondRightData = [] }: IRightContent, ref) => {
    // 編輯器實例
    const editorInstance = useRef<any>();

    // 真實數(shù)據(jù)
    const code: any = useRef(currentRecord ? currentRecord.formulaContent : '');

    // 緩存數(shù)據(jù)
    const cache: any = useRef(code.current);

    // 輸入引號觸發(fā)頁面刷新
    const [refresh, setRefresh] = React.useState<boolean>(false);

    const monacoInstance: any = useRef();
    const options = {
      selectOnLineNumbers: true,
      renderSideBySide: false,
    };

    useImperativeHandle(ref, () => ({
      handleSetEditorVal,
      getEditorData: () => cache.current,
    }));

    useEffect(
      () => () => {
        provider.dispose(); // 彈窗關(guān)閉后 銷毀編輯器實例
      },
      []
    );

    /**
     * 給編輯器設(shè)置值
     * @param value 需要設(shè)置的值
     */
    const handleSetEditorVal = (value: string): void => {
      if (!value) return;
      // 為所選取的值賦值到編輯器中
      if (editorInstance.current && value) {
        const selection = editorInstance?.current?.getSelection?.();
        const range = new _monaco.Range(
          selection.startLineNumber,
          selection.startColumn,
          selection.endLineNumber,
          selection.endColumn
        );
        const id = { major: 1, minor: 1 };
        const op = { identifier: id, range, text: value, forceMoveMarkers: true };
        editorInstance.current.executeEdits('', [op]);
        editorInstance.current.focus();
      }
    };

    /**
     * 編輯器change回調(diào)
     * @param {String} val 當(dāng)前編輯器的值
     */
    const onChangeHandle = (val: string, event: { changes: { text: any }[] }) => {
      const curWord = event.changes[0].text;
      if (curWord === '"') {
        cache.current = val + curWord;
        setRefresh(!refresh); // 刷新頁面
        return;
      }
      cache.current = val;
    };

    /**
     * 初始化編輯器
     * @param {*} editor 編輯器實例
     * @param {*} monaco
     */
    interface ISuggestions {
      label: string;
      kind: string;
      insertText: string;
      detail?: string;
    }
    const editorDidMountHandle = (editor: any, monaco: any) => {
      monacoInstance.current = monaco;
      editorInstance.current = editor;
      const newSecondRightFields: model.dataSource.BaseDataSourceHeader[] = [];
      (secondRightData as model.dataSource.BaseDataSourceHeader[]).forEach((record) => {
        if (record.fields && Array.isArray(record.fields)) {
          record.fields.forEach((item: any) => {
            // fixme
            newSecondRightFields.push(item);
          });
        }
        code.current = newSecondRightFields; // 數(shù)組長度永遠為1
      });
      // 提示項設(shè)值
      provider = monaco.languages.registerCompletionItemProvider('plaintext', {
        provideCompletionItems() {
          const suggestions: ISuggestions[] = [];
          if (code && code.current) {
            code.current.forEach((record) => {
              suggestions.push({
                // label未寫錯 中間加空格為了隱藏大寫字段名稱 大寫字段名稱用于規(guī)避自定義提示不匹配小寫的bug
                label:
                  record.label ||
                  `${record.displayName} (${
                    record.aliasName
                  })                        ${''}(${record.aliasName.toUpperCase()})`, // 顯示名稱
                kind: record.kind || monaco.languages.CompletionItemKind.Field, // 這里Function也可以是別的值顾彰,主要用來顯示不同的圖標(biāo)
                insertText: record.insertText || record.aliasName, // 實際粘貼上的值
                // detail: record.detail || `(property) ${record.aliasName}: String`,
              });
            });
          }
          [
            'CASEWHEN(expression1, value1, expression2, value2, ..., else_value)',
            'CONCAT(str1, str2, ...)',
            'ISNULL (expression, defaultValue)',
            'DATEDIFF_YEAR(startdate,enddate)',
            'DATEDIFF_MONTH(startdate,enddate)',
            'DATEDIFF_DAY(startdate,enddate)',
            'SUM(expression)',
            'AVG(expression)',
            'MAX(expression)',
            'MIN(expression)',
            'COUNT(expression)',
            'DISTINCTCOUNT(expression)',
            'DISTINCTAVG(expression)',
            'DISTINCTSUM(expression)',
            'NOW()',
          ].forEach((item) => {
            suggestions.push(
              // 添加contact()函數(shù)
              {
                label: item, // 顯示名稱
                kind: monaco.languages.CompletionItemKind.Function, // 這里Function也可以是別的值,主要用來顯示不同的圖標(biāo)
                insertText: item, // 實際粘貼上的值
              }
            );
          });
          return {
            suggestions, // 必須使用深拷貝
          };
        },
        quickSuggestions: false, // 默認(rèn)提示關(guān)閉
        // triggerCharacters: ['$', '.', '='], // 觸發(fā)提示的字符胃碾,可以寫多個
      });
      editor.focus();
    };

    return (
      <React.Fragment>
        <MonacoEditor
          width="100%"
          height="400px"
          language="plaintext"
          value={cache.current}
          options={options}
          onChange={onChangeHandle}
          editorDidMount={editorDidMountHandle}
        />
        <Tooltip title="校驗" placement="top">
          <Icon type="" className={styles['data-check-icon']} onClick={handleCheck} />
        </Tooltip>
      </React.Fragment>
    );
  }
);

頁面效果:


image.png

備注:
( 使用react-monaco-editor )還有很多坑涨享,網(wǎng)上的介紹文檔也不多,代碼全靠摸索仆百,這里我遇到的一個最大的坑就是自定義提示時厕隧,不能匹配小寫字母,目前已規(guī)避這個問題俄周,并且在gitHub上給作者提了issue吁讨,多發(fā)現(xiàn)問題并分享出來,程序世界會越來越美好峦朗。
react-monaco-editor作者地址:https://github.com/jaywcjlove/react-monacoeditor/issues/26

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末建丧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子波势,更是在濱河造成了極大的恐慌翎朱,老刑警劉巖橄维,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拴曲,居然都是意外死亡争舞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門澈灼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竞川,“玉大人,你說我怎么就攤上這事叁熔∥冢” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵荣回,是天一觀的道長福澡。 經(jīng)常有香客問我,道長驹马,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任除秀,我火速辦了婚禮糯累,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘册踩。我一直安慰自己泳姐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布暂吉。 她就那樣靜靜地躺著胖秒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪慕的。 梳的紋絲不亂的頭發(fā)上阎肝,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音肮街,去河邊找鬼风题。 笑死,一個胖子當(dāng)著我的面吹牛嫉父,可吹牛的內(nèi)容都是我干的沛硅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼绕辖,長吁一口氣:“原來是場噩夢啊……” “哼摇肌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仪际,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤围小,失蹤者是張志新(化名)和其女友劉穎昵骤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吩抓,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡涉茧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疹娶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伴栓。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雨饺,靈堂內(nèi)的尸體忽然破棺而出钳垮,到底是詐尸還是另有隱情,我是刑警寧澤额港,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布饺窿,位于F島的核電站,受9級特大地震影響移斩,放射性物質(zhì)發(fā)生泄漏肚医。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一向瓷、第九天 我趴在偏房一處隱蔽的房頂上張望肠套。 院中可真熱鬧,春花似錦猖任、人聲如沸你稚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刁赖。三九已至,卻和暖如春长搀,著一層夾襖步出監(jiān)牢的瞬間宇弛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工源请, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涯肩,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓巢钓,卻偏偏與公主長得像病苗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子症汹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344