Subject: bug#39143: 26.3; `widget-complete' in `customize-option'

From: Mauro Aranda <[email-masked]>
Date: Sun, 9 Mar 2025 11:30:38 -0300

On 9/3/25 08:20, Eli Zaretskii wrote:
 >> Date: Sun, 9 Mar 2025 08:08:57 -0300
 >> Cc: [email-masked], [email-masked]
 >> From: Mauro Aranda <[email-masked]>
 >>
 >> On 9/3/25 06:45, Eli Zaretskii wrote:
 >>  >> Date: Fri, 28 Feb 2025 10:28:13 -0300
 >>  >> Cc: Eli Zaretskii <[email-masked]>
 >>  >> From: Mauro Aranda <[email-masked]>
 >>  >>
 >>  >> 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.
 >>  >
 >>  > Thanks.  I think we should install this, but I wonder how can we make
 >>  > sure that whenever a new frame-parameter is added, the list in
 >>  > frame--special-parameters will be considered for updating?
 >>
 >> Thanks Eli.  I don't know of any good way to make sure of that, other
 >> than adding a comment to frame_parms in frame.c.
 >
 > A comment is good, but please add comments in both places
 > cross-referencing the other place.

I attach the updated patch.
From a747f69a9ad227f7647cd152a1e383b328876ba7 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.
* src/frame.c (frame_parms): Add comment to try to keep
frame--special-parameters updated.
---
 lisp/cus-start.el | 11 +++---
 lisp/frame.el     | 88 +++++++++++++++++++++++++++++++++++++++++++----
 src/frame.c       |  4 +++
 3 files changed, 93 insertions(+), 10 deletions(-)

diff --git a/lisp/cus-start.el b/lisp/cus-start.el
index b4c20caccc1..7d17249f78d 100644
--- a/lisp/cus-start.el
+++ b/lisp/cus-start.el
@@ -317,10 +317,13 @@ minibuffer-prompt-properties--setter
 	     (vertical-centering-font-regexp display
 					     (choice (const nil) regexp))
 	     ;; frame.c
-	     (default-frame-alist frames
-	       (repeat (cons :format "%v"
-			     (symbol :tag "Parameter")
-			     (sexp :tag "Value"))))
+             (default-frame-alist
+              frames
+              (repeat (cons :format "%v"
+                            (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..3eccb9a5848 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -60,6 +60,78 @@ display-format-alist
 name string.  The cdr is a symbol giving the window-system that
 handles the corresponding kind of display.")
 
+;; If you're adding a new frame parameter to `frame_parms' in frame.c,
+;; consider if it makes sense for the user to customize it via
+;; `initial-frame-alist' and the like.
+;; If it does, add it here, in order to provide completion for
+;; that parameter in the Customize UI.
+;; If the parameter has some special values, modify
+;; `frame--complete-parameter-value' to provide completion for those
+;; values as well.
+(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 +163,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 +184,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)
diff --git a/src/frame.c b/src/frame.c
index 1a5c2f5c3d4..b386839f34c 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -4392,6 +4392,10 @@ DEFUN ("frame-scale-factor", Fframe_scale_factor, Sframe_scale_factor,
   int sym;
 };
 
+/* If you're adding a new frame parameter here, consider if it makes sense
+   for the user to customize it via `initial-frame-alist' and the like.
+   If it does, add it to `frame--special-parameters' in frame.el, in order
+   to provide completion in the Customize UI for the new parameter.  */
 static const struct frame_parm_table frame_parms[] =
 {
   {"auto-raise",		SYMBOL_INDEX (Qauto_raise)},
-- 
2.34.1