Subject: bug#4835: 23.1; Improper `Invalid face reference' messages. Performance degraded.

From: "Drew Adams" <[email-masked]>
Date: Sat, 31 Oct 2009 00:41:12 -0700

> > This sure seems like a bug to me. If not, please tell me what the
> > problem is.
> 
> The problem is that font-lock-keywords's docstring says:
> 
>    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
> 
> notice that it says "function name" rather than just 
> "function".  So it can't just be a lambda expression, it has
> to be a symbol.

Ah, thank you. I need to put my reading glasses on!

1. However, the Elisp manual, node `Search-based Fontification' speaks of just
`function'. It does not say that it must be a symbol. (Also, the doc string
should be clarified, since "function name" is usually a string, the function
symbol's `symbol-name'.)

  `FUNCTION'
     Find text by calling FUNCTION, and highlight the matches it finds
     using `font-lock-keyword-face'.

     When FUNCTION is called, it receives one argument, the limit of
     the search; it should begin searching at point, and not search
     beyond the limit.  It should return non-`nil' if it succeeds, and
     set the match data to describe the match that was found.
     Returning `nil' indicates failure of the search.

     Fontification will call FUNCTION repeatedly with the same limit,
     and with point where the previous invocation left it, until
     FUNCTION fails.  On failure, FUNCTION need not reset point in any
     particular way.

And further references to it in this node also refer to it only as "a function".
Nowhere does it say that it should be a symbol. So if it must be a symbol, then
this is a doc bug.

2. The code I had seems nevertheless to "work", in the sense that it does what I
expect (highlights the column). Except that it logs those messages and the
performance is terrible. I assume that it is the message logging that degrades
the performance (brings Emacs to its knees).

Is it indeed a bug that binding `message-log-max' to nil does not suppress the
logging here? Or is it simply a doc bug that this limitation of
`message-log-max' is not mentioned?

3. I tried using a symbol, as you and the doc string suggested, but I still get
the same behavior. I used the same code as before, but with `column-marker-find'
redefined as follows, so that it now defines a named function and returns the
new function symbol:

(defun column-marker-find (col)
  (let ((fn-symb  (intern (format "column-marker-move-to-%d" col))))
    (fset
     `,fn-symb
     `(lambda (end)
        (let ((start (point)))
          (when (> end (point-max)) (setq end (point-max)))
          (unless (< (current-column) ,col) (forward-line 1))
          (when (< (current-column) ,col) (move-to-column ,col))
          (while (and (< (current-column) ,col) (< (point) end)
                      (= 0 (+ (forward-line 1) (current-column))))
            (move-to-column ,col))
          (if (and (= ,col (current-column))
                   (<= (point) end) (> (point) start))
              (progn (set-match-data (list (1- (point)) (point)))
                     t)
            (goto-char start)
            nil))))
    fn-symb))

Now I see only these 4 messages repeated over and over, instead of the messages
citing the atoms in the lambda form that I used previously:

Invalid face reference: t
Invalid face reference: prepend
Invalid face reference: 0
Invalid face reference: column-marker-move-to-36

`column-marker-move-to-36' is the symbol created when I did `M-x
column-marker-1'. Its `symbol-function' is this (same as above, with 36 for the
column):

(lambda (end)
  (let ((start (point)))
    (when (> end (point-max))
      (setq end (point-max)))
    (unless (< (current-column) 36)
      (forward-line 1))
    (when (< (current-column) 36)
      (move-to-column 36))
    (while (and (< (current-column) 36)
                (< (point) end)
                (= 0 (+ (forward-line 1)
                        (current-column))))
      (move-to-column 36))
    (if (and (= 36 (current-column))
             (<= (point) end)
             (> (point) start))
        (progn (set-match-data
                (list (1- (point)) (point)))
               t)
      (goto-char start)
      nil)))

The value of variable `column-marker-1' is now this list, which should be OK,
IIUC:

((column-marker-move-to-36
  (0 column-marker-1 prepend t)))

And that is therefore also the relevant portion of `font-lock-keywords'.

What am I missing?

4. Also, the full value of `font-lock-keywords' does in fact have lambda forms
in some of its keyword patterns, but they do not come from this code. They are
present even without it. This, for example:

((lambda (bound)
   (catch 'found
     (while
         (re-search-forward
"\\(\\\\\\\\\\)\\(?:\\(\\\\\\\\\\)\\|\\((\\(?:\\?:\\)?\\|[|)]\\)\\)" bound t)
       (unless (match-beginning 2)
         (let ((face (get-text-property (1- (point)) 'face)))
           (when (or (and (listp face)
                          (memq 'font-lock-string-face face))
                     (eq 'font-lock-string-face face))
             (throw 'found t)))))))
 (1 'font-lock-regexp-grouping-backslash prepend)
 (3 'font-lock-regexp-grouping-construct prepend))

Dunno where that comes from - perhaps from the font-lock machinery itself. In
any case, that lambda form does not seem to be causing any `Invalid face
reference' messages to be logged.

So I still don't understand, and I still haven't found the right way to code
this, so that I don't get the error messages. Please advise.