Subject: bug#77649: [PATCH] Add support for updating *Completions* as you type

From: Spencer Baugh <[email-masked]>
Date: Tue, 08 Apr 2025 11:17:17 -0400

Tags: patch

Add support for updating the *Completions* buffer as you type,
controlled by a new completion metadata symbol 'eager-update and
new defcustom completion-eager-update.

You can configure a completion category to update *Completions*
as you type by setting completion-category-overrides
appropriately; or set completion-eager-update to t to always
update *Completions* as you type.

This is similar to the recently added completion-eager-display.

In GNU Emacs 30.1.50 (build 2, x86_64-pc-linux-gnu, X toolkit, cairo
 version 1.15.12, Xaw scroll bars) of 2025-04-03 built on
 igm-qws-u22796a
Repository revision: e0d429965010d82a05225e53ba6fbb97bea23ae9
Repository branch: emacs-30
Windowing system distributor 'The X.Org Foundation', version 11.0.12011000
System Description: Rocky Linux 8.10 (Green Obsidian)

Configured using:
 'configure --config-cache --with-x-toolkit=lucid --without-gpm
 --without-gconf --without-selinux --without-imagemagick --with-modules
 --with-gif=no --with-cairo --with-rsvg --without-compress-install
 --with-tree-sitter --with-native-compilation=aot'

>From fbfafdc708e8d78ceec4723373ac9bc7da30dd4f Mon Sep 17 00:00:00 2001
From: Spencer Baugh <[email-masked]>
Date: Thu, 3 Apr 2025 15:38:22 -0400
Subject: [PATCH] Add support for updating *Completions* as you type

Add support for updating the *Completions* buffer as you type,
controlled by a new completion metadata symbol 'eager-update and
new defcustom completion-eager-update.

You can configure a completion category to update *Completions*
as you type by setting completion-category-overrides
appropriately; or set completion-eager-update to t to always
update *Completions* as you type.

This is similar to the recently added completion-eager-display.

* lisp/minibuffer.el (completion-eager-update): Add new
defcustom defaulting to 'auto.
(completion--eager-update-p, completions--background-update)
(completions--post-command-update): Add.
(completions--after-change): Call
completions--post-command-update via post-command-hook.
(minibuffer-completion-help): Check completion-eager-update and
install completions--after-change.
(completion-help-at-point): Call completion--eager-update-p if
ONLY-IF-EAGER is non-nil.
---
 lisp/minibuffer.el | 59 +++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 53 insertions(+), 6 deletions(-)

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 1b78fab3d6e..6d5a96f875c 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -1070,6 +1070,21 @@ completion-eager-display
                  (const :tag "If requested by the completion command" auto))
   :version "31.1")
 
+(defcustom completion-eager-update 'auto
+  "Whether typing should update the *Completions* buffer eagerly.
+
+If `t', always update as you type.
+
+If `auto', only update if the completion table has requested it or
+`eager-update' is set in in `completion-category-defaults'.
+
+This only affects the *Completions* buffer if it is already
+displayed."
+  :type '(choice (const :tag "Do nothing when you type" nil)
+                 (const :tag "Auto-update based on the category" auto)
+                 (const :tag "Always update as you type" t))
+  :version "31.1")
+
 (defcustom completion-auto-help t
   "Non-nil means automatically provide help for invalid completion input.
 If the value is t, the *Completions* buffer is displayed whenever completion
@@ -2646,12 +2661,43 @@ completions--deselect
     (goto-char (or (next-single-property-change (point) 'completion--string)
                    (point-max)))))
 
+(defun completion--eager-update-p (start)
+  "Return non-nil if *Completions* should be automatically updated.
+
+If `completion-eager-update' is the symbol `auto', checks completion
+metadata for the string from START to point."
+  (if (eq completion-eager-update 'auto)
+      (completion-metadata-get (completion--field-metadata start) 'eager-update)
+    completion-eager-update))
+
+(defun completions--background-update ()
+  "Try to update *Completions* without blocking input.
+
+This function uses `while-no-input' and sets `non-essential' to t
+so that the update is less likely to interfere with user typing."
+  (while-no-input
+    (let ((non-essential t))
+      (redisplay)
+      (cond
+       (completion-in-region-mode (completion-help-at-point t))
+       ((completion--eager-update-p (minibuffer-prompt-end))
+        (minibuffer-completion-help))))))
+
+(defun completions--post-command-update ()
+  "Update displayed *Completions* buffer after command, once."
+  (remove-hook 'post-command-hook #'completions--post-command-update)
+  (when (and completion-eager-update (get-buffer-window "*Completions*" 0))
+    (completions--background-update)))
+
 (defun completions--after-change (_start _end _old-len)
   "Update displayed *Completions* buffer after change in buffer contents."
-  (when completion-auto-deselect
+  (when (or completion-auto-deselect completion-eager-update)
     (when-let* ((window (get-buffer-window "*Completions*" 0)))
-      (with-selected-window window
-        (completions--deselect)))))
+      (when completion-auto-deselect
+        (with-selected-window window
+          (completions--deselect)))
+      (when completion-eager-update
+        (add-hook 'post-command-hook #'completions--post-command-update)))))
 
 (defun minibuffer-completion-help (&optional start end)
   "Display a list of possible completions of the current minibuffer contents."
@@ -2742,7 +2788,7 @@ minibuffer-completion-help
             (body-function
              . ,#'(lambda (window)
                     (with-current-buffer mainbuf
-                      (when completion-auto-deselect
+                      (when (or completion-auto-deselect completion-eager-update)
                         (add-hook 'after-change-functions #'completions--after-change nil t))
                       ;; Remove the base-size tail because `sort' requires a properly
                       ;; nil-terminated list.
@@ -3131,7 +3177,7 @@ completion-at-point
                   (car res)))
        (cdr res)))))
 
-(defun completion-help-at-point ()
+(defun completion-help-at-point (&optional only-if-eager)
   "Display the completions on the text around point.
 The completion method is determined by `completion-at-point-functions'."
   (interactive)
@@ -3158,7 +3204,8 @@ completion-help-at-point
                `(,start ,(copy-marker end t) ,collection
                         ,(plist-get plist :predicate)))
          (completion-in-region-mode 1)
-         (minibuffer-completion-help start end)))
+         (when (or (not only-if-eager) (completion--eager-update-p start))
+           (minibuffer-completion-help start end))))
       (`(,hookfun . ,_)
        ;; The hook function already performed completion :-(
        ;; Not much we can do at this point.
-- 
2.39.3