1

Question: Why does face-attribute interfer with isearch filter predicates?

The following code is part of my answer to the question about searching for bold text.

If I set the variable isearch-face-bold-p-fun to the predicate function isearch-face-p searching the Emacs man page for daemon with isearch-bold-forward works like expected. Isearch stops at bold occurences of the word daemon.

If I set isearch-face-bold-p-fun to isearch-face-bold-p instead and search the Emacs man page for daemon no matches are found. (There are even stranger effects in other constructed examples.)

The main difference between isearch-face-p and isearch-face-bold-p is the call of face-bold-p which works with face-attribute. That motivates my question.

(defun isearch-face-p (start end)
  "Return non-nil if face at start-position is bold."
  (save-excursion
    (goto-char start)
    (face-at-point t t)))

(defun isearch-face-bold-p (start end)
  "Return non-nil if face at start-position is bold."
  (save-excursion
    (goto-char start)
    (cl-some (lambda (face) (and (facep face) (face-bold-p face))) (face-at-point t t))))

(defvar isearch-face-bold-p-fun #'isearch-face-bold-p
  "Isearch filter for bold text.
The value should be a function suitable for `isearch-filter-predicate'.")

(defun isearch-bold-forward-end-fun ()
  "Uninstall isearch filter `isearch-face-bold-p'."
  (remove-function (local 'isearch-filter-predicate) isearch-face-bold-p-fun)
  (remove-hook 'isearch-mode-end-hook #'isearch-bold-forward-end-fun))


(defun isearch-bold-forward ()
  "Like `isearch-forward' only accepting bold matches."
  (interactive)
  (add-function :after-while (local 'isearch-filter-predicate) isearch-face-bold-p-fun)
  (add-hook 'isearch-mode-end-hook #'isearch-bold-forward-end-fun)
  (call-interactively #'isearch-forward))

Tested with emacs -Q Emacs version GNU Emacs 26.2 (build 2, x86_64-pc-linux-gnu, GTK+ Version 3.22.30) of 2019-04-12.


EDIT:

Drew solved why the search in the man buffer for Emacs failed. Pityngly that is not the only case that fails. In the following code I adapt Drew's proposal to add the non-nil INHERIT argument to face-bold-p.

(defun isearch-face-p (start end)
  "Return non-nil if face at start-position is bold."
  (save-excursion
    (goto-char start)
    (face-at-point nil t)))

(defun isearch-face-bold-p (start end)
  "Return non-nil if face at start-position is bold."
  (save-excursion
    (goto-char start)
    (cl-some (lambda (face) (and (facep face) (face-bold-p face nil t))))))

(defcustom isearch-face-bold-p-fun #'isearch-face-bold-p
  "Isearch filter for bold text.
The value should be a function suitable for `isearch-filter-predicate'."
  :group 'isearch
  :type 'function)

(defun isearch-bold-forward-end-fun ()
  "Uninstall isearch filter `isearch-face-bold-p'."
  (remove-function (local 'isearch-filter-predicate) isearch-face-bold-p-fun)
  (remove-hook 'isearch-mode-end-hook #'isearch-bold-forward-end-fun))

(defun isearch-bold-forward ()
  "Like `isearch-forward' only accepting bold matches."
  (interactive)
  (add-function :after-while (local 'isearch-filter-predicate) isearch-face-bold-p-fun)
  (add-hook 'isearch-mode-end-hook #'isearch-bold-forward-end-fun)
  (call-interactively #'isearch-forward))

New Test: Isearch for bold with M-x isearch-bold-forward starting at the beginning of the following org snippet.

Point jumps from the b of the first occurrence of bold to the o of the second (non-bold) occurrence.

Some of the text here is **bold**.
Some intermediate text.
We do not need to bold everything.
Here is some more **bold** text.

Local Variables:
mode: org
End:
2

The bold text you see in your test does not use face bold. It uses face Man-overstrike. And (face-bold-p 'Man-overstrike) returns nil.

(face-bold-p 'Man-overstrike) evals this: (face-attribute 'Man-overstrike :weight frame inherit), which returns unspecified. Then (memq 'unspecified '(semi-bold bold extra-bold ultra-bold)) returns nil.

You need to pass a non-nil value for arg INHERIT to face-bold-p:

(face-bold-p 'Man-overstrike nil t) returns this: (bold extra-bold ultra-bold) (which is non-nil - the predicate is satisfied).

  • Thanks Drew, that was definitive one part of the problem. But, there is pityingly more. See my EDIT in the question. – Tobias Jul 16 at 22:11
0

What about abstracting it that way:

     (defun ar-next-fontset-attribut-atpt (attribut &optional frame inherit)
  "Takes the desired attribut.

Think of ‘bold’, ‘slant’, ‘underlined’ etc.--"
  (while (and (not (eobp))
          (progn (skip-chars-forward " \t\r\n\f")
             (not (ignore-errors (eq attribut (ignore-errors (face-attribute (face-at-point) :weight frame inherit))))))
          (goto-char (or (next-property-change (point))
                 (point-max)))))
  (push-mark)
  (unless (eobp) (goto-char (next-property-change (point)))))

If needed, replace "bold" by "slant" or whatever attribut searched for:

(defun ar-next-bold-atpt ()
  (interactive)
  (ar-next-fontset-attribut-atpt 'bold))

A test:

(ert-deftest ar-next-bold-atpt-test()
  (with-temp-buffer
    ;; (switch-to-buffer (current-buffer))
    (insert "Some of the text here is **bold**.
Some intermediate text.
We do not need to bold everything.
Here is some more **bold** text.

Local Variables:
mode: org
End:")
    (org-mode)
    (font-lock-ensure)
    (goto-char (point-min))
    (ar-next-bold-atpt)
    (should (eq (char-after) ?.))
    (should (eq (char-before) ?*))
    (ar-next-bold-atpt)
    (should (eq (char-after) ?\s))
    (should (eq (char-before) ?*))))

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.