25

I'm using the ACE editor for interactive JavaScript editing. When I set the editor to JavaScript mode, ACE automatically determines if the code is valid or not, with an error message and line number highlighted when it's not.

During the change event handler, I want to detect if ACE thinks the code is valid or not before I attempt to eval() it. The only way I thought that I might do it is:

var jsMode = require("ace/mode/javascript").Mode;
var editor = ace.edit('mycode'), edEl = document.querySelector('#mycode');
editor.getSession().setMode(new jsMode);
editor.getSession().on('change',function(){
  // bail out if ACE thinks there's an error
  if (edEl.querySelector('div.ace_gutter-cell.ace_error')) return;
  try{
    eval(editor.getSession().getValue());
  }catch(e){}
});

However:

  1. Leaning on the presence of an element in the UI with a particular class seems awfully fragile, but more importantly,
  2. The visual update for parsing occurs after the change callback occurs.

Thus, I actually have to wait more than 500ms (the delay before the JavaScript worker kicks in):

editor.getSession().on('change',function(){
  setTimeout(function(){
    // bail out if ACE thinks there's an error
    if (edEl.querySelector('div.ace_gutter-cell.ace_error')) return;
    try{
      eval(editor.getSession().getValue());
    }catch(e){}
  },550); // Must be longer than timeout delay in javascript_worker.js
});

Is there a better way, something in an undocumented API for the JS mode, to ask whether there are any errors or not?

3
  • I don't really know much about ACE but could you explain why you are using eval? Commented Feb 29, 2012 at 20:51
  • @hradac Better than that, I'll show you the (in progress) work. Commented Feb 29, 2012 at 21:38
  • I'm doing something similar and am hoping for an answer too. Eval is too expensive to run every time. Commented Mar 30, 2012 at 2:41

4 Answers 4

30

The current session fires onChangeAnnotation event when annotations change.

after that the new set of annotations can be retrieved as follows

var annotations = editor.getSession().getAnnotations();

seems to do the trick. It returns a JSON object which has the row as key and an array as value. The value array may have more than one object, depending on whether there are more than one annotation for each row.

the structure is as follows (copied from firebug –for a test script that I wrote)

// annotations would look like
({

82:[
    {/*annotation*/
        row:82, 
        column:22, 
        text:"Use the array literal notation [].", 
        type:"warning", 
        lint:{/*raw output from jslint*/}
    }
],

rownumber : [ {anotation1}, {annotation2} ],

...

});

so..

editor.getSession().on("changeAnnotation", function(){

    var annot = editor.getSession().getAnnotations();

    for (var key in annot){
        if (annot.hasOwnProperty(key))
            console.log("[" + annot[key][0].row + " , " + annot[key][0].column + "] - \t" + annot[key][0].text);
    }

});

// thanks http://stackoverflow.com/a/684692/1405348 for annot.hasOwnProperty(key) :)

should give you a list of all annotations in the current Ace edit session, when the annotations change!

Hope this helps!

Sign up to request clarification or add additional context in comments.

6 Comments

This is nice information, but it does not help detect the problem immediately: the annotations (including errors) are not available in the change callback for the editor; you have to wait until the asynchronous validation occurs.
Edited the answer! Hope it helps now!
The getAnnotations() format has changed... it is now an array of objects.
It should be annot[key].row, not annot[key][0].row.
@OnurYILDIRIM the Annotations object has an array "rownumber : [ {anotation1}, {annotation2} ]" for every row ;see above. Since multiple annotations are possible per row. (Or did the API change now?) edit: I didn't see DrFriedParts' comment above. I will check it and correct it.
|
3

I found a solution that is probably faster than traversing the DOM. The editor's session has a getAnnotations method you can use. Each annotation has a type that shows whether they are an error or not.

Here is how I set my callback for the on 'change'

function callback() {
    var annotation_lists = window.aceEditor.getSession().getAnnotations();
    var has_error = false;

    // Unfortunately, you get back a list of lists. However, the first list is
    //   always length one (but not always index 0)
    go_through:
    for (var l in annotation_lists) {
        for (var a in annotation_lists[l]) {
            var annotation = annotation_lists[l][a];
            console.log(annotation.type);
            if (annotation.type === "error") {
                has_error = true;
                break go_through;
            }
        }
    }

    if (!has_error) {
        try {
            eval(yourCodeFromTextBox);
            prevCode = yourCodeFromTextBox;
        }
        catch (error) {
            eval(prevCode);
        }
    }
}

As far as I know, there are two other types for annotations: "warning" and "info", just in case you'd like to check for those as well.

I kept track of the pervious code that worked in a global (well, outside the scope of the callback function) because often there would be errors in the code but not in the list of annotations. In that case, when eval'ing the errored code, it would be code and eval the older code instead.

Although it seems like two evals would be slower, it seems to me like the performance is no that bad, thus far.

Comments

2

Ace uses JsHint internally (in a worker) and as you can see in the file there is an event emitted:

this.sender.emit("jslint", lint.errors);

You can subscribe to this event, or call the JSHint code yourself (it's pretty short) when needed.

3 Comments

Subscribing to the event sounds like a good idea, so that I don't parse it twice for each edit, and also because require("../narcissus/jsparse") returns null for me. Any idea on what object that session is emitted? The editor? The session? Editing your answer with code showing how to subscribe would definitely win you the accept. :)
Any suggestions on what to register on?
"this" is too vague
2

I found you can subscribe worker events in Ace 1.1.7:

For javascript code, subscribe 'jslint' event:

session.setMode('ace/mode/javascript}');
session.on('changeMode', function() {
  if (session.$worker) {
    session.$worker.on('jslint', function(lint) {
      var messages = lint.data, types;
      if (!messages.length) return ok();
      types = messages.map(function(item) {
        return item.type;
      });
      types.indexOf('error') !== -1 ? ko() : ok();
    });
  }
});

For JSON code, subscribe 'error' and 'ok' event:

session.setMode('ace/mode/json');
session.on('changeMode', function() {

  // session.$worker is available when 'changeMode' event triggered
  // You could subscribe worker events here, whatever changes to the
  // content will trigger 'error' or 'ok' events.

  session.$worker.on('error', ko);
  session.$worker.on('ok', ok);
});

1 Comment

In more recent versions the events have been renamed: error => annotate / ok => terminate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.