]> code.delx.au - gnu-emacs-elpa/blobdiff - ivy.el
Allow to customize the initial input for all commands
[gnu-emacs-elpa] / ivy.el
diff --git a/ivy.el b/ivy.el
index 9b6ef69533ac379716cb4dd84fc1ce1487644f07..c4ff81419286bbbc17815199b9120c335d4b7016 100644 (file)
--- a/ivy.el
+++ b/ivy.el
@@ -4,7 +4,6 @@
 
 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
 ;; URL: https://github.com/abo-abo/swiper
-;; Version: 0.2.3
 ;; Package-Requires: ((emacs "24.1"))
 ;; Keywords: matching
 
@@ -107,6 +106,9 @@ Only \"./\" and \"../\" apply here. They appear in reverse order."
     (define-key map (kbd "SPC") 'self-insert-command)
     (define-key map (kbd "DEL") 'ivy-backward-delete-char)
     (define-key map (kbd "M-DEL") 'ivy-backward-kill-word)
+    (define-key map (kbd "C-d") 'ivy-delete-char)
+    (define-key map (kbd "C-f") 'ivy-forward-char)
+    (define-key map (kbd "M-d") 'ivy-kill-word)
     (define-key map (kbd "M-<") 'ivy-beginning-of-buffer)
     (define-key map (kbd "M->") 'ivy-end-of-buffer)
     (define-key map (kbd "<left>") 'ivy-beginning-of-buffer)
@@ -119,8 +121,11 @@ Only \"./\" and \"../\" apply here. They appear in reverse order."
     (define-key map (kbd "C-M-n") 'ivy-next-line-and-call)
     (define-key map (kbd "C-M-p") 'ivy-previous-line-and-call)
     (define-key map (kbd "M-q") 'ivy-toggle-regexp-quote)
+    (define-key map (kbd "M-j") 'ivy-yank-word)
+    (define-key map (kbd "C-o") 'hydra-ivy/body)
     map)
   "Keymap used in the minibuffer.")
+(autoload 'hydra-ivy/body "ivy-hydra" "" t)
 
 (defvar ivy-mode-map
   (let ((map (make-sparse-keymap)))
@@ -137,7 +142,10 @@ Only \"./\" and \"../\" apply here. They appear in reverse order."
   window
   action
   unwind
-  re-builder)
+  re-builder
+  matcher
+  ;; When this is non-nil, call it for each input change to get new candidates
+  dynamic-collection)
 
 (defvar ivy-last nil
   "The last parameters passed to `ivy-read'.")
@@ -192,6 +200,15 @@ When non-nil, it should contain one %d.")
 (defvar ivy--regex-function 'ivy--regex
   "Current function for building a regex.")
 
+(defvar ivy--subexps 0
+  "Number of groups in the current `ivy--regex'.")
+
+(defvar ivy--full-length nil
+  "When :dynamic-collection is non-nil, this can be the total amount of candidates.")
+
+(defvar ivy--old-text ""
+  "Store old `ivy-text' for dynamic completion.")
+
 (defvar Info-current-file)
 
 (defmacro ivy-quit-and-run (&rest body)
@@ -208,9 +225,9 @@ When non-nil, it should contain one %d.")
   "Insert TEXT and exit minibuffer."
   (if (and ivy--directory
            (not (eq (ivy-state-history ivy-last) 'grep-files-history)))
-      (insert (expand-file-name
-                 text ivy--directory))
-    (insert text))
+      (insert (setq ivy--current (expand-file-name
+                                  text ivy--directory)))
+    (insert (setq ivy--current text)))
   (setq ivy-exit 'done)
   (exit-minibuffer))
 
@@ -247,6 +264,7 @@ Is is a cons cell, related to `tramp-get-completion-function'."
       domain)))
 
 (declare-function tramp-get-completion-function "tramp")
+(declare-function Info-find-node "info")
 
 (defun ivy-alt-done (&optional arg)
   "Exit the minibuffer with the selected candidate.
@@ -265,7 +283,17 @@ When ARG is t, exit with current text, ignoring the candidates."
                                 ivy--current ivy--directory))))))
              (ivy--cd dir)
              (ivy--exhibit))
-            ((string-match "^/\\([^/]+?\\):\\(?:\\(.*\\)@\\)?" ivy-text)
+            ((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)))
+            ((string-match "\\`/\\([^/]+?\\):\\(?:\\(.*\\)@\\)?" ivy-text)
              (let ((method (match-string 1 ivy-text))
                    (user (match-string 2 ivy-text))
                    res)
@@ -275,7 +303,7 @@ When ARG is t, exit with current text, ignoring the candidates."
                (when user
                  (dolist (x res)
                    (setcar x user)))
-               (setq res (cl-delete-duplicates res :test 'equal))
+               (setq res (cl-delete-duplicates res :test #'equal))
                (let ((host (ivy-read "Find File: "
                                      (mapcar #'ivy-build-tramp-name res))))
                  (when host
@@ -290,13 +318,20 @@ When ARG is t, exit with current text, ignoring the candidates."
 
 (defun ivy-partial-or-done ()
   "Complete the minibuffer text as much as possible.
-When called twice in a row, exit the minibuffer with the current
-candidate."
+If the text hasn't changed as a result, forward to `ivy-alt-done'."
   (interactive)
-  (or (ivy-partial)
-      (if (eq this-command last-command)
-         (ivy-done)
-       (ivy-alt-done))))
+  (if (and (eq (ivy-state-collection ivy-last) #'read-file-name-internal)
+           (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))))
+    (or (ivy-partial)
+        (when (or (eq this-command last-command)
+                  (eq ivy--length 1))
+          (ivy-alt-done)))))
 
 (defun ivy-partial ()
   "Complete the minibuffer text as much as possible."
@@ -304,14 +339,20 @@ candidate."
   (let* ((parts (or (split-string ivy-text " " t) (list "")))
          (postfix (car (last parts)))
          (completion-ignore-case t)
-         (new (try-completion postfix
+         (startp (string-match "^\\^" postfix))
+         (new (try-completion (if startp
+                                  (substring postfix 1)
+                                postfix)
                               (mapcar (lambda (str) (substring str (string-match postfix str)))
                                       ivy--old-cands))))
     (cond ((eq new t) nil)
-         ((string= new ivy-text) nil)
+          ((string= new ivy-text) nil)
           (new
            (delete-region (minibuffer-prompt-end) (point-max))
-           (setcar (last parts) new)
+           (setcar (last parts)
+                   (if startp
+                       (concat "^" new)
+                     new))
            (insert (mapconcat #'identity parts " ")
                    (if ivy-tab-space " " ""))
            t))))
@@ -320,7 +361,7 @@ candidate."
   "Exit the minibuffer with the current input."
   (interactive)
   (delete-minibuffer-contents)
-  (insert ivy-text)
+  (insert (setq ivy--current ivy-text))
   (setq ivy-exit 'done)
   (exit-minibuffer))
 
@@ -334,45 +375,60 @@ candidate."
    :require-match (ivy-state-require-match ivy-last)
    :initial-input ivy-text
    :history (ivy-state-history ivy-last)
-   :preselect (regexp-quote ivy--current)
+   :preselect (unless (eq (ivy-state-collection ivy-last)
+                          'read-file-name-internal)
+                (regexp-quote ivy--current))
    :keymap (ivy-state-keymap ivy-last)
    :update-fn (ivy-state-update-fn ivy-last)
    :sort (ivy-state-sort ivy-last)
    :action (ivy-state-action ivy-last)
    :unwind (ivy-state-unwind ivy-last)
-   :re-builder (ivy-state-re-builder ivy-last)))
+   :re-builder (ivy-state-re-builder ivy-last)
+   :matcher (ivy-state-matcher ivy-last)
+   :dynamic-collection (ivy-state-dynamic-collection ivy-last)))
+
+(defvar ivy-calling nil
+  "When non-nil, call the current action when `ivy--index' changes.")
+
+(defun ivy-set-index (index)
+  "Set `ivy--index' to INDEX."
+  (setq ivy--index index)
+  (when ivy-calling
+    (ivy--exhibit)
+    (ivy-call)))
 
 (defun ivy-beginning-of-buffer ()
   "Select the first completion candidate."
   (interactive)
-  (setq ivy--index 0))
+  (ivy-set-index 0))
 
 (defun ivy-end-of-buffer ()
   "Select the last completion candidate."
   (interactive)
-  (setq ivy--index (1- ivy--length)))
+  (ivy-set-index (1- ivy--length)))
 
 (defun ivy-scroll-up-command ()
   "Scroll the candidates upward by the minibuffer height."
   (interactive)
-  (setq ivy--index (min (+ ivy--index ivy-height)
-                        (1- ivy--length))))
+  (ivy-set-index (min (+ ivy--index ivy-height)
+                      (1- ivy--length))))
 
 (defun ivy-scroll-down-command ()
   "Scroll the candidates downward by the minibuffer height."
   (interactive)
-  (setq ivy--index (max (- ivy--index ivy-height)
-                        0)))
+  (ivy-set-index (max (- ivy--index ivy-height)
+                      0)))
 
 (defun ivy-next-line (&optional arg)
   "Move cursor vertically down ARG candidates."
   (interactive "p")
   (setq arg (or arg 1))
-  (cl-incf ivy--index arg)
-  (when (>= ivy--index (1- ivy--length))
-    (if ivy-wrap
-        (ivy-beginning-of-buffer)
-      (setq ivy--index (1- ivy--length)))))
+  (let ((index (+ ivy--index arg)))
+    (if (> index (1- ivy--length))
+        (if ivy-wrap
+            (ivy-beginning-of-buffer)
+          (ivy-set-index (1- ivy--length)))
+      (ivy-set-index index))))
 
 (defun ivy-next-line-or-history (&optional arg)
   "Move cursor vertically down ARG candidates.
@@ -386,11 +442,12 @@ If the input is empty, select the previous history element instead."
   "Move cursor vertically up ARG candidates."
   (interactive "p")
   (setq arg (or arg 1))
-  (cl-decf ivy--index arg)
-  (when (< ivy--index 0)
-    (if ivy-wrap
-        (ivy-end-of-buffer)
-      (setq ivy--index 0))))
+  (let ((index (- ivy--index arg)))
+    (if (< index 0)
+        (if ivy-wrap
+            (ivy-end-of-buffer)
+          (ivy-set-index 0))
+      (ivy-set-index index))))
 
 (defun ivy-previous-line-or-history (arg)
   "Move cursor vertically up ARG candidates.
@@ -400,21 +457,33 @@ If the input is empty, select the previous history element instead."
     (ivy-previous-history-element 1))
   (ivy-previous-line arg))
 
+(defun ivy-toggle-calling ()
+  "Flip `ivy-calling'"
+  (interactive)
+  (when (setq ivy-calling (not ivy-calling))
+    (ivy-call)))
+
+(defun ivy-call ()
+  "Call the current action without exiting completion."
+  (when (ivy-state-action ivy-last)
+    (with-selected-window (ivy-state-window ivy-last)
+      (funcall (ivy-state-action ivy-last) ivy--current))))
+
 (defun ivy-next-line-and-call (&optional arg)
-  "Move cursor vertically down ARG candidates."
+  "Move cursor vertically down ARG candidates.
+Call the permanent action if possible."
   (interactive "p")
   (ivy-next-line arg)
   (ivy--exhibit)
-  (with-selected-window (ivy-state-window ivy-last)
-    (funcall (ivy-state-action ivy-last))))
+  (ivy-call))
 
 (defun ivy-previous-line-and-call (&optional arg)
-  "Move cursor vertically down ARG candidates."
+  "Move cursor vertically down ARG candidates.
+Call the permanent action if possible."
   (interactive "p")
   (ivy-previous-line arg)
   (ivy--exhibit)
-  (with-selected-window (ivy-state-window ivy-last)
-    (funcall (ivy-state-action ivy-last))))
+  (ivy-call))
 
 (defun ivy-previous-history-element (arg)
   "Forward to `previous-history-element' with ARG."
@@ -469,6 +538,24 @@ On error (read-only), call `ivy-on-del-error-function'."
        (when ivy-on-del-error-function
          (funcall ivy-on-del-error-function))))))
 
+(defun ivy-delete-char (arg)
+  "Forward to `delete-char' ARG."
+  (interactive "p")
+  (unless (= (point) (line-end-position))
+    (delete-char arg)))
+
+(defun ivy-forward-char (arg)
+  "Forward to `forward-char' ARG."
+  (interactive "p")
+  (unless (= (point) (line-end-position))
+    (forward-char arg)))
+
+(defun ivy-kill-word (arg)
+  "Forward to `kill-word' ARG."
+  (interactive "p")
+  (unless (= (point) (line-end-position))
+    (kill-word arg)))
+
 (defun ivy-backward-kill-word ()
   "Forward to `backward-kill-word'."
   (interactive)
@@ -527,6 +614,11 @@ The matches will be filtered in a sequence, you can mix the
 regexps that should match and that should not match as you
 like.")
 
+(defvar ivy-initial-inputs-alist
+  '((org-refile . "^")
+    (counsel-M-x . "^"))
+  "Command to initial input table.")
+
 (defcustom ivy-sort-max-size 30000
   "Sorting won't be done for collections larger than this."
   :type 'integer)
@@ -542,9 +634,9 @@ Directories come first."
       (setq seq (delete "./" (delete "../" seq)))
       (when (eq (setq sort-fn (cdr (assoc 'read-file-name-internal
                                           ivy-sort-functions-alist)))
-                'ivy-sort-file-function-default)
+                #'ivy-sort-file-function-default)
         (setq seq (mapcar (lambda (x)
-                            (propertize x 'dirp (string-match-p "/$" x)))
+                            (propertize x 'dirp (string-match-p "/\\'" x)))
                           seq)))
       (when sort-fn
         (setq seq (cl-sort seq sort-fn)))
@@ -556,7 +648,7 @@ Directories come first."
 (cl-defun ivy-read (prompt collection
                     &key predicate require-match initial-input
                       history preselect keymap update-fn sort
-                      action unwind re-builder)
+                      action unwind re-builder matcher dynamic-collection)
   "Read a string in the minibuffer, with completion.
 
 PROMPT is a string to prompt with; normally it ends in a colon
@@ -577,11 +669,20 @@ UPDATE-FN is called each time the current candidate(s) is changed.
 
 When SORT is t, refer to `ivy-sort-functions-alist' for sorting.
 
-ACTION is a lambda to call after a result was selected.
+ACTION is a lambda to call after a result was selected. It should
+take a single argument, usually a string.
 
 UNWIND is a lambda to call before exiting.
 
-RE-BUILDER is a lambda that transforms text into a regex."
+RE-BUILDER is a lambda that transforms text into a regex.
+
+MATCHER can completely override matching.
+
+DYNAMIC-COLLECTION is a function to call to update the list of
+candidates with each input."
+  (unless initial-input
+    (setq initial-input (cdr (assoc this-command
+                                    ivy-initial-inputs-alist))))
   (setq ivy-last
         (make-ivy-state
          :prompt prompt
@@ -597,7 +698,9 @@ RE-BUILDER is a lambda that transforms text into a regex."
          :action action
          :window (selected-window)
          :unwind unwind
-         :re-builder re-builder))
+         :re-builder re-builder
+         :matcher matcher
+         :dynamic-collection dynamic-collection))
   (setq ivy--directory nil)
   (setq ivy--regex-function
         (or re-builder
@@ -609,6 +712,7 @@ RE-BUILDER is a lambda that transforms text into a regex."
   (setq ivy--regexp-quote 'regexp-quote)
   (setq ivy--old-text "")
   (setq ivy-text "")
+  (setq ivy-calling nil)
   (let (coll sort-fn)
     (cond ((eq collection 'Info-read-node-name-1)
            (if (equal Info-current-file "dir")
@@ -616,7 +720,7 @@ RE-BUILDER is a lambda that transforms text into a regex."
                      (mapcar (lambda (x) (format "(%s)" x))
                              (cl-delete-duplicates
                               (all-completions "(" collection predicate)
-                              :test 'equal)))
+                              :test #'equal)))
              (setq coll (all-completions "" collection predicate))))
           ((eq collection 'read-file-name-internal)
            (setq ivy--directory default-directory)
@@ -625,18 +729,12 @@ RE-BUILDER is a lambda that transforms text into a regex."
                  (ivy--sorted-files default-directory))
            (when initial-input
              (unless (or require-match
-                         (equal initial-input default-directory))
+                         (equal initial-input default-directory)
+                         (equal initial-input ""))
                (setq coll (cons initial-input coll)))
              (setq initial-input nil)))
           ((eq collection 'internal-complete-buffer)
-           (setq coll
-                 (mapcar (lambda (x)
-                           (if (with-current-buffer x
-                                 (file-remote-p
-                                  (abbreviate-file-name default-directory)))
-                               (propertize x 'face 'ivy-remote)
-                             x))
-                         (all-completions "" collection predicate))))
+           (setq coll (ivy--buffer-list "" ivy-use-virtual-buffers)))
           ((or (functionp collection)
                (vectorp collection)
                (listp (car collection)))
@@ -656,12 +754,15 @@ RE-BUILDER is a lambda that transforms text into a regex."
                    (<= (length coll) ivy-sort-max-size))
               (setq coll (cl-sort (copy-sequence coll) sort-fn))))))
     (when preselect
-      (unless (or require-match
-                  (cl-find-if `(lambda (x)
-                                 (string-match ,(format "^%s" preselect) x))
-                              coll))
+      (unless (or (and require-match
+                       (not (eq collection 'internal-complete-buffer)))
+                  (let ((re (format "\\`%s" (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))
@@ -685,6 +786,8 @@ RE-BUILDER is a lambda that transforms text into a regex."
              (minibuffer-with-setup-hook
                  #'ivy--minibuffer-setup
                (let* ((hist (or history 'ivy-history))
+                      (minibuffer-completion-table collection)
+                      (minibuffer-completion-predicate predicate)
                       (res (read-from-minibuffer
                             prompt
                             initial-input
@@ -700,7 +803,7 @@ RE-BUILDER is a lambda that transforms text into a regex."
           (when (setq unwind (ivy-state-unwind ivy-last))
             (funcall unwind)))
       (when (setq action (ivy-state-action ivy-last))
-        (funcall action)))))
+        (funcall action ivy--current)))))
 
 (defun ivy-completing-read (prompt collection
                             &optional predicate require-match initial-input
@@ -723,7 +826,9 @@ The history, defaults and input-method arguments are ignored for now."
   (ivy-read prompt collection
             :predicate predicate
             :require-match require-match
-            :initial-input initial-input
+            :initial-input (if (consp initial-input)
+                               (car initial-input)
+                             initial-input)
             :preselect (if (listp def) (car def) def)
             :history history
             :keymap nil
@@ -758,19 +863,16 @@ Minibuffer bindings:
            (lambda (x)
              (string-match initial-input x))
            candidates)))
-  (or (cl-position preselect candidates :test 'equal)
+  (or (cl-position preselect candidates :test #'equal)
       (cl-position-if
        (lambda (x)
-         (string-match preselect x))
+         (string-match (regexp-quote preselect) x))
        candidates)))
 
 ;;* Implementation
 ;;** Regex
-(defvar ivy--subexps 0
-  "Number of groups in the current `ivy--regex'.")
-
 (defvar ivy--regex-hash
-  (make-hash-table :test 'equal)
+  (make-hash-table :test #'equal)
   "Store pre-computed regex.")
 
 (defun ivy--split (str)
@@ -778,17 +880,30 @@ Minibuffer bindings:
 The remaining spaces stick to their left.
 This allows to \"quote\" N spaces by inputting N+1 spaces."
   (let ((len (length str))
-        (start 0)
-        res s)
-    (while (and (string-match " +" str start)
-                (< start len))
-      (setq s (substring str start (1- (match-end 0))))
+        start0
+        (start1 0)
+        res s
+        match-len)
+    (while (and (string-match " +" str start1)
+                (< start1 len))
+      (setq match-len (- (match-end 0) (match-beginning 0)))
+      (if (= match-len 1)
+          (progn
+            (when start0
+              (setq start1 start0)
+              (setq start0 nil))
+            (push (substring str start1 (match-beginning 0)) res)
+            (setq start1 (match-end 0)))
+        (setq str (replace-match
+                   (make-string (1- match-len) ?\ )
+                   nil nil str))
+        (setq start0 (or start0 start1))
+        (setq start1 (1- (match-end 0)))))
+    (if start0
+        (push (substring str start0) res)
+      (setq s (substring str start1))
       (unless (= (length s) 0)
-        (push s res))
-      (setq start (match-end 0)))
-    (setq s (substring str start))
-    (unless (= (length s) 0)
-      (push s res))
+        (push s res)))
     (nreverse res)))
 
 (defun ivy--regex (str &optional greedy)
@@ -809,7 +924,7 @@ When GREEDY is non-nil, join words in a greedy way."
                          (setq ivy--subexps (length subs))
                          (mapconcat
                           (lambda (x)
-                            (if (string-match "^\\\\(.*\\\\)$" x)
+                            (if (string-match "\\`\\\\(.*\\\\)\\'" x)
                                 x
                               (format "\\(%s\\)" x)))
                           subs
@@ -832,7 +947,7 @@ Ignore the order of each group."
        (let ((all (mapconcat #'identity subs "\\|")))
          (mapconcat
           (lambda (x)
-            (if (string-match "^\\\\(.*\\\\)$" x)
+            (if (string-match "\\`\\\\(.*\\\\)\\'" x)
                 x
               (format "\\(%s\\)" x)))
           (make-list len all)
@@ -856,6 +971,15 @@ Everything after \"!\" should not match."
                res)))
       (t (error "Unexpected: use only one !")))))
 
+(defun ivy--regex-fuzzy (str)
+  "Build a regex sequence from STR.
+Insert .* between each char."
+  (if (string-match "\\`\\(\\^?\\)\\(.*?\\)\\(\\$?\\)\\'" str)
+      (concat (match-string 1 str)
+              (mapconcat #'string (string-to-list (match-string 2 str)) ".*")
+              (match-string 3 str))
+    str))
+
 ;;** Rest
 (defun ivy--minibuffer-setup ()
   "Setup ivy completion in the minibuffer."
@@ -881,15 +1005,6 @@ Everything after \"!\" should not match."
     (goto-char (minibuffer-prompt-end))
     (delete-region (line-end-position) (point-max))))
 
-(defvar ivy--dynamic-function nil
-  "When this is non-nil, call it for each input change to get new candidates.")
-
-(defvar ivy--full-length nil
-  "When `ivy--dynamic-function' is non-nil, this can be the total amount of candidates.")
-
-(defvar ivy--old-text ""
-  "Store old `ivy-text' for dynamic completion.")
-
 (defun ivy--insert-prompt ()
   "Update the prompt according to `ivy--prompt'."
   (when ivy--prompt
@@ -897,7 +1012,7 @@ Everything after \"!\" should not match."
                                  counsel-find-symbol))
       (setq ivy--prompt-extra ""))
     (let (head tail)
-      (if (string-match "\\(.*\\): $" ivy--prompt)
+      (if (string-match "\\(.*\\): \\'" ivy--prompt)
           (progn
             (setq head (match-string 1 ivy--prompt))
             (setq tail ": "))
@@ -907,13 +1022,18 @@ Everything after \"!\" should not match."
             (std-props '(front-sticky t rear-nonsticky t field t read-only t))
             (n-str
              (format
-              (concat head
-                      ivy--prompt-extra
-                      tail
-                      (if ivy--directory
-                          (abbreviate-file-name ivy--directory)
-                        ""))
-              (or (and ivy--dynamic-function
+              (concat
+               (if (and (bound-and-true-p minibuffer-depth-indicate-mode)
+                        (> (minibuffer-depth) 1))
+                   (format "[%d] " (minibuffer-depth))
+                 "")
+               head
+               ivy--prompt-extra
+               tail
+               (if ivy--directory
+                   (abbreviate-file-name ivy--directory)
+                 ""))
+              (or (and (ivy-state-dynamic-collection ivy-last)
                        ivy--full-length)
                   ivy--length))))
         (save-excursion
@@ -945,42 +1065,42 @@ Everything after \"!\" should not match."
   "Insert Ivy completions display.
 Should be run via minibuffer `post-command-hook'."
   (setq ivy-text (ivy--input))
-  (if ivy--dynamic-function
+  (if (ivy-state-dynamic-collection ivy-last)
       ;; while-no-input would cause annoying
       ;; "Waiting for process to die...done" message interruptions
       (let ((inhibit-message t))
-        (while-no-input
-          (unless (equal ivy--old-text ivy-text)
-            (let ((store ivy--dynamic-function)
-                  (ivy--dynamic-function nil))
-              (setq ivy--all-candidates (funcall store ivy-text))))
-          (ivy--insert-minibuffer (ivy--format ivy--all-candidates))))
+        (unless (equal ivy--old-text ivy-text)
+          (while-no-input
+            ;; dynamic collection should take care of everything
+            (funcall (ivy-state-dynamic-collection ivy-last) ivy-text)
+            (setq ivy--old-text ivy-text)))
+        (unless (eq ivy--full-length -1)
+          (ivy--insert-minibuffer
+           (ivy--format ivy--all-candidates))))
     (cond (ivy--directory
-           (if (string-match "/$" ivy-text)
+           (if (string-match "/\\'" ivy-text)
                (if (member ivy-text ivy--all-candidates)
                    (ivy--cd (expand-file-name ivy-text ivy--directory))
-                 (when (string-match "//$" ivy-text)
+                 (when (string-match "//\\'" ivy-text)
                    (ivy--cd "/")))
-             (if (string-match "~$" ivy-text)
+             (if (string-match "~\\'" ivy-text)
                  (ivy--cd (expand-file-name "~/")))))
           ((eq (ivy-state-collection ivy-last) 'internal-complete-buffer)
-           (when (or (and (string-match "^ " ivy-text)
-                          (not (string-match "^ " ivy--old-text)))
-                     (and (string-match "^ " ivy--old-text)
-                          (not (string-match "^ " ivy-text))))
+           (when (or (and (string-match "\\` " ivy-text)
+                          (not (string-match "\\` " ivy--old-text)))
+                     (and (string-match "\\` " ivy--old-text)
+                          (not (string-match "\\` " ivy-text))))
              (setq ivy--all-candidates
-                   (all-completions
-                    (if (and (> (length ivy-text) 0)
-                             (eq (aref ivy-text 0)
-                                 ?\ ))
-                        " "
-                      "")
-                    'internal-complete-buffer))
+                   (if (and (> (length ivy-text) 0)
+                            (eq (aref ivy-text 0)
+                                ?\ ))
+                       (ivy--buffer-list " ")
+                     (ivy--buffer-list "" ivy-use-virtual-buffers)))
              (setq ivy--old-re nil))))
     (ivy--insert-minibuffer
      (ivy--format
-      (ivy--filter ivy-text ivy--all-candidates))))
-  (setq ivy--old-text ivy-text))
+      (ivy--filter ivy-text ivy--all-candidates)))
+    (setq ivy--old-text ivy-text)))
 
 (defun ivy--insert-minibuffer (text)
   "Insert TEXT into minibuffer with appropriate cleanup."
@@ -998,6 +1118,8 @@ Should be run via minibuffer `post-command-hook'."
         (forward-line 1)
         (insert text)))))
 
+(declare-function colir-blend-face-background "ext:colir")
+
 (defun ivy--add-face (str face)
   "Propertize STR with FACE.
 `font-lock-append-text-property' is used, since it's better than
@@ -1014,36 +1136,42 @@ Should be run via minibuffer `post-command-hook'."
   "Return all items that match NAME in CANDIDATES.
 CANDIDATES are assumed to be static."
   (let* ((re (funcall ivy--regex-function name))
-         (cands (cond ((and (equal re ivy--old-re)
-                            ivy--old-cands)
-                       ivy--old-cands)
-                      ((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) '(0 2)))
-                       (ignore-errors
-                         (cl-remove-if-not
-                          (lambda (x) (string-match re x))
-                          ivy--old-cands)))
-                      (t
-                       (let ((re-list (if (stringp re) (list (cons re t)) re))
-                             (res candidates))
-                         (dolist (re re-list)
-                           (setq res
-                                 (ignore-errors
-                                   (funcall
-                                    (if (cdr re)
-                                        #'cl-remove-if-not
-                                      #'cl-remove-if)
-                                    `(lambda (x) (string-match ,(car re) x))
-                                    res))))
-                         res))))
+         (matcher (ivy-state-matcher ivy-last))
+         (cands (cond
+                  (matcher
+                   (funcall matcher re candidates))
+                  ((and (equal re ivy--old-re)
+                        ivy--old-cands)
+                   ivy--old-cands)
+                  ((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)
+                              '(0 2)))
+                   (ignore-errors
+                     (cl-remove-if-not
+                      (lambda (x) (string-match re x))
+                      ivy--old-cands)))
+                  (t
+                   (let ((re-list (if (stringp re) (list (cons re t)) re))
+                         (res candidates))
+                     (dolist (re re-list)
+                       (setq res
+                             (ignore-errors
+                               (funcall
+                                (if (cdr re)
+                                    #'cl-remove-if-not
+                                  #'cl-remove-if)
+                                (let ((re (car re)))
+                                  (lambda (x) (string-match re x)))
+                                res))))
+                     res))))
          (tail (nthcdr ivy--index ivy--old-cands))
          idx)
     (when (and tail ivy--old-cands)
@@ -1051,11 +1179,11 @@ CANDIDATES are assumed to be static."
                    (or (setq ivy--index
                              (or
                               (cl-position re cands
-                                           :test 'equal)
+                                           :test #'equal)
                               (and ivy--directory
                                    (cl-position
                                     (concat re "/") cands
-                                    :test 'equal))))))
+                                    :test #'equal))))))
         (while (and tail (null idx))
           ;; Compare with eq to handle equal duplicates in cands
           (setq idx (cl-position (pop tail) cands)))
@@ -1063,7 +1191,7 @@ CANDIDATES are assumed to be static."
     (when (and (string= name "") (not (equal ivy--old-re "")))
       (setq ivy--index
             (or (cl-position (ivy-state-preselect ivy-last)
-                             cands :test 'equal)
+                             cands :test #'equal)
                 ivy--index)))
     (setq ivy--old-re (if cands re ""))
     (setq ivy--old-cands cands)))
@@ -1108,7 +1236,7 @@ CANDS is a list of strings."
            (index (min ivy--index half-height (1- (length cands)))))
       (when ivy--directory
         (setq cands (mapcar (lambda (x)
-                              (if (string-match-p "/$" x)
+                              (if (string-match-p "/\\'" x)
                                   (propertize x 'face 'ivy-subdir)
                                 x))
                             cands)))
@@ -1124,10 +1252,9 @@ CANDS is a list of strings."
   "Store the virtual buffers alist.")
 
 (defvar recentf-list)
-(defvar ido-use-faces)
-(defvar ido-process-ignore-lists)
-(defvar ido-ignored-list)
-(declare-function ido-make-buffer-list "ido")
+
+(defface ivy-virtual '((t :inherit font-lock-builtin-face))
+  "Face used by Ivy for matching virtual buffer names.")
 
 (defun ivy--virtual-buffers ()
   "Adapted from `ido-add-virtual-buffers-to-list'."
@@ -1152,41 +1279,70 @@ CANDS is a list of strings."
            (not (assoc name virtual-buffers))
            (push (cons name head) virtual-buffers)))
     (when virtual-buffers
-      (if ido-use-faces
-          (dolist (comp virtual-buffers)
-            (put-text-property 0 (length (car comp))
-                               'face 'ido-virtual
-                               (car comp))))
+      (dolist (comp virtual-buffers)
+        (put-text-property 0 (length (car comp))
+                           'face 'ivy-virtual
+                           (car comp)))
       (setq ivy--virtual-buffers (nreverse virtual-buffers))
       (mapcar #'car ivy--virtual-buffers))))
 
-(defun ivy-buffer-list ()
-  "Return the current list of buffers.
-See `ido-make-buffer-list'."
-  (require 'ido)
-  (setq ivy--virtual-buffers nil)
-  (let ((ido-process-ignore-lists t)
-        ido-ignored-list)
-    (delete-dups
-     (append (ido-make-buffer-list nil)
-             (and
-              ivy-use-virtual-buffers
-              (ivy--virtual-buffers))))))
+(defun ivy--buffer-list (str &optional virtual)
+  "Return the buffers that match STR.
+When VIRTUAL is non-nil, add virtual buffers."
+  (delete-dups
+   (append
+    (mapcar
+     (lambda (x)
+       (if (with-current-buffer x
+             (file-remote-p
+              (abbreviate-file-name default-directory)))
+           (propertize x 'face 'ivy-remote)
+         x))
+     (all-completions str 'internal-complete-buffer))
+    (and virtual
+         (ivy--virtual-buffers)))))
+
+(defun ivy--switch-buffer-action (buffer)
+  "Switch to BUFFER.
+BUFFER may be a string or nil."
+  (if (zerop (length buffer))
+      (switch-to-buffer
+       ivy-text nil 'force-same-window)
+    (let ((virtual (assoc buffer ivy--virtual-buffers)))
+      (if (and virtual
+               (not (get-buffer buffer)))
+          (find-file (cdr virtual))
+        (switch-to-buffer
+         buffer nil 'force-same-window)))))
 
 (defun ivy-switch-buffer ()
   "Switch to another buffer."
   (interactive)
   (if (not ivy-mode)
       (call-interactively 'switch-to-buffer)
-    (let ((bl (ivy-buffer-list)))
-      (ivy-read "Switch to buffer: " bl
-                :action
-                (lambda ()
-                  (let ((virtual (assoc ivy--current ivy--virtual-buffers)))
-                    (if virtual
-                        (find-file (cdr virtual))
-                      (switch-to-buffer
-                       ivy--current nil 'force-same-window))))))))
+    (ivy-read "Switch to buffer: " 'internal-complete-buffer
+              :preselect (buffer-name (other-buffer (current-buffer)))
+              :action #'ivy--switch-buffer-action)))
+
+(defun ivy-recentf ()
+  "Find a file on `recentf-list'."
+  (interactive)
+  (ivy-read "Recentf: " recentf-list
+            :action #'find-file))
+
+(defun ivy-yank-word ()
+  "Pull next word from buffer into search string."
+  (interactive)
+  (let (amend)
+    (with-selected-window (ivy-state-window ivy-last)
+      (let ((pt (point))
+            (le (line-end-position)))
+        (forward-word 1)
+        (if (> (point) le)
+            (goto-char pt)
+          (setq amend (buffer-substring-no-properties pt (point))))))
+    (when amend
+      (insert amend))))
 
 (provide 'ivy)