GNU bug report logs - #77253
30.1.50; Support RET choosing the selected completion without rebinding arrow keys

Previous Next

Package: emacs;

Reported by: Spencer Baugh <sbaugh <at> janestreet.com>

Date: Tue, 25 Mar 2025 15:12:02 UTC

Severity: normal

Found in version 30.1.50

Full log


Message #29 received at 77253 <at> debbugs.gnu.org (full text, mbox):

From: Spencer Baugh <sbaugh <at> janestreet.com>
To: Juri Linkov <juri <at> linkov.net>
Cc: 77253 <at> debbugs.gnu.org
Subject: Re: bug#77253: 30.1.50; Support RET choosing the selected
 completion without rebinding arrow keys
Date: Thu, 03 Apr 2025 13:52:44 -0400
Juri Linkov <juri <at> linkov.net> writes:

>> - It's not necessarily obvious that closing the completions window will
>>   make the arrow keys operate in the minibuffer.  Actually, maybe we
>>   should update the completion help text to say that?  That might help a
>>   lot.
>
> Maybe it's possible to squeeze this help text just into one additional line?
> Something like:
>
>   Type 'C-g' to close this window and restore arrows to move point.

Maybe we don't even need an additional line?  How about:

Click or type RET on a completion to select it, or C-g to close this window.
Type <right>, <left>, <down>, <up> to move point between completions.

>> - The new completion-eager-display makes *Completions* pop up
>>   immediately; when this happens, it can be surprising that the arrow
>>   keys don't work in the minibuffer from the very start of the
>>   minibuffer session.
>
> Then another possible value for 'minibuffer-visible-completions'
> would be to not rebind left/right arrows when the completions window
> contains only one column (like on the browser's address bar).

True.  Though I guess that could maybe be the default behavior when
completions-format=one-column.

>>> Not sure if it's possible for RET to accept the selected candidate
>>> by default since users might prefer to accept text in the minibuffer.
>>
>> If there's a selected candidate, though, then users have already decided
>> to use M-<up>/M-<down> to interact with completions.  (Or they've
>> switched to the completions buffer and selected one)
>>
>> If they decide they don't want the candidate they selected, and want to
>> continue with text in the minibuffer, completion-auto-deselect will
>> automatically deselect the candidate if they type anything.
>>
>> I think these two facts combined make it possible for RET to accept the
>> selected candidate by default.
>
> Makes sense.  So we need to try how well it performs.

How about this?

(BTW, after writing minibuffer--completions-visible, I'm wondering if
most of the code which calls (get-buffer-window "*Completions*" 0) is
actually buggy, because it's not checking completion-reference-buffer.
Should we replace ~all the calls to (get-buffer-window "*Completions*"
0) with (minibuffer--completions-visible) which does check that?)

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 404fa143194..c026e41a818 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -3209,6 +3209,12 @@ completion-help-at-point
   (define-key map "\n" 'exit-minibuffer)
   (define-key map "\r" 'exit-minibuffer))
 
+(defun minibuffer-choose-completion-just-exit (&optional no-exit)
+  "Choose the selected completion from the minibuffer or call `exit-minibuffer'."
+  (interactive "P")
+  (or (minibuffer-choose-completion-if-selected no-exit)
+      (minibuffer-exit)))
+
 (defvar-keymap minibuffer-local-completion-map
   :doc "Local keymap for minibuffer input with completion."
   :parent minibuffer-local-map
@@ -3218,6 +3224,7 @@ minibuffer-local-completion-map
   ;; another binding for it.
   ;; "M-TAB"  #'minibuffer-force-complete
   "SPC"       #'minibuffer-complete-word
+  "RET"       #'minibuffer-choose-completion-just-exit
   "?"         #'minibuffer-completion-help
   "<prior>"   #'switch-to-completions
   "M-v"       #'switch-to-completions
@@ -3229,7 +3236,7 @@ minibuffer-local-completion-map
 (defvar-keymap minibuffer-local-must-match-map
   :doc "Local keymap for minibuffer input with completion, for exact match."
   :parent minibuffer-local-completion-map
-  "RET" #'minibuffer-complete-and-exit
+  "RET" #'minibuffer-choose-completion-or-exit
   "C-j" #'minibuffer-complete-and-exit)
 
 (defvar-keymap minibuffer-local-filename-completion-map
@@ -3326,18 +3333,34 @@ minibuffer-visible-completions
 (defvar minibuffer-visible-completions--always-bind nil
   "If non-nil, force the `minibuffer-visible-completions' bindings on.")
 
+(defun minibuffer--completions-visible ()
+  "Return the window where the *Completions* buffer for this minibuffer is visible."
+  (when-let ((window (get-buffer-window "*Completions*" 0)))
+    (when (eq (buffer-local-value 'completion-reference-buffer
+                                  (window-buffer window))
+              (window-buffer (active-minibuffer-window)))
+      window)))
+
+(defun minibuffer-choose-completion-if-selected (&optional no-exit no-quit)
+  "Like `minibuffer-choose-completion', but do nothing if no candidate is selected.
+
+Return non-nil if a completion was chosen."
+  (when-let* ((window (minibuffer--completions-visible)))
+    (with-selected-window window
+      ;; Detect selection as if `choose-completion-deselect-if-after' is nil.
+      (when (get-text-property (point) 'completion--string)
+        (choose-completion nil no-exit no-quit)
+        t))))
+
 (defun minibuffer-visible-completions--filter (cmd)
   "Return CMD if `minibuffer-visible-completions' bindings should be active."
   (if minibuffer-visible-completions--always-bind
       cmd
-    (when-let ((window (get-buffer-window "*Completions*" 0)))
-      (when (and (eq (buffer-local-value 'completion-reference-buffer
-                                         (window-buffer window))
-                     (window-buffer (active-minibuffer-window)))
-                 (if (eq cmd #'minibuffer-choose-completion-or-exit)
-                     (with-current-buffer (window-buffer window)
-                       (get-text-property (point) 'completion--string))
-                   t))
+    (when-let ((window (minibuffer--completions-visible)))
+      (when (if (eq cmd #'minibuffer-choose-completion-or-exit)
+                (with-current-buffer (window-buffer window)
+                  (get-text-property (point) 'completion--string))
+              t)
         cmd))))
 
 (defun minibuffer-visible-completions--bind (binding)
@@ -5109,10 +5132,8 @@ minibuffer-choose-completion-or-exit
 in the completions window, then exit the minibuffer using its present
 contents."
   (interactive "P")
-  (condition-case nil
-      (let ((choose-completion-deselect-if-after t))
-        (minibuffer-choose-completion no-exit no-quit))
-    (error (minibuffer-complete-and-exit))))
+  (or (minibuffer-choose-completion-if-selected no-exit no-quit)
+      (minibuffer-complete-and-exit)))
 
 (defun minibuffer-complete-history ()
   "Complete as far as possible using the minibuffer history.




This bug report was last modified 75 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.