Subject: Re: [External] : Re: bug#39143: 26.3; `widget-complete' in `customize-option'

From: Mauro Aranda <[email-masked]>
Date: Fri, 28 Feb 2025 10:28:13 -0300

Drew Adams <[email-masked]> writes:

 > Hi Mauro,
 >
 > Thanks for looking into this.  I'd completely forgotten about it.
 >
 > I do think that improving completion for Customize thingies could
 > encourage more use of the Customize UI.
 >
 > I think everything you said and suggested sounds good.  I agree about
 > the double-edged sword of the added complexity - not just for the
 > coding but also for users.
 >
 > Parameter-specific completion can really help, but with different
 > kinds of parameters some users might get confused by the different
 > kinds of completion for them.
 >
 > Overall, I think it would be a definite improvement.  And with some
 > completion "frameworks" (and perhaps even with some ~recent vanilla
 > Emacs improvements?) users could maybe get interactive help on the
 > completion possibilities.
 >
 > Anything you find you can do to improve this will be welcome.  Use
 > your own judgment; it'll be fine, I'm sure.

Thanks Drew.

The attached patch is alternative 1: Provide better completion for
Parameter and provide completion for Value when we know the
alternatives.  I think it is an improvement, and it doesn't make the UI
more complex, which alternative 2 does.
From 49eb704f5426d72ea2cf45683108e6a713707f05 Mon Sep 17 00:00:00 2001
From: Mauro Aranda <[email-masked]>
Date: Fri, 28 Feb 2025 10:15:52 -0300
Subject: [PATCH] Provide better completion for customizing frame parameters

