]> code.delx.au - gnu-emacs-elpa/blobdiff - ivy.el
ivy.el (ivy-alt-done): Refactor
[gnu-emacs-elpa] / ivy.el
diff --git a/ivy.el b/ivy.el
index b6c8b0818c7371e2d6cab390fcf6c0110c49c387..f70c54f19a4fadafb017c7a5684b639982b859a5 100644 (file)
--- a/ivy.el
+++ b/ivy.el
@@ -170,6 +170,8 @@ Only \"./\" and \"../\" apply here. They appear in reverse order."
     (define-key map (kbd "S-SPC") 'ivy-restrict-to-matches)
     (define-key map (kbd "M-w") 'ivy-kill-ring-save)
     (define-key map (kbd "C-'") 'ivy-avy)
+    (define-key map (kbd "C-M-a") 'ivy-read-action)
+    (define-key map (kbd "C-c C-o") 'ivy-occur)
     map)
   "Keymap used in the minibuffer.")
 (autoload 'hydra-ivy/body "ivy-hydra" "" t)
@@ -189,6 +191,8 @@ Only \"./\" and \"../\" apply here. They appear in reverse order."
   window
   ;; The buffer in which `ivy-read' was called
   buffer
+  ;; The value of `ivy-text' to be used by `ivy-occur'
+  text
   action
   unwind
   re-builder
@@ -277,11 +281,20 @@ When non-nil, it should contain one %d.")
                     ,@body))
      (minibuffer-keyboard-quit)))
 
+(defun ivy-exit-with-action (action)
+  "Quit the minibuffer and call ACTION afterwards."
+  (ivy-set-action
+   `(lambda (x)
+      (funcall ',action x)
+      (ivy-set-action ',(ivy-state-action ivy-last))))
+  (setq ivy-exit 'done)
+  (exit-minibuffer))
+
 (defmacro with-ivy-window (&rest body)
   "Execute BODY in the window from which `ivy-read' was called."
   (declare (indent 0)
            (debug t))
-  `(with-selected-window (ivy-state-window ivy-last)
+  `(with-selected-window (ivy--get-window ivy-last)
      ,@body))
 
 (defun ivy--done (text)
@@ -356,9 +369,10 @@ When non-nil, it should contain one %d.")
   "Select one of the available actions and call `ivy-call'."
   (interactive)
   (let ((actions (copy-sequence (ivy-state-action ivy-last))))
-    (ivy-read-action)
-    (ivy-call)
-    (ivy-set-action actions)))
+    (unwind-protect
+         (when (ivy-read-action)
+           (ivy-call))
+      (ivy-set-action actions))))
 
 (defun ivy-build-tramp-name (x)
   "Reconstruct X into a path.
@@ -379,21 +393,69 @@ When ARG is t, exit with current text, ignoring the candidates."
   (let (dir)
     (cond (arg
            (ivy-immediate-done))
-          ((and ivy--directory
-                (or
-                 (and
-                  (not (equal ivy-text ""))
-                  (file-directory-p ivy-text)
-                  (setq dir (expand-file-name
-                             ivy-text ivy--directory)))
-                 (and
-                  (not (string= ivy--current "./"))
-                  (cl-plusp ivy--length)
+          (ivy--directory
+           (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 (expand-file-name
-                              ivy--current ivy--directory))))))
-           (ivy--cd dir)
-           (ivy--exhibit))
+                   (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))))
           ((eq (ivy-state-collection ivy-last) 'Info-read-node-name-1)
            (if (or (equal ivy--current "(./)")
                    (equal ivy--current "(../)"))
@@ -404,34 +466,6 @@ When ARG is t, exit with current text, ignoring the candidates."
                                      (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)))))
 
@@ -444,13 +478,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))
@@ -542,13 +579,13 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'."
 (defun ivy-scroll-up-command ()
   "Scroll the candidates upward by the minibuffer height."
   (interactive)
-  (ivy-set-index (min (+ ivy--index ivy-height)
+  (ivy-set-index (min (1- (+ ivy--index ivy-height))
                       (1- ivy--length))))
 
 (defun ivy-scroll-down-command ()
   "Scroll the candidates downward by the minibuffer height."
   (interactive)
-  (ivy-set-index (max (- ivy--index ivy-height)
+  (ivy-set-index (max (1+ (- ivy--index ivy-height))
                       0)))
 
 (defun ivy-minibuffer-grow ()
@@ -617,6 +654,15 @@ If the input is empty, select the previous history element instead."
           action
         (cadr (nth (car action) action))))))
 
+(defun ivy--get-window (state)
+  "Get the window from STATE."
+  (let ((window (ivy-state-window state)))
+    (if (window-live-p window)
+        window
+      (if (= (length (window-list)) 1)
+          (selected-window)
+        (next-window)))))
+
 (defun ivy--actionp (x)
   "Return non-nil when X is a list of actions."
   (and x (listp x) (not (eq (car x) 'closure))))
@@ -705,12 +751,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))))
@@ -880,7 +923,9 @@ 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'.
 
-The entry associated to t is used for all fall-through cases."
+The entry associated to t is used for all fall-through cases.
+
+See also `ivy-sort-max-size'."
   :type
   '(alist
     :key-type (choice
@@ -990,8 +1035,9 @@ 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.
+DYNAMIC-COLLECTION is a boolean that determines whether to update
+the list of candidates with each input by calling COLLECTION for
+the current input.
 
 CALLER is a symbol to uniquely identify the caller to `ivy-read'.
 It's used in conjunction with COLLECTION to indentify which
@@ -1004,57 +1050,60 @@ customizations should apply to the current completion session."
                   ("o" ,action "default")
                   ,@extra-actions)
               (delete-dups (append action extra-actions))))))
