I want to create font locking for string like:

interface Foo extends B, C, D, E {};

And I can't understand why my regexp rule doesn't work for font locking:

(defvar my-font-lock-keywords
    ;; Some code is omitted for brevity
    ;; ...

    ;; Just for example
    (,(rx "interface Foo extends B"
          (1+ ?,
              (syntax whitespace)
              (group (or "A" "B" "C" "D"))))
     1 font-lock-type-face)

    ;; Some code is omitted for brevity
    ;; ...

This test fails:

(ert-deftest my-mode-syntax-table/fontify-extends/3 ()
  :tags '(fontification syntax-table)
   "interface Foo extends B, C, D, E {}; "
   (should (eq (test-face-at 23) 'font-lock-type-face))))

As I can see there is no problem with regexp:

ELISP> (require 'rx)
ELISP> (string-match-p
        (rx (1+ ?, (syntax whitespace) (group (or "A" "B" "C" "D"))))
        "B, C, D, E")
1 (#o1, #x1, ?\C-a)

Of course I'm managed to succeed for other cases e.g.:

interface Foo {};

My real code is more complicated. I tried to give a simple example to show my problem.

How to correctly make font-lock regexp for the comma-separated lists?

Update: I'm managed to debug regexp (thanks to @phils) via M-x re-builder so as I said before there is no problem with regexp Imgur

  • Let me know if I should provide a more detailed example or something else – serghei Sep 5 '17 at 7:29
  • 1
    I think font-lock-mode is deactivated in temporary buffers. – politza Sep 7 '17 at 8:07
  • 1
    To track down font-lock related problems, you can use github.com/Lindydancer/font-lock-studio -- it's an interactive debugger that lets you single step each part of a font-lock rule. Also, I'm glad that you are writing tests for your rules (very few people do). However, use test-face-at is really hard to get right. You can try github.com/Lindydancer/faceup intead, it uses a custom markup language and it scales very well (I've used it for regression testing of files thousands of lines long). – Lindydancer Sep 7 '17 at 11:29

Your code works straight out of the box, in a way. When you apply it, the letter "D" is highlighted.

The problem with the code, as it is written, is that only one match is highlighted. In your case, it matches a number of ,<LETTTER>, but when the face is applied, the match data points to the last in the list. (The reason E isn't highlighted is that your regexp only match A, B, C, and D.

A better approach is to use anchored keywords. Think of them as a search within a search. For example:

(defvar my-font-lock-keywords
  '(("\\_<interface .* extends\\_>"
     ("[, \t]*\\(\\<[a-zA-Z_]+\\_>\\)"
      nil   ;; Pre-match form
      nil   ;; Post-match form
      (1 font-lock-type-face)))))

First, font-lock search for interface NAME extends. (If you like, you can extend the rule to highlight the name and the keywords.) Once this is found, it repeatedly search for identifiers (skipping commas and whitespace) and highlight each one using font-lock-type-face.

Normally, the sub-search is performed on the current line, but this can be changed by making the pre-match form return an integer representing the end.

Update: You can use this when defining a new major mode, for example:

(define-derived-mode my-mode fundamental-mode "MY"
  "My mode."
  (setq font-lock-defaults '(my-font-lock-keywords nil)))

Or, if you want to add the rules to an existing mode (in this case the fictitious whatever-mode), you can do something like:

(defun my-whatever-mode-hook ()
   '(("\\_<interface .* extends\\_>"
      ("[, \t]*\\(\\<[a-zA-Z_]+\\_>\\)"
       (1 font-lock-type-face))))))
(add-hook 'whatever-mode-hook #'my-whatever-mode-hook)
  • Looks really interesting. I definitely should try this. Could you please amend your example by adding a way of using my-font-lock-keywords in some custom mode? The only thing I don't understand yet is where I have to apply (or pass) my-font-lock-keywords. – serghei Sep 7 '17 at 17:18
  • @klay, OK done! Good luck with your project! – Lindydancer Sep 8 '17 at 6:58
"interface Foo extends B "

You have a space after B and therefore cannot match B,

Handy hint: Use M-x re-builder to test your regexps. You can switch it to rx syntax via C-c TAB (although it actually uses rx-to-string with the consequence that the implied sequence in your form is lost, so you would need to make that explicit with '(sequence "interface Foo extends B " ....) or equivalent.

  • Actually space doesn't affect. With space or without space font-locking doesn't work. And as I said before I can test rx and see that pattern was matched. – serghei Sep 5 '17 at 5:11
  • Just edited my example – serghei Sep 5 '17 at 5:13

I'm managed to succeed with fontification multiple inheritance list by using following approach:

(defun syntax-propertize-inheritance (start end)
  "Fontify all items in a inheritance list between START and END."
      (goto-char start)
      (while (re-search-forward rx-to-match-each-inherit-item end t)
        (put-text-property (match-beginning 1) (match-end 1)
                           'face 'font-lock-type-face)

Where rx-to-match-each-inherit-item is my regexp to match item in multiple inheritance list.

(defun custom-syntax-propertize-function (start end)
  "Apply propertize rules from START to END."
  (let ((case-fold-search))
    (goto-char start)
      ;; Propertize multiple inheritance causes.
       (0 (ignore (syntax-propertize-inheritance
                   (match-beginning 0)
                   (match-end 0))))))
     start end)))

Where rx-to-match-full-inherit-list is my regexp to match full multiple inheritance list.

;; Used in mode definition
(setq-local syntax-propertize-function #'custom-syntax-propertize-function)
  • I would not recommend using this method. 1) If your major mode already use syntax-propertize-function, this code will replace the original function with this. 2) this will always run before normal font-lock keywords, which might not work as expected. 3) there are no guarantees that syntax-propertize-function is called on the same regions and in the same order as the font-lock keywords 4) you can do the same using a normal font-lock anchored rule (see my answer). – Lindydancer Sep 7 '17 at 12:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.