Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use custom mode #126

Closed
sichvoge opened this issue Jul 6, 2016 · 22 comments
Closed

How to use custom mode #126

sichvoge opened this issue Jul 6, 2016 · 22 comments

Comments

@sichvoge
Copy link

sichvoge commented Jul 6, 2016

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

@jakubkoci
Copy link

jakubkoci commented Jul 19, 2016

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

@sichvoge
Copy link
Author

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.

@lshepstone
Copy link

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

@sichvoge
Copy link
Author

sichvoge commented Aug 6, 2016

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

@lshepstone
Copy link

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

@sichvoge
Copy link
Author

sichvoge commented Aug 7, 2016

Sure, I will.

@AlonBe
Copy link

AlonBe commented Mar 5, 2017

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

@pollen8
Copy link

pollen8 commented Mar 29, 2017

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;
  });

@AlonBe
Copy link

AlonBe commented Mar 29, 2017

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)

@darkhightech
Copy link

darkhightech commented Aug 3, 2017

@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); }

@AlonBe
Copy link

AlonBe commented Aug 21, 2017

@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

@annesof
Copy link

annesof commented Sep 18, 2017

@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.

@newint33h
Copy link

newint33h commented Nov 17, 2017

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.

@PurpleDandelion
Copy link

@newint33h thanks!It works for me.

@shuotongli
Copy link

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

@abrarmahmood
Copy link

abrarmahmood commented Jul 28, 2018

@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

@adesurirey adesurirey mentioned this issue Nov 8, 2018
@TaurusWood
Copy link

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?

@AlonBe
Copy link

AlonBe commented Nov 26, 2019

@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

@Ialimijoro
Copy link

Ialimijoro commented Nov 25, 2020

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.

@danielpaz6
Copy link

Is there any update regarding this issue? Looks like it @newint33h example doesn't work anymore...

@RaviMauryaHootowl
Copy link

RaviMauryaHootowl commented Dec 27, 2021

@danielpaz6
This worked for me 😊
I've used React useEffect and useRef hook to set the mode from a JavaScript file.
Here, RSOC is the name of the programming language I'm trying to create (just to avoid confusion).

RSOCMode.js

import 'brace/mode/text';

export class CustomHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules {
	constructor() {
		super();
		this.$rules = {
			"start" : [
                                {
                                    token : "comment",
                                    regex : /^~.*$/
                                },
                                {
                                    token : "variable",
                                    regex : /:.*$/
                                },
                                {
                                    token : "keyword",
                                    regex : /(?:set|add|show|ifg)\b/,
                                    caseInsensitive: true
                                },
                                {
                                    token : "constant.numeric",
                                    regex : /[0-9]+\b/,
                                }
                       ]
		};
	}
}

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

MyEditor.jsx

const aceEditorRef = useRef(null);

useEffect(() => {
    const rsocMode = new RSOCMode();
    if(aceEditorRef.current != null){
        console.log(aceEditorRef);
        aceEditorRef.current.editor.session.setMode(rsocMode);
    }
}, [aceEditorRef.current])

return (
    <AceEditor
        ref={aceEditorRef}
        mode="text"
        theme="monokai"
        editorProps={{ $blockScrolling: true }}
        showPrintMargin={false}
        fontSize={26}
        value={codeValue}
        onChange={(val) => {}}
        style={{
            width: '100%',
            borderRadius: '8px',
        }}
    />
);

I hope this helps :)

@monicavaldez
Copy link

I'm sorry this sounds basic, but despite adding the dependency, I still don't know why window does not have ace property yet whenever I try to extend window.ace.acequire(...)

Property 'ace' does not exist on type 'Window & typeof globalThis'.ts(2339)

@danielpaz6 This worked for me 😊 I've used React useEffect and useRef hook to set the mode from a JavaScript file. Here, RSOC is the name of the programming language I'm trying to create (just to avoid confusion).

RSOCMode.js

import 'brace/mode/text';

export class CustomHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules {
	constructor() {
		super();
		this.$rules = {
			"start" : [
                                {
                                    token : "comment",
                                    regex : /^~.*$/
                                },
                                {
                                    token : "variable",
                                    regex : /:.*$/
                                },
                                {
                                    token : "keyword",
                                    regex : /(?:set|add|show|ifg)\b/,
                                    caseInsensitive: true
                                },
                                {
                                    token : "constant.numeric",
                                    regex : /[0-9]+\b/,
                                }
                       ]
		};
	}
}

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

MyEditor.jsx

const aceEditorRef = useRef(null);

useEffect(() => {
    const rsocMode = new RSOCMode();
    if(aceEditorRef.current != null){
        console.log(aceEditorRef);
        aceEditorRef.current.editor.session.setMode(rsocMode);
    }
}, [aceEditorRef.current])

return (
    <AceEditor
        ref={aceEditorRef}
        mode="text"
        theme="monokai"
        editorProps={{ $blockScrolling: true }}
        showPrintMargin={false}
        fontSize={26}
        value={codeValue}
        onChange={(val) => {}}
        style={{
            width: '100%',
            borderRadius: '8px',
        }}
    />
);

I hope this helps :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests