How do you customize Komodo?

Posted by jeff.griffiths on 2007-01-15 14:10

A while ago I wrote what ended up being a very simple macro that takes the last word you typed, tried to find a matching Snippet in your toolbox, and inserts it. This was something of an homage to TextMate's macro/bundle system and although it isn't quite as cool ( no tabbing between markers ) it has ened up being quite useful for me personally, so much so that I get quite frustrated if I am using Komodo without it and the large library of snippets I have accumulated along the way.

I find it interesting that Komodo seems 'customizable enough', meaning that although I may find the stock config a little frustrating, I can tweak it to my liking now quite easily. Here is a list of my personal must-have tweaks:

1. install 'trigger'* and 'bracemagic'** macros into toolbox, to hot-rod the editor a bit.
2. go into prefs
a) Editor: enable 'show line numbers'
b) Editor / Key Bindings: create new scheme, and map 'incremental Search' to 'Meta+/' to behave as closely as possible like Firefox or vim.
c) Editor / Smart Editing: enable 'Use tab character to complete words'. Awesome!
3. still in Prefs, go to Fonts and Colors and choose the 'medium' scheme
4. Languages / HTML, set WAI Accessibility conformance level to 'Off'

...ahh, much better.

I used to have to fiddle more with PHP configuration, and I had to manually add *.module as a php file type until I got Trent to add it in by default. On linux i used to always have to muck around with the color schemes, so I fixed that by adding more scheme choices and sane Linux font settings. So Komodo 4.0 actually fixes a lot of past nitpicks of mine.

What do you do? Do you run a stock Komodo, Vi mode, Emacs bindings? Any weird color schemes or key-bindings? Any macros?

* http://blogs.activestate.com/jeffg/2006/08/textmatestyle_m.html

** http://blogs.activestate.com/jeffg/2006/12/brace_completio.html

Tom | Thu, 2007-01-18 13:40

While I couldn't possibly remember all of the tweaks I've made to Komodo some of the most notable include:

