Fix navigating *Completions* with TAB in vertical format

* lisp/simple.el (next-line-completion): Make using TAB to
navigate in vertical format cycle through the minibuffer (bug#81631).

* test/lisp/minibuffer-tests.el (completion-auto-select-test-bug81635):
Run tests for all values of 'completions-format'.
This commit is contained in:
Stephen Berman 2026-08-25 09:52:44 +02:00
parent 9c9a26e60b
commit e8936794ad
2 changed files with 73 additions and 56 deletions

View file

@ -10357,29 +10357,31 @@ of completions.
Also see the `completion-auto-wrap' variable."
(interactive "p")
(let (line column pos found last first)
(when (and (bobp)
(> n 0)
(get-text-property (point) 'mouse-face)
(not (get-text-property (point) 'first-completion)))
(let ((inhibit-read-only t))
(add-text-properties (point) (1+ (point)) '(first-completion t)))
(setq n (1- n)))
(let ((tabcommand (member (this-command-keys) '("\t" [backtab])))
line column pos found last first)
(catch 'bound
(when (and (bobp)
(> n 0)
(get-text-property (point) 'mouse-face)
(not (get-text-property (point) 'first-completion)))
(let ((inhibit-read-only t))
(add-text-properties (point) (1+ (point)) '(first-completion t)))
(setq n (1- n)))
(if (get-text-property (point) 'mouse-face)
;; If in a completion, move to the start of it.
(completion--move-to-candidate-start)
;; Try to move to the previous completion.
(setq pos (previous-single-property-change (point) 'mouse-face))
(if pos
;; Move to the start of the previous completion.
(progn
(goto-char pos)
(unless (get-text-property (point) 'mouse-face)
(goto-char (previous-single-property-change
(point) 'mouse-face nil (point-min)))))
(cond ((> n 0) (setq n (1- n)) (first-completion))
((< n 0) (first-completion)))))
(if (get-text-property (point) 'mouse-face)
;; If in a completion, move to the start of it.
(completion--move-to-candidate-start)
;; Try to move to the previous completion.
(setq pos (previous-single-property-change (point) 'mouse-face))
(if pos
;; Move to the start of the previous completion.
(progn
(goto-char pos)
(unless (get-text-property (point) 'mouse-face)
(goto-char (previous-single-property-change
(point) 'mouse-face nil (point-min)))))
(cond ((> n 0) (setq n (1- n)) (first-completion))
((< n 0) (first-completion)))))
(while (> n 0)
(setq found nil pos (point) column (current-column)
@ -10387,7 +10389,12 @@ Also see the `completion-auto-wrap' variable."
last (= (point) (save-excursion (last-completion) (point))))
(if (and (eq completions-format 'vertical)
completion-auto-wrap last)
(first-completion) ; Wrap from last to first item.
(if (and (eq completion-auto-select t) tabcommand
(minibufferp completion-reference-buffer))
(progn
(completions--clear-selection)
(throw 'bound nil)) ; Skip to minibuffer.
(first-completion)) ; Wrap from last to first item.
(completion--move-to-candidate-end)
(while (and (not found)
(eq (forward-line 1) 0)
@ -10424,7 +10431,12 @@ Also see the `completion-auto-wrap' variable."
first (= (point) (save-excursion (first-completion) (point))))
(if (and (eq completions-format 'vertical)
completion-auto-wrap first)
(last-completion) ; Wrap from first to last item.
(if (and (eq completion-auto-select t) tabcommand
(minibufferp completion-reference-buffer))
(progn
(completions--clear-selection)
(throw 'bound nil)) ; Skip to minibuffer.
(last-completion)) ; Wrap from first to last item.
(completion--move-to-candidate-start)
(while (and (not found)
(eq (forward-line -1) 0)
@ -10461,7 +10473,10 @@ Also see the `completion-auto-wrap' variable."
(setq pos (point))
(forward-line))
(goto-char pos)))))
(setq n (1+ n)))))
(setq n (1+ n))))
(when (/= 0 n)
(switch-to-minibuffer))))
(defun next-completion (&optional n)
"Move according to `completions-format' to next completion item.

View file

@ -640,37 +640,39 @@
(should (eq (current-buffer) (get-buffer "*Completions*"))))))
(ert-deftest completion-auto-select-test-bug81635 ()
(let ((completion-auto-select t)
(completion-auto-wrap t))
(completing-read-with-minibuffer-setup
'("aa" "ab" "ac")
(cl-flet ((selected ()
(and (eq (current-buffer) (get-buffer "*Completions*"))
(get-text-property (point) 'completion--string))))
;; TAB cycles forward through all candidates, then to the
;; minibuffer, then back to the first candidate.
(execute-kbd-macro (kbd "a TAB"))
(should (equal (selected) "aa"))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "ab"))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "ac"))
(execute-kbd-macro (kbd "TAB"))
(should (minibufferp))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "aa"))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "ab"))
;; S-TAB cycles backward, then to the minibuffer, then to the
;; last candidate.
(execute-kbd-macro (kbd "<backtab>"))
(should (equal (selected) "aa"))
(execute-kbd-macro (kbd "<backtab>"))
(should (minibufferp))
(execute-kbd-macro (kbd "<backtab>"))
(should (equal (selected) "ac"))
(execute-kbd-macro (kbd "<backtab>"))
(should (equal (selected) "ab"))))))
(dolist (format '(horizontal vertical one-column))
(let ((completion-auto-select t)
(completion-auto-wrap t)
(completions-format format))
(completing-read-with-minibuffer-setup
'("aa" "ab" "ac")
(cl-flet ((selected ()
(and (eq (current-buffer) (get-buffer "*Completions*"))
(get-text-property (point) 'completion--string))))
;; TAB cycles forward through all candidates, then to the
;; minibuffer, then back to the first candidate.
(execute-kbd-macro (kbd "a TAB"))
(should (equal (selected) "aa"))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "ab"))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "ac"))
(execute-kbd-macro (kbd "TAB"))
(should (minibufferp))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "aa"))
(execute-kbd-macro (kbd "TAB"))
(should (equal (selected) "ab"))
;; S-TAB cycles backward, then to the minibuffer, then to the
;; last candidate.
(execute-kbd-macro (kbd "<backtab>"))
(should (equal (selected) "aa"))
(execute-kbd-macro (kbd "<backtab>"))
(should (minibufferp))
(execute-kbd-macro (kbd "<backtab>"))
(should (equal (selected) "ac"))
(execute-kbd-macro (kbd "<backtab>"))
(should (equal (selected) "ab")))))))
(ert-deftest completion-auto-wrap-test ()
(let ((completion-auto-wrap nil))