React-ace: How to use custom mode

Created on 6 Jul 2016  路  19Comments  路  Source: securingsincity/react-ace

Hi,

My question might be a bit stupid (apologies for that), but I can't figure out how to use a custom mode / syntax highlighter / auto-completion. Can anyone help me? I am very new to brace, ace, and react but I saw some materials on how to create and set your custom mode using Ace. What I couldn't figure out is how to do the same with react-ace.

Christian

Most helpful comment

The following code worked for me:

App.js

import React, { Component } from 'react';
import brace from 'brace';
import AceEditor from 'react-ace';
import CustomSqlMode from './CustomSqlMode.js'

import 'brace/theme/github';

class App extends Component {
  componentDidMount() {
    const customMode = new CustomSqlMode();
    this.refs.aceEditor.editor.getSession().setMode(customMode);
  }

  render() {
    return (
      <div className="App">
        <AceEditor
          ref="aceEditor"
          mode="text"
          theme="github"
          name="UNIQUE_ID_OF_DIV"
          editorProps={{ $blockScrolling: true }}
        />
      </div>
    );
  }
}

export default App;

CustomSqlMode.js

import 'brace/mode/java';

export class CustomHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules {
    constructor() {
        super();
        this.$rules = {
            "start": [{
                token: "comment",
                regex: "#.*$"
            }, {
                token: "string",
                regex: '".*?"'
            }]
        };
    }
}

export default class CustomSqlMode extends window.ace.acequire('ace/mode/java').Mode {
    constructor() {
        super();
        this.HighlightRules = CustomHighlightRules;
    }
}

The previous code highlights the comments and strings only. I'm not sure if what I did was the good practice or not, but at least it works.

All 19 comments

Hi,

and what's your implementation? I can show what works for me. I don't remeber it exactly, but I think I had to install also brace library at first. So

npm install -D brace react-ace

And this is my component with Ace editor:

import React from 'react'
import brace from 'brace'
import AceEditor from 'react-ace'

import 'brace/mode/javascript'
import 'brace/theme/tomorrow'

const MyEditor = () => {
  return (
    <AceEditor
      name="my-editor"
      mode="javascript"
      theme="tomorrow"
      value=""
      width="100%"
      height="500px" />
  )
}

export default MyEditor

And here is screenshot:
screen shot 2016-07-19 at 18 23 14

I could use the default modes from brace indeed. What I wonder is how to use my own mode? I wrote a custom mode by myself and want to include that, but was not able to do that with react-ace so far.

@sichvoge Did you make any progress on this at all?

Not really. In the end I developed my own react component using Ace and not brace.

Fair enough - perhaps you could share it when you're happy with it? Thanks @sichvoge

Sure, I will.

@sichvoge , can you please share your component?
I'm having the same problem and it will be really helpful :)

I am trying to do a similar thing. Importing the mode in the front end doesn't seem to work for me.
It seems that AceEditor is looking for it as a server side file. So I ended up simply serving the mode file separately to my webpack bundled script. (This is probably not at all the right way to do it but it does seem to work....)

So say my custom mode is called 'eikelang'

My React code looks like this

import React from 'react';
import AceEditor from 'react-ace';
import 'brace/theme/github';

export default ({value}) => {
<AceEditor
    mode="eikelang"
    theme="github"
    width="auto"
    height="200px"
    value={value}
  />
};

I have a js file called 'mode-eikelang.js' served from my server's root directory, which is just a rip off of the mysql brace mode file with some names changed:

ace.define('ace/mode/doc_comment_highlight_rules',
  ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'],
  function(acequire, exports, module) {
    'use strict';

    let oop = acequire('../lib/oop');
    let TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules;

    var DocCommentHighlightRules = function() {
      this.$rules = {
        'start': [{
          token: 'comment.doc.tag',
          regex: '@[\\w\\d_]+' // TODO: fix email addresses
        },
          DocCommentHighlightRules.getTagRule(),
        {
          defaultToken: 'comment.doc',
          caseInsensitive: true
        }]
      };
    };

    oop.inherits(DocCommentHighlightRules, TextHighlightRules);

    DocCommentHighlightRules.getTagRule = function(start) {
      return {
        token: 'comment.doc.tag.storage.type',
        regex: '\\b(?:TODO|FIXME|XXX|HACK)\\b'
      };
    };

    DocCommentHighlightRules.getStartRule = function(start) {
      return {
        token: 'comment.doc', // doc comment
        regex: '\\/\\*(?=\\*)',
        next: start
      };
    };

    DocCommentHighlightRules.getEndRule = function(start) {
      return {
        token: 'comment.doc', // closing comment
        regex: '\\*\\/',
        next: start
      };
    };


    exports.DocCommentHighlightRules = DocCommentHighlightRules;
  });

