Komodo is no black box. It's based on Mozilla, which means most of the interesting code is shipped as part of the product. Here are a few different ways you can take advantage of this to make Komodo work the way you want it to, or at least embark on that interesting journey.

First, it helps to know where the source is. There's a lot of code, spread out in various areas, but the two main parts of the code are based off something I'll call "mozhome" (which is <installDir>/lib/mozilla on Windows and Linux, and /Applications/Komodo.app/Contents/MacOS on OS X). While there are a few other areas, the main Komodo code is split into the front-end, in these two directories:

  mozhome/chrome/komodo.jar
  mozhome/chrome/xtk.jar

and the back-end, in these two areas:

  mozhome/python/komodo
  mozhome/components/ko*.py

To explore the chrome code, copy the jar file to a separate directory (it should be empty), unzip it there, and at a later point, if you make modifications you can zip it up and replace the shipped komodo.jar file with your own.

For both Python and chrome files, changes will automatically be picked up by Komodo on restart.

If you know an identifier name, or a string of text, you can search for it quickly at http://grok.openkomodo.com/source/xref/openkomodo/trunk/. Note that this is the code base for Komodo Edit. You can work with features specific to Komodo IDE, but you'll only have local searching. Also if you're using Komodo IDE but looking at the Komodo Edit source, line numbers are different because the files have different licenses at the start of their source. Finally, some of the source files go through preprocessing before being packaged into the Komodo distro, and their name will contain an extra extension such as ".p" or ".unprocessed".

Macros

Now that we've covered where the code is, here's how to extend it.

The quickest way to get started is by using the Macro Recorder, saving the recorded macros in the toolbox, and using them. These often work, but some operations aren't recorded (such as setting items in a dialog box), and due to the way the macros are serialized, they don't always work once they're saved in the toolbox. In particular, macro lines that work with the editor by manipulating Komodo commands should use the Komodo editor API directly.

For example, if you record a sequence of moving down three lines, and to the right by two characters, you'll get this macro:

komodo.assertMacroVersion(2);
if (komodo.view) { komodo.view.setFocus() };
komodo.doCommand('cmd_lineNext')
komodo.doCommand('cmd_lineNext')
komodo.doCommand('cmd_lineNext')
komodo.doCommand('cmd_right')
komodo.doCommand('cmd_right')

If you're thinking of writing macros (or extensions) that work with the editor, it's never too soon to start working with the editor API. It's based on Scintilla, so you can find plenty of documentation on it, as well as in Komodo's help. The above macro would be rewritten like so:

var scimoz = ko.views.manager.currentView.scimoz;
scimoz.lineDown();
scimoz.lineDown();
scimoz.lineDown();
scimoz.charRight();
scimoz.charRight();

Think of the doCommand statements that Komodo generates for you as scaffolding, which you eventually should replace before you're ready to unleash your macros on the world.

Next, macros can be written in either JavaScript or Python. Usually if you're interacting with the front-end, JavaScript means you can manipulate it more directly. However, sometimes there are common libraries in Python that make it too compelling. For example, I have a macro I use constantly that lets me reformat single-line paragraphs into blocks of text more suited for people who read email with older text-based agents. Here's the code:

    import textwrap
    paragraphs = re.split(r'\r?\n\r?\n(?=.)', text)
    lines = []
    for p in paragraphs:
        if longLine_re.search(p):
            lines += textwrap.wrap(p)
        else:
            lines += re.split('\r?\n', p)
        lines.append(eol)

You might be wondering why I'm talking about email packages in an article on hacking Komodo, which isn't known for its mail capabilities. It turns out this macro uses the clipboard for its I/O. I'll show the code, as it shows how to interface Python with Mozilla:

class ClipboardWrapper():
    def __init__(self):
        self.clipboard = components.classes["@mozilla.org/widget/clipboard;1"].getService(components.interfaces.nsIClipboard)
        self.transferable = components.classes["@mozilla.org/widget/transferable;1"].createInstance(components.interfaces.nsITransferable)
        self.transferable.addDataFlavor("text/unicode")
       
    def _getTextFromClipboard(self):
        self.clipboard.getData(self.transferable, self.clipboard.kGlobalClipboard)
        (str, strLength) = self.transferable.getTransferData("text/unicode")
        return str.QueryInterface(components.interfaces.nsISupportsString).data[:strLength/2]

    def _copyTextToClipboard(self, text):
        data = components.classes["@mozilla.org/supports-string;1"].createInstance(components.interfaces.nsISupportsString)
        data.data = text
        self.transferable.setTransferData("text/unicode", data, len(text) * 2)
        self.clipboard.setData(self.transferable, None, self.clipboard.kGlobalClipboard)

