javascriptcssreactjsace-editorreact-ace

How to use the Ace editor validator without instantiating an Ace editor instance?


I use react-ace to create a CSS text editor in my React app.

That looks something like...

import Ace from 'react-ace'

...
  <Ace 
    mode="css" 
    value={value} 
    onChange={onValueChange} 
    onValidate={onValidate} 
    ...
  />
...

This works fine and dandy—highlighting CSS syntax errors and warnings. Also, the onValidate returns the error/warning "annotations" data structure.

However there is a need, elsewhere in the application, to run the same validator used in this React Ace component, but outside of the context of this Component. Essentially I need to pass the content in value through the error/warning annotation system, but can't instantiate this react element.

I've tried the following:

import { EditSession } from 'brace'; # "brace" is the "module" compatible version of the ace editor that our "react-ace" uses
import 'brace/mode/css';

export const getCssAnnotations = (value)=> {
  const editSession = new EditSession(value);
  editSession.setMode('ace/mode/css');
  const annotations = editSession.getAnnotations();
  return annotations;
};

However, the annotations returned by this function are always []! I assume this is because I'm just accessing the annotation setter/getter interface, and not actually running the annotations creator. But I can't figure out what actually does the annotations work normally.

I've looked at docs on Creating a Syntax Highlighter for Ace, but don't understand if/why a web worker would need to be involved here.

Thanks!


Solution

  • This doesn't work, because editSession uses web worker to generate annotations which is async:

    editSession.on('changeAnnotation', () => {
        let annotations = editSession.getAnnotations();
        callback(null, annotations)
    });
    

    docs

    Note that currently each editSession creates a new worker, so it is better to use setValue on an existing instance of editSession, or call editSession.destroy() before calling the callback


    So a full solution might look like:

    const getAnnotationsPromise = (value, mode)=> {
      const editSession = new EditSession(value);
      editSession.setMode(`ace/mode/${mode}`);
    
      return new Promise((resolve)=> {
        editSession.on('changeAnnotation', () => {
          const annotations = editSession.getAnnotations();
          editSession.removeAllListeners('changeAnnotation');
          editSession.destroy();
          resolve(annotations);
        });
      });
    };