2012-12-19

So we would want to insert the end in the first situation, but not the second. This was impossible with the old static snippets, but straightforward with the current ones. Here's the new snippet for Ruby/keywords/if:

1

2    if [[%tabstop:test]]

3        [[%tabstop:#code]]

4    end

5

if [[%tabstop:test]]
[[%tabstop:#code]]
end

-->

This version of Komodo adds a new library file, snippets.js, where we've started to build an API that makes writing snippets easier. You can easily add to the ko.snippets namespace with a startup trigger macro, or even easier by posting a feature request at The Komodo Bug Page. The two members of that namespace shown here should be straightforward. The rightOfFirstRubyKeyword method is used to tell whether keywords like if, unless, while, and until start a block. The ko.snippets.RejectedSnippet needs a bit more explanation. If snippet evaluation throws a JavaScript exception, the snippet's contents aren't inserted into the document. But if the exception is a ko.snippets.RejectedSnippet object, Komodo won't emit a statusbar notification if the attempted insertion was due to auto-abbreviation matching. In other words, Komodo does the right thing. In our Ruby if example, Komodo will expand an if at the start of a line, and silently do nothing if you type a space after a qualifying if. However, if you tried to explicitly invoke an abbreviation by pressing Ctrl-T after a qualifying if, you would get a notification that the insertion was suppressed. Full details will be in the log file.

Repetition a la Emmet

Emmet (possibly better known by its previous name, "Zen Coding") is a very cool extension (available for other editors as well, not just Komodo) that lets you enter a CSS-like selector, press a button, and have it converted into a long bit of HTML. In Komodo it's implemented with an extension, and is only available for HTML. But we can easily bring this type of feature over to other languages, using dynamic snippets.

Case Study: Creating Quick Constructors

For example, a common task in much object-oriented programming is to write a constructor that takes n parameters, and then assign each parameter's value to an attribute with its same name. For example, in Python, you might have an initializer like this, taken from the standard Python library's formatter.py:

1    class DumbWriter(NullWriter):

2        def __init__(self, file=None, maxcol=72):

3            self.file = file or sys.stdout

4            self.maxcol = maxcol

5            NullWriter.__init__(self)

6            self.reset()

As a first cut, let's write a snippet that generates an initializer, and inserts n parameters, tying each parameter to an assignment statement using tabstops. Let's say the macro will be invoked by typing n:ninit followed by a trigger character (remember that everything from the number through the trigger character will be consumed):

1

4    def __init__(self
[[%tabstop:, args]]
, [[%tabstop
:arg
]]
):

13

14            self.[[%tabstop
:arg
]] = [[%tabstop
:arg
]]

15

16            [[%tabstop:]]

def __init__(self
[[%tabstop:, args]]
, [[%tabstop
:arg
]]
):

self.[[%tabstop
:arg
]] = [[%tabstop
:arg
]]

[[%tabstop:]]
-->

OK, this is a bit of a jump in complexity from the previous snippet. I'll walk through it line-by-line and hopefully all will be clear by the end.

Lines 1-3 are a simple JS block that declares two variables, and uses yet another convenient helper that checks for a leading number, and removes it if present. We specify the delimiter, which by default is ":". In particular, you can't use ":" as a delimiter in Ruby, because Komodo will interpret the ":" and all characters that follow it as a symbol name, and then you won't get any expansion at all.

In line 4, we emit some Python code that starts the constructor definition. But notice that the line ends with the start of a second JS block. Why did I put it there, and not at the start of line 5? I want to keep the parameter list on one line. Recall that newlines are ignored inside JavaScript control blocks. So it's a good place to put one.

Things are more complex in lines 5-12, because I'm interleaving JavaScript control code, JavaScript emitted values, verbatim Python text, and, just to keep things more interesting, Komodo snippet tabstops. Hang on.

Lines 5-6 are pure control JavaScript, handling the case where there
are no parameters.

The leading white space in line 7 is ignored (because it's still in control JS). We then emit a tabstop that lets us specify whatever parameters we want, and then we're back into JS control mode.

The JS control mode continues in lines 8-10, where we bring out the useful gun of a JS for loop. If you've looped over database rows in PHP or RHTML, this is the same concept. Only you're doing it to generate code in Komodo. Mind should be getting blown now.

Line 10 continues with intermixing of Komodo Snippet tabstops with emitted JavaScript. By default we're calling the first parameter "arg1", the second "arg2", etc. You'll probably have better names in mind for your actual code. Because all instances of tabstop i are linked, when you change the name of "arg1", all other uses of it will change as well.

Lines 10-12 carry on with a range of control JS, which just ends the current for and else blocks.

Line 12 ends with some verbatim text, namely ): followed by a newline, which in this case we want. A good exercise at this point is to understand why this is the first newline to end up in the final snippet.

Line 13 starts another for loop, which we'll use to generate n initializers.

Line 14 should look a lot like line 10: again, we're intermixing verbatim Python text, tabstops, and emitted JS.

Line 15 ends the for loop.

Finally, line 16 uses an empty tabstop where we can add more code.

Using the Snippet

As you tab through the snippet, you get to set each parameter name, and all the boilerplate code is set automatically. Alpha 2 ships with a variety of different n-abbreviations, in particular nclass for JavaScript, nnew for Perl, ninit for PHP, Python, and Ruby. For example, you would invoke the Python ninit snippet for 3 arguments by typing the following, followed by a space (or any other abbreviation trigger character):

Why not fire up the alpha and try it out?

Another Helper: Getting the Class Name

This is just the start. Many times, your class will be subclassing another class, and you'll want to call its constructor before filling in the details. Using ko.snippets.getTextLine(scimoz=null, currentLine=i), it's not hard to find the superclass name and emit an optional tabstop that would call the superclass's constructor with the same parameters to the current one. Recall that the DumbWriter class in the Python example a few pages back was subclassed off the NullWriter. When you generate the __init__ snippet, it would be nice to grab the superclass name and set up a call to its constructor. In the interest of keeping this post from growing even longer, we'll leave coding this as an exercise. Keep in mind that Python supports multiple inheritance (for most cases, this might not be that useful a tabstop).

Soft Shortcuts: Skipping Through Inserted Code

This version of Komodo also lets you generate "soft" characters in snippets with the [[%soft:text]] shortcut (recall that soft characters are those highlighted characters you can type over, rather than right-arrow or mouse to get over, and are usually closers like "]", ")", or a close-quote). As an example, here's a useful Perl abbreviation for print:

1    print [[%tabstop1:LIST]][[%soft:;]][[%tabstop]]

The soft ; means we can type over it. And finally, by putting an empty tabstop at the end of the line, we can tab to get over it and hop over the ; when we're done typing the guts of the print expression. Common pet peeve solved.

Editing Snippets

With all these new features, editing snippets inside the Snippet Properties dialog has become less fun. So we've added an Edit Snippet menuitem when you right-click on a snippet. This loads the snippet contents into a regular editor view, with a suffix of .snippet, and a language name of Komodo Snippet. This is more of a pseudo-language: it colors anything inside EJS tags as JavaScript, but treats the rest as plain-text (we plan to color tabstops as well in the near future). One huge advantage is that the JavaScript is subject to regular JS syntax-checking. This finds the most common errors in Komodo Snippets (well, the ones we anticipate to be the most common, seeing how this author is the only one to have used this feature): missing or mismatched parentheses around JS conditions, and errant semi-colons inside emitted JS.

The only caveat is when you edit a snippet inside the editor, you lose any cursor position information that the snippet editor manages for you. So you might need to close the snippet in the editor, and revisit its properties to set items like a selection. This information isn't used when you have tabstops though, and we see less of a need to change the contents of simpler snippets in the editor. Also, if you leave the insertion-point inside a conditional block of code, it might not appear in the expanded snippet, and in a tabstop-free snippet, the cursor will appear at the start of the inserted text.

Blurring the Distinction: Should Anyone Care?

Some people might wonder why we didn't just fold snippets into macros. After all, the Emmet (Zen Coding) add-on was done as an extension, which is far more macro-like. But it's a matter of ease of use. Snippets have shortcuts and take care of the details of getting text into your document. Macros don't. But they both now have access to the full Komodo JavaScript API, including the behemoth that is Mozilla's XPCOM. Floor wax or topping, you choose. It doesn't really matter. Just please use the great power responsibly.

-->

Trackback URL for this post:

http://www.activestate.com/trackback/3576

Show more