9

When trying to theme certain modes, it's not possible to place the point on the themed item (e.g. to do C-u C-x =) and identify the face used.

My thought is to use the mouse to identify the face used, but all I can get is the frame x, y position (using (mouse-position))

From there I don't know how to get the face definition at those coords.

Any help is appreciated.

4 Answers 4

4

Here's one way. You need to bind the command to a mouse event. But if you use a mouse-button click then you will likely need to bind the event for the other part of the click (e.g. down) to ignore. This is just an example - you might not want to waste C-mouse1 on such a command.

The main function (command, actually) is describe-char. It describes the point at a given buffer position. Part of that description includes the text properties and overlays at that position, and if property face is one of these then you will see its value.

(defun foo (event)
  (interactive "e")
  (let* ((mouse-pos  (event-start event))
         (pos-pt     (posn-point mouse-pos)))
    (describe-char pos-pt)))

(global-set-key [(control down-mouse-1)] 'ignore)
(global-set-key [(control mouse-1)] 'foo)

This might be slightly better - the above might not work in some contexts:

(defun foo (event)
  (interactive "e")
  (let* ((mouse-pos  (event-start event))
         (mouse-buf  (window-buffer (posn-window mouse-pos)))
         (pos-pt     (posn-point mouse-pos)))
    (with-current-buffer mouse-buf (describe-char pos-pt))))

(Note too that C-x = is bound to what-cursor-position, which uses describe-char. So you were on the right track with C-u C-x =.)


As concerns Ido: Unlike Icomplete, which uses an overlay, Ido mode inserts text in the minibuffer. But the reason the above code does not work on that text is that Ido mode removes the text at the beginning of each command, using pre-command-hook. So when the command above is executed, the text showing the completions has already been removed.

The following code remedies that problem, just for Ido mode. It re-inserts the text showing the completions at the start, and then removes the completions at the end.

(defun foo (event)
  (interactive "e")
  (when (and (boundp 'ido-mode)  ido-mode) (ido-exhibit))
  (let* ((mouse-pos  (event-start event))
         (mouse-buf  (window-buffer (posn-window mouse-pos)))
         (pos-pt     (posn-point mouse-pos)))
    (with-current-buffer mouse-buf (describe-char pos-pt)))
  (when (and (boundp 'ido-mode)  ido-mode) (ido-tidy)))
10
  • Nice one @drew much appreciated
    – ocodo
    Oct 27, 2015 at 4:27
  • Oh, unfortunately when this is used in the minibuffer with ido-vertical (and others) the message : describe-char: No character follows specified position is returned by describe-char. I guess it's due to the minibuffer getting cleared at some point after the event. I assumed the 'ignore would stop this, but that's not happening. Any ideas?
    – ocodo
    Oct 27, 2015 at 8:14
  • Please give a step-by-step recipe. (It works for me: M-x some-text, C-mouse-1 on that text in the minibuffer.) You must of course click the mouse at a position that has text. If you click after the end of the text then you get the error you mentioned.
    – Drew
    Oct 27, 2015 at 15:26
  • Use ido-vertical-mode or ido-grid-mode with ido-mode, now Ctrl+click on the Ido selection text in the minibuffer. Error.
    – ocodo
    Oct 27, 2015 at 15:35
  • When you click on the list of completions for Ido or Icomplete mode, you are clicking past the text in the buffer (minibuffer). You are clicking on an overlay (only). If you click on an overlay that is on top of buffer text then there is no problem, but if you click past the buffer end (which is what you are doing) then you get the error message.
    – Drew
    Oct 27, 2015 at 16:37
2

If you can't put point at the correct place and use C-u C-x =, it might be due to the fact that the relevant element is displayed via an overlay's before/after-string, or because that element disappears/changes when you try to put point in it or when you C-u C-x =.

You can try to avoid those problems as follows:

  • use posn-at-x-y which will return a description of what is at that x/y coordinate. E.g. if it's a piece of text taken from an after/before-string, that string will be mentioned in there (as well as the position within that string), so you can lookup the face property applied to that string at that position.
  • you can run the code "in the background" rather than via a command. E.g. with run-with-timer you can run the code every second, printing the result into some kind of debug buffer of your choice.
1

You can use the packate faceup to create a human readable text representation of a text, with face information. For example:

(defun foo (arg)
  (if arg 1 2))

Run M-x faceup-vire-buffer RET, and the following is shown:

(«k:defun» «f:foo» («v:arg»)
  («k:if» arg 1 2))

The standard font-lock faces are represented using short names, like k for font-lock-keyword-face, whereas non-standard faces are presented using their full name.

(Faceup is a regression test system for highlighting packages, e.g. font-lock keywords, the text representation is stored as a reference file.)

EDIT:

To answer the question in the comment: "If I'm trying to debug the faces used in a minibuffer display is this still going to give me the info?"

Yes, it does. However, you must bind the function to a key since running it using M-x won't work when the minibuffer is in use. For example:

(global-set-key (kbd "<f6>") 'faceup-view-buffer)

If you by the "minibuffer" really meant the echo area -- that it, you would like to inspect the current message you will need a bit more. The following function will do this:

(defun my-faceup-view-current-message ()
  (interactive)
  (let ((msg (current-message)))
    (unless msg
      (error "Echo area is empty"))
    (with-temp-buffer
      (insert msg)
      (faceup-view-buffer))))

For example, the following:

(let ((s "My Message"))
  (add-text-properties 3 (length s) '(face font-lock-warning-face) s)
  (message s)
  (my-faceup-view-current-message))

Will show you:

My «w:Message»
4
  • If I'm trying to debug the faces used in a minibuffer display is this still going to give me the info?
    – ocodo
    Oct 27, 2015 at 8:03
  • @EmacsFodder Yes, see my updated answer. Oct 27, 2015 at 8:42
  • To be very specific, what drove me to ask the question was getting the faces from ido-vertical and ido-grid. They both flush the buffer before the mouse event (it seems).
    – ocodo
    Oct 27, 2015 at 8:48
  • While technically the direct answer to the question is, you cannot. This method is a little bit easier than scouring the emacslisp of whichever mode. Although that is simple as: C-s defface M-s o
    – ocodo
    Nov 6, 2015 at 23:58
1

An alternate solution might be to use a color picker and provide the color value to list-faces-for-color defined below (in case the color picker is a bit off, use the distance argument):

(defun list-faces-for-color (color &optional distance)
  "List faces which use COLOR as fg or bg color.

            Accept colors within DISTANCE which defaults to 0."
  (interactive (list (read-color "Color: ")
                     (and current-prefix-arg
                          (prefix-numeric-value current-prefix-arg))))
  (with-help-window (get-buffer-create (format " *%s*" this-command))
    (dolist (face (sort
                   (list-faces--for-color color distance)
                   (lambda (f1 f2)
                     (string< (symbol-name f1)
                              (symbol-name f2)))))
      (list-faces--print-face face)
      (terpri))))

(defun list-faces--print-face (face)
  "Print face and its parents if any."
  (with-current-buffer standard-output
    (let ((fchain (cdr (list-faces--inheritance-chain face :foreground)))
          (bchain (cdr (list-faces--inheritance-chain face :background))))
      (insert (propertize (format "%s" face) 'face face))
      (cond (fchain
             (dolist (face fchain)
               (insert " > " (propertize (format "%s" face) 'face face))))
            (bchain
             (dolist (face bchain)
               (insert " > " (propertize (format "%s" face) 'face face))))))))

(defun list-faces--inheritance-chain (face attr)
  "Return inheritence change for face and attr."
  (let ((g (face-attribute face attr)))
    (if (and (stringp g)
             (not (string= "unspecified" g)))
        (list face)
      (let ((inherit (face-attribute face :inherit)))
        (when inherit
          (if (facep inherit)
              (cons face
                    (list-faces--inheritance-chain inherit attr))
            (if (consp inherit)
                (cl-dolist (face inherit)
                  (let ((res nil))
                    (when (and (facep face)
                               (setq res (list-faces--inheritance-chain face attr)))
                      (cl-return res)))))))))))


(defun list-faces--attribute (face attr)
  "Get face attribute of face as defined or inherited."
  (let* ((chain (list-faces--inheritance-chain face attr)))
    (cl-dolist (f (nreverse chain))
      (let ((g (face-attribute f attr)))
        (when (and (stringp g)
                   (not (string= "unspecified" g)))
          (cl-return g))))))



(defun list-faces--for-color (color &optional distance)
  "Return all faces with COLOR as fg or bg withing DISTANCE."
  (let ((faces ())
        (distance (or distance 0)))
    (mapatoms (lambda (atom)
                (when (facep atom)
                  (let ((fg (list-faces--attribute atom :foreground))
                        (bg (list-faces--attribute atom  :background)))
                    (when (or (and fg
                                   (<= (color-distance
                                        fg
                                        color)
                                       distance))
                              (and bg
                                   (<= (color-distance
                                        bg
                                        color)
                                       distance)))
                      (push atom faces))))))
    (delete-dups faces)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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