-  (setq ivy-last
-        (make-ivy-state
-         :prompt prompt
-         :collection collection
-         :predicate predicate
-         :require-match require-match
-         :initial-input initial-input
-         :history history
-         :preselect preselect
-         :keymap keymap
-         :update-fn update-fn
-         :sort sort
-         :action action
-         :window (selected-window)
-         :buffer (current-buffer)
-         :unwind unwind
-         :re-builder re-builder
-         :matcher matcher
-         :dynamic-collection dynamic-collection
-         :caller caller))
-  (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)))
-        (remove-hook 'post-command-hook #'ivy--exhibit)
-        (when (setq unwind (ivy-state-unwind ivy-last))
-          (funcall unwind)))
-    (ivy-call)))
+  (let ((recursive-ivy-last (and (window-minibuffer-p) ivy-last)))
+    (setq ivy-last
+          (make-ivy-state
+           :prompt prompt
+           :collection collection
+           :predicate predicate
+           :require-match require-match
+           :initial-input initial-input
+           :history history
+           :preselect preselect
+           :keymap keymap
+           :update-fn update-fn
+           :sort sort
+           :action action
+           :window (selected-window)
+           :buffer (current-buffer)
+           :unwind unwind
+           :re-builder re-builder
+           :matcher matcher
+           :dynamic-collection dynamic-collection
+           :caller caller))
+    (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)))
+          (remove-hook 'post-command-hook #'ivy--exhibit)
+          (when (setq unwind (ivy-state-unwind ivy-last))
+            (funcall unwind)))
+      (ivy-call)
+      (when recursive-ivy-last
+        (ivy--reset-state (setq ivy-last recursive-ivy-last))))))
 
 (defun ivy--reset-state (state)
   "Reset the ivy to STATE.
@@ -1201,6 +1250,12 @@ DEF is the default value.
 _INHERIT-INPUT-METHOD is ignored for now.
 
 The history, defaults and input-method arguments are ignored for now."
+  ;; 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
@@ -1256,13 +1311,15 @@ When MATCHER is non-nil it's used instead of `cl-remove-if-not'."
                  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)))))
+  (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
@@ -1673,10 +1730,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 according to 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))
@@ -1758,6 +1867,13 @@ CANDIDATES are assumed to be static."
   "Function to transform the list of candidates into a string.
 This string will be inserted into the minibuffer.")
 
+(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-default (cands)
   "Transform CANDS into a string for minibuffer."
   (if (bound-and-true-p truncate-lines)
@@ -1767,9 +1883,7 @@ This string will be inserted into the minibuffer.")
       (mapconcat
        (if truncate-lines
            (lambda (s)
-             (if (> (length s) ww)
-                 (concat (substring s 0 (- ww 3)) "...")
-               s))
+             (ivy--truncate-string s ww))
          #'identity)
        cands "\n"))))
 
@@ -2095,6 +2209,170 @@ The selected history element will be inserted into the minibufer."
   (setq ivy--all-candidates
         (ivy--filter ivy-text ivy--all-candidates)))
 
