X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/5d90542e6fa73a7a6f9fb4bd3db67549d7434971..706349fcfae297ee285552af9246bc0cf00d9b7f:/ivy.el diff --git a/ivy.el b/ivy.el index 929b71da3..7d25c501c 100644 --- a/ivy.el +++ b/ivy.el @@ -44,20 +44,54 @@ "Incremental vertical completion." :group 'convenience) +(defgroup ivy-faces nil + "Font-lock faces for `ivy'." + :group 'ivy) + (defface ivy-current-match '((((class color) (background light)) :background "#1a4b77" :foreground "white") (((class color) (background dark)) :background "#65a7e2" :foreground "black")) - "Face used by Ivy for highlighting first match.") + "Face used by Ivy for highlighting the current match.") + +(defface ivy-minibuffer-match-face-1 + '((((class color) (background light)) + :background "#d3d3d3") + (((class color) (background dark)) + :background "#555555")) + "The background face for `ivy' minibuffer matches.") + +(defface ivy-minibuffer-match-face-2 + '((((class color) (background light)) + :background "#e99ce8" :weight bold) + (((class color) (background dark)) + :background "#777777" :weight bold)) + "Face for `ivy' minibuffer matches modulo 1.") + +(defface ivy-minibuffer-match-face-3 + '((((class color) (background light)) + :background "#bbbbff" :weight bold) + (((class color) (background dark)) + :background "#7777ff" :weight bold)) + "Face for `ivy' minibuffer matches modulo 2.") + +(defface ivy-minibuffer-match-face-4 + '((((class color) (background light)) + :background "#ffbbff" :weight bold) + (((class color) (background dark)) + :background "#8a498a" :weight bold)) + "Face for `ivy' minibuffer matches modulo 3.") (defface ivy-confirm-face '((t :foreground "ForestGreen" :inherit minibuffer-prompt)) - "Face used by Ivy to issue a confirmation prompt.") + "Face used by Ivy for a confirmation prompt.") (defface ivy-match-required-face '((t :foreground "red" :inherit minibuffer-prompt)) - "Face used by Ivy to issue a match required prompt.") + "Face used by Ivy for a match required prompt.") + +(setcdr (assoc load-file-name custom-current-group-alist) 'ivy) (defface ivy-subdir '((t (:inherit 'dired-directory))) @@ -71,15 +105,18 @@ '((t (:foreground "#110099"))) "Face used by Ivy for highlighting remotes in the alternatives.") +(defface ivy-virtual + '((t :inherit font-lock-builtin-face)) + "Face used by Ivy for matching virtual buffer names.") + (defcustom ivy-height 10 "Number of lines for the minibuffer window." :type 'integer) (defcustom ivy-count-format "%-4d " - "The style of showing the current candidate count for `ivy-read'. -Set this to \"\" if you don't want the count. You can also set -it to e.g. \"(%d/%d) \" if you want to see both the candidate -index and the candidate count." + "The style to use for displaying the current candidate count for `ivy-read'. +Set this to \"\" to suppress the count visibility. +Set this to \"(%d/%d) \" to display both the index and the count." :type '(choice (const :tag "Count disabled" "") (const :tag "Count matches" "%-4d ") @@ -87,27 +124,27 @@ index and the candidate count." string)) (defcustom ivy-wrap nil - "Whether to wrap around after the first and last candidate." + "When non-nil, wrap around after the first and the last candidate." :type 'boolean) (defcustom ivy-display-style (unless (version< emacs-version "24.5") 'fancy) "The style for formatting the minibuffer. -By default, the matched strings will be copied as they are. +By default, the matched strings are copied as is. -With the fancy method, the matching parts of the regexp will be -additionally highlighted, just like `swiper' does it. +The fancy display style highlights matching parts of the regexp, +a behavior similar to `swiper'. This setting depends on `add-face-text-property' - a C function -available as of 24.5. It will behave poorly in earlier Emacs -versions." +available as of Emacs 24.5. Fancy style will render poorly in +earlier versions of Emacs." :type '(choice (const :tag "Plain" nil) (const :tag "Fancy" fancy))) (defcustom ivy-on-del-error-function 'minibuffer-keyboard-quit "The handler for when `ivy-backward-delete-char' throws. -This is usually meant as a quick exit out of the minibuffer." +Usually a quick exit out of the minibuffer." :type 'function) (defcustom ivy-extra-directories '("../" "./") @@ -214,7 +251,7 @@ This should eventually become a stack so that you could use "History list of candidates entered in the minibuffer. Maximum length of the history list is determined by the value -of `history-length', which see.") +of `history-length'.") (defvar ivy--directory nil "Current directory when completing file names.") @@ -243,7 +280,7 @@ Otherwise, store nil.") (defvar ivy--prompt nil "Store the format-style prompt. -When non-nil, it should contain one %d.") +When non-nil, it should contain at least one %d.") (defvar ivy--prompt-extra "" "Temporary modifications to the prompt.") @@ -285,7 +322,7 @@ When non-nil, it should contain one %d.") "Quit the minibuffer and call ACTION afterwards." (ivy-set-action `(lambda (x) - (funcall ,action x) + (funcall ',action x) (ivy-set-action ',(ivy-state-action ivy-last)))) (setq ivy-exit 'done) (exit-minibuffer)) @@ -390,72 +427,88 @@ Is is a cons cell, related to `tramp-get-completion-function'." "Exit the minibuffer with the selected candidate. When ARG is t, exit with current text, ignoring the candidates." (interactive "P") + (cond (arg + (ivy-immediate-done)) + (ivy--directory + (ivy--directory-done)) + ((eq (ivy-state-collection ivy-last) 'Info-read-node-name-1) + (if (or (equal ivy--current "(./)") + (equal ivy--current "(../)")) + (ivy-quit-and-run + (ivy-read "Go to file: " 'read-file-name-internal + :action (lambda (x) + (Info-find-node + (expand-file-name x ivy--directory) + "Top")))) + (ivy-done))) + (t + (ivy-done)))) + +(defun ivy--directory-done () + "Handle exit from the minibuffer when completing file names." (let (dir) - (cond (arg - (ivy-immediate-done)) - ((and ivy--directory - (equal ivy-text "/sudo::")) - (setq dir (concat ivy-text ivy--directory)) - (ivy--cd dir) - (ivy--exhibit)) - ((and ivy--directory - (or - (and - (not (equal ivy-text "")) - (ignore-errors - (file-directory-p - (setq dir - (file-name-as-directory - (expand-file-name - ivy-text ivy--directory)))))) - (and - (not (string= ivy--current "./")) - (cl-plusp ivy--length) - (file-directory-p - (setq dir (expand-file-name - ivy--current ivy--directory)))))) - (ivy--cd dir) - (ivy--exhibit)) - ((eq (ivy-state-collection ivy-last) 'Info-read-node-name-1) - (if (or (equal ivy--current "(./)") - (equal ivy--current "(../)")) - (ivy-quit-and-run - (ivy-read "Go to file: " 'read-file-name-internal - :action (lambda (x) - (Info-find-node - (expand-file-name x ivy--directory) - "Top")))) - (ivy-done))) - ((and ivy--directory - (string-match "\\`/[^/]+:.*:.*\\'" ivy-text)) - (ivy-done)) - ((and ivy--directory - (string-match - "\\`/\\([^/]+?\\):\\(?:\\(.*\\)@\\)?\\(.*\\)\\'" - ivy-text)) - (let ((method (match-string 1 ivy-text)) - (user (match-string 2 ivy-text)) - (rest (match-string 3 ivy-text)) - res) - (require 'tramp) - (dolist (x (tramp-get-completion-function method)) - (setq res (append res (funcall (car x) (cadr x))))) - (setq res (delq nil res)) - (when user - (dolist (x res) - (setcar x user))) - (setq res (cl-delete-duplicates res :test #'equal)) - (let* ((old-ivy-last ivy-last) - (enable-recursive-minibuffers t) - (host (ivy-read "Find File: " - (mapcar #'ivy-build-tramp-name res) - :initial-input rest))) - (setq ivy-last old-ivy-last) - (when host - (setq ivy--directory "/") - (ivy--cd (concat "/" method ":" host ":")))))) - (t - (ivy-done))))) + (cond + ((equal ivy-text "/sudo::") + (setq dir (concat ivy-text ivy--directory)) + (ivy--cd dir) + (ivy--exhibit)) + ((or + (and + (not (equal ivy-text "")) + (ignore-errors + (file-directory-p + (setq dir + (file-name-as-directory + (expand-file-name + ivy-text ivy--directory)))))) + (and + (not (string= ivy--current "./")) + (cl-plusp ivy--length) + (ignore-errors + (file-directory-p + (setq dir (file-name-as-directory + (expand-file-name + ivy--current ivy--directory))))))) + (ivy--cd dir) + (ivy--exhibit)) + ((or (and (equal ivy--directory "/") + (string-match "\\`[^/]+:.*:.*\\'" ivy-text)) + (string-match "\\`/[^/]+:.*:.*\\'" ivy-text)) + (ivy-done)) + ((or (and (equal ivy--directory "/") + (cond ((string-match + "\\`\\([^/]+?\\):\\(?:\\(.*\\)@\\)?\\(.*\\)\\'" + ivy-text)) + ((string-match + "\\`\\([^/]+?\\):\\(?:\\(.*\\)@\\)?\\(.*\\)\\'" + ivy--current) + (setq ivy-text ivy--current)))) + (string-match + "\\`/\\([^/]+?\\):\\(?:\\(.*\\)@\\)?\\(.*\\)\\'" + ivy-text)) + (let ((method (match-string 1 ivy-text)) + (user (match-string 2 ivy-text)) + (rest (match-string 3 ivy-text)) + res) + (require 'tramp) + (dolist (x (tramp-get-completion-function method)) + (setq res (append res (funcall (car x) (cadr x))))) + (setq res (delq nil res)) + (when user + (dolist (x res) + (setcar x user))) + (setq res (cl-delete-duplicates res :test #'equal)) + (let* ((old-ivy-last ivy-last) + (enable-recursive-minibuffers t) + (host (ivy-read "Find File: " + (mapcar #'ivy-build-tramp-name res) + :initial-input rest))) + (setq ivy-last old-ivy-last) + (when host + (setq ivy--directory "/") + (ivy--cd (concat "/" method ":" host ":")))))) + (t + (ivy-done))))) (defcustom ivy-tab-space nil "When non-nil, `ivy-partial-or-done' should insert a space." @@ -466,13 +519,16 @@ When ARG is t, exit with current text, ignoring the candidates." If the text hasn't changed as a result, forward to `ivy-alt-done'." (interactive) (if (and (eq (ivy-state-collection ivy-last) #'read-file-name-internal) - (string-match "\\`/" ivy-text)) + (or (and (equal ivy--directory "/") + (string-match "\\`[^/]+:.*\\'" ivy-text)) + (string-match "\\`/" ivy-text))) (let ((default-directory ivy--directory)) (minibuffer-complete) (setq ivy-text (ivy--input)) - (when (and (file-directory-p ivy-text) - (= ivy--length 1)) - (ivy--cd (expand-file-name ivy-text)))) + (when (file-directory-p + (expand-file-name ivy-text ivy--directory)) + (ivy--cd (file-name-as-directory + (expand-file-name ivy-text ivy--directory))))) (or (ivy-partial) (when (or (eq this-command last-command) (eq ivy--length 1)) @@ -520,6 +576,8 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." (defun ivy-resume () "Resume the last completion session." (interactive) + (when (eq (ivy-state-caller ivy-last) 'swiper) + (switch-to-buffer (ivy-state-buffer ivy-last))) (with-current-buffer (ivy-state-buffer ivy-last) (ivy-read (ivy-state-prompt ivy-last) @@ -736,12 +794,9 @@ If so, move to that directory, while keeping only the file name." (let ((input (ivy--input)) url) (if (setq url (ffap-url-p input)) - (progn - (ivy-set-action - (lambda (_) - (funcall ffap-url-fetcher url))) - (setq ivy-exit 'done) - (exit-minibuffer)) + (ivy-exit-with-action + (lambda (_) + (funcall ffap-url-fetcher url))) (setq input (expand-file-name input)) (let ((file (file-name-nondirectory input)) (dir (expand-file-name (file-name-directory input)))) @@ -907,11 +962,11 @@ Prioritize directories." "An alist of sorting functions for each collection function. Interactive functions that call completion fit in here as well. -For each entry, nil means no sorting. It's very useful to turn -off the sorting for functions that have candidates in the natural -buffer order, like `org-refile' or `Man-goto-section'. +Nil means no sorting, which is useful to turn off the sorting for +functions that have candidates in the natural buffer order, like +`org-refile' or `Man-goto-section'. -The entry associated to t is used for all fall-through cases. +The entry associated with t is used for all fall-through cases. See also `ivy-sort-max-size'." :type @@ -929,11 +984,12 @@ See also `ivy-sort-max-size'." '((swiper . ivy-recompute-index-swiper) (swiper-multi . ivy-recompute-index-swiper) (counsel-git-grep . ivy-recompute-index-swiper) + (counsel-grep . ivy-recompute-index-swiper-async) (t . ivy-recompute-index-zero)) "An alist of index recomputing functions for each collection function. -When the input changes, calling the appropriate function will -return an integer - the index of the matched candidate that -should be selected.") +When the input changes, the appropriate function returns an +integer - the index of the matched candidate that should be +selected.") (defvar ivy-re-builders-alist '((t . ivy--regex-plus)) @@ -941,10 +997,10 @@ should be selected.") Each function should take a string and return a valid regex or a regex sequence (see below). -The entry associated to t is used for all fall-through cases. +The entry associated with t is used for all fall-through cases. Possible choices: `ivy--regex', `regexp-quote', `ivy--regex-plus'. -In case a function returns a list, it should look like this: +If a function returns a list, it should format like this: '((\"matching-regexp\" . t) (\"non-matching-regexp\") ...). The matches will be filtered in a sequence, you can mix the @@ -989,47 +1045,47 @@ Directories come first." ;;** Entry Point (cl-defun ivy-read (prompt collection - &key predicate require-match initial-input - history preselect keymap update-fn sort - action unwind re-builder matcher dynamic-collection caller) + &key predicate require-match initial-input + history preselect keymap update-fn sort + action unwind re-builder matcher dynamic-collection caller) "Read a string in the minibuffer, with completion. -PROMPT is a string to prompt with; normally it ends in a colon -and a space. When PROMPT contains %d, it will be updated with -the current number of matching candidates. If % appears elsewhere -in the PROMPT it should be quoted as %%. -See also `ivy-count-format'. +PROMPT is a format string, normally ending in a colon and a +space; %d anywhere in the string is replaced by the current +number of matching candidates. For the literal % character, +escape it with %%. See also `ivy-count-format'. -COLLECTION is a list of strings, or a function, or an alist, or a -hash table. +COLLECTION is either a list of strings, a function, an alist, or +a hash table. -If INITIAL-INPUT is non-nil, insert it in the minibuffer initially. +If INITIAL-INPUT is not nil, then insert that input in the +minibuffer initially. -KEYMAP is composed together with `ivy-minibuffer-map'. +KEYMAP is composed with `ivy-minibuffer-map'. -If PRESELECT is non-nil select the corresponding candidate out of -the ones that match INITIAL-INPUT. +If PRESELECT is not nil, then select the corresponding candidate +out of the ones that match the INITIAL-INPUT. UPDATE-FN is called each time the current candidate(s) is changed. -When SORT is t, refer to `ivy-sort-functions-alist' for sorting. +When SORT is t, use `ivy-sort-functions-alist' for sorting. -ACTION is a lambda to call after a result was selected. It should -take a single argument, usually a string. +ACTION is a lambda function to call after selecting a result. It +takes a single string argument. -UNWIND is a lambda to call before exiting. +UNWIND is a lambda function to call before exiting. -RE-BUILDER is a lambda that transforms text into a regex. +RE-BUILDER is a lambda function to call to transform text into a +regex pattern. -MATCHER can completely override matching. +MATCHER is to override matching. -DYNAMIC-COLLECTION is a boolean that determines whether to update -the list of candidates with each input by calling COLLECTION for -the current input. +DYNAMIC-COLLECTION is a boolean to specify if the list of +candidates is updated after each input by calling COLLECTION. CALLER is a symbol to uniquely identify the caller to `ivy-read'. -It's used in conjunction with COLLECTION to indentify which -customizations should apply to the current completion session." +It is used, along with COLLECTION, to determine which +customizations apply to the current completion session." (let ((extra-actions (plist-get ivy--actions-list this-command))) (when extra-actions (setq action @@ -1038,7 +1094,7 @@ customizations should apply to the current completion session." ("o" ,action "default") ,@extra-actions) (delete-dups (append action extra-actions)))))) - (let ((recursive-ivy-last (and (window-minibuffer-p) ivy-last))) + (let ((recursive-ivy-last (and (active-minibuffer-window) ivy-last))) (setq ivy-last (make-ivy-state :prompt prompt @@ -1062,30 +1118,30 @@ customizations should apply to the current completion session." (ivy--reset-state ivy-last) (prog1 (unwind-protect - (minibuffer-with-setup-hook - #'ivy--minibuffer-setup - (let* ((hist (or history 'ivy-history)) - (minibuffer-completion-table collection) - (minibuffer-completion-predicate predicate) - (resize-mini-windows (cond - ((display-graphic-p) nil) - ((null resize-mini-windows) 'grow-only) - (t resize-mini-windows))) - (res (read-from-minibuffer - prompt - (ivy-state-initial-input ivy-last) - (make-composed-keymap keymap ivy-minibuffer-map) - nil - hist))) - (when (eq ivy-exit 'done) - (let ((item (if ivy--directory - ivy--current - ivy-text))) - (unless (equal item "") - (set hist (cons (propertize item 'ivy-index ivy--index) - (delete item - (cdr (symbol-value hist))))))) - res))) + (minibuffer-with-setup-hook + #'ivy--minibuffer-setup + (let* ((hist (or history 'ivy-history)) + (minibuffer-completion-table collection) + (minibuffer-completion-predicate predicate) + (resize-mini-windows (cond + ((display-graphic-p) nil) + ((null resize-mini-windows) 'grow-only) + (t resize-mini-windows))) + (res (read-from-minibuffer + prompt + (ivy-state-initial-input ivy-last) + (make-composed-keymap keymap ivy-minibuffer-map) + nil + hist))) + (when (eq ivy-exit 'done) + (let ((item (if ivy--directory + ivy--current + ivy-text))) + (unless (equal item "") + (set hist (cons (propertize item 'ivy-index ivy--index) + (delete item + (cdr (symbol-value hist))))))) + res))) (remove-hook 'post-command-hook #'ivy--exhibit) (when (setq unwind (ivy-state-unwind ivy-last)) (funcall unwind))) @@ -1096,7 +1152,7 @@ customizations should apply to the current completion session." (defun ivy--reset-state (state) "Reset the ivy to STATE. This is useful for recursive `ivy-read'." - (let ((prompt (ivy-state-prompt state)) + (let ((prompt (or (ivy-state-prompt state) "")) (collection (ivy-state-collection state)) (predicate (ivy-state-predicate state)) (history (ivy-state-history state)) @@ -1106,7 +1162,7 @@ This is useful for recursive `ivy-read'." (dynamic-collection (ivy-state-dynamic-collection state)) (initial-input (ivy-state-initial-input state)) (require-match (ivy-state-require-match state)) - (matcher (ivy-state-matcher state))) + (caller (ivy-state-caller state))) (unless initial-input (setq initial-input (cdr (assoc this-command ivy-initial-inputs-alist)))) @@ -1116,6 +1172,8 @@ This is useful for recursive `ivy-read'." (or re-builder (and (functionp collection) (cdr (assoc collection ivy-re-builders-alist))) + (and caller + (cdr (assoc caller ivy-re-builders-alist))) (cdr (assoc t ivy-re-builders-alist)) 'ivy--regex)) (setq ivy--subexps 0) @@ -1175,20 +1233,32 @@ This is useful for recursive `ivy-read'." (when preselect (unless (or (and require-match (not (eq collection 'internal-complete-buffer))) + dynamic-collection (let ((re (regexp-quote preselect))) (cl-find-if (lambda (x) (string-match re x)) coll))) (setq coll (cons preselect coll)))) - (setq ivy--index (or - (and dynamic-collection - ivy--index) - (and preselect - (ivy--preselect-index - coll initial-input preselect matcher)) - 0)) (setq ivy--old-re nil) (setq ivy--old-cands nil) - (setq ivy--all-candidates coll)) + (when (integerp preselect) + (setq ivy--old-re "") + (setq ivy--index preselect)) + (when initial-input + ;; Needed for anchor to work + (setq ivy--old-cands coll) + (setq ivy--old-cands (ivy--filter initial-input coll))) + (setq ivy--all-candidates coll) + (unless (integerp preselect) + (setq ivy--index (or + (and dynamic-collection + ivy--index) + (and preselect + (ivy--preselect-index + preselect + (if initial-input + ivy--old-cands + coll))) + 0)))) (setq ivy-exit nil) (setq ivy--default (or (thing-at-point 'url) @@ -1225,19 +1295,24 @@ This is useful for recursive `ivy-read'." history def _inherit-input-method) "Read a string in the minibuffer, with completion. -This is an interface that conforms to `completing-read', so that -it can be used for `completing-read-function'. +This interface conforms to `completing-read' and can be used for +`completing-read-function'. PROMPT is a string to prompt with; normally it ends in a colon and a space. COLLECTION can be a list of strings, an alist, an obarray or a hash table. PREDICATE limits completion to a subset of COLLECTION. -REQUIRE-MATCH is considered boolean. See `completing-read'. +REQUIRE-MATCH is specified with a boolean value. See `completing-read'. INITIAL-INPUT is a string that can be inserted into the minibuffer initially. -_HISTORY is ignored for now. +HISTORY is a list of previously selected inputs. DEF is the default value. -_INHERIT-INPUT-METHOD is ignored for now. - -The history, defaults and input-method arguments are ignored for now." +_INHERIT-INPUT-METHOD is currently ignored." + + ;; See the doc of `completing-read'. + (when (consp history) + (when (numberp (cdr history)) + (setq initial-input (nth (1- (cdr history)) + (symbol-value (car history))))) + (setq history (car history))) (ivy-read (replace-regexp-in-string "%" "%%" prompt) collection :predicate predicate @@ -1261,8 +1336,8 @@ The history, defaults and input-method arguments are ignored for now." ;;;###autoload (define-minor-mode ivy-mode "Toggle Ivy mode on or off. -With ARG, turn Ivy mode on if arg is positive, off otherwise. -Turning on Ivy mode will set `completing-read-function' to +Turn Ivy mode on if ARG is positive, off otherwise. +Turning on Ivy mode sets `completing-read-function' to `ivy-completing-read'. Global bindings: @@ -1278,28 +1353,17 @@ Minibuffer bindings: (setq completing-read-function 'ivy-completing-read) (setq completing-read-function 'completing-read-default))) -(defun ivy--preselect-index (candidates initial-input preselect matcher) - "Return the index in CANDIDATES filtered by INITIAL-INPUT for PRESELECT. -When MATCHER is non-nil it's used instead of `cl-remove-if-not'." - (if initial-input - (progn - (setq initial-input (ivy--regex-plus initial-input)) - (setq candidates - (if matcher - (funcall matcher initial-input candidates) - (cl-remove-if-not - (lambda (x) - (string-match initial-input x)) - candidates)))) - (when matcher - (setq candidates (funcall matcher "" candidates)))) - (or (cl-position preselect candidates :test #'equal) - (and (stringp preselect) - (let ((re (regexp-quote preselect))) - (cl-position-if - (lambda (x) - (string-match re x)) - candidates))))) +(defun ivy--preselect-index (preselect candidates) + "Return the index of PRESELECT in CANDIDATES." + (cond ((integerp preselect) + preselect) + ((cl-position preselect candidates :test #'equal)) + ((stringp preselect) + (let ((re (regexp-quote preselect))) + (cl-position-if + (lambda (x) + (string-match re x)) + candidates))))) ;;* Implementation ;;** Regex @@ -1339,7 +1403,7 @@ This allows to \"quote\" N spaces by inputting N+1 spaces." (nreverse res))) (defun ivy--regex (str &optional greedy) - "Re-build regex from STR in case it has a space. + "Re-build regex pattern from STR in case it has a space. When GREEDY is non-nil, join words in a greedy way." (let ((hashed (unless greedy (gethash str ivy--regex-hash)))) @@ -1368,8 +1432,15 @@ When GREEDY is non-nil, join words in a greedy way." ivy--regex-hash))))) (defun ivy--regex-ignore-order (str) - "Re-build regex from STR by splitting it on spaces. -Ignore the order of each group." + "Re-build regex from STR by splitting at spaces. +Ignore the order of each group. + +ATTENTION: This is just a proof of concept and may not work as +expected. Besides ignoring the order of the tokens where 'foo' +and 'bar', 'bar' and 'foo' are matched, it also matches multiple +occurrences of 'foo' and 'bar'. To ignore the sort order and avoid +multiple matches, use `ivy-restrict-to-matches' instead. +" (let* ((subs (split-string str " +" t)) (len (length subs))) (cl-case len @@ -1389,8 +1460,8 @@ Ignore the order of each group." (defun ivy--regex-plus (str) "Build a regex sequence from STR. -Spaces are wild, everything before \"!\" should match. -Everything after \"!\" should not match." +Spaces are wild card characters, everything before \"!\" should +match. Everything after \"!\" should not match." (let ((parts (split-string str "!" t))) (cl-case (length parts) (0 @@ -1450,7 +1521,7 @@ Insert .* between each char." "Update the prompt according to `ivy--prompt'." (when ivy--prompt (unless (memq this-command '(ivy-done ivy-alt-done ivy-partial-or-done - counsel-find-symbol)) + counsel-find-symbol)) (setq ivy--prompt-extra "")) (let (head tail) (if (string-match "\\(.*\\): \\'" ivy--prompt) @@ -1506,7 +1577,7 @@ Insert .* between each char." (constrain-to-field nil (point-max)))))) (defun ivy--set-match-props (str match props) - "Set STR text proprties that match MATCH to PROPS." + "Set STR text properties that match MATCH to PROPS." (when (string-match match str) (set-text-properties (match-beginning 0) @@ -1581,8 +1652,9 @@ Should be run via minibuffer `post-command-hook'." (ivy--buffer-list "" ivy-use-virtual-buffers))) (setq ivy--old-re nil)))) (ivy--insert-minibuffer - (ivy--format - (ivy--filter ivy-text ivy--all-candidates))) + (with-current-buffer (ivy-state-buffer ivy-last) + (ivy--format + (ivy--filter ivy-text ivy--all-candidates)))) (setq ivy--old-text ivy-text)))) (defun ivy--insert-minibuffer (text) @@ -1604,18 +1676,17 @@ Should be run via minibuffer `post-command-hook'." (ivy--resize-minibuffer-to-fit)))) (defun ivy--resize-minibuffer-to-fit () - "Resize the minibuffer window so it has enough space to display -all of the text contained in the minibuffer." + "Resize the minibuffer window size to fit the text in the minibuffer." (with-selected-window (minibuffer-window) (if (fboundp 'window-text-pixel-size) (let ((text-height (cdr (window-text-pixel-size))) (body-height (window-body-height nil t))) (when (> text-height body-height) (window-resize nil (- text-height body-height) nil t t))) - (let ((text-height (count-screen-lines)) - (body-height (window-body-height))) - (when (> text-height body-height) - (window-resize nil (- text-height body-height) nil t)))))) + (let ((text-height (count-screen-lines)) + (body-height (window-body-height))) + (when (> text-height body-height) + (window-resize nil (- text-height body-height) nil t)))))) (declare-function colir-blend-face-background "ext:colir") @@ -1652,7 +1723,7 @@ all of the text contained in the minibuffer." In any completion session, the case folding starts in auto: - when the input is all lower case, `case-fold-search' is t -- otherwise it's nil. +- otherwise nil. You can toggle this to make `case-fold-search' nil regardless of input." (interactive) @@ -1677,15 +1748,15 @@ CANDIDATES are assumed to be static." (and ivy-case-fold-search (string= name (downcase name)))) (cands (cond - (matcher - (funcall matcher re candidates)) - ((and ivy--old-re - (stringp re) - (stringp ivy--old-re) - (not (string-match "\\\\" ivy--old-re)) - (not (equal ivy--old-re "")) - (memq (cl-search - (if (string-match "\\\\)\\'" ivy--old-re) + (matcher + (funcall matcher re candidates)) + ((and ivy--old-re + (stringp re) + (stringp ivy--old-re) + (not (string-match "\\\\" ivy--old-re)) + (not (equal ivy--old-re "")) + (memq (cl-search + (if (string-match "\\\\)\\'" ivy--old-re) (substring ivy--old-re 0 -2) ivy--old-re) re) @@ -1710,10 +1781,62 @@ CANDIDATES are assumed to be static." res))))) (ivy--recompute-index name re-str cands) (setq ivy--old-re (if cands re-str "")) - (when (and (require 'flx nil 'noerror) - (eq ivy--regex-function 'ivy--regex-fuzzy)) - (setq cands (ivy--flx-sort name cands))) - (setq ivy--old-cands cands))))) + (setq ivy--old-cands (ivy--sort name cands)))))) + +(defcustom ivy-sort-matches-functions-alist '((t . nil)) + "An alist of functions used to sort the matching candidates. + +This is different from `ivy-sort-functions-alist', which is used +to sort the whole collection only once. The functions taken from +here are instead used on each input change, but they are used +only on already matching candidates, not on all of them. + +The alist KEY is a collection function or t to match previously +not matched collection functions. + +The alist VAL is a sorting function with the signature of +`ivy--prefix-sort'.") + +(defun ivy--sort-files-by-date (_name candidates) + "Re-soft CANDIDATES according to file modification date." + (let ((default-directory ivy--directory)) + (cl-sort (copy-sequence candidates) + (lambda (f1 f2) + (time-less-p + (nth 5 (file-attributes f2)) + (nth 5 (file-attributes f1))))))) + +(defun ivy--sort (name candidates) + "Re-sort CANDIDATES by NAME. +All CANDIDATES are assumed to match NAME." + (let ((key (or (ivy-state-caller ivy-last) + (when (functionp (ivy-state-collection ivy-last)) + (ivy-state-collection ivy-last)))) + fun) + (cond ((and (require 'flx nil 'noerror) + (eq ivy--regex-function 'ivy--regex-fuzzy)) + (ivy--flx-sort name candidates)) + ((setq fun (cdr (or (assoc key ivy-sort-matches-functions-alist) + (assoc t ivy-sort-matches-functions-alist)))) + (funcall fun name candidates)) + (t + candidates)))) + +(defun ivy--prefix-sort (name candidates) + "Re-sort CANDIDATES. +Prefix matches to NAME are put ahead of the list." + (if (or (string-match "^\\^" name) (string= name "")) + candidates + (let ((re-prefix (concat "^" (funcall ivy--regex-function name))) + res-prefix + res-noprefix) + (dolist (s candidates) + (if (string-match re-prefix s) + (push s res-prefix) + (push s res-noprefix))) + (nconc + (nreverse res-prefix) + (nreverse res-noprefix))))) (defun ivy--recompute-index (name re-str cands) (let* ((caller (ivy-state-caller ivy-last)) @@ -1745,10 +1868,8 @@ CANDIDATES are assumed to be static." (not (equal ivy--old-re ""))) (setq ivy--index (or (ivy--preselect-index - cands - nil (ivy-state-preselect ivy-last) - nil) + cands) ivy--index))))) (defun ivy-recompute-index-swiper (_re-str cands) @@ -1760,6 +1881,28 @@ CANDIDATES are assumed to be static." ;; Compare with eq to handle equal duplicates in cands (setq idx (cl-position (pop tail) cands))) (or idx 0)) + (if ivy--old-cands + ivy--index + ;; already in ivy-state-buffer + (let ((n (line-number-at-pos)) + (res 0) + (i 0)) + (dolist (c cands) + (when (eq n (read (get-text-property 0 'display c))) + (setq res i)) + (cl-incf i)) + res))))) + +(defun ivy-recompute-index-swiper-async (_re-str cands) + (let ((tail (nthcdr ivy--index ivy--old-cands)) + idx) + (if (and tail ivy--old-cands (not (equal "^" ivy--old-re))) + (progn + (while (and tail (null idx)) + ;; Compare with `equal', since the collection is re-created + ;; each time with `split-string' + (setq idx (cl-position (pop tail) cands :test #'equal))) + (or idx 0)) ivy--index))) (defun ivy-recompute-index-zero (_re-str _cands) @@ -1791,63 +1934,65 @@ CANDIDATES are assumed to be static." (error cands))) -(defvar ivy-format-function 'ivy-format-function-default +(defcustom ivy-format-function 'ivy-format-function-default "Function to transform the list of candidates into a string. -This string will be inserted into the minibuffer.") - -(defun ivy-format-function-default (cands) - "Transform CANDS into a string for minibuffer." - (if (bound-and-true-p truncate-lines) - (mapconcat #'identity cands "\n") - (let ((ww (- (window-width) - (if (and (boundp 'fringe-mode) (eq fringe-mode 0)) 1 0)))) - (mapconcat - (if truncate-lines - (lambda (s) - (if (> (length s) ww) - (concat (substring s 0 (- ww 3)) "...") - s)) - #'identity) - cands "\n")))) - -(defun ivy-format-function-arrow (cands) - "Transform CANDS into a string for minibuffer." +This string is inserted into the minibuffer." + :type '(choice + (const :tag "Default" ivy-format-function-default) + (const :tag "Arrow prefix" ivy-format-function-arrow) + (const :tag "Full line" ivy-format-function-line))) + +(defun ivy--truncate-string (str width) + "Truncate STR to WIDTH." + (if (> (string-width str) width) + (concat (substring str 0 (min (- width 3) + (- (length str) 3))) "...") + str)) + +(defun ivy--format-function-generic (selected-fn other-fn cand-pairs separator) + "Transform CAND-PAIRS into a string for minibuffer. +SELECTED-FN and OTHER-FN each take two string arguments. +SEPARATOR is used to join the candidates." (let ((i -1)) (mapconcat - (lambda (s) - (concat (if (eq (cl-incf i) ivy--index) - "> " - " ") - s)) - cands "\n"))) - -(defface ivy-minibuffer-match-face-1 - '((((class color) (background light)) - :background "#d3d3d3") - (((class color) (background dark)) - :background "#555555")) - "The background face for `ivy' minibuffer matches.") - -(defface ivy-minibuffer-match-face-2 - '((((class color) (background light)) - :background "#e99ce8" :weight bold) - (((class color) (background dark)) - :background "#777777" :weight bold)) - "Face for `ivy' minibuffer matches modulo 1.") - -(defface ivy-minibuffer-match-face-3 - '((((class color) (background light)) - :background "#bbbbff" :weight bold) - (((class color) (background dark)) - :background "#7777ff" :weight bold)) - "Face for `ivy' minibuffer matches modulo 2.") - -(defface ivy-minibuffer-match-face-4 - '((((class color) (background light)) - :background "#ffbbff" :weight bold) - (((class color) (background dark)) - :background "#8a498a" :weight bold)) - "Face for `ivy' minibuffer matches modulo 3.") + (lambda (pair) + (let ((str (car pair)) + (extra (cdr pair)) + (curr (eq (cl-incf i) ivy--index))) + (if curr + (funcall selected-fn str extra) + (funcall other-fn str extra)))) + cand-pairs + separator))) + +(defun ivy-format-function-default (cand-pairs) + "Transform CAND-PAIRS into a string for minibuffer." + (ivy--format-function-generic + (lambda (str extra) + (concat (ivy--add-face str 'ivy-current-match) extra)) + #'concat + cand-pairs + "\n")) + +(defun ivy-format-function-arrow (cand-pairs) + "Transform CAND-PAIRS into a string for minibuffer." + (ivy--format-function-generic + (lambda (str extra) + (concat "> " (ivy--add-face str 'ivy-current-match) extra)) + (lambda (str extra) + (concat " " str extra)) + cand-pairs + "\n")) + +(defun ivy-format-function-line (cand-pairs) + "Transform CAND-PAIRS into a string for minibuffer." + (ivy--format-function-generic + (lambda (str extra) + (ivy--add-face (concat str extra "\n") 'ivy-current-match)) + (lambda (str extra) + (concat str extra "\n")) + cand-pairs + "")) (defcustom ivy-minibuffer-faces '(ivy-minibuffer-match-face-1 @@ -1921,13 +2066,11 @@ CANDS is a list of strings." x))) cands)))) (setq ivy--current (copy-sequence (nth index cands))) - (setq cands (mapcar - #'ivy--format-minibuffer-line - cands)) - (setf (nth index cands) - (ivy--add-face (nth index cands) 'ivy-current-match)) (let* ((ivy--index index) - (res (concat "\n" (funcall ivy-format-function cands)))) + (cand-pairs (mapcar + (lambda (cand) + (cons (ivy--format-minibuffer-line cand) nil)) cands)) + (res (concat "\n" (funcall ivy-format-function cand-pairs)))) (put-text-property 0 (length res) 'read-only nil res) res)))) @@ -1936,9 +2079,6 @@ CANDS is a list of strings." (defvar recentf-list) -(defface ivy-virtual '((t :inherit font-lock-builtin-face)) - "Face used by Ivy for matching virtual buffer names.") - (defcustom ivy-virtual-abbreviate 'name "The mode of abbreviation for virtual buffer names." :type '(choice @@ -2111,7 +2251,7 @@ Don't finish completion." (defun ivy-reverse-i-search () "Enter a recursive `ivy-read' session using the current history. -The selected history element will be inserted into the minibufer." +The selected history element will be inserted into the minibuffer." (interactive) (let ((enable-recursive-minibuffers t) (history (symbol-value (ivy-state-history ivy-last))) @@ -2174,12 +2314,12 @@ buffer would modify `ivy-last'.") (defun ivy-occur () "Stop completion and put the current matches into a new buffer. -The new buffer will also remember the current action(s). +The new buffer remembers current action(s). -While in the *ivy-occur* buffer, selecting a cadidate with RET or +While in the *ivy-occur* buffer, selecting a candidate with RET or a mouse click will call the appropriate action for that candidate. -It's possible to have an unlimited amount of *ivy-occur* buffers." +There is no limit on the number of *ivy-occur* buffers." (interactive) (let ((buffer (generate-new-buffer