Context-sensitive font-locking in emacs
Asked Answered
G

2

5

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.

Gothicize answered 3/2, 2013 at 0:36 Comment(2)
I'm trying to make a dirty solution, so far I can make it work for when you open a file. For now, what is the major mode you are using with it?Braxton
Another question that I have is: do you only want this in cases where a = is used? What are other options? Also: Do you want it just for 3 arguments (for that the answer is below) or should it also be possible for more arguments?Braxton
G
9

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.

Georganngeorge answered 3/2, 2013 at 18:9 Comment(8)
Why not simply define a new font-lock-pink?Braxton
Yes, sure. The author can define a coffee-pink using whatever variant of pink he wants (Emacs has ~20 named colors with the word "pink" in them), or use one of the existing faces. Like I said, that's the easy part.Georganngeorge
Terrific! Is there also a way to fix so that the equal sign and value is not colorized when using default arguments? Also the function works somewhat haphazardly. When I open a coffeescript file, the styling is applied to the first function and the one below the point but no other functions are touched. It is not until I edit them that font-lock "realizes" that it should colorize them.Gothicize
Sure. First, for some reason, coffee-mode doesn't modify the syntax table, so it thinks "=" is a symbol constituent. I think we only need to consider underscores as such, so we replace \\s_ with _.Georganngeorge
Second, that's a bug in the matching function, it stopped after the first arguments list it encountered. See the new edits.Georganngeorge
Any way to get it to not highlight after the equal sign too? In foo = (bar = boo) -> it hightlights the words bar and boo, but only bar is a function parameter.Gothicize
Note that while the funTakingFuns example looks fine with my current solution, it's a lucky coincidence, because coffee-mode already highlight targets of all assignments, with the same face that I chose. The match function above only finds arguments of the innermost functions, due to the "don't go back" principle. This is also solvable, but it would require a smarter traversal algorithm.Georganngeorge
Great answer, I went a long time without knowing this was possible in font-lock.Uvulitis
B
2

This is more kind of a hack, it's far from optimal (as I am not familiar at all with coffeescript), but perhaps with a little tweaking yourself, you can get this done.

All the ingredients are there.

The triggering of the commands/functions are based on the assumption that you use coffee-mode. If you do not, this is not a big trouble, you'll just have to hook these things differently.

Put the following line in your .emacs:

(eval-after-load 'coffee '(load "/PATH/custom-coffee-font-lock.el"))

You can just save the below text as a file, and it will:

(1) Font lock when you trigger coffee-mode

(2) Font lock current line when you type the ">" as part of "->"

(3) Allow to font-lock the buffer by running M-x coffee-init-font-lock

;;;; custom-coffee-font-lock

;; Firstly, create a new font for this. 

(make-face 'font-lock-coffeescript-face)
(set-face-foreground 'font-lock-coffeescript-face "pink")


;; Next, one function that should be ran after a file is identified as 
;; a coffeescript file. It will do the font-locking you want on 
;; the whole buffer. It is also possible to run it manually. 


(defun coffee-init-font-lock ()
  (interactive)
  (save-excursion 
    (goto-char 1)
    (while (search-forward-regexp "=.+->" nil t)
      (search-backward-regexp "(")
      (forward-char 1)
      (add-text-properties 
       (point) (- (search-forward-regexp "," nil nil) 1) 
       '(font-lock-face font-lock-coffeescript-face))
      (add-text-properties 
       (point)  (- (search-forward-regexp "," nil nil) 1) 
       '(font-lock-face font-lock-coffeescript-face))
      (add-text-properties 
       (point)  (- (search-forward-regexp ")" nil nil) 1) 
       '(font-lock-face font-lock-coffeescript-face))
      (move-end-of-line 1)))
  )

  ;; This actually runs that function.
  (coffee-init-font-lock)


;; This advice will be ran everytime you write something. It will check 
;; whether "->" is before it, so when you type the final ">", it will
;; do the font locking for the current line (it also checks for your mode).

(defadvice self-insert-command (after coffee-font-lock activate)
  (when (and (looking-back "->") (eq major-mode 'coffee-mode))
    (save-excursion 
        (search-backward-regexp "(")
        (forward-char 1)
        (add-text-properties 
         (point)  (- (search-forward-regexp "," nil nil) 1) 
         '(font-lock-face font-lock-coffeescript-face))
        (add-text-properties 
         (point)  (- (search-forward-regexp "," nil nil) 1) 
         '(font-lock-face font-lock-coffeescript-face))
        (add-text-properties 
         (point)  (- (search-forward-regexp ")" nil nil) 1) 
         '(font-lock-face font-lock-coffeescript-face))))
  )

(provide 'custom-coffee-font-lock)
;;; custom-coffee-font-lock.el

If you have any requests, let me know. Like I said, I do not use CoffeeScript, so this might throw huge errors your way. At the very least it should help with some basic ideas.

Result:

enter image description here

Braxton answered 3/2, 2013 at 16:20 Comment(3)
Nice attempt, but font-lock provides a better way to do this.Georganngeorge
I would indeed say there is. I myself had looked for this functionality before, but could not find it. You did provide the answer.Braxton
Indeed, the doc for font-lock-keywords is rather dense. I didn't see it the first few times, too.Georganngeorge

© 2022 - 2024 — McMap. All rights reserved.