+;;* Occur
+(defvar-local ivy-occur-last nil
+  "Buffer-local value of `ivy-last'.
+Can't re-use `ivy-last' because using e.g. `swiper' in the same
+buffer would modify `ivy-last'.")
+
+(defvar ivy-occur-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [mouse-1] 'ivy-occur-click)
+    (define-key map (kbd "RET") 'ivy-occur-press)
+    (define-key map (kbd "j") 'next-line)
+    (define-key map (kbd "k") 'previous-line)
+    (define-key map (kbd "h") 'backward-char)
+    (define-key map (kbd "l") 'forward-char)
+    (define-key map (kbd "g") 'ivy-occur-press)
+    (define-key map (kbd "a") 'ivy-occur-read-action)
+    (define-key map (kbd "o") 'ivy-occur-dispatch)
+    (define-key map (kbd "q") 'quit-window)
+    map)
+  "Keymap for Ivy Occur mode.")
+
+(define-derived-mode ivy-occur-mode fundamental-mode "Ivy-Occur"
+  "Major mode for output from \\[ivy-occur].
+
+\\{ivy-occur-mode-map}")
+
+(defvar ivy-occur-grep-mode-map
+  (let ((map (copy-keymap ivy-occur-mode-map)))
+    (define-key map (kbd "C-x C-q") 'ivy-wgrep-change-to-wgrep-mode)
+    map)
+  "Keymap for Ivy Occur Grep mode.")
+
+(define-derived-mode ivy-occur-grep-mode grep-mode "Ivy-Occur"
+  "Major mode for output from \\[ivy-occur].
+
+\\{ivy-occur-grep-mode-map}")
+
+(defvar counsel-git-grep-cmd)
+
+(defun ivy-occur ()
+  "Stop completion and put the current matches into a new buffer.
+
+The new buffer will also remember the current action(s).
+
+While in the *ivy-occur* buffer, selecting a cadidate 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."
+  (interactive)
+  (let ((buffer
+         (generate-new-buffer
+          (format "*ivy-occur%s \"%s\"*"
+                  (let (caller)
+                    (if (setq caller (ivy-state-caller ivy-last))
+                        (concat " " (prin1-to-string caller))
+                      ""))
+                  ivy-text)))
+        (do-grep (eq (ivy-state-caller ivy-last) 'counsel-git-grep)))
+    (with-current-buffer buffer
+      (if do-grep
+          (progn
+            (setq ivy--old-cands
+                  (split-string
+                   (shell-command-to-string
+                    (format counsel-git-grep-cmd ivy--old-re))
+                   "\n"
+                   t))
+            (ivy-occur-grep-mode))
+        (ivy-occur-mode))
+      (setf (ivy-state-text ivy-last) ivy-text)
+      (setq ivy-occur-last ivy-last)
+      (setq-local ivy--directory ivy--directory)
+      (let ((inhibit-read-only t))
+        (erase-buffer)
+        (when do-grep
+          ;; Need precise number of header lines for `wgrep' to work.
+          (insert (format "-*- mode:grep; default-directory: %S -*-\n\n\n"
+                          default-directory)))
+        (insert (format "%d candidates:\n" (length ivy--old-cands)))
+        (dolist (cand ivy--old-cands)
+          (let ((str (if do-grep
+                         (concat "./" cand)
+                       (concat "    " cand))))
+            (add-text-properties
+             0 (length str)
+             `(mouse-face
+               highlight
+               help-echo "mouse-1: call ivy-action")
+             str)
+            (insert str "\n")))))
+    (ivy-exit-with-action
+     `(lambda (_) (pop-to-buffer ,buffer)))))
+
+(declare-function wgrep-change-to-wgrep-mode "ext:wgrep")
+
+(defun ivy-wgrep-change-to-wgrep-mode ()
+  "Forward to `wgrep-change-to-wgrep-mode'."
+  (interactive)
+  (if (require 'wgrep nil 'noerror)
+      (wgrep-change-to-wgrep-mode)
+    (error "Package wgrep isn't installed")))
+
+(defun ivy-occur-read-action ()
+  "Select one of the available actions as the current one."
+  (interactive)
+  (let ((ivy-last ivy-occur-last))
+    (ivy-read-action)))
+
+(defun ivy-occur-dispatch ()
+  "Call one of the available actions on the current item."
+  (interactive)
+  (let* ((state-action (ivy-state-action ivy-occur-last))
+         (actions (if (symbolp state-action)
+                      state-action
+                    (copy-sequence state-action))))
+    (unwind-protect
+         (progn
+           (ivy-occur-read-action)
+           (ivy-occur-press))
+      (setf (ivy-state-action ivy-occur-last) actions))))
+
+(defun ivy-occur-click (event)
+  "Execute action for the current candidate.
+EVENT gives the mouse position."
+  (interactive "e")
+  (let ((window (posn-window (event-end event)))
+        (pos (posn-point (event-end event))))
+    (with-current-buffer (window-buffer window)
+      (goto-char pos)
+      (ivy-occur-press))))
+
+(defun ivy-occur-press ()
+  "Execute action for the current candidate."
+  (interactive)
+  (require 'pulse)
+  (when (save-excursion
+          (beginning-of-line)
+          (looking-at "\\(?:./\\|    \\)\\(.*\\)$"))
+    (let* ((ivy-last ivy-occur-last)
+           (ivy-text (ivy-state-text ivy-last))
+           (str (buffer-substring
+                 (match-beginning 1)
+                 (match-end 1)))
+           (coll (ivy-state-collection ivy-last))
+           (action (ivy--get-action ivy-last))
+           (ivy-exit 'done))
+      (with-ivy-window
+        (funcall action
+                 (if (and (consp coll)
+                          (consp (car coll)))
+                     (cdr (assoc str coll))
+                   str))
+        (if (memq (ivy-state-caller ivy-last)
+                  '(swiper counsel-git-grep))
+            (with-current-buffer (window-buffer (selected-window))
+              (swiper--cleanup)
+              (swiper--add-overlays
+               (ivy--regex ivy-text)
+               (line-beginning-position)
+               (line-end-position)
+               (selected-window))
+              (run-at-time 0.5 nil 'swiper--cleanup))
+          (pulse-momentary-highlight-one-line (point)))))))
+
 (provide 'ivy)
 
 ;;; ivy.el ends here