* lisp/frame.el (frame--special-parameters): New const.
(frame--complete-parameter-value): New function.
(initial-frame-alist, minibuffer-frame-alist): Use them in
:type.  (Bug#39143)
lisp/cus-start.el (default-frame-alist): Use them here as well.
---
 lisp/cus-start.el |  6 ++--
 lisp/frame.el     | 80 +++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 78 insertions(+), 8 deletions(-)

diff --git a/lisp/cus-start.el b/lisp/cus-start.el
index c6e4da6790e..34919bb367c 100644
--- a/lisp/cus-start.el
+++ b/lisp/cus-start.el
@@ -319,8 +319,10 @@ minibuffer-prompt-properties--setter
 	     ;; frame.c
 	     (default-frame-alist frames
 	       (repeat (cons :format "%v"
-			     (symbol :tag "Parameter")
-			     (sexp :tag "Value"))))
+			     (symbol :tag "Parameter"
+                                     :completions ,frame--special-parameters)
+			     (sexp :tag "Value"
+                                   :complete frame--complete-parameter-value))))
 	     (mouse-highlight mouse (choice (const :tag "disabled" nil)
 					    (const :tag "always shown" t)
 					    (other :tag "hidden by keypress" 1))
diff --git a/lisp/frame.el b/lisp/frame.el
index e66270130d2..52a33683f94 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -60,6 +60,70 @@ display-format-alist
 name string.  The cdr is a symbol giving the window-system that
 handles the corresponding kind of display.")
 
+(defconst frame--special-parameters
+  '("alpha" "alpha-background" "auto-hide-function" "auto-lower"
+    "auto-raise" "background-color" "background-mode" "border-color"
+    "border-width" "bottom-divider-width" "bottom-visible" "buffer-list"
+    "buffer-predicate" "child-frame-border-width" "cursor-color"
+    "cursor-type" "delete-before" "display" "display-type"
+    "drag-internal-border" "drag-with-header-line" "drag-with-mode-line"
+    "drag-with-tab-line" "explicit-name" "fit-frame-to-buffer-margins"
+    "fit-frame-to-buffer-sizes" "font" "font-backend" "foreground-color"
+    "fullscreen" "fullscreen-restore" "height" "horizontal-scroll-bars"
+    "icon-left" "icon-name" "icon-top" "icon-type"
+    "inhibit-double-buffering" "internal-border-width" "keep-ratio"
+    "left" "left-fringe" "line-spacing" "menu-bar-lines" "min-height"
+    "min-width" "minibuffer" "minibuffer-exit" "mouse-color"
+    "mouse-wheel-frame" "name" "no-accept-focus" "no-focus-on-map"
+    "no-other-frame" "no-special-glyphs" "ns-appearance"
+    "ns-transparent-titlebar" "outer-window-id" "override-redirect"
+    "parent-frame" "right-fringe" "rigth-divider-width" "screen-gamma"
+    "scroll-bar-background" "scroll-bar-foreground" "scroll-bar-height"
+    "scroll-bar-width" "shaded" "skip-taskbar" "snap-width" "sticky"
+    "tab-bar-lines" "title" "tool-bar-lines" "tool-bar-position" "top"
+    "top-visible" "tty-color-mode" "undecorated" "unspittable"
+    "use-frame-synchronization" "user-position" "user-size"
+    "vertical-scroll-bars" "visibility" "wait-for-wm" "width" "z-group")
+  "List of special frame parameters that makes sense to customize.")
+
+(declare-function "widget-field-text-end" "wid-edit")
+(declare-function "widget-field-start" "wid-edit")
+
+(defun frame--complete-parameter-value (widget)
+  "Provide completion for WIDGET, which holds frame parameter's values."
+  (let* ((parameter (widget-value
+                     (nth 0
+                          (widget-get (widget-get widget :parent) :children))))
+         (comps (cond ((eq parameter 'display-type)
+                       '("color" "grayscale" "mono"))
+                      ((eq parameter 'z-group) '("nil" "above" "below"))
+                      ((memq parameter '(fullscreen fullscreen-restore))
+                       '("fullwidth" "fullheight" "fullboth" "maximized"))
+                      ((eq parameter 'cursor-type)
+                       '("t" "nil" "box" "hollow" "bar" "hbar"))
+                      ((eq parameter 'vertical-scroll-bars)
+                       '("nil" "left" "right"))
+                      ((eq parameter 'tool-bar-position)
+                       '("top" "bottom" "left" "right"))
+                      ((eq parameter 'minibuffer)
+                       '("t" "nil" "only"))
+                      ((eq parameter 'minibuffer-exit)
+                       '("nil" "t" "iconify-frame" "delete-frame"))
+                      ((eq parameter 'visibility) '("nil" "t" "icon"))
+                      ((memq parameter '(ns-appearance background-mode))
+                       '("dark" "light"))
+                      ((eq parameter 'font-backend)
+                       '("x" "xft" "xfthb" "ftcr" "ftcrhb" "gdi"
+                         "uniscribe" "harfbuzz"))
+                      ((memq parameter '(buffer-predicate auto-hide-function))
+                       (apply-partially
+                        #'completion-table-with-predicate
+                        obarray #'fboundp 'strict))
+                      (t nil))))
+    (completion-in-region (widget-field-start widget)
+                          (max (point) (widget-field-text-end widget))
+                          comps)))
+
 ;; The initial value given here used to ask for a minibuffer.
 ;; But that's not necessary, because the default is to have one.
 ;; By not specifying it here, we let an X resource specify it.
@@ -91,9 +155,11 @@ initial-frame-alist
 * Set `initial-frame-alist' in your normal init file in a way
   that matches the X resources, to override what you put in
   `default-frame-alist'."
-  :type '(repeat (cons :format "%v"
-		       (symbol :tag "Parameter")
-		       (sexp :tag "Value")))
+  :type `(repeat (cons :format "%v"
+		       (symbol :tag "Parameter"
+                               :completions ,frame--special-parameters)
+		       (sexp :tag "Value"
+                             :complete frame--complete-parameter-value)))
   :group 'frames)
 
 (defcustom minibuffer-frame-alist '((width . 80) (height . 2))
@@ -110,9 +176,11 @@ minibuffer-frame-alist
 
 It is not necessary to include (minibuffer . only); that is
 appended when the minibuffer frame is created."
-  :type '(repeat (cons :format "%v"
-		       (symbol :tag "Parameter")
-		       (sexp :tag "Value")))
+  :type `(repeat (cons :format "%v"
+		       (symbol :tag "Parameter"
+                               :completions ,frame--special-parameters)
+		       (sexp :tag "Value"
+                             :complete frame--complete-parameter-value)))
   :group 'frames)
 
 (defun frame-deletable-p (&optional frame)
-- 
2.34.1