Search code examples
emacssyntax-highlightingcontext-free-grammarfont-lock

Context-sensitive font-locking in emacs


Basically, I'm trying to syntax highlight the following piece of coffeescript code the way I want it. Explanation of the syntax of coffeescript functions can be found here.

nameHere = (tstamp, moo, boo) ->
    ...

The names tstamp, moo and boo should be colored pink (and nothing else, not the commas and not the brackets) because they are parameters to a lambda function.

highOrderFun ((x) -> x * x) someList

Here it is the first x that is the parameter. Parameters can have default arguments:

class Foo
    meth: (msg = "Hello", bar = "foo") ->
        ....

Default arguments can be variables themselves:

defColor = "red"
print = (msg, color = defColor) ->
    ...

So msg and color above should be highlighted, but not defColor. An even trickier case is functions with default arguments that themselves are functions. I think that is to hard for emacs' font-lock to highlight correctly, but I'm including it anyway:

funTakingFuns = (f1 = ((a, b) -> a*b), f2 = ((c, d) -> c/d)) ->
    ...

This appears to be pretty complicated to achieve in emacs because you want the highlighting to be context sensitive. I've read up on the documentation on font-lock but haven't been able to figure it out.

I'd be grateful if someone could show me what to set font-lock-defaults to make it syntax highlight the way I want it.

Update Showing more coffeescript syntax examples.


Solution

  • font-lock-keywords allows function values in the MATCHER field:

    where MATCHER can be either the regexp to search for, or the function name to call to make the search (called with one argument, the limit of the search; it should return non-nil, move point, and set match-data appropriately if it succeeds; like re-search-forward would).

    So we need to write a function that would search for the next function argument in the buffer.

    Something like this:

        (defun coffee-match-next-argument (limit)
          (let ((start (point)))
            ;; Look for the arrow.
            (when (re-search-forward ") *->" limit t)
              ;; Save the position of the closing paren.
              (let ((stop (point)))
                (goto-char (match-beginning 0))
                ;; Go to the opening paren.
                (goto-char (nth 1 (syntax-ppss)))
                ;; If we're before our initial position, go forward.
                ;; We don't want to find the same symbols again.
                (when (> start (point))
                  (goto-char start))
                ;; Look for the next symbol until the arrow.
                (or (re-search-forward "\\((\\|,\\) *\\(\\(\\sw\\|_\\)+\\)" stop 'mv)
                    (coffee-match-next-argument limit))))))
    

    And the setup, to use with existing coffee-mode:

    (font-lock-add-keywords
     'coffee-mode
     '((coffee-match-next-argument 2 font-lock-variable-name-face)))
    

    You can also use this in font-lock-defaults, of course.

    This will likely use some other color than pink, but that's easy to change.