diff options
Diffstat (limited to 'lisp/minibuffer.el')
-rw-r--r-- | lisp/minibuffer.el | 2334 |
1 files changed, 1694 insertions, 640 deletions
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index f13f1fa7984..9f26e4f7f98 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -1,6 +1,6 @@ -;;; minibuffer.el --- Minibuffer completion functions -*- lexical-binding: t -*- +;;; minibuffer.el --- Minibuffer and completion functions -*- lexical-binding: t -*- -;; Copyright (C) 2008-2017 Free Software Foundation, Inc. +;; Copyright (C) 2008-2022 Free Software Foundation, Inc. ;; Author: Stefan Monnier <monnier@iro.umontreal.ca> ;; Package: emacs @@ -83,7 +83,6 @@ ;; - add support for ** to pcm. ;; - Add vc-file-name-completion-table to read-file-name-internal. -;; - A feature like completing-help.el. ;;; Code: @@ -121,6 +120,19 @@ This metadata is an alist. Currently understood keys are: - `annotation-function': function to add annotations in *Completions*. Takes one argument (STRING), which is a possible completion and returns a string to append to STRING. +- `affixation-function': function to prepend/append a prefix/suffix to + entries. Takes one argument (COMPLETIONS) and should return a list + of annotated completions. The elements of the list must be + three-element lists: completion, its prefix and suffix. This + function takes priority over `annotation-function' when both are + provided, so only this function is used. +- `group-function': function for grouping the completion candidates. + Takes two arguments: a completion candidate (COMPLETION) and a + boolean flag (TRANSFORM). If TRANSFORM is nil, the function + returns the group title of the group to which the candidate + belongs. The returned title may be nil. Otherwise the function + returns the transformed candidate. The transformation can remove a + redundant prefix, which is displayed in the group title. - `display-sort-function': function to sort entries in *Completions*. Takes one argument (COMPLETIONS) and should return a new list of completions. Can operate destructively. @@ -129,9 +141,9 @@ This metadata is an alist. Currently understood keys are: The metadata of a completion table should be constant between two boundaries." (let ((metadata (if (functionp table) (funcall table string pred 'metadata)))) - (if (eq (car-safe metadata) 'metadata) - metadata - '(metadata)))) + (cons 'metadata + (if (eq (car-safe metadata) 'metadata) + (cdr metadata))))) (defun completion--field-metadata (field-start) (completion-metadata (buffer-substring-no-properties field-start (point)) @@ -154,14 +166,20 @@ Like CL's `some'." (or res (if firsterror (signal (car firsterror) (cdr firsterror)))))) -(defun complete-with-action (action table string pred) - "Perform completion ACTION. -STRING is the string to complete. -TABLE is the completion table. -PRED is a completion predicate. -ACTION can be one of nil, t or `lambda'." +(defun complete-with-action (action collection string predicate) + "Perform completion according to ACTION. +STRING, COLLECTION and PREDICATE are used as in `try-completion'. + +If COLLECTION is a function, it will be called directly to +perform completion, no matter what ACTION is. + +If ACTION is `metadata' or a list where the first element is +`boundaries', return nil. If ACTION is nil, this function works +like `try-completion'; if it is t, this function works like +`all-completion'; and any other value makes it work like +`test-completion'." (cond - ((functionp table) (funcall table string pred action)) + ((functionp collection) (funcall collection string predicate action)) ((eq (car-safe action) 'boundaries) nil) ((eq action 'metadata) nil) (t @@ -170,14 +188,18 @@ ACTION can be one of nil, t or `lambda'." ((null action) 'try-completion) ((eq action t) 'all-completions) (t 'test-completion)) - string table pred)))) + string collection predicate)))) (defun completion-table-dynamic (fun &optional switch-buffer) "Use function FUN as a dynamic completion table. -FUN is called with one argument, the string for which completion is required, -and it should return an alist containing all the intended possible completions. -This alist may be a full list of possible completions so that FUN can ignore -the value of its argument. +FUN is called with one argument, the string for which completion is requested, +and it should return a completion table containing all the intended possible +completions. +This table is allowed to include elements that do not actually match the +string: they will be automatically filtered out. +The completion table returned by FUN can use any of the usual formats of +completion tables such as lists, alists, and hash-tables. + If SWITCH-BUFFER is non-nil and completion is performed in the minibuffer, FUN will be called in the buffer from which the minibuffer was entered. @@ -185,6 +207,8 @@ was entered. The result of the `completion-table-dynamic' form is a function that can be used as the COLLECTION argument to `try-completion' and `all-completions'. See Info node `(elisp)Programmed Completion'. +The completion table returned by `completion-table-dynamic' has empty +metadata and trivial boundaries. See also the related function `completion-table-with-cache'." (lambda (string pred action) @@ -254,16 +278,17 @@ the form (concat S2 S)." (let* ((str (if (string-prefix-p s1 string completion-ignore-case) (concat s2 (substring string (length s1))))) (res (if str (complete-with-action action table str pred)))) - (when res + (when (or res (eq (car-safe action) 'boundaries)) (cond ((eq (car-safe action) 'boundaries) (let ((beg (or (and (eq (car-safe res) 'boundaries) (cadr res)) 0))) `(boundaries - ,(max (length s1) - (+ beg (- (length s1) (length s2)))) + ,(min (length string) + (max (length s1) + (+ beg (- (length s1) (length s2))))) . ,(and (eq (car-safe res) 'boundaries) (cddr res))))) ((stringp res) - (if (string-prefix-p s2 string completion-ignore-case) + (if (string-prefix-p s2 res completion-ignore-case) (concat s1 (substring res (length s2))))) ((eq action t) (let ((bounds (completion-boundaries str table pred ""))) @@ -392,7 +417,7 @@ obeys predicates." (and (funcall pred1 x) (funcall pred2 x))))) ;; If completion failed and we're not applying pred1 strictly, try ;; again without pred1. - (and (not strict) pred1 pred2 + (and (not strict) pred1 (complete-with-action action table string pred2)))))) (defun completion-table-in-turn (&rest tables) @@ -471,8 +496,17 @@ for use at QPOS." (qsuffix (cdr action)) (ufull (if (zerop (length qsuffix)) ustring (funcall unquote (concat string qsuffix)))) - (_ (cl-assert (string-prefix-p ustring ufull))) - (usuffix (substring ufull (length ustring))) + ;; If (not (string-prefix-p ustring ufull)) we have a problem: + ;; unquoting the qfull gives something "unrelated" to ustring. + ;; E.g. "~/" and "/" where "~//" gets unquoted to just "/" (see + ;; bug#47678). + ;; In that case we can't even tell if we're right before the + ;; "/" or right after it (aka if this "/" is from qstring or + ;; from qsuffix), thus which usuffix to use is very unclear. + (usuffix (if (string-prefix-p ustring ufull) + (substring ufull (length ustring)) + ;; FIXME: Maybe "" is preferable/safer? + qsuffix)) (boundaries (completion-boundaries ustring table pred usuffix)) (qlboundary (car (funcall requote (car boundaries) string))) (qrboundary (if (zerop (cdr boundaries)) 0 ;Common case. @@ -600,9 +634,6 @@ for use at QPOS." (let ((qstr (funcall qfun completion))) (cons qstr (length qstr)))))) -(defun completion--string-equal-p (s1 s2) - (eq t (compare-strings s1 nil nil s2 nil nil 'ignore-case))) - (defun completion--twq-all (string ustring completions boundary _unquote requote) (when completions @@ -616,7 +647,7 @@ for use at QPOS." (qfullprefix (substring string 0 qfullpos)) ;; FIXME: This assertion can be wrong, e.g. in Cygwin, where ;; (unquote "c:\bin") => "/usr/bin" but (unquote "c:\") => "/". - ;;(cl-assert (completion--string-equal-p + ;;(cl-assert (string-equal-ignore-case ;; (funcall unquote qfullprefix) ;; (concat (substring ustring 0 boundary) prefix)) ;; t)) @@ -654,7 +685,7 @@ for use at QPOS." (let* ((rest (substring completion 0 (length prefix))) (qrest (funcall qfun rest))) - (if (completion--string-equal-p qprefix qrest) + (if (string-equal-ignore-case qprefix qrest) (propertize qrest 'face 'completions-common-part) qprefix)))) @@ -662,7 +693,7 @@ for use at QPOS." ;; FIXME: Similarly here, Cygwin's mapping trips this ;; assertion. ;;(cl-assert - ;; (completion--string-equal-p + ;; (string-equal-ignore-case ;; (funcall unquote ;; (concat (substring string 0 qboundary) ;; qcompletion)) @@ -673,13 +704,6 @@ for use at QPOS." completions) qboundary)))) -;; (defmacro complete-in-turn (a b) `(completion-table-in-turn ,a ,b)) -;; (defmacro dynamic-completion-table (fun) `(completion-table-dynamic ,fun)) -(define-obsolete-function-alias - 'complete-in-turn 'completion-table-in-turn "23.1") -(define-obsolete-function-alias - 'dynamic-completion-table 'completion-table-dynamic "23.1") - ;;; Minibuffer completion (defgroup minibuffer nil @@ -687,16 +711,19 @@ for use at QPOS." :link '(custom-manual "(emacs)Minibuffer") :group 'environment) +(defvar minibuffer-message-properties nil + "Text properties added to the text shown by `minibuffer-message'.") + (defun minibuffer-message (message &rest args) "Temporarily display MESSAGE at the end of the minibuffer. The text is displayed for `minibuffer-message-timeout' seconds, or until the next input event arrives, whichever comes first. Enclose MESSAGE in [...] if this is not yet the case. If ARGS are provided, then pass MESSAGE through `format-message'." - (if (not (minibufferp (current-buffer))) + (if (not (minibufferp (current-buffer) t)) (progn (if args - (apply 'message message args) + (apply #'message message args) (message "%s" message)) (prog1 (sit-for (or minibuffer-message-timeout 1000000)) (message nil))) @@ -708,14 +735,20 @@ If ARGS are provided, then pass MESSAGE through `format-message'." (copy-sequence message) (concat " [" message "]"))) (when args (setq message (apply #'format-message message args))) - (let ((ol (make-overlay (point-max) (point-max) nil t t)) - ;; A quit during sit-for normally only interrupts the sit-for, - ;; but since minibuffer-message is used at the end of a command, - ;; at a time when the command has virtually finished already, a C-g - ;; should really cause an abort-recursive-edit instead (i.e. as if - ;; the C-g had been typed at top-level). Binding inhibit-quit here - ;; is an attempt to get that behavior. - (inhibit-quit t)) + (unless (or (null minibuffer-message-properties) + ;; Don't overwrite the face properties the caller has set + (text-properties-at 0 message)) + (setq message (apply #'propertize message minibuffer-message-properties))) + ;; Put overlay either on `minibuffer-message' property, or at EOB. + (let* ((ovpos (minibuffer--message-overlay-pos)) + (ol (make-overlay ovpos ovpos nil t t)) + ;; A quit during sit-for normally only interrupts the sit-for, + ;; but since minibuffer-message is used at the end of a command, + ;; at a time when the command has virtually finished already, a C-g + ;; should really cause an abort-recursive-edit instead (i.e. as if + ;; the C-g had been typed at top-level). Binding inhibit-quit here + ;; is an attempt to get that behavior. + (inhibit-quit t)) (unwind-protect (progn (unless (zerop (length message)) @@ -724,12 +757,122 @@ If ARGS are provided, then pass MESSAGE through `format-message'." ;; before or after the string, so let's spoon-feed it the pos. (put-text-property 0 1 'cursor t message)) (overlay-put ol 'after-string message) + ;; Make sure the overlay with the message is displayed before + ;; any other overlays in that position, in case they have + ;; resize-mini-windows set to nil and the other overlay strings + ;; are too long for the mini-window width. This makes sure the + ;; temporary message will always be visible. + (overlay-put ol 'priority 1100) (sit-for (or minibuffer-message-timeout 1000000))) (delete-overlay ol))))) +(defcustom minibuffer-message-clear-timeout nil + "How long to display an echo-area message when the minibuffer is active. +If the value is a number, it is the time in seconds after which to +remove the echo-area message from the active minibuffer. +If the value is not a number, such messages are never removed, +and their text is displayed until the next input event arrives. +Unlike `minibuffer-message-timeout' used by `minibuffer-message', +this option affects the pair of functions `set-minibuffer-message' +and `clear-minibuffer-message' called automatically via +`set-message-function' and `clear-message-function'." + :type '(choice (const :tag "Never time out" nil) + (integer :tag "Wait for the number of seconds" 2)) + :version "27.1") + +(defvar minibuffer-message-timer nil) +(defvar minibuffer-message-overlay nil) + +(defun minibuffer--message-overlay-pos () + "Return position where minibuffer message functions shall put message overlay. +The minibuffer message functions include `minibuffer-message' and +`set-minibuffer-message'." + ;; Starting from point, look for non-nil `minibuffer-message' + ;; property, and return its position. If none found, return the EOB + ;; position. + (let* ((pt (point)) + (propval (get-text-property pt 'minibuffer-message))) + (if propval pt + (next-single-property-change pt 'minibuffer-message nil (point-max))))) + +(defun set-minibuffer-message (message) + "Temporarily display MESSAGE at the end of the minibuffer. +If some part of the minibuffer text has the `minibuffer-message' property, +the message will be displayed before the first such character, instead of +at the end of the minibuffer. +The text is displayed for `minibuffer-message-clear-timeout' seconds +\(if the value is a number), or until the next input event arrives, +whichever comes first. +Unlike `minibuffer-message', this function is called automatically +via `set-message-function'." + (let* ((minibuf-window (active-minibuffer-window)) + (minibuf-frame (and (window-live-p minibuf-window) + (window-frame minibuf-window)))) + (when (and (not noninteractive) + (window-live-p minibuf-window) + (or (eq (window-frame) minibuf-frame) + (eq (frame-parameter minibuf-frame 'minibuffer) 'only))) + (with-current-buffer (window-buffer minibuf-window) + (setq message (if (string-match-p "\\` *\\[.+\\]\\'" message) + ;; Make sure we can put-text-property. + (copy-sequence message) + (concat " [" message "]"))) + (unless (or (null minibuffer-message-properties) + ;; Don't overwrite the face properties the caller has set + (text-properties-at 0 message)) + (setq message + (apply #'propertize message minibuffer-message-properties))) + + (clear-minibuffer-message) + + (let ((ovpos (minibuffer--message-overlay-pos))) + (setq minibuffer-message-overlay + (make-overlay ovpos ovpos nil t t))) + (unless (zerop (length message)) + ;; The current C cursor code doesn't know to use the overlay's + ;; marker's stickiness to figure out whether to place the cursor + ;; before or after the string, so let's spoon-feed it the pos. + (put-text-property 0 1 'cursor t message)) + (overlay-put minibuffer-message-overlay 'after-string message) + ;; Make sure the overlay with the message is displayed before + ;; any other overlays in that position, in case they have + ;; resize-mini-windows set to nil and the other overlay strings + ;; are too long for the mini-window width. This makes sure the + ;; temporary message will always be visible. + (overlay-put minibuffer-message-overlay 'priority 1100) + + (when (numberp minibuffer-message-clear-timeout) + (setq minibuffer-message-timer + (run-with-timer minibuffer-message-clear-timeout nil + #'clear-minibuffer-message))) + + ;; Return t telling the caller that the message + ;; was handled specially by this function. + t)))) + +(setq set-message-function 'set-minibuffer-message) + +(defun clear-minibuffer-message () + "Clear minibuffer message. +Intended to be called via `clear-message-function'." + (when (not noninteractive) + (when (timerp minibuffer-message-timer) + (cancel-timer minibuffer-message-timer) + (setq minibuffer-message-timer nil)) + (when (overlayp minibuffer-message-overlay) + (delete-overlay minibuffer-message-overlay) + (setq minibuffer-message-overlay nil))) + + ;; Return nil telling the caller that the message + ;; should be also handled by the caller. + nil) + +(setq clear-message-function 'clear-minibuffer-message) + (defun minibuffer-completion-contents () "Return the user input in a minibuffer before point as a string. -In Emacs-22, that was what completion commands operated on." +In Emacs 22, that was what completion commands operated on. +If the current buffer is not a minibuffer, return everything before point." (declare (obsolete nil "24.4")) (buffer-substring (minibuffer-prompt-end) (point))) @@ -741,18 +884,36 @@ If the current buffer is not a minibuffer, erase its entire contents." ;; is on, the field doesn't cover the entire minibuffer contents. (delete-region (minibuffer-prompt-end) (point-max))) +(defun minibuffer--completion-prompt-end () + (let ((end (minibuffer-prompt-end))) + (if (< (point) end) + (user-error "Can't complete in prompt") + end))) + (defvar completion-show-inline-help t "If non-nil, print helpful inline messages during completion.") (defcustom completion-auto-help t "Non-nil means automatically provide help for invalid completion input. -If the value is t the *Completions* buffer is displayed whenever completion +If the value is t, the *Completions* buffer is displayed whenever completion is requested but cannot be done. If the value is `lazy', the *Completions* buffer is only displayed after -the second failed attempt to complete." - :type '(choice (const nil) (const t) (const lazy))) - -(defconst completion-styles-alist +the second failed attempt to complete. +If the value is `always', the *Completions* buffer is always shown +after a completion attempt, and the list of completions is updated if +already visible. +If the value is `visible', the *Completions* buffer is displayed +whenever completion is requested but cannot be done for the first time, +but remains visible thereafter, and the list of completions in it is +updated for subsequent attempts to complete.." + :type '(choice (const :tag "Don't show" nil) + (const :tag "Show only when cannot complete" t) + (const :tag "Show after second failed completion attempt" lazy) + (const :tag + "Leave visible after first failed completion" visible) + (const :tag "Always visible" always))) + +(defvar completion-styles-alist '((emacs21 completion-emacs21-try-completion completion-emacs21-all-completions "Simple prefix-based completion. @@ -787,11 +948,21 @@ Additionally the user can use the char \"*\" as a glob pattern.") I.e. when completing \"foo_bar\" (where _ is the position of point), it will consider all completions candidates matching the glob pattern \"*foo*bar*\".") + (flex + completion-flex-try-completion completion-flex-all-completions + "Completion of an in-order subset of characters. +When completing \"foo\" the glob \"*f*o*o*\" is used, so that +\"foo\" can complete to \"frodo\".") (initials completion-initials-try-completion completion-initials-all-completions "Completion of acronyms and initialisms. E.g. can complete M-x lch to list-command-history -and C-x C-f ~/sew to ~/src/emacs/work.")) +and C-x C-f ~/sew to ~/src/emacs/work.") + (shorthand + completion-shorthand-try-completion completion-shorthand-all-completions + "Completion of symbol shorthands setup in `read-symbol-shorthands'. +E.g. can complete \"x-foo\" to \"xavier-foo\" if the shorthand +((\"x-\" . \"xavier-\")) is set up in the buffer of origin.")) "List of available completion styles. Each element has the form (NAME TRY-COMPLETION ALL-COMPLETIONS DOC): where NAME is the name that should be used in `completion-styles', @@ -827,22 +998,32 @@ and DOC describes the way this style of completion works.") The available styles are listed in `completion-styles-alist'. Note that `completion-category-overrides' may override these -styles for specific categories, such as files, buffers, etc." +styles for specific categories, such as files, buffers, etc. + +Note that Tramp host name completion (e.g., \"/ssh:ho<TAB>\") +currently doesn't work if this list doesn't contain at least one +of `basic', `emacs22' or `emacs21'." :type completion--styles-type :version "23.1") (defvar completion-category-defaults '((buffer (styles . (basic substring))) (unicode-name (styles . (basic substring))) - (project-file (styles . (basic substring))) - (info-menu (styles . (basic substring)))) + ;; A new style that combines substring and pcm might be better, + ;; e.g. one that does not anchor to bos. + (project-file (styles . (substring))) + (xref-location (styles . (substring))) + (info-menu (styles . (basic substring))) + (symbol-help (styles . (basic shorthand substring)))) "Default settings for specific completion categories. Each entry has the shape (CATEGORY . ALIST) where ALIST is an association list that can specify properties such as: - `styles': the list of `completion-styles' to use for that category. - `cycle': the `completion-cycle-threshold' to use for that category. Categories are symbols such as `buffer' and `file', used when -completing buffer and file names, respectively.") +completing buffer and file names, respectively. + +Also see `completion-category-overrides'.") (defcustom completion-category-overrides nil "List of category-specific user overrides for completion styles. @@ -852,7 +1033,9 @@ an association list that can specify properties such as: - `cycle': the `completion-cycle-threshold' to use for that category. Categories are symbols such as `buffer' and `file', used when completing buffer and file names, respectively. -This overrides the defaults specified in `completion-category-defaults'." + +If a property in a category is specified by this variable, it +overrides the default specified in `completion-category-defaults'." :version "25.1" :type `(alist :key-type (choice :tag "Category" (const buffer) @@ -882,9 +1065,6 @@ This overrides the defaults specified in `completion-category-defaults'." (defun completion--nth-completion (n string table pred point metadata) "Call the Nth method of completion styles." - (unless metadata - (setq metadata - (completion-metadata (substring string 0 point) table pred))) ;; We provide special support for quoting/unquoting here because it cannot ;; reliably be done within the normal completion-table routines: Completion ;; styles such as `substring' or `partial-completion' need to match the @@ -895,24 +1075,40 @@ This overrides the defaults specified in `completion-category-defaults'." ;; The quote/unquote function needs to come from the completion table (rather ;; than from completion-extra-properties) because it may apply only to some ;; part of the string (e.g. substitute-in-file-name). - (let ((requote - (when (completion-metadata-get metadata 'completion--unquote-requote) - (cl-assert (functionp table)) - (let ((new (funcall table string point 'completion--unquote))) - (setq string (pop new)) - (setq table (pop new)) - (setq point (pop new)) - (cl-assert (<= point (length string))) - (pop new)))) - (result - (completion--some (lambda (style) - (funcall (nth n (assq style - completion-styles-alist)) - string table pred point)) - (completion--styles metadata)))) + (let* ((md (or metadata + (completion-metadata (substring string 0 point) table pred))) + (requote + (when (and + (completion-metadata-get md 'completion--unquote-requote) + ;; Sometimes a table's metadata is used on another + ;; table (typically that other table is just a list taken + ;; from the output of `all-completions' or something + ;; equivalent, for progressive refinement). + ;; See bug#28898 and bug#16274. + ;; FIXME: Rather than do nothing, we should somehow call + ;; the original table, in that case! + (functionp table)) + (let ((new (funcall table string point 'completion--unquote))) + (setq string (pop new)) + (setq table (pop new)) + (setq point (pop new)) + (cl-assert (<= point (length string))) + (pop new)))) + (result-and-style + (completion--some + (lambda (style) + (let ((probe (funcall + (or (nth n (assq style completion-styles-alist)) + (error "Invalid completion style %s" style)) + string table pred point))) + (and probe (cons probe style)))) + (completion--styles md))) + (adjust-fn (get (cdr result-and-style) 'completion--adjust-metadata))) + (when (and adjust-fn metadata) + (setcdr metadata (cdr (funcall adjust-fn metadata)))) (if requote - (funcall requote result n) - result))) + (funcall requote (car result-and-style) n) + (car result-and-style)))) (defun completion-try-completion (string table pred point &optional metadata) "Try to complete STRING using completion table TABLE. @@ -942,10 +1138,17 @@ in the last `cdr'." (defun completion--replace (beg end newtext) "Replace the buffer text between BEG and END with NEWTEXT. Moves point to the end of the new text." - ;; The properties on `newtext' include things like - ;; completions-first-difference, which we don't want to include - ;; upon insertion. - (set-text-properties 0 (length newtext) nil newtext) + ;; The properties on `newtext' include things like the + ;; `completions-first-difference' face, which we don't want to + ;; include upon insertion. + (setq newtext (copy-sequence newtext)) ;Don't modify the arg by side-effect. + (if minibuffer-allow-text-properties + ;; If we're preserving properties, then just remove the faces + ;; and other properties added by the completion machinery. + (remove-text-properties 0 (length newtext) '(face completion-score) + newtext) + ;; Remove all text properties. + (set-text-properties 0 (length newtext) nil newtext)) ;; Maybe this should be in subr.el. ;; You'd think this is trivial to do, but details matter if you want ;; to keep markers "at the right place" and be robust in the face of @@ -979,7 +1182,8 @@ Moves point to the end of the new text." (defcustom completion-cycle-threshold nil "Number of completion candidates below which cycling is used. Depending on this setting `completion-in-region' may use cycling, -like `minibuffer-force-complete'. +whereby invoking a completion command several times in a row +completes to each of the candidates in turn, in a cyclic manner. If nil, cycling is never used. If t, cycling is always used. If an integer, cycling is used so long as there are not more @@ -987,6 +1191,56 @@ completion candidates than this number." :version "24.1" :type completion--cycling-threshold-type) +(defcustom completions-sort 'alphabetical + "Sort candidates in the *Completions* buffer. + +The value can be nil to disable sorting, `alphabetical' for +alphabetical sorting or a custom sorting function. The sorting +function takes and returns a list of completion candidate +strings." + :type '(choice (const :tag "No sorting" nil) + (const :tag "Alphabetical sorting" alphabetical) + (function :tag "Custom function")) + :version "29.1") + +(defcustom completions-group nil + "Enable grouping of completion candidates in the *Completions* buffer. +See also `completions-group-format' and `completions-group-sort'." + :type 'boolean + :version "28.1") + +(defcustom completions-group-sort nil + "Sort groups in the *Completions* buffer. + +The value can either be nil to disable sorting, `alphabetical' for +alphabetical sorting or a custom sorting function. The sorting +function takes and returns an alist of groups, where each element is a +pair of a group title string and a list of group candidate strings." + :type '(choice (const :tag "No sorting" nil) + (const :tag "Alphabetical sorting" alphabetical) + function) + :version "28.1") + +(defcustom completions-group-format + (concat + (propertize " " 'face 'completions-group-separator) + (propertize " %s " 'face 'completions-group-title) + (propertize " " 'face 'completions-group-separator + 'display '(space :align-to right))) + "Format string used for the group title." + :type 'string + :version "28.1") + +(defface completions-group-title + '((t :inherit shadow :slant italic)) + "Face used for the title text of the candidate group headlines." + :version "28.1") + +(defface completions-group-separator + '((t :inherit shadow :strike-through t)) + "Face used for the separator lines between the candidate groups." + :version "28.1") + (defun completion--cycle-threshold (metadata) (let* ((cat (completion-metadata-get metadata 'category)) (over (completion--category-override cat 'cycle))) @@ -994,7 +1248,8 @@ completion candidates than this number." (defvar-local completion-all-sorted-completions nil) (defvar-local completion--all-sorted-completions-location nil) -(defvar completion-cycling nil) +(defvar completion-cycling nil) ;Function that takes down the cycling map. +(defvar completion-tab-width nil) (defvar completion-fail-discreetly nil "If non-nil, stay quiet when there is no match.") @@ -1026,7 +1281,7 @@ when the buffer's text is already an exact match." (let* ((string (buffer-substring beg end)) (md (completion--field-metadata beg)) (comp (funcall (or try-completion-function - 'completion-try-completion) + #'completion-try-completion) string minibuffer-completion-table minibuffer-completion-predicate @@ -1051,10 +1306,8 @@ when the buffer's text is already an exact match." ;; for appearance, the string is rewritten if the case changes. (let* ((comp-pos (cdr comp)) (completion (car comp)) - (completed (not (eq t (compare-strings completion nil nil - string nil nil t)))) - (unchanged (eq t (compare-strings completion nil nil - string nil nil nil)))) + (completed (not (string-equal-ignore-case completion string))) + (unchanged (string-equal completion string))) (if unchanged (goto-char end) ;; Insert in minibuffer the chars we got. @@ -1106,20 +1359,22 @@ when the buffer's text is already an exact match." (completion--cache-all-sorted-completions beg end comps) (minibuffer-force-complete beg end)) (completed - ;; We could also decide to refresh the completions, - ;; if they're displayed (and assuming there are - ;; completions left). - (minibuffer-hide-completions) - (if exact - ;; If completion did not put point at end of field, - ;; it's a sign that completion is not finished. - (completion--done completion - (if (< comp-pos (length completion)) - 'exact 'unknown)))) + (cond + ((pcase completion-auto-help + ('visible (get-buffer-window "*Completions*" 0)) + ('always t)) + (minibuffer-completion-help beg end)) + (t (minibuffer-hide-completions) + (when exact + ;; If completion did not put point at end of field, + ;; it's a sign that completion is not finished. + (completion--done completion + (if (< comp-pos (length completion)) + 'exact 'unknown)))))) ;; Show the completion table, if requested. ((not exact) (if (pcase completion-auto-help - (`lazy (eq this-command last-command)) + ('lazy (eq this-command last-command)) (_ completion-auto-help)) (minibuffer-completion-help beg end) (completion--message "Next char not unique"))) @@ -1142,10 +1397,9 @@ If no characters can be completed, display a list of possible completions. If you repeat this command after it displayed such a list, scroll the window of possible completions." (interactive) - (when (<= (minibuffer-prompt-end) (point)) - (completion-in-region (minibuffer-prompt-end) (point-max) - minibuffer-completion-table - minibuffer-completion-predicate))) + (completion-in-region (minibuffer--completion-prompt-end) (point-max) + minibuffer-completion-table + minibuffer-completion-predicate)) (defun completion--in-region-1 (beg end) ;; If the previous command was not this, @@ -1162,24 +1416,44 @@ scroll the window of possible completions." (eq t (frame-visible-p (window-frame minibuffer-scroll-window)))) (let ((window minibuffer-scroll-window)) (with-current-buffer (window-buffer window) - (if (pos-visible-in-window-p (point-max) window) - ;; If end is in view, scroll up to the beginning. - (set-window-start window (point-min) nil) - ;; Else scroll down one screen. - (with-selected-window window - (scroll-up))) - nil))) + (cond + ;; Here this is possible only when second-tab, but instead of + ;; scrolling the completion list window, switch to it below, + ;; outside of `with-current-buffer'. + ((eq completion-auto-select 'second-tab)) + ;; Reverse tab + ((equal (this-command-keys) [backtab]) + (if (pos-visible-in-window-p (point-min) window) + ;; If beginning is in view, scroll up to the end. + (set-window-point window (point-max)) + ;; Else scroll down one screen. + (with-selected-window window (scroll-down)))) + ;; Normal tab + (t + (if (pos-visible-in-window-p (point-max) window) + ;; If end is in view, scroll up to the end. + (set-window-start window (point-min) nil) + ;; Else scroll down one screen. + (with-selected-window window (scroll-up)))))) + (when (eq completion-auto-select 'second-tab) + (switch-to-completions)) + nil)) ;; If we're cycling, keep on cycling. ((and completion-cycling completion-all-sorted-completions) (minibuffer-force-complete beg end) t) - (t (pcase (completion--do-completion beg end) - (#b000 nil) - (_ t))))) + (t (prog1 (pcase (completion--do-completion beg end) + (#b000 nil) + (_ t)) + (when (and (eq completion-auto-select t) + (window-live-p minibuffer-scroll-window) + (eq t (frame-visible-p (window-frame minibuffer-scroll-window)))) + ;; When the completion list window was displayed, select it. + (switch-to-completions)))))) (defun completion--cache-all-sorted-completions (beg end comps) (add-hook 'after-change-functions - 'completion--flush-all-sorted-completions nil t) + #'completion--flush-all-sorted-completions nil t) (setq completion--all-sorted-completions-location (cons (copy-marker beg) (copy-marker end))) (setq completion-all-sorted-completions comps)) @@ -1189,8 +1463,10 @@ scroll the window of possible completions." (or (> start (cdr completion--all-sorted-completions-location)) (< end (car completion--all-sorted-completions-location)))) (remove-hook 'after-change-functions - 'completion--flush-all-sorted-completions t) - (setq completion-cycling nil) + #'completion--flush-all-sorted-completions t) + ;; Remove the transient map if applicable. + (when completion-cycling + (funcall (prog1 completion-cycling (setq completion-cycling nil)))) (setq completion-all-sorted-completions nil))) (defun completion--metadata (string base md-at-point table pred) @@ -1201,6 +1477,68 @@ scroll the window of possible completions." (if (eq (car bounds) base) md-at-point (completion-metadata (substring string 0 base) table pred)))) +(defun minibuffer--sort-by-key (elems keyfun) + "Return ELEMS sorted by increasing value of their KEYFUN. +KEYFUN takes an element of ELEMS and should return a numerical value." + (mapcar #'cdr + (sort (mapcar (lambda (x) (cons (funcall keyfun x) x)) elems) + #'car-less-than-car))) + +(defun minibuffer--sort-by-position (hist elems) + "Sort ELEMS by their position in HIST." + (let ((hash (make-hash-table :test #'equal :size (length hist))) + (index 0)) + ;; Record positions in hash + (dolist (c hist) + (unless (gethash c hash) + (puthash c index hash)) + (cl-incf index)) + (minibuffer--sort-by-key + elems (lambda (x) (gethash x hash most-positive-fixnum))))) + +(defun minibuffer--sort-by-length-alpha (elems) + "Sort ELEMS first by length, then alphabetically." + (sort elems (lambda (c1 c2) + (or (< (length c1) (length c2)) + (and (= (length c1) (length c2)) + (string< c1 c2)))))) + +(defun minibuffer--sort-preprocess-history (base) + "Preprocess history. +Remove completion BASE prefix string from history elements." + (let* ((def (if (stringp minibuffer-default) + minibuffer-default + (car-safe minibuffer-default))) + (hist (and (not (eq minibuffer-history-variable t)) + (symbol-value minibuffer-history-variable))) + (base-size (length base))) + ;; Default comes first. + (setq hist (if def (cons def hist) hist)) + ;; Drop base string from the history elements. + (if (= base-size 0) + hist + (delq nil (mapcar + (lambda (c) + (when (string-prefix-p base c) + (substring c base-size))) + hist))))) + +(defun minibuffer--group-by (group-fun sort-fun elems) + "Group ELEMS by GROUP-FUN and sort groups by SORT-FUN." + (let ((groups)) + (dolist (cand elems) + (let* ((key (funcall group-fun cand nil)) + (group (assoc key groups))) + (if group + (setcdr group (cons cand (cdr group))) + (push (list key cand) groups)))) + (setq groups (nreverse groups) + groups (mapc (lambda (x) + (setcdr x (nreverse (cdr x)))) + groups) + groups (funcall sort-fun groups)) + (mapcan #'cdr groups))) + (defun completion-all-sorted-completions (&optional start end) (or completion-all-sorted-completions (let* ((start (or start (minibuffer-prompt-end))) @@ -1220,7 +1558,8 @@ scroll the window of possible completions." base-size md minibuffer-completion-table minibuffer-completion-predicate)) - (sort-fun (completion-metadata-get all-md 'cycle-sort-function))) + (sort-fun (completion-metadata-get all-md 'cycle-sort-function)) + (group-fun (completion-metadata-get all-md 'group-function))) (when last (setcdr last nil) @@ -1230,15 +1569,26 @@ scroll the window of possible completions." (setq all (delete-dups all)) (setq last (last all)) - (setq all (if sort-fun (funcall sort-fun all) - ;; Prefer shorter completions, by default. - (sort all (lambda (c1 c2) (< (length c1) (length c2)))))) - ;; Prefer recently used completions. - (when (minibufferp) - (let ((hist (symbol-value minibuffer-history-variable))) - (setq all (sort all (lambda (c1 c2) - (> (length (member c1 hist)) - (length (member c2 hist)))))))) + (cond + (sort-fun (setq all (funcall sort-fun all))) + ((and completions-group group-fun) + ;; TODO: experiment with re-grouping here. Might be slow + ;; if the group-fun (given by the table and out of our + ;; control) is slow and/or allocates too much. + ) + (t + ;; If the table doesn't stipulate a sorting function or a + ;; group function, sort first by length and + ;; alphabetically. + (setq all (minibuffer--sort-by-length-alpha all)) + ;; Then sort by history position, and put the default, if it + ;; exists, on top. + (when (minibufferp) + (setq all (minibuffer--sort-by-position + (minibuffer--sort-preprocess-history + (substring string 0 base-size)) + all))))) + ;; Cache the result. This is not just for speed, but also so that ;; repeated calls to minibuffer-force-complete can cycle through ;; all possibilities. @@ -1248,29 +1598,40 @@ scroll the window of possible completions." (defun minibuffer-force-complete-and-exit () "Complete the minibuffer with first of the matches and exit." (interactive) - (minibuffer-force-complete) + ;; If `completion-cycling' is t, then surely a + ;; `minibuffer-force-complete' has already executed. This is not + ;; just for speed: the extra rotation caused by the second + ;; unnecessary call would mess up the final result value + ;; (bug#34116). + (unless completion-cycling + (minibuffer-force-complete nil nil 'dont-cycle)) (completion--complete-and-exit - (minibuffer-prompt-end) (point-max) #'exit-minibuffer + (minibuffer--completion-prompt-end) (point-max) #'exit-minibuffer ;; If the previous completion completed to an element which fails ;; test-completion, then we shouldn't exit, but that should be rare. - (lambda () (minibuffer-message "Incomplete")))) + (lambda () + (if minibuffer--require-match + (completion--message "Incomplete") + ;; If a match is not required, exit after all. + (exit-minibuffer))))) -(defun minibuffer-force-complete (&optional start end) +(defun minibuffer-force-complete (&optional start end dont-cycle) "Complete the minibuffer to an exact match. -Repeated uses step through the possible completions." +Repeated uses step through the possible completions. +DONT-CYCLE tells the function not to setup cycling." (interactive) (setq minibuffer-scroll-window nil) ;; FIXME: Need to deal with the extra-size issue here as well. ;; FIXME: ~/src/emacs/t<M-TAB>/lisp/minibuffer.el completes to ;; ~/src/emacs/trunk/ and throws away lisp/minibuffer.el. - (let* ((start (copy-marker (or start (minibuffer-prompt-end)))) + (let* ((start (copy-marker (or start (minibuffer--completion-prompt-end)))) (end (or end (point-max))) ;; (md (completion--field-metadata start)) (all (completion-all-sorted-completions start end)) (base (+ start (or (cdr (last all)) 0)))) (cond ((not (consp all)) - (completion--message + (completion--message (if all "No more completions" "No completions"))) ((not (consp (cdr all))) (let ((done (equal (car all) (buffer-substring-no-properties base end)))) @@ -1281,40 +1642,47 @@ Repeated uses step through the possible completions." (completion--replace base end (car all)) (setq end (+ base (length (car all)))) (completion--done (buffer-substring-no-properties start (point)) 'sole) - ;; Set cycling after modifying the buffer since the flush hook resets it. - (setq completion-cycling t) (setq this-command 'completion-at-point) ;For completion-in-region. - ;; If completing file names, (car all) may be a directory, so we'd now - ;; have a new set of possible completions and might want to reset - ;; completion-all-sorted-completions to nil, but we prefer not to, - ;; so that repeated calls minibuffer-force-complete still cycle - ;; through the previous possible completions. - (let ((last (last all))) - (setcdr last (cons (car all) (cdr last))) - (completion--cache-all-sorted-completions start end (cdr all))) - ;; Make sure repeated uses cycle, even though completion--done might - ;; have added a space or something that moved us outside of the field. - ;; (bug#12221). - (let* ((table minibuffer-completion-table) - (pred minibuffer-completion-predicate) - (extra-prop completion-extra-properties) - (cmd - (lambda () "Cycle through the possible completions." - (interactive) - (let ((completion-extra-properties extra-prop)) - (completion-in-region start (point) table pred))))) - (set-transient-map - (let ((map (make-sparse-keymap))) - (define-key map [remap completion-at-point] cmd) - (define-key map (vector last-command-event) cmd) - map))))))) + ;; Set cycling after modifying the buffer since the flush hook resets it. + (unless dont-cycle + ;; If completing file names, (car all) may be a directory, so we'd now + ;; have a new set of possible completions and might want to reset + ;; completion-all-sorted-completions to nil, but we prefer not to, + ;; so that repeated calls minibuffer-force-complete still cycle + ;; through the previous possible completions. + (let ((last (last all))) + (setcdr last (cons (car all) (cdr last))) + (completion--cache-all-sorted-completions start end (cdr all))) + ;; Make sure repeated uses cycle, even though completion--done might + ;; have added a space or something that moved us outside of the field. + ;; (bug#12221). + (let* ((table minibuffer-completion-table) + (pred minibuffer-completion-predicate) + (extra-prop completion-extra-properties) + (cmd + (lambda () "Cycle through the possible completions." + (interactive) + (let ((completion-extra-properties extra-prop)) + (completion-in-region start (point) table pred))))) + (setq completion-cycling + (set-transient-map + (let ((map (make-sparse-keymap))) + (define-key map [remap completion-at-point] cmd) + (define-key map (vector last-command-event) cmd) + map))))))))) (defvar minibuffer-confirm-exit-commands - '(completion-at-point minibuffer-complete - minibuffer-complete-word PC-complete PC-complete-word) - "A list of commands which cause an immediately following + '( completion-at-point minibuffer-complete + minibuffer-complete-word) + "List of commands which cause an immediately following `minibuffer-complete-and-exit' to ask for extra confirmation.") +(defvar minibuffer--require-match nil + "Value of REQUIRE-MATCH passed to `completing-read'.") + +(defvar minibuffer--original-buffer nil + "Buffer that was current when `completing-read' was called.") + (defun minibuffer-complete-and-exit () "Exit if the minibuffer contains a valid completion. Otherwise, try to complete the minibuffer contents. If @@ -1330,7 +1698,7 @@ If `minibuffer-completion-confirm' is `confirm-after-completion', `minibuffer-confirm-exit-commands', and accept the input otherwise." (interactive) - (completion-complete-and-exit (minibuffer-prompt-end) (point-max) + (completion-complete-and-exit (minibuffer--completion-prompt-end) (point-max) #'exit-minibuffer)) (defun completion-complete-and-exit (beg end exit-function) @@ -1353,52 +1721,57 @@ If `minibuffer-completion-confirm' is `confirm-after-completion', "Exit from `require-match' minibuffer. COMPLETION-FUNCTION is called if the current buffer's content does not appear to be a match." - (cond - ;; Allow user to specify null string + (cond + ;; Allow user to specify null string ((= beg end) (funcall exit-function)) - ((test-completion (buffer-substring beg end) - minibuffer-completion-table - minibuffer-completion-predicate) - ;; FIXME: completion-ignore-case has various slightly - ;; incompatible meanings. E.g. it can reflect whether the user - ;; wants completion to pay attention to case, or whether the - ;; string will be used in a context where case is significant. - ;; E.g. usually try-completion should obey the first, whereas - ;; test-completion should obey the second. - (when completion-ignore-case - ;; Fixup case of the field, if necessary. - (let* ((string (buffer-substring beg end)) - (compl (try-completion - string - minibuffer-completion-table - minibuffer-completion-predicate))) - (when (and (stringp compl) (not (equal string compl)) - ;; If it weren't for this piece of paranoia, I'd replace - ;; the whole thing with a call to do-completion. - ;; This is important, e.g. when the current minibuffer's - ;; content is a directory which only contains a single - ;; file, so `try-completion' actually completes to - ;; that file. - (= (length string) (length compl))) - (completion--replace beg end compl)))) - (funcall exit-function)) - - ((memq minibuffer-completion-confirm '(confirm confirm-after-completion)) - ;; The user is permitted to exit with an input that's rejected - ;; by test-completion, after confirming her choice. - (if (or (eq last-command this-command) - ;; For `confirm-after-completion' we only ask for confirmation - ;; if trying to exit immediately after typing TAB (this - ;; catches most minibuffer typos). - (and (eq minibuffer-completion-confirm 'confirm-after-completion) - (not (memq last-command minibuffer-confirm-exit-commands)))) + ;; The CONFIRM argument is a predicate. + ((and (functionp minibuffer-completion-confirm) + (funcall minibuffer-completion-confirm + (buffer-substring beg end))) + (funcall exit-function)) + ;; See if we have a completion from the table. + ((test-completion (buffer-substring beg end) + minibuffer-completion-table + minibuffer-completion-predicate) + ;; FIXME: completion-ignore-case has various slightly + ;; incompatible meanings. E.g. it can reflect whether the user + ;; wants completion to pay attention to case, or whether the + ;; string will be used in a context where case is significant. + ;; E.g. usually try-completion should obey the first, whereas + ;; test-completion should obey the second. + (when completion-ignore-case + ;; Fixup case of the field, if necessary. + (let* ((string (buffer-substring beg end)) + (compl (try-completion + string + minibuffer-completion-table + minibuffer-completion-predicate))) + (when (and (stringp compl) (not (equal string compl)) + ;; If it weren't for this piece of paranoia, I'd replace + ;; the whole thing with a call to do-completion. + ;; This is important, e.g. when the current minibuffer's + ;; content is a directory which only contains a single + ;; file, so `try-completion' actually completes to + ;; that file. + (= (length string) (length compl))) + (completion--replace beg end compl)))) + (funcall exit-function)) + ;; The user is permitted to exit with an input that's rejected + ;; by test-completion, after confirming her choice. + ((memq minibuffer-completion-confirm '(confirm confirm-after-completion)) + (if (or (eq last-command this-command) + ;; For `confirm-after-completion' we only ask for confirmation + ;; if trying to exit immediately after typing TAB (this + ;; catches most minibuffer typos). + (and (eq minibuffer-completion-confirm 'confirm-after-completion) + (not (memq last-command minibuffer-confirm-exit-commands)))) (funcall exit-function) - (minibuffer-message "Confirm") - nil)) + (minibuffer-message "Confirm") + nil)) - (t - ;; Call do-completion, but ignore errors. - (funcall completion-function)))) + (t + ;; Call do-completion, but ignore errors. + (funcall completion-function)))) (defun completion--try-word-completion (string table predicate point md) (let ((comp (completion-try-completion string table predicate point md))) @@ -1496,40 +1869,68 @@ is added, provided that matches some possible completion. Return nil if there is no valid completion, else t." (interactive) (completion-in-region--single-word - (minibuffer-prompt-end) (point-max) - minibuffer-completion-table minibuffer-completion-predicate)) - -(defun completion-in-region--single-word (beg end collection - &optional predicate) - (let ((minibuffer-completion-table collection) - (minibuffer-completion-predicate predicate)) - (pcase (completion--do-completion beg end - #'completion--try-word-completion) + (minibuffer--completion-prompt-end) (point-max))) + +(defun completion-in-region--single-word (beg end) + (pcase (completion--do-completion beg end #'completion--try-word-completion) (#b000 nil) - (_ t)))) + (_ t))) + +(defface completions-annotations '((t :inherit (italic shadow))) + "Face to use for annotations in the *Completions* buffer. +This face is only used if the strings used for completions +doesn't already specify a face.") + +(defface completions-highlight + '((t :inherit highlight)) + "Default face for highlighting the current completion candidate." + :version "29.1") -(defface completions-annotations '((t :inherit italic)) - "Face to use for annotations in the *Completions* buffer.") +(defcustom completions-highlight-face 'completions-highlight + "A face name to highlight the current completion candidate. +If the value is nil, no highlighting is performed." + :type '(choice (const nil) face) + :version "29.1") (defcustom completions-format 'horizontal "Define the appearance and sorting of completions. If the value is `vertical', display completions sorted vertically in columns in the *Completions* buffer. -If the value is `horizontal', display completions sorted -horizontally in alphabetical order, rather than down the screen." - :type '(choice (const horizontal) (const vertical)) +If the value is `horizontal', display completions sorted in columns +horizontally in alphabetical order, rather than down the screen. +If the value is `one-column', display completions down the screen +in one column." + :type '(choice (const horizontal) (const vertical) (const one-column)) :version "23.2") -(defun completion--insert-strings (strings) +(defcustom completions-detailed nil + "When non-nil, display completions with details added as prefix/suffix. +This makes some commands (for instance, \\[describe-symbol]) provide a +detailed view with more information prepended or appended to +completions." + :type 'boolean + :version "28.1") + +(defcustom completions-header-format + (propertize "%s possible completions:\n" 'face 'shadow) + "Format of completions header. +It may contain one %s to show the total count of completions. +When nil, no header is shown." + :type '(choice (const :tag "No header" nil) + (string :tag "Header format string")) + :version "29.1") + +(defun completion--insert-strings (strings &optional group-fun) "Insert a list of STRINGS into the current buffer. -Uses columns to keep the listing readable but compact. -It also eliminates runs of equal strings." +The candidate strings are inserted into the buffer depending on the +completions format as specified by the variable `completions-format'. +Runs of equal candidate strings are eliminated. GROUP-FUN is a +`group-function' used for grouping the completion candidates." (when (consp strings) - (let* ((length (apply 'max + (let* ((length (apply #'max (mapcar (lambda (s) (if (consp s) - (+ (string-width (car s)) - (string-width (cadr s))) + (apply #'+ (mapcar #'string-width s)) (string-width s))) strings))) (window (get-buffer-window (current-buffer) 0)) @@ -1540,82 +1941,159 @@ It also eliminates runs of equal strings." ;; Don't allocate more columns than we can fill. ;; Windows can't show less than 3 lines anyway. (max 1 (/ (length strings) 2)))) - (colwidth (/ wwidth columns)) - (column 0) - (rows (/ (length strings) columns)) - (row 0) - (first t) - (laststring nil)) - ;; The insertion should be "sensible" no matter what choices were made - ;; for the parameters above. - (dolist (str strings) - (unless (equal laststring str) ; Remove (consecutive) duplicates. - (setq laststring str) + (colwidth (/ wwidth columns))) + (unless (or tab-stop-list (null completion-tab-width) + (zerop (mod colwidth completion-tab-width))) + ;; Align to tab positions for the case + ;; when the caller uses tabs inside prefix. + (setq colwidth (- colwidth (mod colwidth completion-tab-width)))) + (funcall (intern (format "completion--insert-%s" completions-format)) + strings group-fun length wwidth colwidth columns)))) + +(defun completion--insert-horizontal (strings group-fun + length wwidth + colwidth _columns) + (let ((column 0) + (first t) + (last-title nil) + (last-string nil)) + (dolist (str strings) + (unless (equal last-string str) ; Remove (consecutive) duplicates. + (setq last-string str) + (when group-fun + (let ((title (funcall group-fun (if (consp str) (car str) str) nil))) + (unless (equal title last-title) + (setq last-title title) + (when title + (insert (if first "" "\n") (format completions-group-format title) "\n") + (setq column 0 + first t))))) + (unless first ;; FIXME: `string-width' doesn't pay attention to ;; `display' properties. - (let ((length (if (consp str) - (+ (string-width (car str)) - (string-width (cadr str))) - (string-width str)))) - (cond - ((eq completions-format 'vertical) - ;; Vertical format - (when (> row rows) - (forward-line (- -1 rows)) - (setq row 0 column (+ column colwidth))) - (when (> column 0) - (end-of-line) - (while (> (current-column) column) - (if (eobp) - (insert "\n") - (forward-line 1) - (end-of-line))) - (insert " \t") - (set-text-properties (1- (point)) (point) - `(display (space :align-to ,column))))) - (t - ;; Horizontal format - (unless first - (if (< wwidth (+ (max colwidth length) column)) - ;; No space for `str' at point, move to next line. - (progn (insert "\n") (setq column 0)) - (insert " \t") - ;; Leave the space unpropertized so that in the case we're - ;; already past the goal column, there is still - ;; a space displayed. - (set-text-properties (1- (point)) (point) - ;; We can't just set tab-width, because - ;; completion-setup-function will kill - ;; all local variables :-( - `(display (space :align-to ,column))) - nil)))) - (setq first nil) - (if (not (consp str)) - (put-text-property (point) (progn (insert str) (point)) - 'mouse-face 'highlight) - (put-text-property (point) (progn (insert (car str)) (point)) - 'mouse-face 'highlight) - (let ((beg (point)) - (end (progn (insert (cadr str)) (point)))) - (put-text-property beg end 'mouse-face nil) - (font-lock-prepend-text-property beg end 'face - 'completions-annotations))) - (cond - ((eq completions-format 'vertical) - ;; Vertical format - (if (> column 0) - (forward-line) - (insert "\n")) - (setq row (1+ row))) - (t - ;; Horizontal format - ;; Next column to align to. - (setq column (+ column - ;; Round up to a whole number of columns. - (* colwidth (ceiling length colwidth)))))))))))) - -(defvar completion-common-substring nil) -(make-obsolete-variable 'completion-common-substring nil "23.1") + (if (< wwidth (+ column (max colwidth + (if (consp str) + (apply #'+ (mapcar #'string-width str)) + (string-width str))))) + ;; No space for `str' at point, move to next line. + (progn (insert "\n") (setq column 0)) + (insert " \t") + ;; Leave the space unpropertized so that in the case we're + ;; already past the goal column, there is still + ;; a space displayed. + (set-text-properties (1- (point)) (point) + ;; We can set tab-width using + ;; completion-tab-width, but + ;; the caller can prefer using + ;; \t to align prefixes. + `(display (space :align-to ,column))) + nil)) + (setq first nil) + (completion--insert str group-fun) + ;; Next column to align to. + (setq column (+ column + ;; Round up to a whole number of columns. + (* colwidth (ceiling length colwidth)))))))) + +(defun completion--insert-vertical (strings group-fun + _length _wwidth + colwidth columns) + (while strings + (let ((group nil) + (column 0) + (row 0) + (rows) + (last-string nil)) + (if group-fun + (let* ((str (car strings)) + (title (funcall group-fun (if (consp str) (car str) str) nil))) + (while (and strings + (equal title (funcall group-fun + (if (consp (car strings)) + (car (car strings)) + (car strings)) + nil))) + (push (car strings) group) + (pop strings)) + (setq group (nreverse group))) + (setq group strings + strings nil)) + (setq rows (/ (length group) columns)) + (when group-fun + (let* ((str (car group)) + (title (funcall group-fun (if (consp str) (car str) str) nil))) + (when title + (goto-char (point-max)) + (insert (format completions-group-format title) "\n")))) + (dolist (str group) + (unless (equal last-string str) ; Remove (consecutive) duplicates. + (setq last-string str) + (when (> row rows) + (forward-line (- -1 rows)) + (setq row 0 column (+ column colwidth))) + (when (> column 0) + (end-of-line) + (while (> (current-column) column) + (if (eobp) + (insert "\n") + (forward-line 1) + (end-of-line))) + (insert " \t") + (set-text-properties (1- (point)) (point) + `(display (space :align-to ,column)))) + (completion--insert str group-fun) + (if (> column 0) + (forward-line) + (insert "\n")) + (setq row (1+ row))))))) + +(defun completion--insert-one-column (strings group-fun &rest _) + (let ((last-title nil) (last-string nil)) + (dolist (str strings) + (unless (equal last-string str) ; Remove (consecutive) duplicates. + (setq last-string str) + (when group-fun + (let ((title (funcall group-fun (if (consp str) (car str) str) nil))) + (unless (equal title last-title) + (setq last-title title) + (when title + (insert (format completions-group-format title) "\n"))))) + (completion--insert str group-fun) + (insert "\n"))) + (delete-char -1))) + +(defun completion--insert (str group-fun) + (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)) + ;; 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 + ;; is a prefix, and the third element is a suffix. + (let* ((prefix (when (nth 2 str) (nth 1 str))) + (suffix (or (nth 2 str) (nth 1 str)))) + (when prefix + (let ((beg (point)) + (end (progn (insert prefix) (point)))) + (add-text-properties beg end `(mouse-face nil completion--string ,(car str))))) + (completion--insert (car str) group-fun) + (let ((beg (point)) + (end (progn (insert suffix) (point)))) + (add-text-properties beg end `(mouse-face nil completion--string ,(car str))) + ;; Put the predefined face only when suffix + ;; is added via annotation-function without prefix, + ;; and when the caller doesn't use own face. + (unless (or prefix (text-property-not-all + 0 (length suffix) 'face nil suffix)) + (font-lock-prepend-text-property + beg end 'face 'completions-annotations)))))) (defvar completion-setup-hook nil "Normal hook run at the end of setting up a completion list buffer. @@ -1626,14 +2104,13 @@ See also `display-completion-list'.") (defface completions-first-difference '((t (:inherit bold))) - "Face for the first uncommon character in completions. + "Face for the first character after point in completions. See also the face `completions-common-part'.") -(defface completions-common-part '((t nil)) - "Face for the common prefix substring in completions. -The idea of this face is that you can use it to make the common parts -less visible than normal, so that the differing parts are emphasized -by contrast. +(defface completions-common-part + '((((class color) (min-colors 16) (background light)) :foreground "blue3") + (((class color) (min-colors 16) (background dark)) :foreground "lightblue")) + "Face for the parts of completions which matched the pattern. See also the face `completions-first-difference'.") (defun completion-hilit-commonality (completions prefix-len &optional base-size) @@ -1653,7 +2130,7 @@ and with BASE-SIZE appended as the last element." (lambda (elem) (let ((str ;; Don't modify the string itself, but a copy, since the - ;; the string may be read-only or used for other purposes. + ;; string may be read-only or used for other purposes. ;; Furthermore, since `completions' may come from ;; display-completion-list, `elem' may be a list. (if (consp elem) @@ -1676,7 +2153,7 @@ and with BASE-SIZE appended as the last element." completions) base-size)))) -(defun display-completion-list (completions &optional common-substring) +(defun display-completion-list (completions &optional common-substring group-fun) "Display the list of completions, COMPLETIONS, using `standard-output'. Each element may be just a symbol or string or may be a list of two strings to be printed as if concatenated. @@ -1686,7 +2163,9 @@ alternative, the second serves as annotation. The actual completion alternatives, as inserted, are given `mouse-face' properties of `highlight'. At the end, this runs the normal hook `completion-setup-hook'. -It can find the completion buffer in `standard-output'." +It can find the completion buffer in `standard-output'. +GROUP-FUN is a `group-function' used for grouping the completion +candidates." (declare (advertised-calling-convention (completions) "24.4")) (if common-substring (setq completions (completion-hilit-commonality @@ -1698,21 +2177,17 @@ It can find the completion buffer in `standard-output'." (with-temp-buffer (let ((standard-output (current-buffer)) (completion-setup-hook nil)) - (display-completion-list completions common-substring)) + (with-suppressed-warnings ((callargs display-completion-list)) + (display-completion-list completions common-substring group-fun))) (princ (buffer-string))) (with-current-buffer standard-output (goto-char (point-max)) - (if (null completions) - (insert "There are no possible completions of what you have typed.") - (insert "Possible completions are:\n") - (completion--insert-strings completions)))) - - ;; The hilit used to be applied via completion-setup-hook, so there - ;; may still be some code that uses completion-common-substring. - (with-no-warnings - (let ((completion-common-substring common-substring)) - (run-hooks 'completion-setup-hook))) + (when completions-header-format + (insert (format completions-header-format (length completions)))) + (completion--insert-strings completions group-fun))) + + (run-hooks 'completion-setup-hook) nil) (defvar completion-extra-properties nil @@ -1726,6 +2201,14 @@ These include: completion). The function can access the completion data via `minibuffer-completion-table' and related variables. +`:affixation-function': Function to prepend/append a prefix/suffix to + completions. The function must accept one argument, a list of + completions, and return a list of annotated completions. The + elements of the list must be three-element lists: completion, its + prefix and suffix. This function takes priority over + `:annotation-function' when both are provided, so only this + function is used. + `:exit-function': Function to run after completion is performed. The function must accept two arguments, STRING and STATUS. @@ -1737,25 +2220,6 @@ These include: `exact' - text is a valid completion but may be further completed.") -(defvar completion-annotate-function - nil - ;; Note: there's a lot of scope as for when to add annotations and - ;; what annotations to add. E.g. completing-help.el allowed adding - ;; the first line of docstrings to M-x completion. But there's - ;; a tension, since such annotations, while useful at times, can - ;; actually drown the useful information. - ;; So completion-annotate-function should be used parsimoniously, or - ;; else only used upon a user's request (e.g. we could add a command - ;; to completion-list-mode to add annotations to the current - ;; completions). - "Function to add annotations in the *Completions* buffer. -The function takes a completion and should either return nil, or a string that -will be displayed next to the completion. The function can access the -completion table and predicates via `minibuffer-completion-table' and related -variables.") -(make-obsolete-variable 'completion-annotate-function - 'completion-extra-properties "24.1") - (defun completion--done (string &optional finished message) (let* ((exit-fun (plist-get completion-extra-properties :exit-function)) (pre-msg (and exit-fun (current-message)))) @@ -1774,11 +2238,24 @@ variables.") (equal pre-msg (and exit-fun (current-message)))) (completion--message message)))) +(defcustom completions-max-height nil + "Maximum height for *Completions* buffer window." + :type '(choice (const nil) natnum) + :version "29.1") + +(defun completions--fit-window-to-buffer (&optional win &rest _) + "Resize *Completions* buffer window." + (if temp-buffer-resize-mode + (let ((temp-buffer-max-height (or completions-max-height + temp-buffer-max-height))) + (resize-temp-buffer-window win)) + (fit-window-to-buffer win completions-max-height))) + (defun minibuffer-completion-help (&optional start end) "Display a list of possible completions of the current minibuffer contents." (interactive) (message "Making completion list...") - (let* ((start (or start (minibuffer-prompt-end))) + (let* ((start (or start (minibuffer--completion-prompt-end))) (end (or end (point-max))) (string (buffer-substring start end)) (md (completion--field-metadata start)) @@ -1797,117 +2274,159 @@ variables.") ;; the sole completion, then hide (previous&stale) completions. (minibuffer-hide-completions) (ding) - (minibuffer-message + (completion--message (if completions "Sole completion" "No completions"))) (let* ((last (last completions)) (base-size (or (cdr last) 0)) (prefix (unless (zerop base-size) (substring string 0 base-size))) + (base-prefix (buffer-substring (minibuffer--completion-prompt-end) + (+ start base-size))) + (base-suffix (buffer-substring (point) (point-max))) (all-md (completion--metadata (buffer-substring-no-properties start (point)) base-size md minibuffer-completion-table minibuffer-completion-predicate)) - (afun (or (completion-metadata-get all-md 'annotation-function) - (plist-get completion-extra-properties - :annotation-function) - completion-annotate-function)) + (ann-fun (or (completion-metadata-get all-md 'annotation-function) + (plist-get completion-extra-properties + :annotation-function))) + (aff-fun (or (completion-metadata-get all-md 'affixation-function) + (plist-get completion-extra-properties + :affixation-function))) + (sort-fun (completion-metadata-get all-md 'display-sort-function)) + (group-fun (completion-metadata-get all-md 'group-function)) + (mainbuf (current-buffer)) ;; If the *Completions* buffer is shown in a new ;; window, mark it as softly-dedicated, so bury-buffer in ;; minibuffer-hide-completions will know whether to ;; delete the window or not. - (display-buffer-mark-dedicated 'soft) - ;; Disable `pop-up-windows' temporarily to allow - ;; `display-buffer--maybe-pop-up-frame-or-window' - ;; in the display actions below to pop up a frame - ;; if `pop-up-frames' is non-nil, but not to pop up a window. - (pop-up-windows nil)) - (with-displayed-buffer-window + (display-buffer-mark-dedicated 'soft)) + (with-current-buffer-window "*Completions*" ;; This is a copy of `display-buffer-fallback-action' ;; where `display-buffer-use-some-window' is replaced ;; with `display-buffer-at-bottom'. `((display-buffer--maybe-same-window display-buffer-reuse-window - display-buffer--maybe-pop-up-frame-or-window + display-buffer--maybe-pop-up-frame ;; Use `display-buffer-below-selected' for inline completions, ;; but not in the minibuffer (e.g. in `eval-expression') ;; for which `display-buffer-at-bottom' is used. ,(if (eq (selected-window) (minibuffer-window)) 'display-buffer-at-bottom 'display-buffer-below-selected)) - ,(if temp-buffer-resize-mode - '(window-height . resize-temp-buffer-window) - '(window-height . fit-window-to-buffer)) - ,(when temp-buffer-resize-mode - '(preserve-size . (nil . t)))) - nil - ;; Remove the base-size tail because `sort' requires a properly - ;; nil-terminated list. - (when last (setcdr last nil)) - (setq completions - ;; FIXME: This function is for the output of all-completions, - ;; not completion-all-completions. Often it's the same, but - ;; not always. - (let ((sort-fun (completion-metadata-get - all-md 'display-sort-function))) - (if sort-fun - (funcall sort-fun completions) - (sort completions 'string-lessp)))) - (when afun - (setq completions - (mapcar (lambda (s) - (let ((ann (funcall afun s))) - (if ann (list s ann) s))) - completions))) - - (with-current-buffer standard-output - (set (make-local-variable 'completion-base-position) - (list (+ start base-size) - ;; FIXME: We should pay attention to completion - ;; boundaries here, but currently - ;; completion-all-completions does not give us the - ;; necessary information. - end)) - (set (make-local-variable 'completion-list-insert-choice-function) - (let ((ctable minibuffer-completion-table) - (cpred minibuffer-completion-predicate) - (cprops completion-extra-properties)) - (lambda (start end choice) - (unless (or (zerop (length prefix)) - (equal prefix - (buffer-substring-no-properties - (max (point-min) - (- start (length prefix))) - start))) - (message "*Completions* out of date")) - ;; FIXME: Use `md' to do quoting&terminator here. - (completion--replace start end choice) - (let* ((minibuffer-completion-table ctable) - (minibuffer-completion-predicate cpred) - (completion-extra-properties cprops) - (result (concat prefix choice)) - (bounds (completion-boundaries - result ctable cpred ""))) - ;; If the completion introduces a new field, then - ;; completion is not finished. - (completion--done result - (if (eq (car bounds) (length result)) - 'exact 'finished))))))) - - (display-completion-list completions)))) + (window-height . completions--fit-window-to-buffer) + ,(when temp-buffer-resize-mode + '(preserve-size . (nil . t))) + (body-function + . ,#'(lambda (_window) + (with-current-buffer mainbuf + ;; Remove the base-size tail because `sort' requires a properly + ;; nil-terminated list. + (when last (setcdr last nil)) + + ;; Sort first using the `display-sort-function'. + ;; FIXME: This function is for the output of + ;; all-completions, not + ;; completion-all-completions. Often it's the + ;; same, but not always. + (setq completions (if sort-fun + (funcall sort-fun completions) + (pcase completions-sort + ('nil completions) + ('alphabetical (sort completions #'string-lessp)) + (_ (funcall completions-sort completions))))) + + ;; After sorting, group the candidates using the + ;; `group-function'. + (when group-fun + (setq completions + (minibuffer--group-by + group-fun + (pcase completions-group-sort + ('nil #'identity) + ('alphabetical + (lambda (groups) + (sort groups + (lambda (x y) + (string< (car x) (car y)))))) + (_ completions-group-sort)) + completions))) + + (cond + (aff-fun + (setq completions + (funcall aff-fun completions))) + (ann-fun + (setq completions + (mapcar (lambda (s) + (let ((ann (funcall ann-fun s))) + (if ann (list s ann) s))) + completions)))) + + (with-current-buffer standard-output + (setq-local completion-base-position + (list (+ start base-size) + ;; FIXME: We should pay attention to completion + ;; boundaries here, but currently + ;; completion-all-completions does not give us the + ;; necessary information. + end)) + (setq-local completion-base-affixes + (list base-prefix base-suffix)) + (setq-local completion-list-insert-choice-function + (let ((ctable minibuffer-completion-table) + (cpred minibuffer-completion-predicate) + (cprops completion-extra-properties)) + (lambda (start end choice) + (if (and (stringp start) (stringp end)) + (progn + (delete-minibuffer-contents) + (insert start choice) + ;; Keep point after completion before suffix + (save-excursion (insert end))) + (unless (or (zerop (length prefix)) + (equal prefix + (buffer-substring-no-properties + (max (point-min) + (- start (length prefix))) + start))) + (message "*Completions* out of date")) + ;; FIXME: Use `md' to do quoting&terminator here. + (completion--replace start end choice)) + (let* ((minibuffer-completion-table ctable) + (minibuffer-completion-predicate cpred) + (completion-extra-properties cprops) + (result (concat prefix choice)) + (bounds (completion-boundaries + result ctable cpred ""))) + ;; If the completion introduces a new field, then + ;; completion is not finished. + (completion--done result + (if (eq (car bounds) (length result)) + 'exact 'finished))))))) + + (display-completion-list completions nil group-fun))))) + nil))) nil)) (defun minibuffer-hide-completions () "Get rid of an out-of-date *Completions* buffer." ;; FIXME: We could/should use minibuffer-scroll-window here, but it ;; can also point to the minibuffer-parent-window, so it's a bit tricky. + (interactive) (let ((win (get-buffer-window "*Completions*" 0))) (if win (with-selected-window win (bury-buffer))))) (defun exit-minibuffer () "Terminate this minibuffer argument." (interactive) + (when (minibufferp) + (when (not (minibuffer-innermost-command-loop-p)) + (error "%s" "Not in most nested command loop")) + (when (not (innermost-minibuffer-p)) + (error "%s" "Not in most nested minibuffer"))) ;; If the command that uses this has made modifications in the minibuffer, ;; we don't want them to cause deactivation of the mark in the original ;; buffer. @@ -1917,6 +2436,29 @@ variables.") (setq deactivate-mark nil) (throw 'exit nil)) +(defun minibuffer-restore-windows () + "Restore some windows on exit from minibuffer. +When `read-minibuffer-restore-windows' is nil, then this function +added to `minibuffer-exit-hook' will remove at least the window +that displays the \"*Completions*\" buffer." + (unless read-minibuffer-restore-windows + (minibuffer-hide-completions))) + +(add-hook 'minibuffer-exit-hook 'minibuffer-restore-windows) + +(defun minibuffer-quit-recursive-edit (&optional levels) + "Quit the command that requested this recursive edit or minibuffer input. +Do so without terminating keyboard macro recording or execution. +LEVELS specifies the number of nested recursive edits to quit. +If nil, it defaults to 1." + (unless levels + (setq levels 1)) + (if (> levels 1) + ;; See Info node `(elisp)Recursive Editing' for an explanation + ;; of throwing a function to `exit'. + (throw 'exit (lambda () (minibuffer-quit-recursive-edit (1- levels)))) + (throw 'exit (lambda () (signal 'minibuffer-quit nil))))) + (defun self-insert-and-exit () "Terminate minibuffer input." (interactive) @@ -1988,14 +2530,15 @@ Also respects the obsolete wrapper hook `completion-in-region-functions'. (completion-in-region-mode 1)) (completion--in-region-1 start end)))) -(defvar completion-in-region-mode-map - (let ((map (make-sparse-keymap))) - ;; FIXME: Only works if completion-in-region-mode was activated via - ;; completion-at-point called directly. - (define-key map "\M-?" 'completion-help-at-point) - (define-key map "\t" 'completion-at-point) - map) - "Keymap activated during `completion-in-region'.") +(defvar-keymap completion-in-region-mode-map + :doc "Keymap activated during `completion-in-region'." + ;; FIXME: Only works if completion-in-region-mode was activated via + ;; completion-at-point called directly. + "M-?" #'completion-help-at-point + "TAB" #'completion-at-point + "M-<up>" #'minibuffer-previous-completion + "M-<down>" #'minibuffer-next-completion + "M-RET" #'minibuffer-choose-completion) ;; It is difficult to know when to exit completion-in-region-mode (i.e. hide ;; the *Completions*). Here's how previous packages did it: @@ -2042,6 +2585,7 @@ Also respects the obsolete wrapper hook `completion-in-region-functions'. (cl-assert completion-in-region-mode-predicate) (setq completion-in-region-mode--predicate completion-in-region-mode-predicate) + (setq-local minibuffer-completion-auto-choose nil) (add-hook 'post-command-hook #'completion-in-region--postch) (push `(completion-in-region-mode . ,completion-in-region-mode-map) minor-mode-overriding-map-alist))) @@ -2068,7 +2612,13 @@ Currently supported properties are all the properties that can appear in match the text at point, then instead of reporting a completion failure, the completion should try the next completion function. As is the case with most hooks, the functions are responsible for -preserving things like point and current buffer.") +preserving things like point and current buffer. + +NOTE: These functions should be cheap to run since they're sometimes +run from `post-command-hook'; and they should ideally only choose +which kind of completion table to use, and not pre-filter it based +on the current text between START and END (e.g., they should not +obey `completion-styles').") (defvar completion--capf-misbehave-funs nil "List of functions found on `completion-at-point-functions' that misbehave. @@ -2085,9 +2635,9 @@ a completion function or god knows what else.") ;; like comint-completion-at-point or mh-letter-completion-at-point, which ;; could be sometimes safe and sometimes misbehaving (and sometimes neither). (if (pcase which - (`all t) - (`safe (member fun completion--capf-safe-funs)) - (`optimist (not (member fun completion--capf-misbehave-funs)))) + ('all t) + ('safe (member fun completion--capf-safe-funs)) + ('optimist (not (member fun completion--capf-misbehave-funs)))) (let ((res (funcall fun))) (cond ((and (consp res) (not (functionp res))) @@ -2177,72 +2727,110 @@ The completion method is determined by `completion-at-point-functions'." ;;; Key bindings. (let ((map minibuffer-local-map)) - (define-key map "\C-g" 'abort-recursive-edit) - (define-key map "\r" 'exit-minibuffer) - (define-key map "\n" 'exit-minibuffer)) - -(defvar minibuffer-local-completion-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map minibuffer-local-map) - (define-key map "\t" 'minibuffer-complete) - ;; M-TAB is already abused for many other purposes, so we should find - ;; another binding for it. - ;; (define-key map "\e\t" 'minibuffer-force-complete) - (define-key map " " 'minibuffer-complete-word) - (define-key map "?" 'minibuffer-completion-help) - map) - "Local keymap for minibuffer input with completion.") - -(defvar minibuffer-local-must-match-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map minibuffer-local-completion-map) - (define-key map "\r" 'minibuffer-complete-and-exit) - (define-key map "\n" 'minibuffer-complete-and-exit) - map) - "Local keymap for minibuffer input with completion, for exact match.") - -(defvar minibuffer-local-filename-completion-map - (let ((map (make-sparse-keymap))) - (define-key map " " nil) - map) - "Local keymap for minibuffer input with completion for filenames. + (define-key map "\C-g" 'abort-minibuffers) + (define-key map "\M-<" 'minibuffer-beginning-of-buffer) + + ;; Put RET last so that it is shown in doc strings in preference to + ;; C-j, when using the \\[exit-minibuffer] notation. + (define-key map "\n" 'exit-minibuffer) + (define-key map "\r" 'exit-minibuffer)) + +(defvar-keymap minibuffer-local-completion-map + :doc "Local keymap for minibuffer input with completion." + :parent minibuffer-local-map + "TAB" #'minibuffer-complete + "<backtab>" #'minibuffer-complete + ;; M-TAB is already abused for many other purposes, so we should find + ;; another binding for it. + ;; "M-TAB" #'minibuffer-force-complete + "SPC" #'minibuffer-complete-word + "?" #'minibuffer-completion-help + "<prior>" #'switch-to-completions + "M-v" #'switch-to-completions + "M-g M-c" #'switch-to-completions + "M-<up>" #'minibuffer-previous-completion + "M-<down>" #'minibuffer-next-completion + "M-RET" #'minibuffer-choose-completion) + +(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 + "C-j" #'minibuffer-complete-and-exit) + +(defvar-keymap minibuffer-local-filename-completion-map + :doc "Local keymap for minibuffer input with completion for filenames. Gets combined either with `minibuffer-local-completion-map' or -with `minibuffer-local-must-match-map'.") - -(define-obsolete-variable-alias 'minibuffer-local-must-match-filename-map - 'minibuffer-local-filename-must-match-map "23.1") -(defvar minibuffer-local-filename-must-match-map (make-sparse-keymap)) -(make-obsolete-variable 'minibuffer-local-filename-must-match-map nil "24.1") - -(let ((map minibuffer-local-ns-map)) - (define-key map " " 'exit-minibuffer) - (define-key map "\t" 'exit-minibuffer) - (define-key map "?" 'self-insert-and-exit)) - -(defvar minibuffer-inactive-mode-map - (let ((map (make-keymap))) - (suppress-keymap map) - (define-key map "e" 'find-file-other-frame) - (define-key map "f" 'find-file-other-frame) - (define-key map "b" 'switch-to-buffer-other-frame) - (define-key map "i" 'info) - (define-key map "m" 'mail) - (define-key map "n" 'make-frame) - (define-key map [mouse-1] 'view-echo-area-messages) - ;; So the global down-mouse-1 binding doesn't clutter the execution of the - ;; above mouse-1 binding. - (define-key map [down-mouse-1] #'ignore) - map) - "Keymap for use in the minibuffer when it is not active. +with `minibuffer-local-must-match-map'." + "SPC" nil) + +(defvar-keymap minibuffer-local-ns-map + :doc "Local keymap for the minibuffer when spaces are not allowed." + :parent minibuffer-local-map + "SPC" #'exit-minibuffer + "TAB" #'exit-minibuffer + "?" #'self-insert-and-exit) + +(defun read-no-blanks-input (prompt &optional initial inherit-input-method) + "Read a string from the terminal, not allowing blanks. +Prompt with PROMPT. Whitespace terminates the input. If INITIAL is +non-nil, it should be a string, which is used as initial input, with +point positioned at the end, so that SPACE will accept the input. +\(Actually, INITIAL can also be a cons of a string and an integer. +Such values are treated as in `read-from-minibuffer', but are normally +not useful in this function.) + +Third arg INHERIT-INPUT-METHOD, if non-nil, means the minibuffer inherits +the current input method and the setting of `enable-multibyte-characters'. + +If `inhibit-interaction' is non-nil, this function will signal an +`inhibited-interaction' error." + (read-from-minibuffer prompt initial minibuffer-local-ns-map + nil 'minibuffer-history nil inherit-input-method)) + +;;; Major modes for the minibuffer + +(defvar-keymap minibuffer-inactive-mode-map + :doc "Keymap for use in the minibuffer when it is not active. The non-mouse bindings in this keymap can only be used in minibuffer-only frames, since the minibuffer can normally not be selected when it is -not active.") +not active." + :full t + :suppress t + "e" #'find-file-other-frame + "f" #'find-file-other-frame + "b" #'switch-to-buffer-other-frame + "i" #'info + "m" #'mail + "n" #'make-frame + "<mouse-1>" #'view-echo-area-messages + ;; So the global down-mouse-1 binding doesn't clutter the execution of the + ;; above mouse-1 binding. + "<down-mouse-1>" #'ignore) (define-derived-mode minibuffer-inactive-mode nil "InactiveMinibuffer" - :abbrev-table nil ;abbrev.el is not loaded yet during dump. ;; Note: this major mode is called from minibuf.c. "Major mode to use in the minibuffer when it is not active. -This is only used when the minibuffer area has no active minibuffer.") +This is only used when the minibuffer area has no active minibuffer. + +Note that the minibuffer may change to this mode more often than +you might expect. For instance, typing \\`M-x' may change the +buffer to this mode, then to a different mode, and then back +again to this mode upon exit. Code running from +`minibuffer-inactive-mode-hook' has to be prepared to run +multiple times per minibuffer invocation. Also see +`minibuffer-exit-hook'.") + +(defvaralias 'minibuffer-mode-map 'minibuffer-local-map) + +(define-derived-mode minibuffer-mode nil "Minibuffer" + "Major mode used for active minibuffers. + +For customizing this mode, it is better to use +`minibuffer-setup-hook' and `minibuffer-exit-hook' rather than +the mode hook of this mode." + :syntax-table nil + :interactive nil) ;;; Completion tables. @@ -2258,21 +2846,21 @@ Useful to give the user default values that won't be substituted." (if (and (not (file-name-quoted-p filename)) (file-name-absolute-p filename) (string-match-p (if (memq system-type '(windows-nt ms-dos)) - "[/\\\\]~" "/~") + "[/\\]~" "/~") (file-local-name filename))) (file-name-quote filename) (minibuffer--double-dollars filename))) (defun completion--make-envvar-table () (mapcar (lambda (enventry) - (substring enventry 0 (string-match-p "=" enventry))) + (substring enventry 0 (string-search "=" enventry))) process-environment)) (defconst completion--embedded-envvar-re ;; We can't reuse env--substitute-vars-regexp because we need to match only ;; potentially-unfinished envvars at end of string. (concat "\\(?:^\\|[^$]\\(?:\\$\\$\\)*\\)" - "$\\([[:alnum:]_]*\\|{\\([^}]*\\)\\)\\'")) + "\\$\\([[:alnum:]_]*\\|{\\([^}]*\\)\\)\\'")) (defun completion--embedded-envvar-table (string _pred action) "Completion table for envvars embedded in a string. @@ -2313,7 +2901,7 @@ same as `substitute-in-file-name'." (match-beginning 0))))))) (t (if (eq (aref string (1- beg)) ?{) - (setq table (apply-partially 'completion-table-with-terminator + (setq table (apply-partially #'completion-table-with-terminator "}" table))) ;; Even if file-name completion is case-insensitive, we want ;; envvar completion to be case-sensitive. @@ -2334,7 +2922,7 @@ same as `substitute-in-file-name'." pred action)) ((eq (car-safe action) 'boundaries) (let ((start (length (file-name-directory string))) - (end (string-match-p "/" (cdr action)))) + (end (string-search "/" (cdr action)))) `(boundaries ;; if `string' is "C:" in w32, (file-name-directory string) ;; returns "C:/", so `start' is 3 rather than 2. @@ -2384,11 +2972,6 @@ same as `substitute-in-file-name'." all)))))) (file-error nil))) ;PCM often calls with invalid directories. -(defvar read-file-name-predicate nil - "Current predicate used by `read-file-name-internal'.") -(make-obsolete-variable 'read-file-name-predicate - "use the regular PRED argument" "23.2") - (defun completion--sifn-requote (upos qstr) ;; We're looking for `qpos' such that: ;; (equal (substring (substitute-in-file-name qstr) 0 upos) @@ -2413,26 +2996,30 @@ same as `substitute-in-file-name'." (let* ((ustr (substitute-in-file-name qstr)) (uprefix (substring ustr 0 upos)) qprefix) - ;; Main assumption: nothing after qpos should affect the text before upos, - ;; so we can work our way backward from the end of qstr, one character - ;; at a time. - ;; Second assumptions: If qpos is far from the end this can be a bit slow, - ;; so we speed it up by doing a first loop that skips a word at a time. - ;; This word-sized loop is careful not to cut in the middle of env-vars. - (while (let ((boundary (string-match "\\(\\$+{?\\)?\\w+\\W*\\'" qstr))) - (and boundary - (progn - (setq qprefix (substring qstr 0 boundary)) + (if (eq upos (length ustr)) + ;; Easy and common case. This not only speed things up in a very + ;; common case but it also avoids problems in some cases (bug#53053). + (cons (length qstr) #'minibuffer-maybe-quote-filename) + ;; Main assumption: nothing after qpos should affect the text before upos, + ;; so we can work our way backward from the end of qstr, one character + ;; at a time. + ;; Second assumptions: If qpos is far from the end this can be a bit slow, + ;; so we speed it up by doing a first loop that skips a word at a time. + ;; This word-sized loop is careful not to cut in the middle of env-vars. + (while (let ((boundary (string-match "\\(\\$+{?\\)?\\w+\\W*\\'" qstr))) + (and boundary + (progn + (setq qprefix (substring qstr 0 boundary)) + (string-prefix-p uprefix + (substitute-in-file-name qprefix))))) + (setq qstr qprefix)) + (let ((qpos (length qstr))) + (while (and (> qpos 0) (string-prefix-p uprefix - (substitute-in-file-name qprefix))))) - (setq qstr qprefix)) - (let ((qpos (length qstr))) - (while (and (> qpos 0) - (string-prefix-p uprefix - (substitute-in-file-name - (substring qstr 0 (1- qpos))))) - (setq qpos (1- qpos))) - (cons qpos #'minibuffer-maybe-quote-filename)))) + (substitute-in-file-name + (substring qstr 0 (1- qpos))))) + (setq qpos (1- qpos))) + (cons qpos #'minibuffer-maybe-quote-filename))))) (defalias 'completion--file-name-table (completion-table-with-quoting #'completion-file-name-table @@ -2447,7 +3034,7 @@ except that it passes the file name through `substitute-in-file-name'.") #'completion--file-name-table) "Internal subroutine for `read-file-name'. Do not call this.") -(defvar read-file-name-function 'read-file-name-default +(defvar read-file-name-function #'read-file-name-default "The function called by `read-file-name' to do its work. It should accept the same arguments as `read-file-name'.") @@ -2474,6 +3061,15 @@ such as making the current buffer visit no file in the case of `set-visited-file-name'." :type 'boolean) +(defcustom minibuffer-beginning-of-buffer-movement nil + "Control how the \\<minibuffer-local-map>\\[minibuffer-beginning-of-buffer] \ +command in the minibuffer behaves. +If non-nil, the command will go to the end of the prompt (if +point is after the end of the prompt). If nil, it will behave +like the `beginning-of-buffer' command." + :version "27.1" + :type 'boolean) + ;; Not always defined, but only called if next-read-file-uses-dialog-p says so. (declare-function x-file-dialog "xfns.c" (prompt dir &optional default-filename mustmatch only-dir-p)) @@ -2536,10 +3132,16 @@ Fourth arg MUSTMATCH can take the following values: input, but she needs to confirm her choice if she called `minibuffer-complete' right before `minibuffer-complete-and-exit' and the input is not an existing file. +- a function, which will be called with the input as the + argument. If the function returns a non-nil value, the + minibuffer is exited with that argument as the value. - anything else behaves like t except that typing RET does not exit if it does non-null completion. -Fifth arg INITIAL specifies text to start with. +Fifth arg INITIAL specifies text to start with. It will be +interpreted as the trailing part of DEFAULT-FILENAME, so using a +full file name for INITIAL will usually lead to surprising +results. Sixth arg PREDICATE, if non-nil, should be a function of one argument; then a file name is considered an acceptable completion @@ -2595,8 +3197,13 @@ See `read-file-name' for the meaning of the arguments." (unless dir (setq dir (or default-directory "~/"))) (unless (file-name-absolute-p dir) (setq dir (expand-file-name dir))) (unless default-filename - (setq default-filename (if initial (expand-file-name initial dir) - buffer-file-name))) + (setq default-filename + (cond + ((null initial) buffer-file-name) + ;; Special-case "" because (expand-file-name "" "/tmp/") returns + ;; "/tmp" rather than "/tmp/" (bug#39057). + ((equal "" initial) dir) + (t (expand-file-name initial dir))))) ;; If dir starts with user's homedir, change that to ~. (setq dir (abbreviate-file-name dir)) ;; Likewise for default-filename. @@ -2613,7 +3220,7 @@ See `read-file-name' for the meaning of the arguments." (minibuffer-maybe-quote-filename dir))) (initial (cons (minibuffer-maybe-quote-filename initial) 0))))) - (let ((completion-ignore-case read-file-name-completion-ignore-case) + (let ((ignore-case read-file-name-completion-ignore-case) (minibuffer-completing-file-name t) (pred (or predicate 'file-exists-p)) (add-to-history nil)) @@ -2641,10 +3248,11 @@ See `read-file-name' for the meaning of the arguments." minibuffer-default)) (setq minibuffer-default (cdr-safe minibuffer-default))) + (setq-local completion-ignore-case ignore-case) ;; On the first request on `M-n' fill ;; `minibuffer-default' with a list of defaults ;; relevant for file-name reading. - (set (make-local-variable 'minibuffer-default-add-function) + (setq-local minibuffer-default-add-function (lambda () (with-current-buffer (window-buffer (minibuffer-selected-window)) @@ -2712,17 +3320,9 @@ See `read-file-name' for the meaning of the arguments." (if (string= val1 (cadr file-name-history)) (pop file-name-history) (setcar file-name-history val1))) - (if add-to-history - ;; Add the value to the history--but not if it matches - ;; the last value already there. - (let ((val1 (minibuffer-maybe-quote-filename val))) - (unless (and (consp file-name-history) - (equal (car file-name-history) val1)) - (setq file-name-history - (cons val1 - (if history-delete-duplicates - (delete val1 file-name-history) - file-name-history))))))) + (when add-to-history + (add-to-history 'file-name-history + (minibuffer-maybe-quote-filename val)))) val)))) (defun internal-complete-buffer-except (&optional buffer) @@ -2730,8 +3330,8 @@ See `read-file-name' for the meaning of the arguments." BUFFER nil or omitted means use the current buffer. Like `internal-complete-buffer', but removes BUFFER from the completion list." (let ((except (if (stringp buffer) buffer (buffer-name buffer)))) - (apply-partially 'completion-table-with-predicate - 'internal-complete-buffer + (apply-partially #'completion-table-with-predicate + #'internal-complete-buffer (lambda (name) (not (equal (if (consp name) (car name) name) except))) nil))) @@ -2795,10 +3395,9 @@ Return the new suffix." suffix)) (defun completion-basic--pattern (beforepoint afterpoint bounds) - (delete - "" (list (substring beforepoint (car bounds)) - 'point - (substring afterpoint 0 (cdr bounds))))) + (list (substring beforepoint (car bounds)) + 'point + (substring afterpoint 0 (cdr bounds)))) (defun completion-basic-try-completion (string table pred point) (let* ((beforepoint (substring string 0 point)) @@ -2816,10 +3415,9 @@ Return the new suffix." (length completion)))) (let* ((suffix (substring afterpoint (cdr bounds))) (prefix (substring beforepoint 0 (car bounds))) - (pattern (delete - "" (list (substring beforepoint (car bounds)) - 'point - (substring afterpoint 0 (cdr bounds))))) + (pattern (completion-pcm--optimize-pattern + (completion-basic--pattern + beforepoint afterpoint bounds))) (all (completion-pcm--all-completions prefix pattern table pred))) (if minibuffer-completing-file-name (setq all (completion-pcm--filename-try-filter all))) @@ -2934,9 +3532,7 @@ or a symbol, see `completion-pcm--merge-completions'." (when (> (length string) p0) (if pending (push pending pattern)) (push (substring string p0) pattern)) - ;; An empty string might be erroneously added at the beginning. - ;; It should be avoided properly, but it's so easy to remove it here. - (delete "" (nreverse pattern))))) + (nreverse pattern)))) (defun completion-pcm--optimize-pattern (p) ;; Remove empty strings in a separate phase since otherwise a "" @@ -2945,16 +3541,14 @@ or a symbol, see `completion-pcm--merge-completions'." (let ((n '())) (while p (pcase p - (`(,(and s1 (pred stringp)) ,(and s2 (pred stringp)) . ,rest) - (setq p (cons (concat s1 s2) rest))) - (`(,(and p1 (pred symbolp)) ,(and p2 (guard (eq p1 p2))) . ,_) + (`(,(or 'any 'any-delim) ,(or 'any 'point) . ,_) (setq p (cdr p))) - (`(star ,(pred symbolp) . ,rest) (setq p `(star . ,rest))) - (`(,(pred symbolp) star . ,rest) (setq p `(star . ,rest))) - (`(point ,(or `any `any-delim) . ,rest) (setq p `(point . ,rest))) - (`(,(or `any `any-delim) point . ,rest) (setq p `(point . ,rest))) - (`(any ,(or `any `any-delim) . ,rest) (setq p `(any . ,rest))) - (`(,(pred symbolp)) (setq p nil)) ;Implicit terminating `any'. + ;; This is not just a performance improvement: it turns a + ;; terminating `point' into an implicit `any', which affects + ;; the final position of point (because `point' gets turned + ;; into a non-greedy ".*?" regexp whereas we need it to be + ;; greedy when it's at the end, see bug#38458). + (`(point) (setq p nil)) ;Implicit terminating `any'. (_ (push (pop p) n)))) (nreverse n))) @@ -2979,6 +3573,17 @@ or a symbol, see `completion-pcm--merge-completions'." (setq re (replace-match "" t t re 1))) re)) +(defun completion-pcm--pattern-point-idx (pattern) + "Return index of subgroup corresponding to `point' element of PATTERN. +Return nil if there's no such element." + (let ((idx nil) + (i 0)) + (dolist (x pattern) + (unless (stringp x) + (cl-incf i) + (if (eq x 'point) (setq idx i)))) + idx)) + (defun completion-pcm--all-completions (prefix pattern table pred) "Find all completions for PATTERN in TABLE obeying PRED. PATTERN is as returned by `completion-pcm--string->pattern'." @@ -3008,26 +3613,122 @@ PATTERN is as returned by `completion-pcm--string->pattern'." (when (string-match-p regex c) (push c poss))) (nreverse poss)))))) +(defvar flex-score-match-tightness 3 + "Controls how the `flex' completion style scores its matches. + +Value is a positive number. A number smaller than 1 makes the +scoring formula reward matches scattered along the string, while +a number greater than one make the formula reward matches that +are clumped together. I.e \"foo\" matches both strings +\"fbarbazoo\" and \"fabrobazo\", which are of equal length, but +only a value greater than one will score the former (which has +one large \"hole\" and a clumped-together \"oo\" match) higher +than the latter (which has two \"holes\" and three +one-letter-long matches).") + (defun completion-pcm--hilit-commonality (pattern completions) - (when completions - (let* ((re (completion-pcm--pattern->regex pattern '(point))) - (case-fold-search completion-ignore-case)) + "Show where and how well PATTERN matches COMPLETIONS. +PATTERN, a list of symbols and strings as seen +`completion-pcm--merge-completions', is assumed to match every +string in COMPLETIONS. Return a deep copy of COMPLETIONS where +each string is propertized with `completion-score', a number +between 0 and 1, and with faces `completions-common-part', +`completions-first-difference' in the relevant segments." + (cond + ((and completions (cl-loop for e in pattern thereis (stringp e))) + (let* ((re (completion-pcm--pattern->regex pattern 'group)) + (point-idx (completion-pcm--pattern-point-idx pattern)) + (case-fold-search completion-ignore-case) + last-md) (mapcar (lambda (str) ;; Don't modify the string itself. (setq str (copy-sequence str)) (unless (string-match re str) (error "Internal error: %s does not match %s" re str)) - (let ((pos (or (match-beginning 1) (match-end 0)))) - (put-text-property 0 pos - 'font-lock-face 'completions-common-part - str) + (let* ((pos (if point-idx (match-beginning point-idx) (match-end 0))) + (match-end (match-end 0)) + (md (cddr (setq last-md (match-data t last-md)))) + (from 0) + (end (length str)) + ;; To understand how this works, consider these simple + ;; ascii diagrams showing how the pattern "foo" + ;; flex-matches "fabrobazo", "fbarbazoo" and + ;; "barfoobaz": + + ;; f abr o baz o + ;; + --- + --- + + + ;; f barbaz oo + ;; + ------ ++ + + ;; bar foo baz + ;; +++ + + ;; "+" indicates parts where the pattern matched. A + ;; "hole" in the middle of the string is indicated by + ;; "-". Note that there are no "holes" near the edges + ;; of the string. The completion score is a number + ;; bound by (0..1] (i.e., larger than (but not equal + ;; to) zero, and smaller or equal to one): the higher + ;; the better and only a perfect match (pattern equals + ;; string) will have score 1. The formula takes the + ;; form of a quotient. For the numerator, we use the + ;; number of +, i.e. the length of the pattern. For + ;; the denominator, it first computes + ;; + ;; hole_i_contrib = 1 + (Li-1)^(1/tightness) + ;; + ;; , for each hole "i" of length "Li", where tightness + ;; is given by `flex-score-match-tightness'. The + ;; final value for the denominator is then given by: + ;; + ;; (SUM_across_i(hole_i_contrib) + 1) * len + ;; + ;; , where "len" is the string's length. + (score-numerator 0) + (score-denominator 0) + (last-b 0) + (update-score-and-face + (lambda (a b) + "Update score and face given match range (A B)." + (add-face-text-property a b + 'completions-common-part + nil str) + (setq + score-numerator (+ score-numerator (- b a))) + (unless (or (= a last-b) + (zerop last-b) + (= a (length str))) + (setq + score-denominator (+ score-denominator + 1 + (expt (- a last-b 1) + (/ 1.0 + flex-score-match-tightness))))) + (setq + last-b b)))) + (while md + (funcall update-score-and-face from (pop md)) + (setq from (pop md))) + ;; If `pattern' doesn't have an explicit trailing any, the + ;; regex `re' won't produce match data representing the + ;; region after the match. We need to account to account + ;; for that extra bit of match (bug#42149). + (unless (= from match-end) + (funcall update-score-and-face from match-end)) (if (> (length str) pos) - (put-text-property pos (1+ pos) - 'font-lock-face 'completions-first-difference - str))) - str) - completions)))) + (add-face-text-property + pos (1+ pos) + 'completions-first-difference + nil str)) + (unless (zerop (length str)) + (put-text-property + 0 1 'completion-score + (/ score-numerator (* end (1+ score-denominator)) 1.0) str))) + str) + completions))) + (t completions))) (defun completion-pcm--find-all-completions (string table pred point &optional filter) @@ -3044,7 +3745,8 @@ filter out additional entries (because TABLE might not obey PRED)." firsterror) (setq string (substring string (car bounds) (+ point (cdr bounds)))) (let* ((relpoint (- point (car bounds))) - (pattern (completion-pcm--string->pattern string relpoint)) + (pattern (completion-pcm--optimize-pattern + (completion-pcm--string->pattern string relpoint))) (all (condition-case-unless-debug err (funcall filter (completion-pcm--all-completions @@ -3055,68 +3757,69 @@ filter out additional entries (because TABLE might not obey PRED)." (null (ignore-errors (try-completion prefix table pred)))) ;; The prefix has no completions at all, so we should try and fix ;; that first. - (let ((substring (substring prefix 0 -1))) - (pcase-let ((`(,subpat ,suball ,subprefix ,_subsuffix) - (completion-pcm--find-all-completions - substring table pred (length substring) filter))) - (let ((sep (aref prefix (1- (length prefix)))) - ;; Text that goes between the new submatches and the - ;; completion substring. - (between nil)) - ;; Eliminate submatches that don't end with the separator. - (dolist (submatch (prog1 suball (setq suball ()))) - (when (eq sep (aref submatch (1- (length submatch)))) - (push submatch suball))) - (when suball - ;; Update the boundaries and corresponding pattern. - ;; We assume that all submatches result in the same boundaries - ;; since we wouldn't know how to merge them otherwise anyway. - ;; FIXME: COMPLETE REWRITE!!! - (let* ((newbeforepoint - (concat subprefix (car suball) - (substring string 0 relpoint))) - (leftbound (+ (length subprefix) (length (car suball)))) - (newbounds (completion-boundaries - newbeforepoint table pred afterpoint))) - (unless (or (and (eq (cdr bounds) (cdr newbounds)) - (eq (car newbounds) leftbound)) - ;; Refuse new boundaries if they step over - ;; the submatch. - (< (car newbounds) leftbound)) - ;; The new completed prefix does change the boundaries - ;; of the completed substring. - (setq suffix (substring afterpoint (cdr newbounds))) - (setq string - (concat (substring newbeforepoint (car newbounds)) - (substring afterpoint 0 (cdr newbounds)))) - (setq between (substring newbeforepoint leftbound - (car newbounds))) - (setq pattern (completion-pcm--string->pattern - string - (- (length newbeforepoint) - (car newbounds))))) - (dolist (submatch suball) - (setq all (nconc - (mapcar - (lambda (s) (concat submatch between s)) - (funcall filter - (completion-pcm--all-completions - (concat subprefix submatch between) - pattern table pred))) - all))) - ;; FIXME: This can come in handy for try-completion, - ;; but isn't right for all-completions, since it lists - ;; invalid completions. - ;; (unless all - ;; ;; Even though we found expansions in the prefix, none - ;; ;; leads to a valid completion. - ;; ;; Let's keep the expansions, tho. - ;; (dolist (submatch suball) - ;; (push (concat submatch between newsubstring) all))) - )) - (setq pattern (append subpat (list 'any (string sep)) - (if between (list between)) pattern)) - (setq prefix subprefix))))) + (pcase-let* ((substring (substring prefix 0 -1)) + (`(,subpat ,suball ,subprefix ,_subsuffix) + (completion-pcm--find-all-completions + substring table pred (length substring) filter)) + (sep (aref prefix (1- (length prefix)))) + ;; Text that goes between the new submatches and the + ;; completion substring. + (between nil)) + ;; Eliminate submatches that don't end with the separator. + (dolist (submatch (prog1 suball (setq suball ()))) + (when (eq sep (aref submatch (1- (length submatch)))) + (push submatch suball))) + (when suball + ;; Update the boundaries and corresponding pattern. + ;; We assume that all submatches result in the same boundaries + ;; since we wouldn't know how to merge them otherwise anyway. + ;; FIXME: COMPLETE REWRITE!!! + (let* ((newbeforepoint + (concat subprefix (car suball) + (substring string 0 relpoint))) + (leftbound (+ (length subprefix) (length (car suball)))) + (newbounds (completion-boundaries + newbeforepoint table pred afterpoint))) + (unless (or (and (eq (cdr bounds) (cdr newbounds)) + (eq (car newbounds) leftbound)) + ;; Refuse new boundaries if they step over + ;; the submatch. + (< (car newbounds) leftbound)) + ;; The new completed prefix does change the boundaries + ;; of the completed substring. + (setq suffix (substring afterpoint (cdr newbounds))) + (setq string + (concat (substring newbeforepoint (car newbounds)) + (substring afterpoint 0 (cdr newbounds)))) + (setq between (substring newbeforepoint leftbound + (car newbounds))) + (setq pattern (completion-pcm--optimize-pattern + (completion-pcm--string->pattern + string + (- (length newbeforepoint) + (car newbounds)))))) + (dolist (submatch suball) + (setq all (nconc + (mapcar + (lambda (s) (concat submatch between s)) + (funcall filter + (completion-pcm--all-completions + (concat subprefix submatch between) + pattern table pred))) + all))) + ;; FIXME: This can come in handy for try-completion, + ;; but isn't right for all-completions, since it lists + ;; invalid completions. + ;; (unless all + ;; ;; Even though we found expansions in the prefix, none + ;; ;; leads to a valid completion. + ;; ;; Let's keep the expansions, tho. + ;; (dolist (submatch suball) + ;; (push (concat submatch between newsubstring) all))) + )) + (setq pattern (append subpat (list 'any (string sep)) + (if between (list between)) pattern)) + (setq prefix subprefix))) (if (and (null all) firsterror) (signal (car firsterror) (cdr firsterror)) (list pattern all prefix suffix))))) @@ -3307,7 +4010,12 @@ the same set of elements." ;;; Substring completion ;; Mostly derived from the code of `basic' completion. -(defun completion-substring--all-completions (string table pred point) +(defun completion-substring--all-completions + (string table pred point &optional transform-pattern-fn) + "Match the presumed substring STRING to the entries in TABLE. +Respect PRED and POINT. The pattern used is a PCM-style +substring pattern, but it be massaged by TRANSFORM-PATTERN-FN, if +that is non-nil." (let* ((beforepoint (substring string 0 point)) (afterpoint (substring string point)) (bounds (completion-boundaries beforepoint table pred afterpoint)) @@ -3318,6 +4026,10 @@ the same set of elements." (pattern (if (not (stringp (car basic-pattern))) basic-pattern (cons 'prefix basic-pattern))) + (pattern (completion-pcm--optimize-pattern + (if transform-pattern-fn + (funcall transform-pattern-fn pattern) + pattern))) (all (completion-pcm--all-completions prefix pattern table pred))) (list all pattern prefix suffix (car bounds)))) @@ -3337,6 +4049,95 @@ the same set of elements." (nconc (completion-pcm--hilit-commonality pattern all) (length prefix))))) +;;; "flex" completion, also known as flx/fuzzy/scatter completion +;; Completes "foo" to "frodo" and "farfromsober" + +(defcustom completion-flex-nospace nil + "Non-nil if `flex' completion rejects spaces in search pattern." + :version "27.1" + :type 'boolean) + +(put 'flex 'completion--adjust-metadata 'completion--flex-adjust-metadata) + +(defun completion--flex-adjust-metadata (metadata) + "If `flex' is actually doing filtering, adjust sorting." + (let ((flex-is-filtering-p + ;; JT@2019-12-23: FIXME: this is kinda wrong. What we need + ;; to test here is "some input that actually leads/led to + ;; flex filtering", not "something after the minibuffer + ;; prompt". E.g. The latter is always true for file + ;; searches, meaning we'll be doing extra work when we + ;; needn't. + (or (not (window-minibuffer-p)) + (> (point-max) (minibuffer-prompt-end)))) + (existing-dsf + (completion-metadata-get metadata 'display-sort-function)) + (existing-csf + (completion-metadata-get metadata 'cycle-sort-function))) + (cl-flet + ((compose-flex-sort-fn + (existing-sort-fn) ; wish `cl-flet' had proper indentation... + (lambda (completions) + (sort + (funcall existing-sort-fn completions) + (lambda (c1 c2) + (let ((s1 (get-text-property 0 'completion-score c1)) + (s2 (get-text-property 0 'completion-score c2))) + (> (or s1 0) (or s2 0)))))))) + `(metadata + ,@(and flex-is-filtering-p + `((display-sort-function + . ,(compose-flex-sort-fn (or existing-dsf #'identity))))) + ,@(and flex-is-filtering-p + `((cycle-sort-function + . ,(compose-flex-sort-fn (or existing-csf #'identity))))) + ,@(cdr metadata))))) + +(defun completion-flex--make-flex-pattern (pattern) + "Convert PCM-style PATTERN into PCM-style flex pattern. + +This turns + (prefix \"foo\" point) +into + (prefix \"f\" any \"o\" any \"o\" any point) +which is at the core of flex logic. The extra +`any' is optimized away later on." + (mapcan (lambda (elem) + (if (stringp elem) + (mapcan (lambda (char) + (list (string char) 'any)) + elem) + (list elem))) + pattern)) + +(defun completion-flex-try-completion (string table pred point) + "Try to flex-complete STRING in TABLE given PRED and POINT." + (unless (and completion-flex-nospace (string-search " " string)) + (pcase-let ((`(,all ,pattern ,prefix ,suffix ,_carbounds) + (completion-substring--all-completions + string table pred point + #'completion-flex--make-flex-pattern))) + (if minibuffer-completing-file-name + (setq all (completion-pcm--filename-try-filter all))) + ;; Try some "merging", meaning add as much as possible to the + ;; user's pattern without losing any possible matches in `all'. + ;; i.e this will augment "cfi" to "config" if all candidates + ;; contain the substring "config". FIXME: this still won't + ;; augment "foo" to "froo" when matching "frodo" and + ;; "farfromsober". + (completion-pcm--merge-try pattern all prefix suffix)))) + +(defun completion-flex-all-completions (string table pred point) + "Get flex-completions of STRING in TABLE, given PRED and POINT." + (unless (and completion-flex-nospace (string-search " " string)) + (pcase-let ((`(,all ,pattern ,prefix ,_suffix ,_carbounds) + (completion-substring--all-completions + string table pred point + #'completion-flex--make-flex-pattern))) + (when all + (nconc (completion-pcm--hilit-commonality pattern all) + (length prefix)))))) + ;; Initials completion ;; Complete /ums to /usr/monnier/src or lch to list-command-history. @@ -3378,8 +4179,42 @@ the same set of elements." (let ((newstr (completion-initials-expand string table pred))) (when newstr (completion-pcm-try-completion newstr table pred (length newstr))))) + +;; Shorthand completion +;; +;; Iff there is a (("x-" . "string-library-")) shorthand setup and +;; string-library-foo is in candidates, complete x-foo to it. + +(defun completion-shorthand-try-completion (string table pred point) + "Try completion with `read-symbol-shorthands' of original buffer." + (cl-loop with expanded + for (short . long) in + (with-current-buffer minibuffer--original-buffer + read-symbol-shorthands) + for probe = + (and (> point (length short)) + (string-prefix-p short string) + (try-completion (setq expanded + (concat long + (substring + string + (length short)))) + table pred)) + when probe + do (message "Shorthand expansion") + and return (cons expanded (max (length long) + (+ (- point (length short)) + (length long)))))) + +(defun completion-shorthand-all-completions (_string _table _pred _point) + ;; no-op: For now, we don't want shorthands to list all the possible + ;; locally active longhands. For the completion categories where + ;; this style is active, it could hide other more interesting + ;; matches from subsequent styles. + nil) + -(defvar completing-read-function 'completing-read-default +(defvar completing-read-function #'completing-read-default "The function called by `completing-read' to do its work. It should accept the same arguments as `completing-read'.") @@ -3396,11 +4231,7 @@ See `completing-read' for the meaning of the arguments." ;; `read-from-minibuffer' uses 1-based index. (1+ (cdr initial-input))))) - (let* ((minibuffer-completion-table collection) - (minibuffer-completion-predicate predicate) - (minibuffer-completion-confirm (unless (eq require-match t) - require-match)) - (base-keymap (if require-match + (let* ((base-keymap (if require-match minibuffer-local-must-match-map minibuffer-local-completion-map)) (keymap (if (memq minibuffer-completing-file-name '(nil lambda)) @@ -3413,8 +4244,22 @@ See `completing-read' for the meaning of the arguments." ;; in minibuffer-local-filename-completion-map can ;; override bindings in base-keymap. base-keymap))) - (result (read-from-minibuffer prompt initial-input keymap - nil hist def inherit-input-method))) + (buffer (current-buffer)) + (c-i-c completion-ignore-case) + (result + (minibuffer-with-setup-hook + (lambda () + (setq-local minibuffer-completion-table collection) + (setq-local minibuffer-completion-predicate predicate) + ;; FIXME: Remove/rename this var, see the next one. + (setq-local minibuffer-completion-confirm + (unless (eq require-match t) require-match)) + (setq-local minibuffer--require-match require-match) + (setq-local minibuffer--original-buffer buffer) + ;; Copy the value from original buffer to the minibuffer. + (setq-local completion-ignore-case c-i-c)) + (read-from-minibuffer prompt initial-input keymap + nil hist def inherit-input-method)))) (when (and (equal result "") def) (setq result (if (consp def) (car def) def))) result)) @@ -3430,6 +4275,215 @@ See `completing-read' for the meaning of the arguments." (when file-name-at-point (insert file-name-at-point)))) +(defun minibuffer-beginning-of-buffer (&optional arg) + "Move to the logical beginning of the minibuffer. +This command behaves like `beginning-of-buffer', but if point is +after the end of the prompt, move to the end of the prompt. +Otherwise move to the start of the buffer." + (declare (interactive-only "use `(goto-char (point-min))' instead.")) + (interactive "^P") + (when (or (consp arg) + (region-active-p)) + (push-mark)) + (goto-char (cond + ;; We want to go N/10th of the way from the beginning. + ((and arg (not (consp arg))) + (+ (point-min) 1 + (/ (* (- (point-max) (point-min)) + (prefix-numeric-value arg)) + 10))) + ;; Go to the start of the buffer. + ((or (null minibuffer-beginning-of-buffer-movement) + (<= (point) (minibuffer-prompt-end))) + (point-min)) + ;; Go to the end of the minibuffer. + (t + (minibuffer-prompt-end)))) + (when (and arg (not (consp arg))) + (forward-line 1))) + +(defmacro with-minibuffer-selected-window (&rest body) + "Execute the forms in BODY from the minibuffer in its original window. +When used in a minibuffer window, select the window selected just before +the minibuffer was activated, and execute the forms." + (declare (indent 0) (debug t)) + `(let ((window (minibuffer-selected-window))) + (when window + (with-selected-window window + ,@body)))) + +(defun minibuffer-recenter-top-bottom (&optional arg) + "Run `recenter-top-bottom' from the minibuffer in its original window." + (interactive "P") + (with-minibuffer-selected-window + (recenter-top-bottom arg))) + +(defun minibuffer-scroll-up-command (&optional arg) + "Run `scroll-up-command' from the minibuffer in its original window." + (interactive "^P") + (with-minibuffer-selected-window + (scroll-up-command arg))) + +(defun minibuffer-scroll-down-command (&optional arg) + "Run `scroll-down-command' from the minibuffer in its original window." + (interactive "^P") + (with-minibuffer-selected-window + (scroll-down-command arg))) + +(defun minibuffer-scroll-other-window (&optional arg) + "Run `scroll-other-window' from the minibuffer in its original window." + (interactive "P") + (with-minibuffer-selected-window + (scroll-other-window arg))) + +(defun minibuffer-scroll-other-window-down (&optional arg) + "Run `scroll-other-window-down' from the minibuffer in its original window." + (interactive "^P") + (with-minibuffer-selected-window + (scroll-other-window-down arg))) + +(defmacro with-minibuffer-completions-window (&rest body) + "Execute the forms in BODY from the minibuffer in its completions window. +When used in a minibuffer window, select the window with completions, +and execute the forms." + (declare (indent 0) (debug t)) + `(let ((window (or (get-buffer-window "*Completions*" 0) + ;; Make sure we have a completions window. + (progn (minibuffer-completion-help) + (get-buffer-window "*Completions*" 0))))) + (when window + (with-selected-window window + ,@body)))) + +(defcustom minibuffer-completion-auto-choose t + "Non-nil means to automatically insert completions to the minibuffer. +When non-nil, then `minibuffer-next-completion' and +`minibuffer-previous-completion' will insert the completion +selected by these commands to the minibuffer." + :type 'boolean + :version "29.1") + +(defun minibuffer-next-completion (&optional n) + "Move to the next item in its completions window from the minibuffer. +When `minibuffer-completion-auto-choose' is non-nil, then also +insert the selected completion to the minibuffer." + (interactive "p") + (let ((auto-choose minibuffer-completion-auto-choose)) + (with-minibuffer-completions-window + (when completions-highlight-face + (setq-local cursor-face-highlight-nonselected-window t)) + (next-completion (or n 1)) + (when auto-choose + (let ((completion-use-base-affixes t)) + (choose-completion nil t t)))))) + +(defun minibuffer-previous-completion (&optional n) + "Move to the previous item in its completions window from the minibuffer. +When `minibuffer-completion-auto-choose' is non-nil, then also +insert the selected completion to the minibuffer." + (interactive "p") + (minibuffer-next-completion (- (or n 1)))) + +(defun minibuffer-choose-completion (&optional no-exit no-quit) + "Run `choose-completion' from the minibuffer in its completions window. +With prefix argument NO-EXIT, insert the completion at point to the +minibuffer, but don't exit the minibuffer. When the prefix argument +is not provided, then whether to exit the minibuffer depends on the value +of `completion-no-auto-exit'. +If NO-QUIT is non-nil, insert the completion at point to the +minibuffer, but don't quit the completions window." + (interactive "P") + (with-minibuffer-completions-window + (let ((completion-use-base-affixes t)) + (choose-completion nil no-exit no-quit)))) + +(defun minibuffer-complete-history () + "Complete the minibuffer history as far as possible. +Like `minibuffer-complete' but completes on the history items +instead of the default completion table." + (interactive) + (let* ((history (symbol-value minibuffer-history-variable)) + (completions + (if (listp history) + ;; Support e.g. `C-x ESC ESC TAB' as + ;; a replacement of `list-command-history' + (mapcar (lambda (h) + (if (stringp h) h (format "%S" h))) + history) + (user-error "No history available")))) + ;; FIXME: Can we make it work for CRM? + (completion-in-region + (minibuffer--completion-prompt-end) (point-max) + (lambda (string pred action) + (if (eq action 'metadata) + '(metadata (display-sort-function . identity) + (cycle-sort-function . identity)) + (complete-with-action action completions string pred)))))) + +(defun minibuffer-complete-defaults () + "Complete minibuffer defaults as far as possible. +Like `minibuffer-complete' but completes on the default items +instead of the completion table." + (interactive) + (when (and (not minibuffer-default-add-done) + (functionp minibuffer-default-add-function)) + (setq minibuffer-default-add-done t + minibuffer-default (funcall minibuffer-default-add-function))) + (let ((completions (ensure-list minibuffer-default))) + (completion-in-region + (minibuffer--completion-prompt-end) (point-max) + (lambda (string pred action) + (if (eq action 'metadata) + '(metadata (display-sort-function . identity) + (cycle-sort-function . identity)) + (complete-with-action action completions string pred)))))) + +(define-key minibuffer-local-map [?\C-x up] 'minibuffer-complete-history) +(define-key minibuffer-local-map [?\C-x down] 'minibuffer-complete-defaults) + +(defcustom minibuffer-default-prompt-format " (default %s)" + "Format string used to output \"default\" values. +When prompting for input, there will often be a default value, +leading to prompts like \"Number of articles (default 50): \". +The \"default\" part of that prompt is controlled by this +variable, and can be set to, for instance, \" [%s]\" if you want +a shorter displayed prompt, or \"\", if you don't want to display +the default at all. + +This variable is used by the `format-prompt' function." + :version "28.1" + :type 'string) + +(defun format-prompt (prompt default &rest format-args) + "Format PROMPT with DEFAULT according to `minibuffer-default-prompt-format'. +If FORMAT-ARGS is nil, PROMPT is used as a plain string. If +FORMAT-ARGS is non-nil, PROMPT is used as a format control +string, and FORMAT-ARGS are the arguments to be substituted into +it. See `format' for details. + +Both PROMPT and `minibuffer-default-prompt-format' are run +through `substitute-command-keys' (which see). In particular, +this means that single quotes may be displayed by equivalent +characters, according to the capabilities of the terminal. + +If DEFAULT is a list, the first element is used as the default. +If not, the element is used as is. + +If DEFAULT is nil or an empty string, no \"default value\" string +is included in the return value." + (concat + (if (null format-args) + (substitute-command-keys prompt) + (apply #'format (substitute-command-keys prompt) format-args)) + (and default + (or (not (stringp default)) + (length> default 0)) + (format (substitute-command-keys minibuffer-default-prompt-format) + (if (consp default) + (car default) + default))) + ": ")) + (provide 'minibuffer) ;;; minibuffer.el ends here |