From: Spencer Baugh Date: Thu, 23 Nov 2023 13:37:29 +0000 (+0000) Subject: Deselect the selected completion candidate when typing X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=3c093148958d56e0ed8e12a8e00ced1ef052259a;p=emacs.git Deselect the selected completion candidate when typing minibuffer-choose-completion-or-exit submits 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 so that minibuffer-choose-completion-or-exit will not choose it. minibuffer-choose-completion has the same behavior as before, and is not affected by the deselection. * lisp/minibuffer.el (completion-auto-deselect, completions--deselect) (completions--after-change): Add. (minibuffer-completion-help): Add completions--after-change hook. (minibuffer-next-completion): Bind completion-auto-deselect to nil to avoid immediately deselecting the completion. (minibuffer-choose-completion-or-exit): Bind choose-completion-deselect-if-after so deselection takes effect. (display-completion-list): Guarantee a newline at the beginning of *Completions* to avoid ambiguity about candidate selection. * lisp/simple.el (choose-completion-deselect-if-after): Add. (choose-completion): Check choose-completion-deselect-if-after. * etc/NEWS: Announce. --- diff --git a/etc/NEWS b/etc/NEWS index da00ea9dbda..3d26f276604 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -628,6 +628,15 @@ completions window. When the completions window is not visible, then all these keys have their usual meaning in the minibuffer. This option is supported for in-buffer completion as well. +*** Selected completion candidates are deselected on typing. +When a user types, point in the *Completions* window will be moved off +any completion candidates. 'minibuffer-choose-completion' ('M-RET') +will still choose a previously-selected completion candidate, but the +new command 'minibuffer-choose-completion-or-exit' (bound by +'minibuffer-visible-completions') will exit with the minibuffer +contents instead. The deselection behavior can be controlled with the +new user option 'completion-auto-deselect'. + ** Pcomplete --- diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 5c12d9fc914..382d4458e26 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2310,8 +2310,11 @@ candidates." (with-current-buffer standard-output (goto-char (point-max)) - (when completions-header-format - (insert (format completions-header-format (length completions)))) + (if completions-header-format + (insert (format completions-header-format (length completions))) + (unless completion-show-help + ;; Ensure beginning-of-buffer isn't a completion. + (insert (propertize "\n" 'face '(:height 0))))) (completion--insert-strings completions group-fun))) (run-hooks 'completion-setup-hook) @@ -2378,6 +2381,33 @@ These include: (resize-temp-buffer-window win)) (fit-window-to-buffer win completions-max-height))) +(defcustom completion-auto-deselect t + "If non-nil, deselect the selected completion candidate when you type. + +A non-nil value means that after typing, point in *Completions* +will be moved off any completion candidates. This means +`minibuffer-choose-completion-or-exit' will exit with the +minibuffer's current contents, instead of a completion candidate." + :type '(choice (const :tag "Candidates in *Completions* stay selected as you type" nil) + (const :tag "Typing deselects any completion candidate in *Completions*" t)) + :version "30.1") + +(defun completions--deselect () + "If point is in a completion candidate, move to just after the end of it. + +The candidate will still be chosen by `choose-completion' unless +`choose-completion-deselect-if-after' is non-nil." + (when (get-text-property (point) 'completion--string) + (goto-char (or (next-single-property-change (point) 'completion--string) + (point-max))))) + +(defun completions--after-change (_start _end _old-len) + "Update displayed *Completions* buffer after change in buffer contents." + (when completion-auto-deselect + (when-let (window (get-buffer-window "*Completions*" 0)) + (with-selected-window window + (completions--deselect))))) + (defun minibuffer-completion-help (&optional start end) "Display a list of possible completions of the current minibuffer contents." (interactive) @@ -2400,6 +2430,7 @@ These include: ;; 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 'after-change-functions #'completions--after-change t) (if completions (completion--message "Sole completion") (unless completion-fail-discreetly @@ -2460,6 +2491,8 @@ These include: (body-function . ,#'(lambda (_window) (with-current-buffer mainbuf + (when completion-auto-deselect + (add-hook 'after-change-functions #'completions--after-change t)) ;; Remove the base-size tail because `sort' requires a properly ;; nil-terminated list. (when last (setcdr last nil)) @@ -4673,7 +4706,8 @@ insert the selected completion candidate to the minibuffer." (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) + (completion-auto-deselect nil)) (choose-completion nil t t)))))) (defun minibuffer-previous-completion (&optional n) @@ -4721,7 +4755,8 @@ in the completions window, then exit the minibuffer using its present contents." (interactive "P") (condition-case nil - (minibuffer-choose-completion no-exit no-quit) + (let ((choose-completion-deselect-if-after t)) + (minibuffer-choose-completion no-exit no-quit)) (error (minibuffer-complete-and-exit)))) (defun minibuffer-complete-history () diff --git a/lisp/simple.el b/lisp/simple.el index 652fc7ba540..e0b27658df6 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -10094,6 +10094,11 @@ Also see the `completion-auto-wrap' variable." (if pos (goto-char pos)))) (setq n (1+ n))))) +(defvar choose-completion-deselect-if-after nil + "If non-nil, don't choose a completion candidate if point is right after it. + +This makes `completions--deselect' effective.") + (defun choose-completion (&optional event no-exit no-quit) "Choose the completion at point. If EVENT, use EVENT's position to determine the starting position. @@ -10114,6 +10119,10 @@ minibuffer, but don't quit the completions window." (insert-function completion-list-insert-choice-function) (completion-no-auto-exit (if no-exit t completion-no-auto-exit)) (choice + (if choose-completion-deselect-if-after + (if-let ((str (get-text-property (posn-point (event-start event)) 'completion--string))) + (substring-no-properties str) + (error "No completion here")) (save-excursion (goto-char (posn-point (event-start event))) (let (beg) @@ -10129,7 +10138,7 @@ minibuffer, but don't quit the completions window." beg 'completion--string) beg)) (substring-no-properties - (get-text-property beg 'completion--string)))))) + (get-text-property beg 'completion--string))))))) (unless (buffer-live-p buffer) (error "Destination buffer is dead"))