branch: master
commit ed60d31fc31de0dc4258696c2ecec9f1c20b06c7
Author: Oleh Krehel <[email protected]>
Commit: Oleh Krehel <[email protected]>
Make counsel-git-grep fully async
* counsel.el (counsel-git-grep-count): Rename to `counsel--gg-count' and
make it async.
(counsel-git-grep-function): Set `ivy--full-length' to -1. It means that
`ivy--exhibit' should do nothing until the length is re-computed.
(counsel-git-grep): Use the sync version of `counsel-git-grep-count' for
the initial repo line count.
(counsel--gg-candidates): New defun. When called, kill the previous
git grep process and start a new one. The sentinel will insert the
candidates, bypassing the `ivy--exhibit'.
(counsel--gg-sentinel): New defun.
(counsel--gg-count): Rename from `counsel-git-grep-count'. When finished
computing, redisplay.
* ivy.el (ivy--exhibit): Don't expect dynamic collection to return the
candidates as before. Just call it and expect it to redisplay the
minibuffer.
---
counsel.el | 81 +++++++++++++++++++++++++++++++++++++++++++++++++----------
ivy.el | 18 +++++++------
2 files changed, 77 insertions(+), 22 deletions(-)
diff --git a/counsel.el b/counsel.el
index 174a1b9..57bad38 100644
--- a/counsel.el
+++ b/counsel.el
@@ -198,14 +198,6 @@
(defvar counsel--git-grep-dir nil
"Store the base git directory.")
-(defun counsel-git-grep-count (str)
- "Quickly count the amount of git grep STR matches."
- (let* ((default-directory counsel--git-grep-dir)
- (out (shell-command-to-string
- (format "git grep -i -c '%s' | sed 's/.*:\\(.*\\)/\\1/g' | awk
'{s+=$1} END {print s}'"
- (ivy--regex str)))))
- (string-to-number out)))
-
(defvar counsel--git-grep-count nil
"Store the line count in current repository.")
@@ -224,10 +216,11 @@
(if (<= counsel--git-grep-count 20000)
(progn
(setq res (shell-command-to-string cmd))
- (setq ivy--full-length nil))
- (setq res (shell-command-to-string (concat cmd " | head -n 2000")))
- (setq ivy--full-length (counsel-git-grep-count ivy-text)))
- (split-string res "\n" t))))
+ (setq ivy--full-length nil)
+ (split-string res "\n" t))
+ (setq ivy--full-length -1)
+ (counsel--gg-candidates (ivy--regex string))
+ nil))))
(defvar counsel-git-grep-map
(let ((map (make-sparse-keymap)))
@@ -237,7 +230,7 @@
(defun counsel-git-grep-recenter ()
(interactive)
(with-selected-window (ivy-state-window ivy-last)
- (counsel-git-grep-action)
+ (counsel-git-grep-action ivy--current)
(recenter-top-bottom)))
(defun counsel-git-grep-action (x)
@@ -257,7 +250,7 @@
(locate-dominating-file default-directory ".git"))
(if (null counsel--git-grep-dir)
(error "Not in a git repository")
- (setq counsel--git-grep-count (counsel-git-grep-count ""))
+ (setq counsel--git-grep-count (counsel--gg-count "" t))
(ivy-read "pattern: " 'counsel-git-grep-function
:initial-input initial-input
:matcher #'counsel-git-grep-matcher
@@ -421,6 +414,66 @@ The libraries are offered from `load-path'."
(get-text-property 0 'full-name x)))
:keymap counsel-describe-map)))
+(defun counsel--gg-candidates (regex)
+ "Return git grep candidates for REGEX."
+ (counsel--gg-count regex)
+ (let* ((default-directory counsel--git-grep-dir)
+ (counsel-gg-process " *counsel-gg*")
+ (proc (get-process counsel-gg-process))
+ (buff (get-buffer counsel-gg-process)))
+ (when proc
+ (delete-process proc))
+ (when buff
+ (kill-buffer buff))
+ (setq proc (start-process-shell-command
+ counsel-gg-process
+ counsel-gg-process
+ (format "git --no-pager grep --full-name -n --no-color -i -e
\"%s\" | head -n 200"
+ regex)))
+ (set-process-sentinel
+ proc
+ #'counsel--gg-sentinel)))
+
+(defun counsel--gg-sentinel (process event)
+ (if (string= event "finished\n")
+ (progn
+ (with-current-buffer (process-buffer process)
+ (setq ivy--all-candidates (split-string (buffer-string) "\n" t))
+ (setq ivy--old-cands ivy--all-candidates))
+ (unless (eq ivy--full-length -1)
+ (ivy--insert-minibuffer
+ (ivy--format ivy--all-candidates))))
+ (if (string= event "exited abnormally with code 1\n")
+ (message "Error"))))
+
+(defun counsel--gg-count (regex &optional no-async)
+ "Quickly and asynchronously count the amount of git grep REGEX matches.
+When NO-ASYNC is non-nil, do it synchronously."
+ (let ((default-directory counsel--git-grep-dir)
+ (cmd (format "git grep -i -c '%s' | sed 's/.*:\\(.*\\)/\\1/g' | awk
'{s+=$1} END {print s}'"
+ regex))
+ (counsel-ggc-process " *counsel-gg-count*"))
+ (if no-async
+ (string-to-number (shell-command-to-string cmd))
+ (let ((proc (get-process counsel-ggc-process))
+ (buff (get-buffer counsel-ggc-process)))
+ (when proc
+ (delete-process proc))
+ (when buff
+ (kill-buffer buff))
+ (setq proc (start-process-shell-command
+ counsel-ggc-process
+ counsel-ggc-process
+ cmd))
+ (set-process-sentinel
+ proc
+ #'(lambda (process event)
+ (when (string= event "finished\n")
+ (with-current-buffer (process-buffer process)
+ (setq ivy--full-length (string-to-number (buffer-string))))
+ (ivy--insert-minibuffer
+ (ivy--format ivy--all-candidates)))))))))
+
(provide 'counsel)
;;; counsel.el ends here
diff --git a/ivy.el b/ivy.el
index 2115555..9ac4917 100644
--- a/ivy.el
+++ b/ivy.el
@@ -1013,12 +1013,14 @@ Should be run via minibuffer `post-command-hook'."
;; 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)
- (cl-letf ((store (ivy-state-dynamic-collection ivy-last))
- ((ivy-state-dynamic-collection ivy-last) 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 (member ivy-text ivy--all-candidates)
@@ -1041,8 +1043,8 @@ Should be run via minibuffer `post-command-hook'."
(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."