Revenge of the auto-wrap (type type type DING!)

"I do not think anyone wants Komodo to add breaks -- that is not what word-wrap does."
-- Cory Trese on komodo-discuss March 2002

As someone who uses Komodo mostly to edit text and HTML, my perspective on how an editor should behave differs somewhat from users who spend all day hacking in dynamic languages. One such behavior I find it hard to live without is "auto-wrap", the automatic insertion of line breaks at a pre-defined column.

Now, I didn't know it when I filed this bug, but Komodo did at one time have an auto-wrap feature.

Once Scintilla grew the ability to wrap lines without line breaks, word wrap was added to Komodo and auto-wrap relegated to handling comment wrapping only. This has always been a frustration for me, but I learned to work around it by hitting 'Ctrl'+'Q' to reflow paragraphs as I typed.

However, when I started using Komodo as an email editor, reflowing each line whenever I hit the line edge started seeming a bit absurd. It suddenly occurred to me that what I was doing was equivalent to using a manual typewriter with a bell that rang when you got close to the margin stop.

type type type type type type DING (ka-
chunk) type type type...

Knowing just enough about Komodo macros to get myself into trouble, I managed to get Todd interested enough about my cryptic questions about event listeners to come help me with the tricky bits. Let's walk through what we came up with - tutorial style.

The first thing we'll need is an event listener to keep an eye on our cursor position in the editor. There are more elegant (and efficient) ways to do this in Komodo, but this is just a quick and dirty macro.

ko.views.manager.topView.addEventListener("keypress", autoReflow, true);

This watches the editor pane (all tabs) for keypress events and runs "autoReflow" each time (I didn't claim this was going to be efficient code). Let's create that now:

autoReflow = function (event) {
  var scimoz = ko.views.manager.currentView.scimoz;
  if (scimoz.getColumn(scimoz.currentPos) > 72) {
    alert("DING!");
  }
}

The first line defines our "autoReflow" function under the global window namespace. The next gives us a convenient shortcut for the scimoz object which we then use to get the current position of the cursor. If it's greater than 72, we pop up an alert and take a giant leap backwards in usability. Let's do something a little more helpful.

Looking through Komodo's command ID list I see that "Reflow paragraph(s)" is accessed via "cmd_editReflow". To run that in a macro we use:

  ko.commands.doCommand("cmd_editReflow");

Putting it all together we get:

autoReflow = function (event) {
  var scimoz = ko.views.manager.currentView.scimoz;
  if (scimoz.getColumn(scimoz.currentPos) > 72) {
    ko.commands.doCommand("cmd_editReflow");
  }
}
ko.views.manager.topView.addEventListener("keypress", autoReflow, true);

Now we can type away to our hearts content and Komodo will automatically wrap lines for us when we get to column 72. This is great until we start editing code. We need a way to turn this off, which we can do with another macro that cancels the event handler:

ko.views.manager.topView.removeEventListener("keypress", autoReflow, true);

Not the fanciest way to do it, but I've put these two macros in a custom toolbar so that I can turn wrapping on and off when I need to.

I would be soundly spanked by the Komodo developers if I did not add one more "best practices" element to this macro. The autoReflow function is currently residing in the global namespace. To guard against the possibility that it might conflict with a function in another macro or extension, we should put it in our own namespace:

if (typeof (mymacros) == "undefined") {
  mymacros = {};
}
mymacros.autoReflow = function (event) {
  var scimoz = ko.views.manager.currentView.scimoz;
  if (scimoz.getColumn(scimoz.currentPos) > 72) {
    ko.commands.doCommand("cmd_editReflow");
  }
}
ko.views.manager.topView.addEventListener("keypress", mymacros.autoReflow, true);

... and of course the same thing in the "stop" macro:

if (typeof (mymacros) == "undefined") {
  mymacros = {};
}
ko.views.manager.topView.removeEventListener("keypress", mymacros.autoReflow, true);

If creating this namespace in all your macros looks like too much effort, you can create it in a separate macro that's triggered when you start Komodo. That way, the "mymacros" namespace is available in all your macros.