Field

Drawing things in the Text Editor (or, how to syntax color Clojure in Field)

One of the languages that we're keeping a very close eye on is Clojure. For the uninitiated it's a Lisp-like language on the JVM with a great concurrency story. Drawing heavily on Lisp can mean only one thing — brackets, and lots of them. Here's a piece of code in Clojure:

(defn lazy-seq-fibo 
    ([] (concat [0 1] (lazy-seq-fibo 0 1))) 
    ([a b] 
        (let [n (+ a b)] 
            (lazy-seq 
                (cons n (lazy-seq-fibo b n))))))

Now for some people, myself included, those brackets are a real issue. We're used to a far wider variety of syntax markers, under the fingers and under the eye-balls. This code is hard, for me, to read and write. I deeply appreciate precisely what the brackets "give" Clojure, but it's just not the way that I think. The code above has been very carefully indented — trying to write it from scratch and too much of my brain is continually re-parsing the brackets I've written.

Really, you have three options:

  • keep hitting your head against Clojure until you learn to read the brackets or give up and do something else. This seems, after some effort, not only quite hard but not really in the category of hard things I like doing. I need to move quickly in front of the keyboard.
  • search for an IDE that gives some support to you. The Lisp community seems to be heavily into Emacs. I'm not. Also, quite a fraction of the Clojure mailing list seems taken up by Emacs integration issues.
  • canvas the language designers to remove the brackets and introduce some semi-colons and keywords. Seriously, people actually try this from time to time.

In fact, I find the brackets issue to be so hard, for me, that it's interesting. What can Field do here?

Simple — Field, as an environment that's self-extensible, lets you add graphics to the text editor using code written in that very text editor. So I can investigate just what kind of "decoration" over the text editor I need in order to help me read and write Clojure.

The magic code (written against the source tree / beta 12), written iteratively and experimentally in Field, looks like this:

from org.eclipse.swt.graphics import Color

paint= _self.python_plugin_.getTextDecorator()

@paint
def drawBrackets(g, inside):

        text = inside.getText()
        rectangles = []

        for n in range(len(text)):
                if (text[n]=='('):
                        rectangles.append(inside.getLocationAtOffset(n))
                if (text[n]==')'):
                        if (len(rectangles)==0): continue
                        start = rectangles.pop()
                        end = inside.getLocationAtOffset(n)
                        end.y+= inside.getLineHeight()

                        g.setBackground(Color(g.getDevice(), 255,255,240))
                        g.setAlpha(16)
                        g.fillRectangle(Math.min(start.x, end.x), Math.min(start.y, end.y), Math.max(start.x,   end.x)-Math.min(start.x, end.x), Math.max(start.y, end.y)-Math.min(start.y, end.y))
                        g.setForeground(Color(g.getDevice(), 255,255,240))
                        g.setAlpha(46)
                        g.drawRectangle(Math.min(start.x, end.x), Math.min(start.y, end.y), Math.max(start.x, end.x)-Math.min(start.x, end.x), Math.max(start.y, end.y)-Math.min(start.y, end.y))


Things to note:

  • _self.python_plugin_.getTextDecorator() get's you a callback that lets you paint over the text editor.
  • @paint — this is a pattern common in Field. Basically, you can execute this definition over and over again as you tune it (any exceptions will spill out into the output window below).
  • def drawBrackets(g, inside) — your functions will be called with two arguments — an SWT GC object to draw into and a StyledText that's the text editor that you are in.
  • insider.modelToView — this is the call inside JEditorPane that converts from a position in the text editor to a rectangle to draw into.

The rest of this code is just straight-up SWT GC canvas.

The result:

As I introduce my eyes and hands to Clojure, I'm continually tweaking what syntax overlay I'm using. A slightly extended version of the above works for me; and it guides me as I write and read Clojure.

Real programmers would surely write their live-codeing custom syntax overlay for Clojure in Clojure. I'm getting there...