Friday, October 12, 2012

ACE UndoManager and setValue()

‹prev | My Chain | next›

While working with the ACE Editor, I noticed a terrifying feature. They call it "undo".

OK so maybe it's not that terrifying, but it does have a terrifying behavior—it undos back beyond when a new project is started. That is, it undos back to when the editor was empty. So, hitting multiple Ctrl-Z's will eventually land you, not on the original version of the project, but on a blank page. Since I am auto-saving my work in localStorage, I very quickly have a blank project with no obvious means to restore my work.

That's frustrating for me, but would be horrifying for a kid working through Gaming JavaScript.

I do not see any obvious way to remove an entry from ACE's UndoManager. I do see that EditSession has a setUnderManager() method—perhaps I can use that to initialize the undo at the point just after the editor is updated with setValue().

So I do just that:
var ace = ace.edit("editor");
ace.setTheme("ace/theme/chrome");
ace.getSession().setMode("ace/mode/javascript");
ace.getSession().setUseWrapMode(true);
ace.getSession().setUseSoftTabs(true);
ace.setPrintMarginColumn(false);
ace.setDisplayIndentGuides(false);
ace.setFontSize('18px');
var emacs = require("ace/keyboard/emacs").handler;
ace.setKeyboardHandler(emacs);
ace.setValue((documents.length > 0) ? documents[ 0 ].code : templates[ 0 ].code, -1);

ace.getSession().setUndoManager(new UndoManager());
I will have to do the same anywhere else that setValue() changes the contents (or generalize a code editor setValue function). But first, I need to make sure this works.

It does not. In the JavaScript console, I see:
Uncaught ReferenceError: UndoManager is not defined 
Ah, it seems that I will have to require.js it into my current namespace. I try:
var UndoManager = require("ace/ace").UndoManager;
But that does not work. It seems that UndoManager is not a property of the ACE constructor.

The define() statement for UndoManager is:
define('ace/undomanager', /* ... */);
This suggests that the following might work:
var UndoManager = require("ace/undomanager");
editor.getSession().setUndoManager(new UndoManager());
However, I still seem to lack a constructor:
Uncaught TypeError: object is not a function
If I check this out in the JavaScript console, it seems that I need the UndoManger property of that require object:


And indeed, the following does the trick:
// ...
ace.setValue(/* ... */);

var UndoManager = require("ace/undomanager").UndoManager;
ace.getSession().setUndoManager(new UndoManager());
Not only so the page load without error, but undo works as desired. I can type a bunch of junk in my current project, Ctrl-Z to my heart's content, but never move back to the blank page before the first setValue().

I believe that removes any objection that I might have had to replacing CodeMirror with ACE. They both seem very similar—both in features and quality. The only real difference is that I had to implement a very hacky fix to a CodeMirror+Chrome paint issue. Any issues that I have had with ACE, I have been able to resolve via the API. I do think that I will take some time tomorrow to see if I can get JavaScript syntax checking working under ACE when mixing HTML and JavaScript. That would be very nice.

Day #537

2 comments:

  1. Hello! Thank you for you post. It helped me to solve the same problem.

    But i found a much more easy solution:

    var undo_manager = ace.getSession().getUndoManager();
    undo_manager.reset();
    ace.getSession().setUndoManager(undo_manager);

    ReplyDelete
  2. Interestingly I tried reset as well and it didn't work! Immediately after calling reset the undo manager reported no undo available, but later it would report and perform undo all the way back to an empty document.

    ReplyDelete