From: Oleh Krehel Date: Thu, 4 Jun 2015 16:45:40 +0000 (+0200) Subject: Make counsel-git-grep fully async X-Git-Url: https://code.delx.au/gnu-emacs-elpa/commitdiff_plain/ed60d31fc31de0dc4258696c2ecec9f1c20b06c7 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. --- diff --git a/counsel.el b/counsel.el index 174a1b98e..57bad3863 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 211555501..9ac491794 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."