1. Enable line numbers and word-wrap - I can't work without these two essential features!
2. Adjust the editor settings to match my coding style (indent using tabs, UNIX line endings etc).
3. Set PHP as the default file type for new files and add my own custom file extensions (e.g. tpl for HTML templates).
4. Create a macro to run at start-up to force Komodo to start maximised (taken from http://aspn.activestate.com/ASPN/Mail/Message/komodo-discuss/2800804) - Komodo 4.0 should hopefully eliminate the need for this.
5. Change the fonts used by Komodo - ProggyClean for fixed-width and Verdana for proportional (used for comments).
6. Change the colour schemes to the ones I've used for years. The PHP colour scheme, which I've used since the days when HomeSite was my editor of choice, gets the biggest overhaul (for those who are interested here's a sample: http://img245.imageshack.us/img245/1949/coloursfu8.png).

I also have a number of snippets etc, the most useful of which being one to insert the current date and time, which I have created a shortcut for (Control + D). I also have snippets for phpDocumentor templates and Xdebug functions; macros to validate HTML files (using the packaged version of Tidy and displaying the results in the command output tab), showing the length of the selected text in a dialog box using %ask (set to Control + L), and updating the dates used in my custom @updated phpDocumentor tags (which is triggered on save); and shortcuts to open things like php.ini, the PHP manual, phpMyAdmin, CuteFTP, Word etc.

If you haven't guessed by now I'm a PHP developer. :-)

tclineks | Tue, 2007-02-06 18:53

I've made a little addition to jeff's trigger macro to enable simple paramter passing to snippets.

Basically you can enter then invoke the macro
snippetname.param
and if snippetname is a valid snippet it will replace 'snippetname.param' with 'param' then insert the snippet.

This allows use of [[%s]] in snippets which makes a world of difference.
You can also use '/' instead of '.' (or make the regex match whatever).

komodo.view.setFocus();
try {
    komodo.editor.wordLeftExtend();
    var snippet = komodo.interpolate('%s');
    var tmpl = komodo.findPart("snippet", snippet, "container");
    if(tmpl) {
        Snippet_insert(tmpl);
    } else {
/* added */
        komodo.editor.charLeftExtend();
        var new_selection = komodo.interpolate('%s');
        // if new character matches regex, treat as delimiter
        if (new_selection.match(/^[\.\/]/)) {
            var parameter = snippet; // reassigns original selection as parameter to new snippet

            komodo.editor.wordLeftExtend();
            var snippet = komodo.interpolate('%s');
            snippet = snippet.slice(0, -1*new_selection.length);

            var tmpl = komodo.findPart("snippet", snippet, "container");
            if(tmpl) {
                komodo.editor.replaceSel(parameter);
                komodo.editor.wordLeftExtend();
                a = Snippet_insert(tmpl);
                return;
            }
        }
        else {
            komodo.editor.charRightExtend();
        }
/* end added */
        var msg = "no snippet found named " + snippet;
        StatusBar_AddMessage(msg,"debugger",5000,true);
    }
} catch(e) {
    alert(e);
}

example snippet 'e':
<&#63;php echo $[[%s]]; &#63;>
then
e.hello_world
after invoking the macro would result in:
<&#63;php echo $hello_world; &#63;>

The way I put this together was meant to maintain the existing code and it can be prettier without such restriction.

Enjoy!

hebetude | Fri, 2007-09-07 10:23

I added one extra line, basically if the snippet uses a parameter and you don't enter one, it won't just default to inputting the snippet name as that parameter. You will need to protect your %s with a orask/else, because this empties your selection and the snippet will fail when trying to determine the value for %s. This is a revision of tigerplus's code

komodo.view.setFocus();
try {
    komodo.editor.wordLeftExtend();
    var snippet = komodo.interpolate('%s');
    var tmpl = komodo.findPart("snippet", snippet, "container");
    if(tmpl) {
/* added */
        //changed Default "insert snippet", so it does not add unwanted text
        komodo.editor.delWordRight();
        Snippet_insert(tmpl);
/* end added */
    } else {
        komodo.editor.charLeftExtend();
        var new_selection = komodo.interpolate('%s');
        // if new character matches regex, treat as delimiter
        if (new_selection.match(/^[\.\/]/)) {
            var parameter = snippet; // reassigns original selection as parameter to new snippet
            komodo.editor.wordLeftExtend();
            var snippet = komodo.interpolate('%s');
            snippet = snippet.slice(0, -1*new_selection.length);
            var tmpl = komodo.findPart("snippet", snippet, "container");
            if(tmpl) {
                komodo.editor.replaceSel(parameter);
                komodo.editor.wordLeftExtend();
                a = Snippet_insert(tmpl);
                return;
            }
        }
        else {
            komodo.editor.charRightExtend();
        }
        var msg = "no snippet found named " + snippet;
        StatusBar_AddMessage(msg,"debugger",5000,true);
    }
} catch(e) {
    alert(e);
}

jeff.griffiths | Fri, 2007-09-07 11:36

Here's a re-write that works the same but also supports tabstops, and is re-factored to use the new ko.* JavaScript API:

function tabstop_handler(snippet) {
    var rx = /(«|\%tabstop\:)/g;
    if (rx.test(snippet.value)) {
        ko.commands.doCommand('cmd_indent');
    }
}

try {    
    ko.views.manager.currentView.setFocus();
    var ke = ko.views.manager.currentView.scimoz;
    var interp = ko.interpolate.interpolateStrings;
    ke.wordLeftExtend();
    var name = interp('%s');
    var snippet = ko.projects.findPart("snippet", name, "container");
    if(snippet) {
        ke.delWordRight();
        Snippet_insert(snippet);
        tabstop_handler(snippet);
    } else {
        ke.charLeftExtend();
        var new_selection = interp('%s');
        if (new_selection.match(/^[\.\/]/)) {
            var parameter = name; // reassigns original selection as parameter to new snippet
            ke.wordLeftExtend();
            var name = interp('%s');
            name = name.slice(0, -1*new_selection.length);
            var snippet = ko.projects.findPart("snippet", name, "container");
            if(snippet) {
                ke.replaceSel(parameter);
                ke.wordLeftExtend();
                Snippet_insert(snippet);
                tabstop_handler(snippet);
            }
        }
    }
    if(!snippet) {
        var msg = "no snippet found named " + name;
        StatusBar_AddMessage(msg,"debugger",5000,true);        
    }
} catch(e) {
    alert(e);
}

I think I'll start using this full-time as it is a big limitation of the original trigger that it doesn't handle selection interpolations nicely. The one edge-case problem is if you want to use a param that happens to mmatch the name of a snippet in your toolbox somewhere, so I guess naming is important. I also wonder if the use of '.' as the delimiter will end up causing grief as it is an operator or namespace delimiter in the most common languages used?

hebetude | Fri, 2007-09-07 13:04

As far as grief, it only scans back one delimiter/whitespace at a time. It shouldn't cause too many problems. I guess it depends on how extensive your snippet library is.

Alright, one more annoyance can you nest tabstop behind an else?
ie.
[[%s:else:[[%tabstop:8in]]]]

tclineks | Fri, 2007-09-07 13:46

Good to see some enhancement here -- I was going to work on that same change Jeff.

I think the really killer thing that can happen next with Komodo in regard to the snippet system is autocompletion for snippets.
An alteration would be inserting textmate style if you have put enough of the name for it to only match a single snippet. e.g. snippet foosnippet would be inserted if you tried to insert just 'foo' (assuming no other snippets started with foo).

Other ideas but not as major:
- auto-inserting snippet parameters into tabstops.
- multiple snippet params p.pid.pbody => <p id="pid">pbody</p> (may tie into above)

jeff.griffiths | Fri, 2007-09-07 14:02

Parts of the ko JavaScript api that my implementation above relies on were implemented just prior to the beta 7 release, and thus will not work in previous versions of Komodo.

As for the else logic with tabstops, no you cannot nest them. This:

[[%s:else:[[%tabstop:8in]]]]

...doesn't work.

--
JeffG

jscheel | Fri, 2007-09-14 13:14

I'm not sure about all of my customizations, but one that I would really, really, really like to see is automatic brace completion. Gary Haran's gEdit plugin does this really well. You can check his out at: garyharen.com

ericp
ActiveState Staff
Fri, 2007-09-14 14:32

* Completes the following characters: < {['""']}>

We aren't handling angle-brackets. We are handling parentheses. This is also
done on a per-language basis. For example, in SQL single-quotes delimit
strings, but double-quotes don't. So we only handle single-quotes.

* deletes both inserted characters on backspace or deletes only the closing character if you hit delete

Yes.

* inserts proper indentation when you hit the Enter between {}

Yes.

* pads selected text with a pair when you type the starting character

We tried it, but pulled it out because it was too slow. It's easy to write
a macro to do this though.

This is all available in the latest beta of 4.2.

jscheel | Fri, 2007-09-14 14:55

Awesome! I haven't had a chance to install the 4.2 beta yet. Off topic, can I run the beta simultaneously on the same XP system as the current stable release?

ericp
ActiveState Staff
Fri, 2007-09-14 15:09

The two versions by default get installed in different directories,
keep their settings in different areas, and can be run at the same
time. So go ahead and give it a try.