Tuesday, March 5, 2013

ACE Events: Removing and Handling

‹prev | My Chain | next›

The ACE code editor really stumped me last night. There is some very odd interaction between ACE's web clipboard and my clipboard manager (and only when syncing Ctrl+C and the X Windows Selection). Rather than bang my head against that again, I opt to focus on other bugs that are likely to affect real people.

I am using ACE as the text editor in my fork of Mr Doob's code editor. This is the centerpiece for 3D Game Programming for Kids, so I need to get it as close to right as possible.

First up, I have noticed that, when opening new projects, the project double loads. I have already tried, unsuccessfully it would seem, to work around this by clearing a global update timeout when first loading the editor:
editor.setValue((documents.length > 0) ? documents[ 0 ].code : templates[ 0 ].code, -1);
// Don't consider initial setValue to be a change requiring an update:
clearTimeout( interval );
There are two problems here. First, clearing the update timer does not always have the desired effect because of callback ordering. Second, even when it does have the desired effect, it only does so on initial load—not when switching between projects.

The update timer is responsible for updating the visualization layer behind the code layer:



It is a plain-old setTimeout() so that the visualization is not constantly refreshing with every keystroke.

As a solution, I replace all editor.setValue() calls, along with any associated timeout handling, with a call to a new setContent() wrapper function:
setContent((documents.length > 0) ? documents[0].code : templates[0].code, -1);
What makes setContent() doubly effective is that it sidesteps the update timer by completely removing the updated code handling:
function setContent(data) {
  editor.getSession().removeListener('change', handleChange);
  editor.setValue(data);
  editor.getSession().setUndoManager(new UndoManager());
  editor.getSession().on('change', handleChange);
  update(); // visualization layer
}
The other problem that I need to solve in ACE is preventing updates when moving around with arrow keys. This should work with something like:
document.addEventListener('keydown', function (event) {
  resetUpdateTimer();
});
Except that it doesn't.

It turns out that ACE intercepts arrow key events and prevents them from propagating. I eventually find the solution in a Stack Overflow post:
editor.keyBinding.originalOnCommandKey = editor.keyBinding.onCommandKey;
editor.keyBinding.onCommandKey = function(e, hashId, keyCode) {
  if (keyCode >=37 && keyCode <= 40) {
    if (interval) resetUpdateTimer();
  }
  this.originalOnCommandKey(e, hashId, keyCode);
};
For whatever reason, arrow keys are considered command keys in ACE. So all I do here is, check for an arrow keycode and if there is a running update timer, then I give it a reset.

Both of these were way more than I wanted to know about ACE, but I feel better for having tackled them.

Day #681

2 comments:

  1. editor.getSession().removeListener('change', handleChange) doesn't remove event. How can I make it work ?

    ReplyDelete