branch: master commit 8abed590c9904fbdf7d6fbac3b7aed87627ed5c1 Author: Oleh Krehel <ohwoeo...@gmail.com> Commit: Oleh Krehel <ohwoeo...@gmail.com>
Add counsel-grep * ivy.el (ivy--reset-state): Don't push preselect onto collection for :dynamic-collection. (ivy-recompute-index-swiper-async): New defun. It's useful for re-anchoring on collections produced async processes. The major difference from `ivy-recompute-index-swiper' is using `equal' instead of `eq'. * counsel.el (counsel--async-sentinel): Add index recomputing logic. When `ivy--old-cands' are null, recompute the index according to :preselect, otherwise try `ivy--recompute-index'. (counsel-grep): New command. Very similar to `swiper', except calls an external process for each key update. Should be much faster for very large files, both for startup and for matching. For smaller files, it's less convenient. (counsel-grep-function): New defun. (counsel-grep-action): New defun. Fixes #299 --- counsel.el | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ivy.el | 14 ++++++++++++++ 2 files changed, 70 insertions(+), 0 deletions(-) diff --git a/counsel.el b/counsel.el index 7e6b9fd..020c917 100644 --- a/counsel.el +++ b/counsel.el @@ -562,6 +562,16 @@ Or the time of the last minibuffer update.") (setq ivy--all-candidates (ivy--sort-maybe (split-string (buffer-string) "\n" t))) + (if (null ivy--old-cands) + (setq ivy--index + (or (ivy--preselect-index + (ivy-state-preselect ivy-last) + ivy--all-candidates) + 0)) + (ivy--recompute-index + ivy-text + (funcall ivy--regex-function ivy-text) + ivy--all-candidates)) (setq ivy--old-cands ivy--all-candidates)) (ivy--exhibit)) (if (string= event "exited abnormally with code 1\n") @@ -1107,6 +1117,7 @@ Usable with `ivy-resume', `ivy-next-line-and-call' and (format "ag --vimgrep %S" regex)) nil))) +;;;###autoload (defun counsel-ag (&optional initial-input initial-directory) "Grep for a string in the current directory using ag. INITIAL-INPUT can be given as the initial minibuffer input." @@ -1121,6 +1132,51 @@ INITIAL-INPUT can be given as the initial minibuffer input." (counsel-delete-process) (swiper--cleanup)))) +;;;###autoload +(defun counsel-grep () + "Grep for a string in the current file." + (interactive) + (setq counsel--git-grep-dir (buffer-file-name)) + (ivy-read "grep: " 'counsel-grep-function + :dynamic-collection t + :preselect (format "%d:%s" + (line-number-at-pos) + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position))) + :history 'counsel-git-grep-history + :update-fn (lambda () + (counsel-grep-action ivy--current)) + :action #'counsel-grep-action + :unwind (lambda () + (counsel-delete-process) + (swiper--cleanup)) + :caller 'counsel-grep)) + +(defun counsel-grep-function (string &optional _pred &rest _unused) + "Grep in the current directory for STRING." + (if (< (length string) 3) + (counsel-more-chars 3) + (let ((regex (counsel-unquote-regex-parens + (setq ivy--old-re + (ivy--regex string))))) + (counsel--async-command + (format "grep -nP --ignore-case '%s' %s" regex counsel--git-grep-dir)) + nil))) + +(defun counsel-grep-action (x) + (when (string-match "\\`\\([0-9]+\\):\\(.*\\)\\'" x) + (with-ivy-window + (let ((file-name counsel--git-grep-dir) + (line-number (match-string-no-properties 1 x))) + (find-file file-name) + (goto-char (point-min)) + (forward-line (1- (string-to-number line-number))) + (re-search-forward (ivy--regex ivy-text t) (line-end-position) t) + (unless (eq ivy-exit 'done) + (swiper--cleanup) + (swiper--add-overlays (ivy--regex ivy-text))))))) + (defun counsel-recoll-function (string &optional _pred &rest _unused) "Grep in the current directory for STRING." (if (< (length string) 3) diff --git a/ivy.el b/ivy.el index af786c0..f83d0d0 100644 --- a/ivy.el +++ b/ivy.el @@ -947,6 +947,7 @@ 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 @@ -1192,6 +1193,7 @@ 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))) @@ -1851,6 +1853,18 @@ Prefix matches to NAME are put ahead of the list." (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) 0)