How does a progn form in font-lock-keywords work?

242 views Asked by At

Following code will visually replace "hello world" with "HW" by passing a progn form to font lock keywords.

(font-lock-add-keywords
 nil '(("\\(hello world\\)"
        (0 (progn (put-text-property (match-beginning 1) (match-end 1)
                                     'display "HW")
                  nil)))))

I've look into C-h v font-lock-keywords to see if this is a documented feature of font lock. The hello world element seemed to be of this form:

(MATCHER HIGHLIGHT ...)

which would mean that (0 ...) is HIGHLIGHT and the doc says

HIGHLIGHT should be either MATCH-HIGHLIGHT or MATCH-ANCHORED.

and

MATCH-HIGHLIGHT should be of the form:

 (SUBEXP FACENAME [OVERRIDE [LAXMATCH]])

So I guessed 0 was SUBEXP and (progn ...) was FACENAME. But if (progn ..) were a valid FACENAME, the following code would work but it doesn't work.

;;  (MATCHER . FACENAME)
(font-lock-add-keywords
 nil '(("goodbye lenin"
        . (progn (put-text-property (match-beginning 1) (match-end 1)
                                    'display "GL")
                 nil))))

That brings me to the question of how the first code works and whether it is relying on an undocumented feature.


Update:

Side note: simpler way of visual replacement without font lock errors

(font-lock-add-keywords
 nil '(("my llama"
        (0 (progn (put-text-property (match-beginning 0) (match-end 0)
                                     'display "ML")
                  nil)))))
2

There are 2 answers

5
jlahd On BEST ANSWER

It does work - but your MATCHER is not correct - the result of the match is not stored. This, for example does not work:

(font-lock-add-keywords
 nil '(("goodbye lenin"
        (0 (progn (put-text-property (match-beginning 1) (match-end 1)
                                    'display "GL")
                  nil)))))

while this does:

(font-lock-add-keywords
 nil '(("\\(goodbye lenin\\)"
        . (progn (put-text-property (match-beginning 1) (match-end 1)
                                    'display "GL")
                 nil))))

The documentation says: "FACENAME is an expression whose value is the face name to use. Instead of a face, FACENAME can evaluate to a property list of the form (face FACE PROP1 VAL1 PROP2 VAL2 ...) in which case all the listed text-properties will be set rather than just FACE."

Here, the FACENAME expression (progn) evaluates to nil, so no properties or faces are being set - the only effect that caused by put-text-property.

2
Stefan On

One problem with ("goodbye lenin" . (progn (put-text-property (match-beginning 1) (match-end 1) 'display "GL") nil)) is that it is just another way to write: ("goodbye lenin" progn (put-text-property (match-beginning 1) (match-end 1) 'display "GL") nil), and these equivalences can lead to ambiguities, which is why in this case you get errors.

So the form (MATCHER . HIGHLIGHT), (match . FACENAME) and such should only be used when HIGHLIGHT and FACENAME are not themselves lists.