emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Navigating completions from minibuffer


From: sbaugh
Subject: Re: Navigating completions from minibuffer
Date: Sat, 18 Nov 2023 20:58:51 +0000
User-agent: Gnus/5.13 (Gnus v5.13)

Spencer Baugh <sbaugh@janestreet.com> writes:

> Juri Linkov <juri@linkov.net> writes:
>>> Also... doesn't this mean that with
>>>
>>> (setq completion-show-help nil
>>>       completions-header-format nil)
>>>
>>> *Completions* will start out with the point on the first completion
>>> candidate?  So M-RET will select it, and with
>>> minibuffer-visible-completions=t, even RET will select it?
>>>
>>> Maybe point should uniformly start at the end of *Completions* instead
>>> of at the start?  In the one character worth of whitespace we might add?
>>
>> Starting at the end of *Completions* would work only when
>> completion-auto-wrap is t.  Another variant is to add a narrow
>> character at the beginning of *Completions* like used for
>> an empty rectangular region in rectangle-mark-mode.
>
> Yes, that sounds good.  And then we could uniformly move point before
> the candidate when deselecting it, which IMO would be a little visually
> nicer.

OK, how about this?  It's an adapted version of my
completions-auto-update patch, which provides (for now) only the feature
of deselecting the completion when point moves or the minibuffer
changes.  The infrastructure for doing this turns out to be basically
identical, which is interesting.

It sets completions-auto-update to 'deselect by default, which I think
is reasonable?

>From beb15769fdacc388a059e6bc79e924bc219bcb98 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <sbaugh@catern.com>
Date: Sat, 18 Nov 2023 20:55:18 +0000
Subject: [PATCH] Deselect the selected completion candidate when typing

minibuffer-visible-completions makes RET submit the selected
completion candidate, if any, ignoring the contents of the minibuffer.
But a user might select a completion candidate and then want to type
something else in the minibuffer and submit what they typed.

Now typing will automatically deselect the selected completion
candidate.

* lisp/minibuffer.el (completion--insert): Add a space before each
candidate.
(completions-auto-update, completions--deselect)
(completions--update-if-displayed, completions--after-change)
(minibuffer--old-point, completions--post-command): Add.
(minibuffer-completion-help): Add completions--after-change and
completions--post-command as hooks.
(minibuffer-next-completion): Bind completions-auto-update to nil to
avoid immediately deselecting the completion.
---
 lisp/minibuffer.el | 72 +++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 62 insertions(+), 10 deletions(-)

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index e0017d88780..026613c9eb2 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2196,15 +2196,18 @@ completion--insert
              (equal (or (car-safe str) str) selected))
     (setq completion--selected-posn (point)))
   (if (not (consp str))
-      (add-text-properties
-       (point)
-       (progn
-         (insert
-          (if group-fun
-              (funcall group-fun str 'transform)
-            str))
-         (point))
-       `(mouse-face highlight cursor-face ,completions-highlight-face 
completion--string ,str))
+      (progn
+        ;; We move point to this character to deselect the completion 
candidate.
+        (insert " ")
+        (add-text-properties
+         (point)
+         (progn
+           (insert
+            (if group-fun
+                (funcall group-fun str 'transform)
+              str))
+           (point))
+         `(mouse-face highlight cursor-face ,completions-highlight-face 
completion--string ,str)))
     ;; If `str' is a list that has 2 elements,
     ;; then the second element is a suffix annotation.
     ;; If `str' has 3 elements, then the second element
@@ -2391,6 +2394,49 @@ completions--fit-window-to-buffer
         (resize-temp-buffer-window win))
     (fit-window-to-buffer win completions-max-height)))
 
+(defcustom completions-auto-update 'deselect
+  "If non-nil, change the *Completions* buffer as you type.
+
+If `deselect', if a completion candidate in *Completions* is
+selected (point is on it), it will be deselected (point will be
+moved just before it) when the minibuffer point or contents
+change.
+
+This only affects the *Completions* buffer if it is already
+displayed."
+  :type '(choice (const :tag "*Completions* doesn't change as you type" nil)
+                 (const :tag "Typing deselects any completion candidate in 
*Completions*" deselect))
+  :version "30.1")
+
+(defun completions--deselect ()
+  "If in a completion candidate, move just before the start of it."
+  (when (get-text-property (point) 'mouse-face)
+    (when (and (not (bobp)) (get-text-property (1- (point)) 'mouse-face))
+      (goto-char (previous-single-property-change (point) 'mouse-face)))
+    (unless (bobp)
+      (backward-char 1))))
+
+(defun completions--update-if-displayed ()
+  "Update a displayed *Completions* buffer based on `completions-auto-update'"
+  (when completions-auto-update
+    (when-let (window (get-buffer-window "*Completions*" 0))
+      (with-selected-window window
+        (when (eq completions-auto-update 'deselect)
+          (completions--deselect))))))
+
+(defun completions--after-change ()
+  "Update displayed *Completions* buffer after change in minibuffer contents."
+  (when (minibufferp)
+    (completions--update-if-displayed)))
+
+(defvar-local minibuffer--old-point nil)
+
+(defun completions--post-command ()
+  "Update displayed *Completions* buffer after change in minibuffer point."
+  (when (and (minibufferp) (not (eq minibuffer--old-point (point))))
+    (setq minibuffer--old-point (point))
+    (completions--update-if-displayed)))
+
 (defun minibuffer-completion-help (&optional start end)
   "Display a list of possible completions of the current minibuffer contents."
   (interactive)
@@ -2413,6 +2459,8 @@ minibuffer-completion-help
           ;; If there are no completions, or if the current input is already
           ;; the sole completion, then hide (previous&stale) completions.
           (minibuffer-hide-completions)
+          (remove-hook 'post-command-hook #'completions--post-command t)
+          (remove-hook 'after-change-hook #'completions--after-change t)
           (if completions
               (completion--message "Sole completion")
             (unless completion-fail-discreetly
@@ -2476,6 +2524,9 @@ minibuffer-completion-help
             (body-function
              . ,#'(lambda (window)
                     (with-current-buffer mainbuf
+                      (when completions-auto-update
+                        (add-hook 'post-command-hook 
#'completions--post-command nil t)
+                        (add-hook 'after-change-hook 
#'completions--after-change t))
                       ;; Remove the base-size tail because `sort' requires a 
properly
                       ;; nil-terminated list.
                       (when last (setcdr last nil))
@@ -4688,7 +4739,8 @@ minibuffer-next-completion
           (next-line-completion (or n 1))
         (next-completion (or n 1)))
       (when auto-choose
-        (let ((completion-use-base-affixes t))
+        (let ((completion-use-base-affixes t)
+              (completions-auto-update nil))
           (choose-completion nil t t))))))
 
 (defun minibuffer-previous-completion (&optional n)
-- 
2.42.1


reply via email to

[Prev in Thread] Current Thread [Next in Thread]