(require 'swiper)
(require 'etags)
+(require 'esh-util)
;;* Utility
(defun counsel-more-chars (n)
"Store the time when a new process was started.
Or the time of the last minibuffer update.")
+(defvar counsel--async-exit-code-plist nil
+ "Associates exit codes with reasons.")
+
+(defun counsel-set-async-exit-code (cmd number str)
+ "For CMD, associate NUMBER exit code with STR."
+ (let ((plist (plist-get counsel--async-exit-code-plist cmd)))
+ (setq counsel--async-exit-code-plist
+ (plist-put
+ counsel--async-exit-code-plist
+ cmd
+ (plist-put plist number str)))))
+
(defvar counsel-async-split-string-re "\n"
"Store the regexp for splitting shell command output.")
(set-process-filter proc (or process-filter #'counsel--async-filter))))
(defun counsel--async-sentinel (process event)
- (if (string= event "finished\n")
- (progn
- (with-current-buffer (process-buffer process)
- (ivy--set-candidates
- (ivy--sort-maybe
- (split-string
- (buffer-string)
- counsel-async-split-string-re
- t)))
- (if (null ivy--old-cands)
- (setq ivy--index
- (or (ivy--preselect-index
- (ivy-state-preselect ivy-last)
- ivy--all-candidates)
- 0))
- (let ((re (funcall ivy--regex-function ivy-text)))
- (unless (stringp re)
- (setq re (caar re)))
- (ivy--recompute-index
- ivy-text re ivy--all-candidates)))
- (setq ivy--old-cands ivy--all-candidates))
- (if (null ivy--all-candidates)
- (ivy--insert-minibuffer "")
- (ivy--exhibit)))
- (if (string-match "exited abnormally with code \\([0-9]+\\)\n" event)
- (progn
- (setq ivy--all-candidates (list (format "error code %s" (match-string 1 event))))
- (setq ivy--old-cands ivy--all-candidates)
- (ivy--exhibit)))))
+ (let ((cands
+ (cond ((string= event "finished\n")
+ (with-current-buffer (process-buffer process)
+ (split-string
+ (buffer-string)
+ counsel-async-split-string-re
+ t)))
+ ((string-match "exited abnormally with code \\([0-9]+\\)\n" event)
+ (let* ((exit-code-plist (plist-get counsel--async-exit-code-plist
+ (ivy-state-caller ivy-last)))
+ (exit-num (read (match-string 1 event)))
+ (exit-code (plist-get exit-code-plist exit-num)))
+ (list
+ (or exit-code
+ (format "error code %d" exit-num))))))))
+ (cond ((string= event "finished\n")
+ (ivy--set-candidates
+ (ivy--sort-maybe
+ cands))
+ (if (null ivy--old-cands)
+ (setq ivy--index
+ (or (ivy--preselect-index
+ (ivy-state-preselect ivy-last)
+ ivy--all-candidates)
+ 0))
+ (let ((re (funcall ivy--regex-function ivy-text)))
+ (unless (stringp re)
+ (setq re (caar re)))
+ (ivy--recompute-index
+ ivy-text re ivy--all-candidates)))
+ (setq ivy--old-cands ivy--all-candidates)
+ (if (null ivy--all-candidates)
+ (ivy--insert-minibuffer "")
+ (ivy--exhibit)))
+ ((string-match "exited abnormally with code \\([0-9]+\\)\n" event)
+ (setq ivy--all-candidates cands)
+ (setq ivy--old-cands ivy--all-candidates)
+ (ivy--exhibit)))))
(defun counsel--async-filter (process str)
"Receive from PROCESS the output STR.
(ivy--format ivy--all-candidates)))
(setq counsel--async-time (current-time)))))
+(defcustom counsel-prompt-function 'counsel-prompt-function-default
+ "A function to return a full prompt string from a basic prompt string."
+ :type
+ '(choice
+ (const :tag "Plain" counsel-prompt-function-default)
+ (const :tag "Directory" counsel-prompt-function-dir)
+ (function :tag "Custom")))
+
+(defun counsel-prompt-function-default (prompt)
+ "Return PROMPT appended with a semicolon."
+ (format "%s: " prompt))
+
+(defun counsel-prompt-function-dir (prompt)
+ "Return PROMPT appended with the parent directory."
+ (let ((directory counsel--git-grep-dir))
+ (format "%s [%s]: "
+ prompt
+ (let ((dir-list (eshell-split-path directory)))
+ (if (> (length dir-list) 3)
+ (apply #'concat
+ (append '("...")
+ (cl-subseq dir-list (- (length dir-list) 3))))
+ directory)))))
+
(defun counsel-delete-process ()
(let ((process (get-process " *counsel*")))
(when process
"git ls-files --full-name --")
"\n"
t)))
- (ivy-read "Find file: " cands
+ (ivy-read (funcall counsel-prompt-function "Find file")
+ cands
:action #'counsel-git-action)))
(defun counsel-git-action (x)
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-l") 'counsel-git-grep-recenter)
(define-key map (kbd "M-q") 'counsel-git-grep-query-replace)
+ (define-key map (kbd "C-c C-m") 'counsel-git-grep-switch-cmd)
map))
(ivy-set-occur 'counsel-git-grep 'counsel-git-grep-occur)
(if (null counsel--git-grep-dir)
(error "Not in a git repository")
(setq counsel--git-grep-count (counsel--gg-count "" t))
- (ivy-read "git grep: " 'counsel-git-grep-function
- :initial-input initial-input
- :matcher #'counsel-git-grep-matcher
- :dynamic-collection (> counsel--git-grep-count 20000)
- :keymap counsel-git-grep-map
- :action #'counsel-git-grep-action
- :unwind #'swiper--cleanup
- :history 'counsel-git-grep-history
- :caller 'counsel-git-grep)))
+ (ivy-read
+ (funcall counsel-prompt-function "git grep")
+ 'counsel-git-grep-function
+ :initial-input initial-input
+ :matcher #'counsel-git-grep-matcher
+ :dynamic-collection (> counsel--git-grep-count 20000)
+ :keymap counsel-git-grep-map
+ :action #'counsel-git-grep-action
+ :unwind #'swiper--cleanup
+ :history 'counsel-git-grep-history
+ :caller 'counsel-git-grep)))
+
+(defun counsel-git-grep-switch-cmd ()
+ "Set `counsel-git-grep-cmd' to a different value."
+ (interactive)
+ (setq counsel-git-grep-cmd
+ (ivy-read "cmd: " counsel-git-grep-cmd-history
+ :history 'counsel-git-grep-cmd-history))
+ (setq counsel-git-grep-cmd-history
+ (delete-dups counsel-git-grep-cmd-history))
+ (unless (ivy-state-dynamic-collection ivy-last)
+ (setq ivy--all-candidates
+ (all-completions "" 'counsel-git-grep-function))))
(defvar counsel-gg-state nil
"The current state of candidates / count sync.")
(add-to-list 'ivy-ffap-url-functions 'counsel-github-url-p)
(add-to-list 'ivy-ffap-url-functions 'counsel-emacs-url-p)
+(ivy-set-actions
+ 'counsel-find-file
+ '(("f" find-file-other-window "other window")))
(defcustom counsel-find-file-at-point nil
"When non-nil, add file-at-point to the list of candidates."
(make-obsolete-variable 'counsel-locate-options 'counsel-locate-cmd "0.7.0")
-(defcustom counsel-locate-cmd (if (eq system-type 'darwin)
- 'counsel-locate-cmd-noregex
- 'counsel-locate-cmd-default)
+(defcustom counsel-locate-cmd (cond ((eq system-type 'darwin)
+ 'counsel-locate-cmd-noregex)
+ ((and (eq system-type 'windows-nt)
+ (executable-find "es.exe"))
+ 'counsel-locate-cmd-es)
+ (t
+ 'counsel-locate-cmd-default))
"The function for producing a locate command string from the input.
The function takes a string - the current input, and returns a
:type '(choice
(const :tag "Default" counsel-locate-cmd-default)
(const :tag "No regex" counsel-locate-cmd-noregex)
- (const :tag "mdfind" counsel-locate-cmd-mdfind)))
+ (const :tag "mdfind" counsel-locate-cmd-mdfind)
+ (const :tag "everything" counsel-locate-cmd-es)))
(ivy-set-actions
'counsel-locate
'(("x" counsel-locate-action-extern "xdg-open")
("d" counsel-locate-action-dired "dired")))
+(counsel-set-async-exit-code 'counsel-locate 1 "Nothing found")
+
(defvar counsel-locate-history nil
"History for `counsel-locate'.")
"Return a shell command based on INPUT."
(format "mdfind -name '%s'" input))
+(defun counsel-locate-cmd-es (input)
+ "Return a shell command based on INPUT."
+ (format "es.exe -i -r %s"
+ (counsel-unquote-regex-parens
+ (ivy--regex input))))
+
(defun counsel-locate-function (input)
(if (< (length input) 3)
(counsel-more-chars 3)
;;* Grep
;;** `counsel-ag'
-(defcustom counsel-ag-base-command "ag --vimgrep %S"
+(defcustom counsel-ag-base-command "ag --vimgrep %s"
"Format string to use in `cousel-ag-function' to construct the
command. %S will be replaced by the regex string. The default is
\"ag --vimgrep %S\"."
- :type 'stringp
+ :type 'string
:group 'ivy)
+(counsel-set-async-exit-code 'counsel-ag 1 "No matches found")
+(ivy-set-occur 'counsel-ag 'counsel-ag-occur)
+
(defun counsel-ag-function (string)
"Grep in the current directory for STRING."
(if (< (length string) 3)
(setq ivy--old-re
(ivy--regex string)))))
(counsel--async-command
- (format counsel-ag-base-command regex))
+ (format counsel-ag-base-command (shell-quote-argument 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."
- (interactive)
+ (interactive
+ (list nil
+ (when current-prefix-arg
+ (read-directory-name "ag in directory: "))))
(setq counsel--git-grep-dir (or initial-directory default-directory))
- (ivy-read "ag: " 'counsel-ag-function
+ (ivy-read (funcall counsel-prompt-function "ag")
+ 'counsel-ag-function
:initial-input initial-input
:dynamic-collection t
:history 'counsel-git-grep-history
(swiper--cleanup))
:caller 'counsel-ag))
+(defun counsel-ag-occur ()
+ "Generate a custom occur buffer for `counsel-ag'."
+ (ivy-occur-grep-mode)
+ (setq default-directory counsel--git-grep-dir)
+ (let* ((regex (counsel-unquote-regex-parens
+ (setq ivy--old-re
+ (ivy--regex ivy-text))))
+ (cands (split-string
+ (shell-command-to-string
+ (format counsel-ag-base-command (shell-quote-argument regex)))
+ "\n"
+ t)))
+ ;; 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 cands)))
+ (ivy--occur-insert-lines
+ (mapcar
+ (lambda (cand) (concat "./" cand))
+ cands))))
+
;;** `counsel-grep'
(defun counsel-grep-function (string)
"Grep in the current directory for STRING."