Subject: bug#74362: 31.0.50; Add completions for font names

From: Stefan Monnier <[email-masked]>
Date: Fri, 15 Nov 2024 12:33:14 -0500

>> Comments/objections?
>
> I suggest to try this on a system that has a lot of fonts installed,

I don't have such a thing.  My `font-family-list` has only 1k entries.
But I tried it with `corfu-mode` on a slow build (debug +
enable-checking) and I did not see any lag even though it presumably
rebuilds the list after each character I type.

This said, clearly, there is room for improvement via caching, so the
new patch below does that.

> to make sure this doesn't slow down customize-face too much.

It should make no difference in general: the list of completions is
computed only when a completion is actually requested on that field.

> Also, the doc string of customize-face should be amended to say that
> it offers completion on fonts.

Hmm... the docstring currently doesn't describe the UI at all (and
I can't think of a reason why it should).  Similarly the manual doesn't
seem to describe any part of the UI of `customize-face`.
Apparently, we presume the UI is self-explanatory.  If we want to fix
that, I think it's beyond the scope of this patch submission.

> Btw, how does one invoke completion on the face attributes which are
> supposed to provide one, such as weight or colors?  M-C-i doesn't seem
> to do it for me, or what am I missing?

M-TAB worked for me on the "Foregound" color name field.
For weights, I see a menu rather than a text field so there doesn't seem
to be a "completion" option.

        Stefan
diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el
index 8eba4270bcb..0ef52948b39 100644
--- a/lisp/cus-edit.el
+++ b/lisp/cus-edit.el
@@ -3425,6 +3425,25 @@ custom-visibility
 
 ;;; The `custom-face-edit' Widget.
 
+(defalias 'custom-face--font-completion
+  (let ((lastlist nil)
+        (lasttime nil)
+        (lastframe nil))
+    (completion-table-case-fold
+     (completion-table-dynamic
+      (lambda (_string)
+        ;; Cache the list for a max of 60s.
+        (let ((time (float-time)))
+         (if (and lastlist (eq (selected-frame) lastframe)
+                  (> 60 (- time lasttime)))
+             lastlist
+           (message "last list time: %s" (if lasttime (- time lasttime)))
+           (setq lasttime time)
+           (setq lastframe (selected-frame))
+           (setq lastlist
+                 (nconc (mapcar #'car face-font-family-alternatives)
+                        (font-family-list))))))))))
+
 (define-widget 'custom-face-edit 'checklist
   "Widget for editing face attributes.
 The following properties have special meanings for this widget:
diff --git a/lisp/cus-face.el b/lisp/cus-face.el
index d0a1a66e29f..9a0ab4b47c7 100644
--- a/lisp/cus-face.el
+++ b/lisp/cus-face.el
@@ -48,6 +48,7 @@ custom-declare-face
 (defconst custom-face-attributes
   `((:family
      (string :tag "Font Family"
+	     :completions custom-face--font-completion
 	     :help-echo "Font family or fontset alias name."))
 
     (:foundry