ace.define('ace/mode/eikelang_highlight_rules', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/lib/lang', 'ace/mode/doc_comment_highlight_rules', 'ace/mode/text_highlight_rules'], function(acequire, exports, module) {
  let oop = acequire('../lib/oop');
  let lang = acequire('../lib/lang');
  let DocCommentHighlightRules = acequire('./doc_comment_highlight_rules').DocCommentHighlightRules;
  let TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules;

  let EikelangHighlightRules = function() {
    let builtins = 'drop|hash|keep|linear_transformation|map|rename|string_manipulation|string_submatcher';

    let keywordMapper = this.createKeywordMapper({
      'support.function': builtins
    }, 'identifier', true);


    function string(rule) {
      let start = rule.start;
      let escapeSeq = rule.escape;
      return {
        token: 'string.start',
        regex: start,
        next: [
                {token: 'constant.language.escape', regex: escapeSeq},
                {token: 'string.end', next: 'start', regex: start},
                {defaultToken: 'string'}
        ]
      };
    }

    this.$rules = {
      'start': [{
        token: 'comment', regex: '(?:-- |#).*$'
      },
        string({start: '"', escape: /\\[0'"bnrtZ\\%_]?/}),
        string({start: '\'', escape: /\\[0'"bnrtZ\\%_]?/}),
        DocCommentHighlightRules.getStartRule('doc-start'),
      {
        token: 'comment', // multi line comment
        regex: /\/\*/,
        next: 'comment'
      }, {
        token: 'constant.numeric', // hex
        regex: /0[xX][0-9a-fA-F]+|[xX]'[0-9a-fA-F]+'|0[bB][01]+|[bB]'[01]+'/
      }, {
        token: 'constant.numeric', // float
        regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b'
      }, {
        token: keywordMapper,
        regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b'
      }, {
        token: 'constant.class',
        regex: '@@?[a-zA-Z_$][a-zA-Z0-9_$]*\\b'
      }, {
        token: 'constant.buildin',
        regex: '`[^`]*`'
      }, {
        token: 'keyword.operator',
        regex: '\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|='
      }, {
        token: 'paren.lparen',
        regex: '[\\(]'
      }, {
        token: 'paren.rparen',
        regex: '[\\)]'
      }, {
        token: 'text',
        regex: '\\s+'
      }],
      'comment': [
            {token: 'comment', regex: '\\*\\/', next: 'start'},
            {defaultToken: 'comment'}
      ]
    };

    this.embedRules(DocCommentHighlightRules, 'doc-', [DocCommentHighlightRules.getEndRule('start')]);
    this.normalizeRules();
  };

  oop.inherits(EikelangHighlightRules, TextHighlightRules);

  exports.EikelangHighlightRules = EikelangHighlightRules;
});

ace.define('ace/mode/eikelang', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text', 'ace/mode/eikelang_highlight_rules'],
  function(acequire, exports, module) {
    let oop = acequire('../lib/oop');
    let TextMode = acequire('../mode/text').Mode;
    let EikelangHighlightRules = acequire('./eikelang_highlight_rules').EikelangHighlightRules;

    let Mode = function() {
      this.HighlightRules = EikelangHighlightRules;
      this.$behaviour = this.$defaultBehaviour;
    };
    oop.inherits(Mode, TextMode);

    (function() {
      this.lineCommentStart = ['--', '#']; // todo space
      this.blockComment = {start: '/*', end: '*/'};

      this.$id = 'ace/mode/eikelang';
    }).call(Mode.prototype);

    exports.Mode = Mode;
  });

Hi @pollen8,
If you take a look at react-ace code, you'll see that the component tries to set the mode using it's name

Meaning that you have to make sure your mode is already in "Ace" cache using ace.define and oop.inherits(Mode, TextMode) and other native Ace code, which i find pretty old when you have ES6 on your side.
So why not writing your custom mode simply as we write any other class?
It would make your custom mode class look really better
(less lines of code + better syntax => less maintenance)

So I:

  1. Created my custom mode class (pure ES6 code)
  2. Initialized the component with an existing mode name (such as "sql")
  3. Used componentDidMount function and called session.setMode with an instance of my custom mode.

My custom mode is:

export default class CustomSqlMode extends ace.acequire('ace/mode/text').Mode {

    constructor(){
        super();
        // Your code goes here
    }
}

And my react-ace code looks like:

render() {
        return <div>
            <AceEditor
                ref="aceEditor"
                mode="sql"     // Default value since this props must be set.
                theme="chrome" // Default value since this props must be set.
            />
        </div>;
    }

    componentDidMount() {
        const customMode = new CustomSqlMode();
        this.refs.aceEditor.editor.getSession().setMode(customMode);
    }

The only downside it see is depending on ace.acequire('ace/mode/text').Mode to be a valid line when i define the mode class.
But it's valid anyway since react-ace puts the "ace" module under window.ace (https://github.com/ajaxorg/ace/blob/4c7e5eb3f5d5ca9434847be51834a4e41661b852/lib/ace/worker/worker.js#L19)

@AlonBe @pollen8
Does anyone have a working jsfiddle or example for this?

Tried following the directions that @AlonBe listed with the custom mode provided by @pollen8 and I cannot unfortunately get the syntax highlighter to register correctly... :(

Any help would be appreciated.

custom_mode

Where my editor is being rendered:

componentDidMount () { const customMode = new CustomSqlMode(); this.refs.ace.editor.getSession().setMode(test); console.log(customMode); }

@solemnify, Did you try to do this.refs.ace.editor.getSession().setMode(customMode);?
Try to follow the CustomSqlMode class I created.
To see if it works you can put inside the constructor the following line:
this.lineCommentStart = '--';

If you want to use syntax highlighter you need to override this.HighlightRules of the custom mode class.

Take a look at one of the current modes such as sql_mode

@AlonBe
Could you provide an example of code to put in your CustomSqlMode component ?
I don't know how to transform the highlight rules in es6...
Thanks in advance for your help.

The following code worked for me:

App.js

import React, { Component } from 'react';
import brace from 'brace';
import AceEditor from 'react-ace';
import CustomSqlMode from './CustomSqlMode.js'

import 'brace/theme/github';

class App extends Component {
  componentDidMount() {
    const customMode = new CustomSqlMode();
    this.refs.aceEditor.editor.getSession().setMode(customMode);
  }

  render() {
    return (
      <div className="App">
        <AceEditor
          ref="aceEditor"
          mode="text"
          theme="github"
          name="UNIQUE_ID_OF_DIV"
          editorProps={{ $blockScrolling: true }}
        />
      </div>
    );
  }
}

export default App;

CustomSqlMode.js

import 'brace/mode/java';

export class CustomHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules {
    constructor() {
        super();
        this.$rules = {
            "start": [{
                token: "comment",
                regex: "#.*$"
            }, {
                token: "string",
                regex: '".*?"'
            }]
        };
    }
}

export default class CustomSqlMode extends window.ace.acequire('ace/mode/java').Mode {
    constructor() {
        super();
        this.HighlightRules = CustomHighlightRules;
    }
}

The previous code highlights the comments and strings only. I'm not sure if what I did was the good practice or not, but at least it works.

@newint33h thanks!It works for me.

@newint33h I tried to use this structure but it complains that TypeError: Cannot read property 'editor' of undefined.

@shuotongli I had the same problem and it looks like that issue is because React's this.refs is now deprecated. There's are a few ways to create a ref, depending on the version of React you're using. For the currently supported way, check this out:
https://reactjs.org/docs/refs-and-the-dom.html#creating-refs

The following code worked for me:

App.js

import React, { Component } from 'react';
import brace from 'brace';
import AceEditor from 'react-ace';
import CustomSqlMode from './CustomSqlMode.js'

import 'brace/theme/github';

class App extends Component {
  componentDidMount() {
    const customMode = new CustomSqlMode();
    this.refs.aceEditor.editor.getSession().setMode(customMode);
  }

  render() {
    return (
      <div className="App">
        <AceEditor
          ref="aceEditor"
          mode="text"
          theme="github"
          name="UNIQUE_ID_OF_DIV"
          editorProps={{ $blockScrolling: true }}
        />
      </div>
    );
  }
}

export default App;

CustomSqlMode.js

import 'brace/mode/java';

export class CustomHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules {
  constructor() {
      super();
      this.$rules = {
          "start": [{
              token: "comment",
              regex: "#.*$"
          }, {
              token: "string",
              regex: '".*?"'
          }]
      };
  }
}

export default class CustomSqlMode extends window.ace.acequire('ace/mode/java').Mode {
  constructor() {
      super();
      this.HighlightRules = CustomHighlightRules;
  }
}

The previous code highlights the comments and strings only. I'm not sure if what I did was the good practice or not, but at least it works.

why the mode='text', what does text mean?

@TaurusWood, as the first render occurs before componentDidMount - you must pass a valid "mode" to react-ace Component.
later on, on componentDidMount you can see that the code overrides that mode (setMode)

and BTW, if I'm not getting wrong, mode='text' is the basic mode of Ace that all the others are overriding

Can anyone explain why is't it working anymore, all of my codes are interpreted as a text instead of java file after setting a custom mode? Thanks

The following code worked for me:

App.js

import React, { Component } from 'react';
import brace from 'brace';
import AceEditor from 'react-ace';
import CustomSqlMode from './CustomSqlMode.js'

import 'brace/theme/github';

class App extends Component {
  componentDidMount() {
    const customMode = new CustomSqlMode();
    this.refs.aceEditor.editor.getSession().setMode(customMode);
  }

  render() {
    return (
      <div className="App">
        <AceEditor
          ref="aceEditor"
          mode="text"
          theme="github"
          name="UNIQUE_ID_OF_DIV"
          editorProps={{ $blockScrolling: true }}
        />
      </div>
    );
  }
}

export default App;

CustomSqlMode.js

import 'brace/mode/java';

export class CustomHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules {
  constructor() {
      super();
      this.$rules = {
          "start": [{
              token: "comment",
              regex: "#.*$"
          }, {
              token: "string",
              regex: '".*?"'
          }]
      };
  }
}

export default class CustomSqlMode extends window.ace.acequire('ace/mode/java').Mode {
  constructor() {
      super();
      this.HighlightRules = CustomHighlightRules;
  }
}

The previous code highlights the comments and strings only. I'm not sure if what I did was the good practice or not, but at least it works.

Was this page helpful?
0 / 5 - 0 ratings