This code also shows you how to interface with the Mozilla API via XPCOM. It turns out this is actually slightly easier from Python than from JavaScript, as the PyXPCOM library knows how to manage out-variables. In JavaScript you'd have to write the above getter like so:

var str = {};
var strLength = {};
this.transferable.getTransferData("text/unicode", str, strLength);
return str.value.QueryInterface(Components.interfaces.nsISupportsString).
       data.substring(0, strLength.value / 2);

For some reason, the Python XPCOM library spells "Components" with a lower-case "c". If you're getting tripped up by this, you can get around it with this line:

from xpcom import components as Components

If you're thinking I've left out a lot of material, you're right. The Komodo API is large, and the Mozilla XPCOM library is larger.

It's also worth mentioning that Komodo picks up changes to macros immediately, as opposed to any other kind of change.

Macro Documentation

Both macros in JavaScript and Python let you take advantage of a higher-level Komodo API, documented at http://docs.activestate.com/komodo/5.0/macroapi.html (note that these functions are available only inside macros). You can find info on the standard Komodo API at http://docs.activestate.com/komodo/5.0/komodo-js-api.html

Debugging Macros

There might be an easy way of debugging macros, but I doubt it. Both JavaScript and Python macros are run with a single eval block (exec for Python). The JavaScript debuggers I've tried won't step into these eval blocks. You can't easily use print statements either, and writing to the Komodo output window is more complicated than one would want for a quick print statement. Instead I use the Komodo version of Ted's Extension Developer Extension, available here, and in particular use the JavaScript shell to test out macro lines incrementally. It might look like you can only enter one line at a time in the shell. To enter a block of code, you can either press Shift-Return at the end of each line, or better still, write the code in Komodo, select and copy it, and paste it into the shell, which knows how to handle multi-line insertions.

The extension is also a reasonable way of interactively exploring much of Komodo's front-end code. Type "ko." followed by two tabs to get a list of the main components in the "ko" namespace. Most of the time you'll be working with ko.views for editor files, and ko.projects.

Restyling Komodo

While you could modify the CSS files in komodo.jar, Komodo supports the same mechanism other Mozilla apps use, and you can simply override Komodo's default settings by providing a "userChrome.css" file in your profile. See http://community.activestate.com/faq/customizing-the-komodo-ui for more info.

Extensions

Once you've reached the limits of what you can do with macros, the next step is to write an extension. In Komodo this gives you access to adding new parts to the UI, new menu items, new sidebars. Most of the resources I mentioned above are useful for extension writing as well.

The "Komodo Extension" project template will help you get started, but like recorded macros, it's more of a scaffold generator than a code generator.

It's definitely worth spending time reading the source of extensions you use, to understand how they're assembled. The best source for Komodo extensions is at http://community.activestate.com/addons. Extensions end with ".xpi", so the host application (that's Komodo here, but also Firefox and other Mozilla apps) knows how to load them when they try to open them, but they're just zip files.

When you're working on an extension, you might find the following cycle gets old fast:

  • Edit and save extension source
  • Rebuild and rezip
  • Reinstall the extension into Komodo
  • Restart Komodo
  • Test changes

Here's a useful shortcut for Mozilla extensions I wish I had learned years ago. These extensions live in the application profile directory, which is something like /5.0/host-hostname/XRE/extensions. Let's say you're working on an extension called foo@mycom.org -- normally installing an extension in Komodo will create a directory called "foo@mycom.org" in the extensions directory, and the contents of this directory will contain the extension. However, you can use a platform-independent shortcut -- delete the installed foo@mycom.org directory (you have the source for this elsewhere, right), and replace it with a file with the same name, "foo@mycom.org". Its contents should be one line containing the absolute path to the extension code (the Komodo extension project template will create this automatically). Now you don't need to reinstall your extension each time. Instead the work cycle is now:

  • Edit and save extension source
  • Rebuild
  • killall komodo-bin (or use the Task Manager on Windows to quickly close Komodo). When you're in this cycle you most likely don't want to take the extra time Komodo needs to shut down gracefully.
  • Restart Komodo
  • Test changes

Same number of steps, but you no longer need to do the extension install dance.

There is much more on writing extensions, but this is supposed to be a short article.

Hacking and Patching

As you write increasingly ambitious macros and extensions, you'll no doubt have spent some time reading Komodo code, and found some opportunities to make improvements.

The main problem with this is that while macros and extensions live across versions (although extensions will need to be reinstalled, and macros will often need to be exported into a package, and then reloaded in the new version), changes to the Komodo code base will remain with earlier versions. At this point you're advised to submit your patches -- see http://www.openkomodo.com/participate for details.