diff options
| author | Stephen Berman | 2025-09-22 16:04:42 +0200 |
|---|---|---|
| committer | Stephen Berman | 2025-09-22 16:04:42 +0200 |
| commit | 77ca60b48d018425c95c110b4c736cddd2d1d336 (patch) | |
| tree | 50bed0386450ddfafac63b0bd98739c47030da9a /test | |
| parent | aeadaf77488a85838547ed8253a2f0b017cf4774 (diff) | |
| download | emacs-77ca60b48d018425c95c110b4c736cddd2d1d336.tar.gz emacs-77ca60b48d018425c95c110b4c736cddd2d1d336.zip | |
Navigate *Completions* buffer based on 'completions-format'
This patch makes 'next-completion' and 'previous-completion' work
in the vertical completions format analogously to how they work in
the default horizontal format (bug#78959). It also fixes wrapping
in the vertical format and confines navigation (including
wrapping) in column-wise movement in the vertical format to the
current line, analogously to how navigation (including wrapping)
in line-wise movement in the horizontal format is confined to the
current column.
* doc/emacs/mini.texi (Completion): Fix several typos and improve
wording is several places.
(Completion Commands): Document navigation of the *Completions*
buffer in the vertical format. Document the difference between
format-sensitive movement and strictly column-wise or line-wise
movement. Document 'minibuffer-complete-and-exit' and update the
documentation of 'minibuffer-completion-auto-choose' and
'minibuffer-choose-completion'. Document the use of a numeric
prefix argument with the navigation commands.
(Completion Options): Rearrange and improve documentation of
'completions-sort', 'completions-format' and
'completion-auto-wrap', updating the latter to document the new
behavior.
* lisp/minibuffer.el (minibuffer-visible-completions-map): Rebind
"<left>" to 'minibuffer-previous-column-completion' and "<right>"
to 'minibuffer-next-column-completion'.
(minibuffer-next-completion): Add check for whether completions
format is vertical to decide whether to call
'next-line-completion' and replace calling 'next-completion' by
'next-column-completion'.
(minibuffer-next-column-completion)
(minibuffer-previous-column-completion): New commands.
* lisp/simple.el (completion-list-mode-map): Rebind "<left>" to
'previous-column-completion' and "<right>" to 'next-column-completion'.
(last-completion): Add handling for vertical completions format.
(completion--move-to-candidate-end): Always move point to the
position immediately after the last character of the completion
candidate. This unifies the behavior, simplifies the
implementation and facilitates implementing the improved
navigation of the *Completions* buffer.
(previous-column-completion, next-column-completion): New
commands, replacing the previous definitions of
'previous-completion' and 'next-completion' to reflect their
column-wise operation. Confine navigation (including wrapping) in
vertical format to the current line.
(previous-line-completion, next-line-completion): Implement
line-wise navigation (including wrapping) through all completions
in vertical format, not just those in the current column as in
horiztonal format. Update doc strings.
(next-completion, previous-completion): Redefine to call
'{next,previous}-line-completion' when completions format is
vertical and '{next,previous}-column-completion' otherwise.
* test/lisp/minibuffer-tests.el
(completions-format-navigation--tests): New function providing a
template to define tests of the navigation and wrapping behavior
with specified numbers of completion candidates.
(completions-format-navigation-test-{2,3,4,5,10,15,16}): New tests.
Diffstat (limited to 'test')
| -rw-r--r-- | test/lisp/minibuffer-tests.el | 295 |
1 files changed, 294 insertions, 1 deletions
diff --git a/test/lisp/minibuffer-tests.el b/test/lisp/minibuffer-tests.el index 6954643976a..27e9bbbefb4 100644 --- a/test/lisp/minibuffer-tests.el +++ b/test/lisp/minibuffer-tests.el | |||
| @@ -734,10 +734,303 @@ | |||
| 734 | (let ((completion-auto-wrap nil)) | 734 | (let ((completion-auto-wrap nil)) |
| 735 | (first-completion) | 735 | (first-completion) |
| 736 | (next-line-completion 7) | 736 | (next-line-completion 7) |
| 737 | (should (equal "ac2" (get-text-property (point) 'completion--string))) | 737 | (should (equal "ac1" (get-text-property (point) 'completion--string))) |
| 738 | (previous-line-completion 7) | 738 | (previous-line-completion 7) |
| 739 | (should (equal "aa1" (get-text-property (point) 'completion--string)))))) | 739 | (should (equal "aa1" (get-text-property (point) 'completion--string)))))) |
| 740 | 740 | ||
| 741 | (defun completions-format-navigation--tests (n) | ||
| 742 | "Make tests for navigating buffer of N completion candidate. | ||
| 743 | The tests check expected results of navigating with and without wrapping | ||
| 744 | for combinations of the values of `completion-auto-wrap' and | ||
| 745 | `completions-format' (see bug#78959 for motivation and discussion of the | ||
| 746 | expected behavior). The tests are actually run by calling this | ||
| 747 | function, with specific values of N (> 1 to have a \"*Completions*\" | ||
| 748 | buffer), from functions defined by `ert-deftest.'" | ||
| 749 | (let* ( | ||
| 750 | ;; Make list of N unique completions. | ||
| 751 | (letters (mapcar 'string (number-sequence 97 122))) | ||
| 752 | (gen-compl (lambda (x) | ||
| 753 | (let (comps) | ||
| 754 | (dotimes (_ x) | ||
| 755 | (push (concat (car comps) (pop letters)) comps)) | ||
| 756 | (nreverse comps)))) | ||
| 757 | (completions (funcall gen-compl n)) | ||
| 758 | |||
| 759 | ;; Navigation tests. | ||
| 760 | ;; (i) For both horizontal and vertical formats. | ||
| 761 | (all-completions | ||
| 762 | (lambda (type) | ||
| 763 | (let ((next-fn (pcase type | ||
| 764 | ('any 'next-completion) | ||
| 765 | ('column 'next-column-completion) | ||
| 766 | ('line 'next-line-completion))) | ||
| 767 | (prev-fn (pcase type | ||
| 768 | ('any 'previous-completion) | ||
| 769 | ('column 'previous-column-completion) | ||
| 770 | ('line 'previous-line-completion)))) | ||
| 771 | (completing-read-with-minibuffer-setup completions | ||
| 772 | (insert (car completions)) | ||
| 773 | (minibuffer-completion-help) | ||
| 774 | (switch-to-completions) | ||
| 775 | ;; Sanity check that we're on first completion candidate. | ||
| 776 | (should | ||
| 777 | (equal (car completions) | ||
| 778 | (get-text-property (point) 'completion--string))) | ||
| 779 | ;; Double check. | ||
| 780 | (first-completion) | ||
| 781 | (should | ||
| 782 | (equal (car completions) | ||
| 783 | (get-text-property (point) 'completion--string))) | ||
| 784 | ;; Test moving from first to Ith next completion | ||
| 785 | ;; candidate (0<I<N-1). | ||
| 786 | (dolist (i (number-sequence 1 (1- n))) | ||
| 787 | (funcall next-fn i) | ||
| 788 | (should | ||
| 789 | (equal (nth i completions) | ||
| 790 | (get-text-property (point) 'completion--string))) | ||
| 791 | (if (< i (1- n)) | ||
| 792 | (first-completion) | ||
| 793 | (last-completion) | ||
| 794 | (should | ||
| 795 | (equal (nth i completions) | ||
| 796 | (get-text-property (point) 'completion--string))) | ||
| 797 | (funcall next-fn 1) | ||
| 798 | (if completion-auto-wrap | ||
| 799 | ;; Wrap around to first completion candidate. | ||
| 800 | (should | ||
| 801 | (equal (car completions) | ||
| 802 | (get-text-property (point) 'completion--string))) | ||
| 803 | ;; No wrapping. | ||
| 804 | (should | ||
| 805 | (equal (nth i completions) | ||
| 806 | (get-text-property (point) 'completion--string)))))) | ||
| 807 | (last-completion) | ||
| 808 | ;; Test moving from last to Ith previous completion | ||
| 809 | ;; candidate (0<I<N-1). | ||
| 810 | (dolist (i (number-sequence 1 (1- n))) | ||
| 811 | (funcall prev-fn i) | ||
| 812 | (should | ||
| 813 | (equal (nth (- n i 1) completions) | ||
| 814 | (get-text-property (point) 'completion--string))) | ||
| 815 | (if (< i (1- n)) | ||
| 816 | (last-completion) | ||
| 817 | (first-completion) | ||
| 818 | (should | ||
| 819 | (equal (car completions) | ||
| 820 | (get-text-property (point) 'completion--string))) | ||
| 821 | (funcall prev-fn 1) | ||
| 822 | (if completion-auto-wrap | ||
| 823 | ;; Wrap around to last completion candidate. | ||
| 824 | (should | ||
| 825 | (equal (nth (1- n) completions) | ||
| 826 | (get-text-property (point) 'completion--string))) | ||
| 827 | ;; No wrapping. | ||
| 828 | (should | ||
| 829 | (equal (car completions) | ||
| 830 | (get-text-property (point) | ||
| 831 | 'completion--string)))))))))) | ||
| 832 | |||
| 833 | ;; (ii) Only for horizontal format. | ||
| 834 | (within-column | ||
| 835 | (lambda () | ||
| 836 | (completing-read-with-minibuffer-setup completions | ||
| 837 | (insert (car completions)) | ||
| 838 | (minibuffer-completion-help) | ||
| 839 | (switch-to-completions) | ||
| 840 | (while (not (eolp)) | ||
| 841 | (completion--move-to-candidate-start) | ||
| 842 | (let* ((first (get-text-property (point) 'completion--string)) | ||
| 843 | (pos (point)) | ||
| 844 | (i 0) | ||
| 845 | last1 last2) | ||
| 846 | ;; Keep moving to next completion candidate in this | ||
| 847 | ;; column until we reach the last one, and then wrap | ||
| 848 | ;; back to the first candidate, if | ||
| 849 | ;; `completion-auto-wrap' is non-nil, otherwise stay | ||
| 850 | ;; on the last one. | ||
| 851 | (while (or (and completion-auto-wrap | ||
| 852 | (not (equal last1 first))) | ||
| 853 | (not (equal last1 | ||
| 854 | (get-text-property | ||
| 855 | (point) 'completion--string)))) | ||
| 856 | (setq last2 last1) | ||
| 857 | (next-line-completion 1) | ||
| 858 | (incf i) | ||
| 859 | ;; Set `last1' to either the first or last | ||
| 860 | ;; candidate, depending on the value of | ||
| 861 | ;; `completion-auto-wrap'. | ||
| 862 | (setq last1 (get-text-property (point) 'completion--string))) | ||
| 863 | (setq last1 last2 | ||
| 864 | last2 nil) | ||
| 865 | (decf i) | ||
| 866 | (when completion-auto-wrap | ||
| 867 | (should (equal (get-text-property (point) 'completion--string) | ||
| 868 | first)) | ||
| 869 | ;; Test wrapping from last to first line in this column. | ||
| 870 | (next-line-completion i) ; Move to last candidate. | ||
| 871 | (should (equal (get-text-property (point) 'completion--string) | ||
| 872 | last1))) | ||
| 873 | ;; Now keeping move from last to first completion | ||
| 874 | ;; candidate in this column. | ||
| 875 | (while (or (not (equal last2 first)) | ||
| 876 | (not (equal last2 | ||
| 877 | (get-text-property | ||
| 878 | (point) 'completion--string)))) | ||
| 879 | (previous-line-completion 1) | ||
| 880 | (setq last2 (get-text-property (point) 'completion--string))) | ||
| 881 | ;; Test wrapping from first to last line in this column. | ||
| 882 | (when completion-auto-wrap | ||
| 883 | (should (equal (get-text-property (point) 'completion--string) | ||
| 884 | first)) | ||
| 885 | (next-line-completion i) | ||
| 886 | (should (equal (get-text-property (point) 'completion--string) | ||
| 887 | last1))) | ||
| 888 | ;; Move to first candidate in next column to continue loop | ||
| 889 | (completion--move-to-candidate-end) | ||
| 890 | (unless (eolp) | ||
| 891 | (goto-char pos) | ||
| 892 | (next-column-completion 1))))))) | ||
| 893 | |||
| 894 | ;; (iii) Only for vertical format. | ||
| 895 | (within-line | ||
| 896 | (lambda () | ||
| 897 | (completing-read-with-minibuffer-setup completions | ||
| 898 | (insert (car completions)) | ||
| 899 | (minibuffer-completion-help) | ||
| 900 | (switch-to-completions) | ||
| 901 | (let ((one-col (save-excursion | ||
| 902 | (first-completion) | ||
| 903 | (completion--move-to-candidate-end) | ||
| 904 | (eolp)))) | ||
| 905 | (while (not (eobp)) | ||
| 906 | (let ((first (get-text-property (point) 'completion--string)) | ||
| 907 | last pos) | ||
| 908 | ;; Test moving to next column in this line. | ||
| 909 | (while (not (eolp)) | ||
| 910 | (next-column-completion 1) | ||
| 911 | (let ((next (get-text-property (point) 'completion--string))) | ||
| 912 | (should | ||
| 913 | ;; FIXME: tautology? | ||
| 914 | (equal (nth (seq-position completions next) completions) | ||
| 915 | next))) | ||
| 916 | ;; If this is the last completion in this line, | ||
| 917 | ;; exit the loop. | ||
| 918 | (when (or (> (current-column) 0) | ||
| 919 | (save-excursion (and (forward-line) (eobp))) | ||
| 920 | (unless one-col | ||
| 921 | (save-excursion | ||
| 922 | (and (progn | ||
| 923 | (completion--move-to-candidate-start) | ||
| 924 | (bolp)) | ||
| 925 | (progn | ||
| 926 | (completion--move-to-candidate-end) | ||
| 927 | (eolp)))))) | ||
| 928 | (completion--move-to-candidate-end))) | ||
| 929 | (backward-char) | ||
| 930 | (completion--move-to-candidate-start) | ||
| 931 | (setq last (get-text-property (point) 'completion--string) | ||
| 932 | pos (point)) | ||
| 933 | ;; We're on the last column, so next move either | ||
| 934 | ;; wraps or stays put. | ||
| 935 | (next-column-completion 1) | ||
| 936 | (if completion-auto-wrap | ||
| 937 | ;; We wrapped around to first candidate in this line. | ||
| 938 | (progn | ||
| 939 | (should (bolp)) | ||
| 940 | (should | ||
| 941 | (equal (get-text-property (point) 'completion--string) | ||
| 942 | first)) | ||
| 943 | ;; Go back to last completion in this line for next test. | ||
| 944 | (goto-char (if one-col pos (pos-eol))) | ||
| 945 | (backward-char)) | ||
| 946 | (should | ||
| 947 | (equal (get-text-property (point) 'completion--string) | ||
| 948 | last))) | ||
| 949 | ;; Test moving to previous column in this line. | ||
| 950 | (while (if one-col | ||
| 951 | (save-excursion | ||
| 952 | (forward-line -1) | ||
| 953 | (get-text-property (point) 'completion--string)) | ||
| 954 | (not (bolp))) | ||
| 955 | (previous-column-completion 1) | ||
| 956 | (let ((prev (get-text-property (point) 'completion--string))) | ||
| 957 | (should | ||
| 958 | ;; FIXME: tautology? | ||
| 959 | (equal (nth (seq-position completions prev) completions) | ||
| 960 | prev)))) | ||
| 961 | ;; We're on the first column, so next move either | ||
| 962 | ;; wraps or stay put. | ||
| 963 | (previous-column-completion 1) | ||
| 964 | (if completion-auto-wrap | ||
| 965 | ;; We wrapped around to last candidate in this line. | ||
| 966 | (progn | ||
| 967 | (completion--move-to-candidate-end) | ||
| 968 | (should (eolp)) | ||
| 969 | (backward-char) | ||
| 970 | (should | ||
| 971 | (equal (get-text-property (point) 'completion--string) | ||
| 972 | last))) | ||
| 973 | ;; We stayed on the first candidate. | ||
| 974 | (should | ||
| 975 | (equal (get-text-property (point) 'completion--string) | ||
| 976 | first))) | ||
| 977 | (if one-col | ||
| 978 | (goto-char (point-max)) | ||
| 979 | (forward-line))))))))) | ||
| 980 | |||
| 981 | ;; Run tests. | ||
| 982 | ;; Test navigation with wrapping... | ||
| 983 | (let ((completion-auto-wrap t)) | ||
| 984 | ;; ...in horizontal format, | ||
| 985 | (let ((completions-format 'horizontal)) | ||
| 986 | (funcall all-completions 'any) | ||
| 987 | (funcall all-completions 'column) | ||
| 988 | (funcall within-column)) | ||
| 989 | ;; ...in vertical format. | ||
| 990 | (let ((completions-format 'vertical)) | ||
| 991 | (funcall all-completions 'any) | ||
| 992 | (funcall all-completions 'line) | ||
| 993 | (funcall within-line))) | ||
| 994 | ;; Test navigation without wrapping... | ||
| 995 | (let ((completion-auto-wrap nil)) | ||
| 996 | ;; ...in horizontal format, | ||
| 997 | (let ((completions-format 'horizontal)) | ||
| 998 | (funcall all-completions 'any) | ||
| 999 | (funcall all-completions 'column) | ||
| 1000 | (funcall within-column)) | ||
| 1001 | ;; ...in vertical format. | ||
| 1002 | (let ((completions-format 'vertical)) | ||
| 1003 | (funcall all-completions 'any) | ||
| 1004 | (funcall all-completions 'line) | ||
| 1005 | (funcall within-line))))) | ||
| 1006 | |||
| 1007 | ;; (ert-deftest completions-format-navigation-test-1 () | ||
| 1008 | ;; (completions-format-navigation--tests 1)) | ||
| 1009 | |||
| 1010 | (ert-deftest completions-format-navigation-test-2 () | ||
| 1011 | (completions-format-navigation--tests 2)) | ||
| 1012 | |||
| 1013 | (ert-deftest completions-format-navigation-test-3 () | ||
| 1014 | (completions-format-navigation--tests 3)) | ||
| 1015 | |||
| 1016 | (ert-deftest completions-format-navigation-test-4 () | ||
| 1017 | (completions-format-navigation--tests 4)) | ||
| 1018 | |||
| 1019 | (ert-deftest completions-format-navigation-test-5 () | ||
| 1020 | (completions-format-navigation--tests 5)) | ||
| 1021 | |||
| 1022 | (ert-deftest completions-format-navigation-test-9 () | ||
| 1023 | (completions-format-navigation--tests 9)) | ||
| 1024 | |||
| 1025 | (ert-deftest completions-format-navigation-test-10 () | ||
| 1026 | (completions-format-navigation--tests 10)) | ||
| 1027 | |||
| 1028 | (ert-deftest completions-format-navigation-test-15 () | ||
| 1029 | (completions-format-navigation--tests 15)) | ||
| 1030 | |||
| 1031 | (ert-deftest completions-format-navigation-test-16 () | ||
| 1032 | (completions-format-navigation--tests 16)) | ||
| 1033 | |||
| 741 | (ert-deftest completion-cycle () | 1034 | (ert-deftest completion-cycle () |
| 742 | (completing-read-with-minibuffer-setup '("aaa" "bbb" "ccc") | 1035 | (completing-read-with-minibuffer-setup '("aaa" "bbb" "ccc") |
| 743 | (let ((completion-cycle-threshold t)) | 1036 | (let ((completion-cycle-threshold t)) |