]> code.delx.au - gnu-emacs-elpa/blob - counsel.el
Add optional exit-code table to counsel--async-cmd
[gnu-emacs-elpa] / counsel.el
1 ;;; counsel.el --- Various completion functions using Ivy -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
4
5 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
6 ;; URL: https://github.com/abo-abo/swiper
7 ;; Package-Requires: ((emacs "24.1") (swiper "0.4.0"))
8 ;; Keywords: completion, matching
9
10 ;; This file is part of GNU Emacs.
11
12 ;; This file is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 3, or (at your option)
15 ;; any later version.
16
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
21
22 ;; For a full copy of the GNU General Public License
23 ;; see <http://www.gnu.org/licenses/>.
24
25 ;;; Commentary:
26 ;;
27 ;; Just call one of the interactive functions in this file to complete
28 ;; the corresponding thing using `ivy'.
29 ;;
30 ;; Currently available: Elisp symbols, Clojure symbols, Git files.
31
32 ;;; Code:
33
34 (require 'swiper)
35 (require 'etags)
36
37 ;;* Utility
38 (defun counsel-more-chars (n)
39 "Return two fake candidates prompting for at least N input."
40 (list ""
41 (format "%d chars more" (- n (length ivy-text)))))
42
43 (defun counsel-unquote-regex-parens (str)
44 (replace-regexp-in-string
45 "\\\\)" ")"
46 (replace-regexp-in-string
47 "\\\\(" "("
48 str)))
49
50 (defun counsel-directory-parent (dir)
51 "Return the directory parent of directory DIR."
52 (concat (file-name-nondirectory
53 (directory-file-name dir)) "/"))
54
55 (defun counsel-string-compose (prefix str)
56 "Make PREFIX the display prefix of STR though text properties."
57 (let ((str (copy-sequence str)))
58 (put-text-property
59 0 1 'display
60 (concat prefix (substring str 0 1))
61 str)
62 str))
63
64 ;;* Async Utility
65 (defvar counsel--async-time nil
66 "Store the time when a new process was started.
67 Or the time of the last minibuffer update.")
68
69 (defvar counsel--async-exit-code-plist nil
70 "Associates exit codes with reasons.")
71
72 (defun counsel-set-async-exit-code (cmd number str)
73 "For CMD, associate NUMBER exit code with STR."
74 (let ((plist (plist-get counsel--async-exit-code-plist cmd)))
75 (setq counsel--async-exit-code-plist
76 (plist-put
77 counsel--async-exit-code-plist
78 cmd
79 (plist-put plist number str)))))
80
81 (defvar counsel-async-split-string-re "\n"
82 "Store the regexp for splitting shell command output.")
83
84 (defun counsel--async-command (cmd &optional process-sentinel process-filter)
85 (let* ((counsel--process " *counsel*")
86 (proc (get-process counsel--process))
87 (buff (get-buffer counsel--process)))
88 (when proc
89 (delete-process proc))
90 (when buff
91 (kill-buffer buff))
92 (setq proc (start-process-shell-command
93 counsel--process
94 counsel--process
95 cmd))
96 (setq counsel--async-time (current-time))
97 (set-process-sentinel proc (or process-sentinel #'counsel--async-sentinel))
98 (set-process-filter proc (or process-filter #'counsel--async-filter))))
99
100 (defun counsel--async-sentinel (process event)
101 (if (string= event "finished\n")
102 (progn
103 (with-current-buffer (process-buffer process)
104 (ivy--set-candidates
105 (ivy--sort-maybe
106 (split-string
107 (buffer-string)
108 counsel-async-split-string-re
109 t)))
110 (if (null ivy--old-cands)
111 (setq ivy--index
112 (or (ivy--preselect-index
113 (ivy-state-preselect ivy-last)
114 ivy--all-candidates)
115 0))
116 (let ((re (funcall ivy--regex-function ivy-text)))
117 (unless (stringp re)
118 (setq re (caar re)))
119 (ivy--recompute-index
120 ivy-text re ivy--all-candidates)))
121 (setq ivy--old-cands ivy--all-candidates))
122 (if (null ivy--all-candidates)
123 (ivy--insert-minibuffer "")
124 (ivy--exhibit)))
125 (if (string-match "exited abnormally with code \\([0-9]+\\)\n" event)
126 (let* ((exit-code-plist (plist-get counsel--async-exit-code-plist
127 (ivy-state-caller ivy-last)))
128 (exit-num (read (match-string 1 event)))
129 (exit-code (plist-get exit-code-plist exit-num)))
130 (setq ivy--all-candidates
131 (list
132 (or exit-code
133 (format "error code %d" exit-num))))
134 (setq ivy--old-cands ivy--all-candidates)
135 (ivy--exhibit)))))
136
137 (defun counsel--async-filter (process str)
138 "Receive from PROCESS the output STR.
139 Update the minibuffer with the amount of lines collected every
140 0.5 seconds since the last update."
141 (with-current-buffer (process-buffer process)
142 (insert str))
143 (let (size)
144 (when (time-less-p
145 ;; 0.5s
146 '(0 0 500000 0)
147 (time-since counsel--async-time))
148 (with-current-buffer (process-buffer process)
149 (goto-char (point-min))
150 (setq size (- (buffer-size) (forward-line (buffer-size))))
151 (ivy--set-candidates
152 (split-string
153 (buffer-string)
154 counsel-async-split-string-re
155 t)))
156 (let ((ivy--prompt (format
157 (concat "%d++ " (ivy-state-prompt ivy-last))
158 size)))
159 (ivy--insert-minibuffer
160 (ivy--format ivy--all-candidates)))
161 (setq counsel--async-time (current-time)))))
162
163 (defun counsel-delete-process ()
164 (let ((process (get-process " *counsel*")))
165 (when process
166 (delete-process process))))
167
168 ;;* Completion at point
169 ;;** `counsel-el'
170 ;;;###autoload
171 (defun counsel-el ()
172 "Elisp completion at point."
173 (interactive)
174 (let* ((bnd (unless (and (looking-at ")")
175 (eq (char-before) ?\())
176 (bounds-of-thing-at-point
177 'symbol)))
178 (str (if bnd
179 (buffer-substring-no-properties
180 (car bnd)
181 (cdr bnd))
182 ""))
183 (ivy-height 7)
184 (funp (eq (char-before (car bnd)) ?\())
185 symbol-names)
186 (if bnd
187 (progn
188 (setq ivy-completion-beg
189 (move-marker (make-marker) (car bnd)))
190 (setq ivy-completion-end
191 (move-marker (make-marker) (cdr bnd))))
192 (setq ivy-completion-beg nil)
193 (setq ivy-completion-end nil))
194 (if (string= str "")
195 (mapatoms
196 (lambda (x)
197 (when (symbolp x)
198 (push (symbol-name x) symbol-names))))
199 (setq symbol-names
200 (all-completions str obarray
201 (and funp
202 (lambda (x)
203 (or (functionp x)
204 (macrop x)
205 (special-form-p x)))))))
206 (ivy-read "Symbol name: " symbol-names
207 :predicate (and funp #'functionp)
208 :initial-input str
209 :action #'ivy-completion-in-region-action)))
210
211 ;;** `counsel-cl'
212 (declare-function slime-symbol-start-pos "ext:slime")
213 (declare-function slime-symbol-end-pos "ext:slime")
214 (declare-function slime-contextual-completions "ext:slime-c-p-c")
215
216 ;;;###autoload
217 (defun counsel-cl ()
218 "Common Lisp completion at point."
219 (interactive)
220 (setq ivy-completion-beg (slime-symbol-start-pos))
221 (setq ivy-completion-end (slime-symbol-end-pos))
222 (ivy-read "Symbol name: "
223 (car (slime-contextual-completions
224 ivy-completion-beg
225 ivy-completion-end))
226 :action #'ivy-completion-in-region-action))
227
228 ;;** `counsel-jedi'
229 (declare-function deferred:sync! "ext:deferred")
230 (declare-function jedi:complete-request "ext:jedi-core")
231 (declare-function jedi:ac-direct-matches "ext:jedi")
232
233 (defun counsel-jedi ()
234 "Python completion at point."
235 (interactive)
236 (let ((bnd (bounds-of-thing-at-point 'symbol)))
237 (if bnd
238 (progn
239 (setq ivy-completion-beg (car bnd))
240 (setq ivy-completion-end (cdr bnd)))
241 (setq ivy-completion-beg nil)
242 (setq ivy-completion-end nil)))
243 (deferred:sync!
244 (jedi:complete-request))
245 (ivy-read "Symbol name: " (jedi:ac-direct-matches)
246 :action #'counsel--py-action))
247
248 (defun counsel--py-action (symbol)
249 "Insert SYMBOL, erasing the previous one."
250 (when (stringp symbol)
251 (with-ivy-window
252 (when ivy-completion-beg
253 (delete-region
254 ivy-completion-beg
255 ivy-completion-end))
256 (setq ivy-completion-beg
257 (move-marker (make-marker) (point)))
258 (insert symbol)
259 (setq ivy-completion-end
260 (move-marker (make-marker) (point)))
261 (when (equal (get-text-property 0 'symbol symbol) "f")
262 (insert "()")
263 (setq ivy-completion-end
264 (move-marker (make-marker) (point)))
265 (backward-char 1)))))
266
267 ;;** `counsel-clj'
268 (declare-function cider-sync-request:complete "ext:cider-client")
269 (defun counsel--generic (completion-fn)
270 "Complete thing at point with COMPLETION-FN."
271 (let* ((bnd (or (bounds-of-thing-at-point 'symbol)
272 (cons (point) (point))))
273 (str (buffer-substring-no-properties
274 (car bnd) (cdr bnd)))
275 (candidates (funcall completion-fn str))
276 (ivy-height 7)
277 (res (ivy-read (format "pattern (%s): " str)
278 candidates)))
279 (when (stringp res)
280 (when bnd
281 (delete-region (car bnd) (cdr bnd)))
282 (insert res))))
283
284 ;;;###autoload
285 (defun counsel-clj ()
286 "Clojure completion at point."
287 (interactive)
288 (counsel--generic
289 (lambda (str)
290 (mapcar
291 #'cl-caddr
292 (cider-sync-request:complete str ":same")))))
293
294 ;;** `counsel-unicode-char'
295 (defvar counsel-unicode-char-history nil
296 "History for `counsel-unicode-char'.")
297
298 ;;;###autoload
299 (defun counsel-unicode-char ()
300 "Insert a Unicode character at point."
301 (interactive)
302 (let ((minibuffer-allow-text-properties t))
303 (setq ivy-completion-beg (point))
304 (setq ivy-completion-end (point))
305 (ivy-read "Unicode name: "
306 (mapcar (lambda (x)
307 (propertize
308 (format "% -6X% -60s%c" (cdr x) (car x) (cdr x))
309 'result (cdr x)))
310 (ucs-names))
311 :action (lambda (char)
312 (with-ivy-window
313 (delete-region ivy-completion-beg ivy-completion-end)
314 (setq ivy-completion-beg (point))
315 (insert-char (get-text-property 0 'result char))
316 (setq ivy-completion-end (point))))
317 :history 'counsel-unicode-char-history)))
318
319 ;;* Elisp symbols
320 ;;** `counsel-describe-variable'
321 (defvar counsel-describe-map
322 (let ((map (make-sparse-keymap)))
323 (define-key map (kbd "C-.") #'counsel-find-symbol)
324 (define-key map (kbd "C-,") #'counsel--info-lookup-symbol)
325 map))
326
327 (ivy-set-actions
328 'counsel-describe-variable
329 '(("i" counsel-info-lookup-symbol "info")
330 ("d" counsel--find-symbol "definition")))
331
332 (defvar counsel-describe-symbol-history nil
333 "History for `counsel-describe-variable' and `counsel-describe-function'.")
334
335 (defun counsel-find-symbol ()
336 "Jump to the definition of the current symbol."
337 (interactive)
338 (ivy-exit-with-action #'counsel--find-symbol))
339
340 (defun counsel--info-lookup-symbol ()
341 "Lookup the current symbol in the info docs."
342 (interactive)
343 (ivy-exit-with-action #'counsel-info-lookup-symbol))
344
345 (defun counsel--find-symbol (x)
346 "Find symbol definition that corresponds to string X."
347 (with-ivy-window
348 (with-no-warnings
349 (ring-insert find-tag-marker-ring (point-marker)))
350 (let ((full-name (get-text-property 0 'full-name x)))
351 (if full-name
352 (find-library full-name)
353 (let ((sym (read x)))
354 (cond ((and (eq (ivy-state-caller ivy-last)
355 'counsel-describe-variable)
356 (boundp sym))
357 (find-variable sym))
358 ((fboundp sym)
359 (find-function sym))
360 ((boundp sym)
361 (find-variable sym))
362 ((or (featurep sym)
363 (locate-library
364 (prin1-to-string sym)))
365 (find-library
366 (prin1-to-string sym)))
367 (t
368 (error "Couldn't fild definition of %s"
369 sym))))))))
370
371 (define-obsolete-function-alias 'counsel-symbol-at-point
372 'ivy-thing-at-point "0.7.0")
373
374 (defun counsel-variable-list ()
375 "Return the list of all currently bound variables."
376 (let (cands)
377 (mapatoms
378 (lambda (vv)
379 (when (or (get vv 'variable-documentation)
380 (and (boundp vv) (not (keywordp vv))))
381 (push (symbol-name vv) cands))))
382 cands))
383
384 ;;;###autoload
385 (defun counsel-describe-variable ()
386 "Forward to `describe-variable'."
387 (interactive)
388 (let ((enable-recursive-minibuffers t))
389 (ivy-read
390 "Describe variable: "
391 (counsel-variable-list)
392 :keymap counsel-describe-map
393 :preselect (ivy-thing-at-point)
394 :history 'counsel-describe-symbol-history
395 :require-match t
396 :sort t
397 :action (lambda (x)
398 (describe-variable
399 (intern x)))
400 :caller 'counsel-describe-variable)))
401
402 ;;** `counsel-describe-function'
403 (ivy-set-actions
404 'counsel-describe-function
405 '(("i" counsel-info-lookup-symbol "info")
406 ("d" counsel--find-symbol "definition")))
407
408 ;;;###autoload
409 (defun counsel-describe-function ()
410 "Forward to `describe-function'."
411 (interactive)
412 (let ((enable-recursive-minibuffers t))
413 (ivy-read "Describe function: "
414 (let (cands)
415 (mapatoms
416 (lambda (x)
417 (when (fboundp x)
418 (push (symbol-name x) cands))))
419 cands)
420 :keymap counsel-describe-map
421 :preselect (ivy-thing-at-point)
422 :history 'counsel-describe-symbol-history
423 :require-match t
424 :sort t
425 :action (lambda (x)
426 (describe-function
427 (intern x)))
428 :caller 'counsel-describe-function)))
429
430 ;;** `counsel-info-lookup-symbol'
431 (defvar info-lookup-mode)
432 (declare-function info-lookup->completions "info-look")
433 (declare-function info-lookup->mode-value "info-look")
434 (declare-function info-lookup-select-mode "info-look")
435 (declare-function info-lookup-change-mode "info-look")
436 (declare-function info-lookup "info-look")
437
438 ;;;###autoload
439 (defun counsel-info-lookup-symbol (symbol &optional mode)
440 "Forward to (`info-describe-symbol' SYMBOL MODE) with ivy completion."
441 (interactive
442 (progn
443 (require 'info-look)
444 (let* ((topic 'symbol)
445 (mode (cond (current-prefix-arg
446 (info-lookup-change-mode topic))
447 ((info-lookup->mode-value
448 topic (info-lookup-select-mode))
449 info-lookup-mode)
450 ((info-lookup-change-mode topic))))
451 (completions (info-lookup->completions topic mode))
452 (enable-recursive-minibuffers t)
453 (value (ivy-read
454 "Describe symbol: "
455 (mapcar #'car completions)
456 :sort t)))
457 (list value info-lookup-mode))))
458 (require 'info-look)
459 (info-lookup 'symbol symbol mode))
460
461 ;;** `counsel-M-x'
462 (ivy-set-actions
463 'counsel-M-x
464 '(("d" counsel--find-symbol "definition")))
465
466 (ivy-set-display-transformer
467 'counsel-M-x
468 'counsel-M-x-transformer)
469
470 (defun counsel-M-x-transformer (cmd)
471 "Return CMD appended with the corresponding binding in the current window."
472 (let ((binding (substitute-command-keys (format "\\[%s]" cmd))))
473 (setq binding (replace-regexp-in-string "C-x 6" "<f2>" binding))
474 (if (string-match "^M-x" binding)
475 cmd
476 (format "%s (%s)"
477 cmd (propertize binding 'face 'font-lock-keyword-face)))))
478
479 (defvar smex-initialized-p)
480 (defvar smex-ido-cache)
481 (declare-function smex-initialize "ext:smex")
482 (declare-function smex-detect-new-commands "ext:smex")
483 (declare-function smex-update "ext:smex")
484 (declare-function smex-rank "ext:smex")
485
486 (defun counsel--M-x-prompt ()
487 "M-x plus the string representation of `current-prefix-arg'."
488 (if (not current-prefix-arg)
489 "M-x "
490 (concat
491 (if (eq current-prefix-arg '-)
492 "- "
493 (if (integerp current-prefix-arg)
494 (format "%d " current-prefix-arg)
495 (if (= (car current-prefix-arg) 4)
496 "C-u "
497 (format "%d " (car current-prefix-arg)))))
498 "M-x ")))
499
500 ;;;###autoload
501 (defun counsel-M-x (&optional initial-input)
502 "Ivy version of `execute-extended-command'.
503 Optional INITIAL-INPUT is the initial input in the minibuffer."
504 (interactive)
505 (unless initial-input
506 (setq initial-input (cdr (assoc this-command
507 ivy-initial-inputs-alist))))
508 (let* ((cands obarray)
509 (pred 'commandp)
510 (sort t))
511 (when (require 'smex nil 'noerror)
512 (unless smex-initialized-p
513 (smex-initialize))
514 (smex-detect-new-commands)
515 (smex-update)
516 (setq cands smex-ido-cache)
517 (setq pred nil)
518 (setq sort nil))
519 (ivy-read (counsel--M-x-prompt) cands
520 :predicate pred
521 :require-match t
522 :history 'extended-command-history
523 :action
524 (lambda (cmd)
525 (when (featurep 'smex)
526 (smex-rank (intern cmd)))
527 (let ((prefix-arg current-prefix-arg)
528 (this-command (intern cmd)))
529 (command-execute (intern cmd) 'record)))
530 :sort sort
531 :keymap counsel-describe-map
532 :initial-input initial-input
533 :caller 'counsel-M-x)))
534
535 ;;** `counsel-load-library'
536 ;;;###autoload
537 (defun counsel-load-library ()
538 "Load a selected the Emacs Lisp library.
539 The libraries are offered from `load-path'."
540 (interactive)
541 (let ((dirs load-path)
542 (suffix (concat (regexp-opt '(".el" ".el.gz") t) "\\'"))
543 (cands (make-hash-table :test #'equal))
544 short-name
545 old-val
546 dir-parent
547 res)
548 (dolist (dir dirs)
549 (when (file-directory-p dir)
550 (dolist (file (file-name-all-completions "" dir))
551 (when (string-match suffix file)
552 (unless (string-match "pkg.elc?$" file)
553 (setq short-name (substring file 0 (match-beginning 0)))
554 (if (setq old-val (gethash short-name cands))
555 (progn
556 ;; assume going up directory once will resolve name clash
557 (setq dir-parent (counsel-directory-parent (cdr old-val)))
558 (puthash short-name
559 (cons
560 (counsel-string-compose dir-parent (car old-val))
561 (cdr old-val))
562 cands)
563 (setq dir-parent (counsel-directory-parent dir))
564 (puthash (concat dir-parent short-name)
565 (cons
566 (propertize
567 (counsel-string-compose
568 dir-parent short-name)
569 'full-name (expand-file-name file dir))
570 dir)
571 cands))
572 (puthash short-name
573 (cons (propertize
574 short-name
575 'full-name (expand-file-name file dir))
576 dir) cands)))))))
577 (maphash (lambda (_k v) (push (car v) res)) cands)
578 (ivy-read "Load library: " (nreverse res)
579 :action (lambda (x)
580 (load-library
581 (get-text-property 0 'full-name x)))
582 :keymap counsel-describe-map)))
583
584 ;;** `counsel-load-theme'
585 (declare-function powerline-reset "ext:powerline")
586
587 (defun counsel-load-theme-action (x)
588 "Disable current themes and load theme X."
589 (condition-case nil
590 (progn
591 (mapc #'disable-theme custom-enabled-themes)
592 (load-theme (intern x))
593 (when (fboundp 'powerline-reset)
594 (powerline-reset)))
595 (error "Problem loading theme %s" x)))
596
597 ;;;###autoload
598 (defun counsel-load-theme ()
599 "Forward to `load-theme'.
600 Usable with `ivy-resume', `ivy-next-line-and-call' and
601 `ivy-previous-line-and-call'."
602 (interactive)
603 (ivy-read "Load custom theme: "
604 (mapcar 'symbol-name
605 (custom-available-themes))
606 :action #'counsel-load-theme-action
607 :caller 'counsel-load-theme))
608
609 ;;** `counsel-descbinds'
610 (ivy-set-actions
611 'counsel-descbinds
612 '(("d" counsel-descbinds-action-find "definition")
613 ("i" counsel-descbinds-action-info "info")))
614
615 (defvar counsel-descbinds-history nil
616 "History for `counsel-descbinds'.")
617
618 (defun counsel--descbinds-cands ()
619 (let ((buffer (current-buffer))
620 (re-exclude (regexp-opt
621 '("<vertical-line>" "<bottom-divider>" "<right-divider>"
622 "<mode-line>" "<C-down-mouse-2>" "<left-fringe>"
623 "<right-fringe>" "<header-line>"
624 "<vertical-scroll-bar>" "<horizontal-scroll-bar>")))
625 res)
626 (with-temp-buffer
627 (let ((indent-tabs-mode t))
628 (describe-buffer-bindings buffer))
629 (goto-char (point-min))
630 ;; Skip the "Key translations" section
631 (re-search-forward "\f")
632 (forward-char 1)
633 (while (not (eobp))
634 (when (looking-at "^\\([^\t\n]+\\)\t+\\(.*\\)$")
635 (let ((key (match-string 1))
636 (fun (match-string 2))
637 cmd)
638 (unless (or (member fun '("??" "self-insert-command"))
639 (string-match re-exclude key)
640 (not (or (commandp (setq cmd (intern-soft fun)))
641 (member fun '("Prefix Command")))))
642 (push
643 (cons (format
644 "%-15s %s"
645 (propertize key 'face 'font-lock-builtin-face)
646 fun)
647 (cons key cmd))
648 res))))
649 (forward-line 1)))
650 (nreverse res)))
651
652 (defun counsel-descbinds-action-describe (x)
653 (let ((cmd (cdr x)))
654 (describe-function cmd)))
655
656 (defun counsel-descbinds-action-find (x)
657 (let ((cmd (cdr x)))
658 (counsel--find-symbol (symbol-name cmd))))
659
660 (defun counsel-descbinds-action-info (x)
661 (let ((cmd (cdr x)))
662 (counsel-info-lookup-symbol (symbol-name cmd))))
663
664 ;;;###autoload
665 (defun counsel-descbinds ()
666 "Show a list of all defined keys, and their definitions.
667 Describe the selected candidate."
668 (interactive)
669 (ivy-read "Bindings: " (counsel--descbinds-cands)
670 :action #'counsel-descbinds-action-describe
671 :history 'counsel-descbinds-history
672 :caller 'counsel-descbinds))
673 ;;* Git
674 ;;** `counsel-git'
675 (defvar counsel--git-dir nil
676 "Store the base git directory.")
677
678 ;;;###autoload
679 (defun counsel-git ()
680 "Find file in the current Git repository."
681 (interactive)
682 (setq counsel--git-dir (expand-file-name
683 (locate-dominating-file
684 default-directory ".git")))
685 (let* ((default-directory counsel--git-dir)
686 (cands (split-string
687 (shell-command-to-string
688 "git ls-files --full-name --")
689 "\n"
690 t)))
691 (ivy-read "Find file: " cands
692 :action #'counsel-git-action)))
693
694 (defun counsel-git-action (x)
695 (with-ivy-window
696 (let ((default-directory counsel--git-dir))
697 (find-file x))))
698
699 ;;** `counsel-git-grep'
700 (defvar counsel-git-grep-map
701 (let ((map (make-sparse-keymap)))
702 (define-key map (kbd "C-l") 'counsel-git-grep-recenter)
703 (define-key map (kbd "M-q") 'counsel-git-grep-query-replace)
704 (define-key map (kbd "C-c C-m") 'counsel-git-grep-switch-cmd)
705 map))
706
707 (ivy-set-occur 'counsel-git-grep 'counsel-git-grep-occur)
708 (ivy-set-display-transformer 'counsel-git-grep 'counsel-git-grep-transformer)
709
710 (defvar counsel-git-grep-cmd "git --no-pager grep --full-name -n --no-color -i -e %S"
711 "Store the command for `counsel-git-grep'.")
712
713 (defvar counsel--git-grep-dir nil
714 "Store the base git directory.")
715
716 (defvar counsel--git-grep-count nil
717 "Store the line count in current repository.")
718
719 (defvar counsel-git-grep-history nil
720 "History for `counsel-git-grep'.")
721
722 (defvar counsel-git-grep-cmd-history
723 '("git --no-pager grep --full-name -n --no-color -i -e %S")
724 "History for `counsel-git-grep' shell commands.")
725
726 (defun counsel-git-grep-function (string &optional _pred &rest _unused)
727 "Grep in the current git repository for STRING."
728 (if (and (> counsel--git-grep-count 20000)
729 (< (length string) 3))
730 (counsel-more-chars 3)
731 (let* ((default-directory counsel--git-grep-dir)
732 (cmd (format counsel-git-grep-cmd
733 (setq ivy--old-re (ivy--regex string t)))))
734 (if (<= counsel--git-grep-count 20000)
735 (split-string (shell-command-to-string cmd) "\n" t)
736 (counsel--gg-candidates (ivy--regex string))
737 nil))))
738
739 (defun counsel-git-grep-action (x)
740 (when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" x)
741 (with-ivy-window
742 (let ((file-name (match-string-no-properties 1 x))
743 (line-number (match-string-no-properties 2 x)))
744 (find-file (expand-file-name file-name counsel--git-grep-dir))
745 (goto-char (point-min))
746 (forward-line (1- (string-to-number line-number)))
747 (re-search-forward (ivy--regex ivy-text t) (line-end-position) t)
748 (unless (eq ivy-exit 'done)
749 (swiper--cleanup)
750 (swiper--add-overlays (ivy--regex ivy-text)))))))
751
752 (defun counsel-git-grep-matcher (regexp candidates)
753 (or (and (equal regexp ivy--old-re)
754 ivy--old-cands)
755 (prog1
756 (setq ivy--old-cands
757 (cl-remove-if-not
758 (lambda (x)
759 (ignore-errors
760 (when (string-match "^[^:]+:[^:]+:" x)
761 (setq x (substring x (match-end 0)))
762 (if (stringp regexp)
763 (string-match regexp x)
764 (let ((res t))
765 (dolist (re regexp)
766 (setq res
767 (and res
768 (ignore-errors
769 (if (cdr re)
770 (string-match (car re) x)
771 (not (string-match (car re) x)))))))
772 res)))))
773 candidates))
774 (setq ivy--old-re regexp))))
775
776 (defun counsel-git-grep-transformer (str)
777 "Higlight file and line number in STR."
778 (when (string-match "\\`\\([^:]+\\):\\([^:]+\\):" str)
779 (set-text-properties (match-beginning 1)
780 (match-end 1)
781 '(face compilation-info)
782 str)
783 (set-text-properties (match-beginning 2)
784 (match-end 2)
785 '(face compilation-line-number)
786 str))
787 str)
788
789 ;;;###autoload
790 (defun counsel-git-grep (&optional cmd initial-input)
791 "Grep for a string in the current git repository.
792 When CMD is a string, use it as a \"git grep\" command.
793 When CMD is non-nil, prompt for a specific \"git grep\" command.
794 INITIAL-INPUT can be given as the initial minibuffer input."
795 (interactive "P")
796 (cond
797 ((stringp cmd)
798 (setq counsel-git-grep-cmd cmd))
799 (cmd
800 (setq counsel-git-grep-cmd
801 (ivy-read "cmd: " counsel-git-grep-cmd-history
802 :history 'counsel-git-grep-cmd-history))
803 (setq counsel-git-grep-cmd-history
804 (delete-dups counsel-git-grep-cmd-history)))
805 (t
806 (setq counsel-git-grep-cmd "git --no-pager grep --full-name -n --no-color -i -e %S")))
807 (setq counsel--git-grep-dir
808 (locate-dominating-file default-directory ".git"))
809 (if (null counsel--git-grep-dir)
810 (error "Not in a git repository")
811 (setq counsel--git-grep-count (counsel--gg-count "" t))
812 (ivy-read "git grep: " 'counsel-git-grep-function
813 :initial-input initial-input
814 :matcher #'counsel-git-grep-matcher
815 :dynamic-collection (> counsel--git-grep-count 20000)
816 :keymap counsel-git-grep-map
817 :action #'counsel-git-grep-action
818 :unwind #'swiper--cleanup
819 :history 'counsel-git-grep-history
820 :caller 'counsel-git-grep)))
821
822 (defun counsel-git-grep-switch-cmd ()
823 "Set `counsel-git-grep-cmd' to a different value."
824 (interactive)
825 (setq counsel-git-grep-cmd
826 (ivy-read "cmd: " counsel-git-grep-cmd-history
827 :history 'counsel-git-grep-cmd-history))
828 (setq counsel-git-grep-cmd-history
829 (delete-dups counsel-git-grep-cmd-history))
830 (unless (ivy-state-dynamic-collection ivy-last)
831 (setq ivy--all-candidates
832 (all-completions "" 'counsel-git-grep-function))))
833
834 (defvar counsel-gg-state nil
835 "The current state of candidates / count sync.")
836
837 (defun counsel--gg-candidates (regex)
838 "Return git grep candidates for REGEX."
839 (setq counsel-gg-state -2)
840 (counsel--gg-count regex)
841 (let* ((default-directory counsel--git-grep-dir)
842 (counsel-gg-process " *counsel-gg*")
843 (proc (get-process counsel-gg-process))
844 (buff (get-buffer counsel-gg-process)))
845 (when proc
846 (delete-process proc))
847 (when buff
848 (kill-buffer buff))
849 (setq proc (start-process-shell-command
850 counsel-gg-process
851 counsel-gg-process
852 (concat
853 (format counsel-git-grep-cmd regex)
854 " | head -n 200")))
855 (set-process-sentinel
856 proc
857 #'counsel--gg-sentinel)))
858
859 (defun counsel--gg-sentinel (process event)
860 (if (string= event "finished\n")
861 (progn
862 (with-current-buffer (process-buffer process)
863 (setq ivy--all-candidates
864 (or (split-string (buffer-string) "\n" t)
865 '("")))
866 (setq ivy--old-cands ivy--all-candidates))
867 (when (= 0 (cl-incf counsel-gg-state))
868 (ivy--exhibit)))
869 (if (string= event "exited abnormally with code 1\n")
870 (progn
871 (setq ivy--all-candidates '("Error"))
872 (setq ivy--old-cands ivy--all-candidates)
873 (ivy--exhibit)))))
874
875 (defun counsel--gg-count (regex &optional no-async)
876 "Quickly and asynchronously count the amount of git grep REGEX matches.
877 When NO-ASYNC is non-nil, do it synchronously."
878 (let ((default-directory counsel--git-grep-dir)
879 (cmd
880 (concat
881 (format
882 (replace-regexp-in-string
883 "--full-name" "-c"
884 counsel-git-grep-cmd)
885 ;; "git grep -i -c '%s'"
886 (replace-regexp-in-string
887 "-" "\\\\-"
888 (replace-regexp-in-string "'" "''" regex)))
889 " | sed 's/.*:\\(.*\\)/\\1/g' | awk '{s+=$1} END {print s}'"))
890 (counsel-ggc-process " *counsel-gg-count*"))
891 (if no-async
892 (string-to-number (shell-command-to-string cmd))
893 (let ((proc (get-process counsel-ggc-process))
894 (buff (get-buffer counsel-ggc-process)))
895 (when proc
896 (delete-process proc))
897 (when buff
898 (kill-buffer buff))
899 (setq proc (start-process-shell-command
900 counsel-ggc-process
901 counsel-ggc-process
902 cmd))
903 (set-process-sentinel
904 proc
905 #'(lambda (process event)
906 (when (string= event "finished\n")
907 (with-current-buffer (process-buffer process)
908 (setq ivy--full-length (string-to-number (buffer-string))))
909 (when (= 0 (cl-incf counsel-gg-state))
910 (ivy--exhibit)))))))))
911
912 (defun counsel-git-grep-occur ()
913 "Generate a custom occur buffer for `counsel-git-grep'."
914 (ivy-occur-grep-mode)
915 (setq default-directory counsel--git-grep-dir)
916 (let ((cands (split-string
917 (shell-command-to-string
918 (format counsel-git-grep-cmd
919 (setq ivy--old-re (ivy--regex ivy-text t))))
920 "\n"
921 t)))
922 ;; Need precise number of header lines for `wgrep' to work.
923 (insert (format "-*- mode:grep; default-directory: %S -*-\n\n\n"
924 default-directory))
925 (insert (format "%d candidates:\n" (length cands)))
926 (ivy--occur-insert-lines
927 (mapcar
928 (lambda (cand) (concat "./" cand))
929 cands))))
930
931 (defun counsel-git-grep-query-replace ()
932 "Start `query-replace' with string to replace from last search string."
933 (interactive)
934 (if (null (window-minibuffer-p))
935 (user-error
936 "Should only be called in the minibuffer through `counsel-git-grep-map'")
937 (let* ((enable-recursive-minibuffers t)
938 (from (ivy--regex ivy-text))
939 (to (query-replace-read-to from "Query replace" t)))
940 (ivy-exit-with-action
941 (lambda (_)
942 (let (done-buffers)
943 (dolist (cand ivy--old-cands)
944 (when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" cand)
945 (with-ivy-window
946 (let ((file-name (match-string-no-properties 1 cand)))
947 (setq file-name (expand-file-name file-name counsel--git-grep-dir))
948 (unless (member file-name done-buffers)
949 (push file-name done-buffers)
950 (find-file file-name)
951 (goto-char (point-min)))
952 (perform-replace from to t t nil)))))))))))
953
954 (defun counsel-git-grep-recenter ()
955 (interactive)
956 (with-ivy-window
957 (counsel-git-grep-action ivy--current)
958 (recenter-top-bottom)))
959
960 ;;** `counsel-git-stash'
961 (defun counsel-git-stash-kill-action (x)
962 (when (string-match "\\([^:]+\\):" x)
963 (kill-new (message (format "git stash apply %s" (match-string 1 x))))))
964
965 ;;;###autoload
966 (defun counsel-git-stash ()
967 "Search through all available git stashes."
968 (interactive)
969 (let ((dir (locate-dominating-file default-directory ".git")))
970 (if (null dir)
971 (error "Not in a git repository")
972 (let ((cands (split-string (shell-command-to-string
973 "IFS=$'\n'
974 for i in `git stash list --format=\"%gd\"`; do
975 git stash show -p $i | grep -H --label=\"$i\" \"$1\"
976 done") "\n" t)))
977 (ivy-read "git stash: " cands
978 :action 'counsel-git-stash-kill-action
979 :caller 'counsel-git-stash)))))
980 ;;** `counsel-git-log'
981 (defun counsel-git-log-function (input)
982 (if (< (length input) 3)
983 (counsel-more-chars 3)
984 ;; `counsel--yank-pop-format-function' uses this
985 (setq ivy--old-re (funcall ivy--regex-function input))
986 (counsel--async-command
987 ;; "git log --grep" likes to have groups quoted e.g. \(foo\).
988 ;; But it doesn't like the non-greedy ".*?".
989 (format "GIT_PAGER=cat git log --grep '%s'"
990 (replace-regexp-in-string
991 "\\.\\*\\?" ".*"
992 ivy--old-re)))
993 nil))
994
995 (defun counsel-git-log-action (x)
996 (message "%S" (kill-new x)))
997
998 (defcustom counsel-yank-pop-truncate-radius 2
999 "When non-nil, truncate the display of long strings."
1000 :type 'integer
1001 :group 'ivy)
1002
1003 ;;;###autoload
1004 (defun counsel-git-log ()
1005 "Call the \"git log --grep\" shell command."
1006 (interactive)
1007 (let ((counsel-async-split-string-re "\ncommit ")
1008 (counsel-yank-pop-truncate-radius 5)
1009 (ivy-format-function #'counsel--yank-pop-format-function)
1010 (ivy-height 4))
1011 (ivy-read "Grep log: " #'counsel-git-log-function
1012 :dynamic-collection t
1013 :action #'counsel-git-log-action
1014 :unwind #'counsel-delete-process
1015 :caller 'counsel-git-log)))
1016
1017 ;;* File
1018 ;;** `counsel-find-file'
1019 (defvar counsel-find-file-map
1020 (let ((map (make-sparse-keymap)))
1021 (define-key map (kbd "C-DEL") 'counsel-up-directory)
1022 (define-key map (kbd "C-<backspace>") 'counsel-up-directory)
1023 map))
1024
1025 (add-to-list 'ivy-ffap-url-functions 'counsel-github-url-p)
1026 (add-to-list 'ivy-ffap-url-functions 'counsel-emacs-url-p)
1027
1028 (defcustom counsel-find-file-at-point nil
1029 "When non-nil, add file-at-point to the list of candidates."
1030 :type 'boolean
1031 :group 'ivy)
1032
1033 (defcustom counsel-find-file-ignore-regexp nil
1034 "A regexp of files to ignore while in `counsel-find-file'.
1035 These files are un-ignored if `ivy-text' matches them. The
1036 common way to show all files is to start `ivy-text' with a dot.
1037
1038 Example value: \"\\(?:\\`[#.]\\)\\|\\(?:[#~]\\'\\)\". This will hide
1039 temporary and lock files.
1040 \\<ivy-minibuffer-map>
1041 Choosing the dotfiles option, \"\\`\\.\", might be convenient,
1042 since you can still access the dotfiles if your input starts with
1043 a dot. The generic way to toggle ignored files is \\[ivy-toggle-ignore],
1044 but the leading dot is a lot faster."
1045 :group 'ivy
1046 :type '(choice
1047 (const :tag "None" nil)
1048 (const :tag "Dotfiles" "\\`\\.")
1049 (regexp :tag "Regex")))
1050
1051 (defun counsel--find-file-matcher (regexp candidates)
1052 "Return REGEXP-matching CANDIDATES.
1053 Skip some dotfiles unless `ivy-text' requires them."
1054 (let ((res (ivy--re-filter regexp candidates)))
1055 (if (or (null ivy-use-ignore)
1056 (null counsel-find-file-ignore-regexp)
1057 (string-match "\\`\\." ivy-text))
1058 res
1059 (or (cl-remove-if
1060 (lambda (x)
1061 (and
1062 (string-match counsel-find-file-ignore-regexp x)
1063 (not (member x ivy-extra-directories))))
1064 res)
1065 res))))
1066
1067 (declare-function ffap-guesser "ffap")
1068
1069 ;;;###autoload
1070 (defun counsel-find-file (&optional initial-input)
1071 "Forward to `find-file'.
1072 When INITIAL-INPUT is non-nil, use it in the minibuffer during completion."
1073 (interactive)
1074 (ivy-read "Find file: " 'read-file-name-internal
1075 :matcher #'counsel--find-file-matcher
1076 :initial-input initial-input
1077 :action
1078 (lambda (x)
1079 (with-ivy-window
1080 (find-file (expand-file-name x ivy--directory))))
1081 :preselect (when counsel-find-file-at-point
1082 (require 'ffap)
1083 (ffap-guesser))
1084 :require-match 'confirm-after-completion
1085 :history 'file-name-history
1086 :keymap counsel-find-file-map))
1087
1088 (defun counsel-up-directory ()
1089 "Go to the parent directory preselecting the current one."
1090 (interactive)
1091 (let ((dir-file-name
1092 (directory-file-name (expand-file-name ivy--directory))))
1093 (ivy--cd (file-name-directory dir-file-name))
1094 (setf (ivy-state-preselect ivy-last)
1095 (file-name-as-directory (file-name-nondirectory dir-file-name)))))
1096
1097 (defun counsel-at-git-issue-p ()
1098 "Whe point is at an issue in a Git-versioned file, return the issue string."
1099 (and (looking-at "#[0-9]+")
1100 (or
1101 (eq (vc-backend (buffer-file-name)) 'Git)
1102 (memq major-mode '(magit-commit-mode)))
1103 (match-string-no-properties 0)))
1104
1105 (defun counsel-github-url-p ()
1106 "Return a Github issue URL at point."
1107 (let ((url (counsel-at-git-issue-p)))
1108 (when url
1109 (let ((origin (shell-command-to-string
1110 "git remote get-url origin"))
1111 user repo)
1112 (cond ((string-match "\\`git@github.com:\\([^/]+\\)/\\(.*\\)\\.git$"
1113 origin)
1114 (setq user (match-string 1 origin))
1115 (setq repo (match-string 2 origin)))
1116 ((string-match "\\`https://github.com/\\([^/]+\\)/\\(.*\\)$"
1117 origin)
1118 (setq user (match-string 1 origin))
1119 (setq repo (match-string 2 origin))))
1120 (when user
1121 (setq url (format "https://github.com/%s/%s/issues/%s"
1122 user repo (substring url 1))))))))
1123
1124 (defun counsel-emacs-url-p ()
1125 "Return a Debbugs issue URL at point."
1126 (let ((url (counsel-at-git-issue-p)))
1127 (when url
1128 (let ((origin (shell-command-to-string
1129 "git remote get-url origin")))
1130 (when (string-match "git.sv.gnu.org:/srv/git/emacs.git" origin)
1131 (format "http://debbugs.gnu.org/cgi/bugreport.cgi?bug=%s"
1132 (substring url 1)))))))
1133
1134 ;;** `counsel-locate'
1135 (defcustom counsel-locate-options nil
1136 "Command line options for `locate`."
1137 :group 'ivy
1138 :type '(repeat string))
1139
1140 (make-obsolete-variable 'counsel-locate-options 'counsel-locate-cmd "0.7.0")
1141
1142 (defcustom counsel-locate-cmd (if (eq system-type 'darwin)
1143 'counsel-locate-cmd-noregex
1144 'counsel-locate-cmd-default)
1145 "The function for producing a locate command string from the input.
1146
1147 The function takes a string - the current input, and returns a
1148 string - the full shell command to run."
1149 :group 'ivy
1150 :type '(choice
1151 (const :tag "Default" counsel-locate-cmd-default)
1152 (const :tag "No regex" counsel-locate-cmd-noregex)
1153 (const :tag "mdfind" counsel-locate-cmd-mdfind)))
1154
1155 (ivy-set-actions
1156 'counsel-locate
1157 '(("x" counsel-locate-action-extern "xdg-open")
1158 ("d" counsel-locate-action-dired "dired")))
1159
1160 (counsel-set-async-exit-code 'counsel-locate 1 "Nothing found")
1161
1162 (defvar counsel-locate-history nil
1163 "History for `counsel-locate'.")
1164
1165 (defun counsel-locate-action-extern (x)
1166 "Use xdg-open shell command on X."
1167 (call-process shell-file-name nil
1168 nil nil
1169 shell-command-switch
1170 (format "%s %s"
1171 (if (eq system-type 'darwin)
1172 "open"
1173 "xdg-open")
1174 (shell-quote-argument x))))
1175
1176 (declare-function dired-jump "dired-x")
1177
1178 (defun counsel-locate-action-dired (x)
1179 "Use `dired-jump' on X."
1180 (dired-jump nil x))
1181
1182 (defun counsel-locate-cmd-default (input)
1183 "Return a shell command based on INPUT."
1184 (format "locate -i --regex '%s'"
1185 (counsel-unquote-regex-parens
1186 (ivy--regex input))))
1187
1188 (defun counsel-locate-cmd-noregex (input)
1189 "Return a shell command based on INPUT."
1190 (format "locate -i '%s'" input))
1191
1192 (defun counsel-locate-cmd-mdfind (input)
1193 "Return a shell command based on INPUT."
1194 (format "mdfind -name '%s'" input))
1195
1196 (defun counsel-locate-function (input)
1197 (if (< (length input) 3)
1198 (counsel-more-chars 3)
1199 (counsel--async-command
1200 (funcall counsel-locate-cmd input))
1201 '("" "working...")))
1202
1203 ;;;###autoload
1204 (defun counsel-locate (&optional initial-input)
1205 "Call the \"locate\" shell command.
1206 INITIAL-INPUT can be given as the initial minibuffer input."
1207 (interactive)
1208 (ivy-read "Locate: " #'counsel-locate-function
1209 :initial-input initial-input
1210 :dynamic-collection t
1211 :history 'counsel-locate-history
1212 :action (lambda (file)
1213 (with-ivy-window
1214 (when file
1215 (find-file file))))
1216 :unwind #'counsel-delete-process
1217 :caller 'counsel-locate))
1218
1219 ;;* Grep
1220 ;;** `counsel-ag'
1221 (defcustom counsel-ag-base-command "ag --vimgrep %s"
1222 "Format string to use in `cousel-ag-function' to construct the
1223 command. %S will be replaced by the regex string. The default is
1224 \"ag --vimgrep %S\"."
1225 :type 'string
1226 :group 'ivy)
1227
1228 (counsel-set-async-exit-code 'counsel-ag 1 "No matches found")
1229
1230 (defun counsel-ag-function (string)
1231 "Grep in the current directory for STRING."
1232 (if (< (length string) 3)
1233 (counsel-more-chars 3)
1234 (let ((default-directory counsel--git-grep-dir)
1235 (regex (counsel-unquote-regex-parens
1236 (setq ivy--old-re
1237 (ivy--regex string)))))
1238 (counsel--async-command
1239 (format counsel-ag-base-command (shell-quote-argument regex)))
1240 nil)))
1241
1242 ;;;###autoload
1243 (defun counsel-ag (&optional initial-input initial-directory)
1244 "Grep for a string in the current directory using ag.
1245 INITIAL-INPUT can be given as the initial minibuffer input."
1246 (interactive)
1247 (setq counsel--git-grep-dir (or initial-directory default-directory))
1248 (ivy-read "ag: " 'counsel-ag-function
1249 :initial-input initial-input
1250 :dynamic-collection t
1251 :history 'counsel-git-grep-history
1252 :action #'counsel-git-grep-action
1253 :unwind (lambda ()
1254 (counsel-delete-process)
1255 (swiper--cleanup))
1256 :caller 'counsel-ag))
1257
1258 ;;** `counsel-grep'
1259 (defun counsel-grep-function (string)
1260 "Grep in the current directory for STRING."
1261 (if (< (length string) 3)
1262 (counsel-more-chars 3)
1263 (let ((regex (counsel-unquote-regex-parens
1264 (setq ivy--old-re
1265 (ivy--regex string)))))
1266 (counsel--async-command
1267 (format "grep -nP --ignore-case '%s' %s" regex counsel--git-grep-dir))
1268 nil)))
1269
1270 (defun counsel-grep-action (x)
1271 (when (string-match "\\`\\([0-9]+\\):\\(.*\\)\\'" x)
1272 (with-ivy-window
1273 (let ((file-name counsel--git-grep-dir)
1274 (line-number (match-string-no-properties 1 x)))
1275 (find-file file-name)
1276 (goto-char (point-min))
1277 (forward-line (1- (string-to-number line-number)))
1278 (re-search-forward (ivy--regex ivy-text t) (line-end-position) t)
1279 (unless (eq ivy-exit 'done)
1280 (swiper--cleanup)
1281 (swiper--add-overlays (ivy--regex ivy-text)))))))
1282
1283 ;;;###autoload
1284 (defun counsel-grep ()
1285 "Grep for a string in the current file."
1286 (interactive)
1287 (setq counsel--git-grep-dir (buffer-file-name))
1288 (ivy-read "grep: " 'counsel-grep-function
1289 :dynamic-collection t
1290 :preselect (format "%d:%s"
1291 (line-number-at-pos)
1292 (buffer-substring-no-properties
1293 (line-beginning-position)
1294 (line-end-position)))
1295 :history 'counsel-git-grep-history
1296 :update-fn (lambda ()
1297 (counsel-grep-action ivy--current))
1298 :action #'counsel-grep-action
1299 :unwind (lambda ()
1300 (counsel-delete-process)
1301 (swiper--cleanup))
1302 :caller 'counsel-grep))
1303 ;;** `counsel-recoll'
1304 (defun counsel-recoll-function (string)
1305 "Grep in the current directory for STRING."
1306 (if (< (length string) 3)
1307 (counsel-more-chars 3)
1308 (counsel--async-command
1309 (format "recoll -t -b '%s'" string))
1310 nil))
1311
1312 ;; This command uses the recollq command line tool that comes together
1313 ;; with the recoll (the document indexing database) source:
1314 ;; http://www.lesbonscomptes.com/recoll/download.html
1315 ;; You need to build it yourself (together with recoll):
1316 ;; cd ./query && make && sudo cp recollq /usr/local/bin
1317 ;; You can try the GUI version of recoll with:
1318 ;; sudo apt-get install recoll
1319 ;; Unfortunately, that does not install recollq.
1320 (defun counsel-recoll (&optional initial-input)
1321 "Search for a string in the recoll database.
1322 You'll be given a list of files that match.
1323 Selecting a file will launch `swiper' for that file.
1324 INITIAL-INPUT can be given as the initial minibuffer input."
1325 (interactive)
1326 (ivy-read "recoll: " 'counsel-recoll-function
1327 :initial-input initial-input
1328 :dynamic-collection t
1329 :history 'counsel-git-grep-history
1330 :action (lambda (x)
1331 (when (string-match "file://\\(.*\\)\\'" x)
1332 (let ((file-name (match-string 1 x)))
1333 (find-file file-name)
1334 (unless (string-match "pdf$" x)
1335 (swiper ivy-text)))))
1336 :unwind #'counsel-delete-process
1337 :caller 'counsel-recoll))
1338 ;;* Misc Emacs
1339 ;;** `counsel-org-tag'
1340 (defvar counsel-org-tags nil
1341 "Store the current list of tags.")
1342
1343 (defvar org-outline-regexp)
1344 (defvar org-indent-mode)
1345 (defvar org-indent-indentation-per-level)
1346 (defvar org-tags-column)
1347 (declare-function org-get-tags-string "org")
1348 (declare-function org-move-to-column "org-compat")
1349
1350 (defun counsel-org-change-tags (tags)
1351 (let ((current (org-get-tags-string))
1352 (col (current-column))
1353 level)
1354 ;; Insert new tags at the correct column
1355 (beginning-of-line 1)
1356 (setq level (or (and (looking-at org-outline-regexp)
1357 (- (match-end 0) (point) 1))
1358 1))
1359 (cond
1360 ((and (equal current "") (equal tags "")))
1361 ((re-search-forward
1362 (concat "\\([ \t]*" (regexp-quote current) "\\)[ \t]*$")
1363 (point-at-eol) t)
1364 (if (equal tags "")
1365 (delete-region
1366 (match-beginning 0)
1367 (match-end 0))
1368 (goto-char (match-beginning 0))
1369 (let* ((c0 (current-column))
1370 ;; compute offset for the case of org-indent-mode active
1371 (di (if (bound-and-true-p org-indent-mode)
1372 (* (1- org-indent-indentation-per-level) (1- level))
1373 0))
1374 (p0 (if (equal (char-before) ?*) (1+ (point)) (point)))
1375 (tc (+ org-tags-column (if (> org-tags-column 0) (- di) di)))
1376 (c1 (max (1+ c0) (if (> tc 0) tc (- (- tc) (string-width tags)))))
1377 (rpl (concat (make-string (max 0 (- c1 c0)) ?\ ) tags)))
1378 (replace-match rpl t t)
1379 (and c0 indent-tabs-mode (tabify p0 (point)))
1380 tags)))
1381 (t (error "Tags alignment failed")))
1382 (org-move-to-column col)))
1383
1384 (defun counsel-org--set-tags ()
1385 (counsel-org-change-tags
1386 (if counsel-org-tags
1387 (format ":%s:"
1388 (mapconcat #'identity counsel-org-tags ":"))
1389 "")))
1390
1391 (defvar org-agenda-bulk-marked-entries)
1392
1393 (declare-function org-get-at-bol "org")
1394 (declare-function org-agenda-error "org-agenda")
1395
1396 (defun counsel-org-tag-action (x)
1397 (if (member x counsel-org-tags)
1398 (progn
1399 (setq counsel-org-tags (delete x counsel-org-tags)))
1400 (unless (equal x "")
1401 (setq counsel-org-tags (append counsel-org-tags (list x)))
1402 (unless (member x ivy--all-candidates)
1403 (setq ivy--all-candidates (append ivy--all-candidates (list x))))))
1404 (let ((prompt (counsel-org-tag-prompt)))
1405 (setf (ivy-state-prompt ivy-last) prompt)
1406 (setq ivy--prompt (concat "%-4d " prompt)))
1407 (cond ((memq this-command '(ivy-done
1408 ivy-alt-done
1409 ivy-immediate-done))
1410 (if (eq major-mode 'org-agenda-mode)
1411 (if (null org-agenda-bulk-marked-entries)
1412 (let ((hdmarker (or (org-get-at-bol 'org-hd-marker)
1413 (org-agenda-error))))
1414 (with-current-buffer (marker-buffer hdmarker)
1415 (goto-char hdmarker)
1416 (counsel-org--set-tags)))
1417 (let ((add-tags (copy-sequence counsel-org-tags)))
1418 (dolist (m org-agenda-bulk-marked-entries)
1419 (with-current-buffer (marker-buffer m)
1420 (save-excursion
1421 (goto-char m)
1422 (setq counsel-org-tags
1423 (delete-dups
1424 (append (split-string (org-get-tags-string) ":" t)
1425 add-tags)))
1426 (counsel-org--set-tags))))))
1427 (counsel-org--set-tags)))
1428 ((eq this-command 'ivy-call)
1429 (delete-minibuffer-contents))))
1430
1431 (defun counsel-org-tag-prompt ()
1432 (format "Tags (%s): "
1433 (mapconcat #'identity counsel-org-tags ", ")))
1434
1435 (defvar org-setting-tags)
1436 (defvar org-last-tags-completion-table)
1437 (defvar org-tag-persistent-alist)
1438 (defvar org-tag-alist)
1439 (defvar org-complete-tags-always-offer-all-agenda-tags)
1440
1441 (declare-function org-at-heading-p "org")
1442 (declare-function org-back-to-heading "org")
1443 (declare-function org-get-buffer-tags "org")
1444 (declare-function org-global-tags-completion-table "org")
1445 (declare-function org-agenda-files "org")
1446 (declare-function org-agenda-set-tags "org-agenda")
1447
1448 ;;;###autoload
1449 (defun counsel-org-tag ()
1450 "Add or remove tags in org-mode."
1451 (interactive)
1452 (save-excursion
1453 (if (eq major-mode 'org-agenda-mode)
1454 (if org-agenda-bulk-marked-entries
1455 (setq counsel-org-tags nil)
1456 (let ((hdmarker (or (org-get-at-bol 'org-hd-marker)
1457 (org-agenda-error))))
1458 (with-current-buffer (marker-buffer hdmarker)
1459 (goto-char hdmarker)
1460 (setq counsel-org-tags
1461 (split-string (org-get-tags-string) ":" t)))))
1462 (unless (org-at-heading-p)
1463 (org-back-to-heading t))
1464 (setq counsel-org-tags (split-string (org-get-tags-string) ":" t)))
1465 (let ((org-setting-tags t)
1466 (org-last-tags-completion-table
1467 (append org-tag-persistent-alist
1468 (or org-tag-alist (org-get-buffer-tags))
1469 (and
1470 (or org-complete-tags-always-offer-all-agenda-tags
1471 (eq major-mode 'org-agenda-mode))
1472 (org-global-tags-completion-table
1473 (org-agenda-files))))))
1474 (ivy-read (counsel-org-tag-prompt)
1475 (lambda (str &rest _unused)
1476 (delete-dups
1477 (all-completions str 'org-tags-completion-function)))
1478 :history 'org-tags-history
1479 :action 'counsel-org-tag-action
1480 :caller 'counsel-org-tag))))
1481
1482 ;;;###autoload
1483 (defun counsel-org-tag-agenda ()
1484 "Set tags for the current agenda item."
1485 (interactive)
1486 (let ((store (symbol-function 'org-set-tags)))
1487 (unwind-protect
1488 (progn
1489 (fset 'org-set-tags
1490 (symbol-function 'counsel-org-tag))
1491 (org-agenda-set-tags nil nil))
1492 (fset 'org-set-tags store))))
1493
1494 ;;** `counsel-tmm'
1495 (defvar tmm-km-list nil)
1496 (declare-function tmm-get-keymap "tmm")
1497 (declare-function tmm--completion-table "tmm")
1498 (declare-function tmm-get-keybind "tmm")
1499
1500 (defun counsel-tmm-prompt (menu)
1501 "Select and call an item from the MENU keymap."
1502 (let (out
1503 choice
1504 chosen-string)
1505 (setq tmm-km-list nil)
1506 (map-keymap (lambda (k v) (tmm-get-keymap (cons k v))) menu)
1507 (setq tmm-km-list (nreverse tmm-km-list))
1508 (setq out (ivy-read "Menu bar: " (tmm--completion-table tmm-km-list)
1509 :require-match t
1510 :sort nil))
1511 (setq choice (cdr (assoc out tmm-km-list)))
1512 (setq chosen-string (car choice))
1513 (setq choice (cdr choice))
1514 (cond ((keymapp choice)
1515 (counsel-tmm-prompt choice))
1516 ((and choice chosen-string)
1517 (setq last-command-event chosen-string)
1518 (call-interactively choice)))))
1519
1520 (defvar tmm-table-undef)
1521
1522 ;;;###autoload
1523 (defun counsel-tmm ()
1524 "Text-mode emulation of looking and choosing from a menubar."
1525 (interactive)
1526 (require 'tmm)
1527 (run-hooks 'menu-bar-update-hook)
1528 (setq tmm-table-undef nil)
1529 (counsel-tmm-prompt (tmm-get-keybind [menu-bar])))
1530
1531 ;;** `counsel-yank-pop'
1532 (defun counsel--yank-pop-truncate (str)
1533 (condition-case nil
1534 (let* ((lines (split-string str "\n" t))
1535 (n (length lines))
1536 (first-match (cl-position-if
1537 (lambda (s) (string-match ivy--old-re s))
1538 lines))
1539 (beg (max 0 (- first-match
1540 counsel-yank-pop-truncate-radius)))
1541 (end (min n (+ first-match
1542 counsel-yank-pop-truncate-radius
1543 1)))
1544 (seq (cl-subseq lines beg end)))
1545 (if (null first-match)
1546 (error "Could not match %s" str)
1547 (when (> beg 0)
1548 (setcar seq (concat "[...] " (car seq))))
1549 (when (< end n)
1550 (setcar (last seq)
1551 (concat (car (last seq)) " [...]")))
1552 (mapconcat #'identity seq "\n")))
1553 (error str)))
1554
1555 (defun counsel--yank-pop-format-function (cand-pairs)
1556 (ivy--format-function-generic
1557 (lambda (str)
1558 (mapconcat
1559 (lambda (s)
1560 (ivy--add-face s 'ivy-current-match))
1561 (split-string
1562 (counsel--yank-pop-truncate str) "\n" t)
1563 "\n"))
1564 (lambda (str)
1565 (counsel--yank-pop-truncate str))
1566 cand-pairs
1567 "\n"))
1568
1569 (defun counsel-yank-pop-action (s)
1570 "Insert S into the buffer, overwriting the previous yank."
1571 (with-ivy-window
1572 (delete-region ivy-completion-beg
1573 ivy-completion-end)
1574 (insert (substring-no-properties s))
1575 (setq ivy-completion-end (point))))
1576
1577 ;;;###autoload
1578 (defun counsel-yank-pop ()
1579 "Ivy replacement for `yank-pop'."
1580 (interactive)
1581 (if (eq last-command 'yank)
1582 (progn
1583 (setq ivy-completion-end (point))
1584 (setq ivy-completion-beg
1585 (save-excursion
1586 (search-backward (car kill-ring))
1587 (point))))
1588 (setq ivy-completion-beg (point))
1589 (setq ivy-completion-end (point)))
1590 (let ((candidates (cl-remove-if
1591 (lambda (s)
1592 (or (< (length s) 3)
1593 (string-match "\\`[\n[:blank:]]+\\'" s)))
1594 (delete-dups kill-ring))))
1595 (let ((ivy-format-function #'counsel--yank-pop-format-function)
1596 (ivy-height 5))
1597 (ivy-read "kill-ring: " candidates
1598 :action 'counsel-yank-pop-action
1599 :caller 'counsel-yank-pop))))
1600
1601 ;;** `counsel-imenu'
1602 (defvar imenu-auto-rescan)
1603 (declare-function imenu--subalist-p "imenu")
1604 (declare-function imenu--make-index-alist "imenu")
1605
1606 (defun counsel-imenu-get-candidates-from (alist &optional prefix)
1607 "Create a list of (key . value) from ALIST.
1608 PREFIX is used to create the key."
1609 (cl-mapcan (lambda (elm)
1610 (if (imenu--subalist-p elm)
1611 (counsel-imenu-get-candidates-from
1612 (cl-loop for (e . v) in (cdr elm) collect
1613 (cons e (if (integerp v) (copy-marker v) v)))
1614 ;; pass the prefix to next recursive call
1615 (concat prefix (if prefix ".") (car elm)))
1616 (let ((key (concat prefix (if prefix ".") (car elm))))
1617 (list (cons key
1618 ;; create a imenu candidate here
1619 (cons key (if (overlayp (cdr elm))
1620 (overlay-start (cdr elm))
1621 (cdr elm))))))))
1622 alist))
1623
1624 ;;;###autoload
1625 (defun counsel-imenu ()
1626 "Jump to a buffer position indexed by imenu."
1627 (interactive)
1628 (unless (featurep 'imenu)
1629 (require 'imenu nil t))
1630 (let* ((imenu-auto-rescan t)
1631 (items (imenu--make-index-alist t))
1632 (items (delete (assoc "*Rescan*" items) items)))
1633 (ivy-read "imenu items:" (counsel-imenu-get-candidates-from items)
1634 :preselect (thing-at-point 'symbol)
1635 :require-match t
1636 :action (lambda (candidate)
1637 (with-ivy-window
1638 ;; In org-mode, (imenu candidate) will expand child node
1639 ;; after jump to the candidate position
1640 (imenu candidate)))
1641 :caller 'counsel-imenu)))
1642
1643 ;;** `counsel-list-processes'
1644 (defun counsel-list-processes-action-delete (x)
1645 (delete-process x)
1646 (setf (ivy-state-collection ivy-last)
1647 (setq ivy--all-candidates
1648 (delete x ivy--all-candidates))))
1649
1650 (defun counsel-list-processes-action-switch (x)
1651 (let* ((proc (get-process x))
1652 (buf (and proc (process-buffer proc))))
1653 (if buf
1654 (switch-to-buffer x)
1655 (message "Process %s doesn't have a buffer" x))))
1656
1657 ;;;###autoload
1658 (defun counsel-list-processes ()
1659 "Offer completion for `process-list'
1660 The default action deletes the selected process.
1661 An extra action allows to switch to the process buffer."
1662 (interactive)
1663 (list-processes--refresh)
1664 (ivy-read "Process: " (mapcar #'process-name (process-list))
1665 :require-match t
1666 :action
1667 '(1
1668 ("o" counsel-list-processes-action-delete "kill")
1669 ("s" counsel-list-processes-action-switch "switch"))
1670 :caller 'counsel-list-processes))
1671
1672 ;;* Misc OS
1673 ;;** `counsel-rhythmbox'
1674 (defvar rhythmbox-library)
1675 (declare-function rhythmbox-load-library "ext:helm-rhythmbox")
1676 (declare-function dbus-call-method "dbus")
1677 (declare-function dbus-get-property "dbus")
1678 (declare-function rhythmbox-song-uri "ext:helm-rhythmbox")
1679 (declare-function helm-rhythmbox-candidates "ext:helm-rhythmbox")
1680
1681 (defun counsel-rhythmbox-enqueue-song (song)
1682 "Let Rhythmbox enqueue SONG."
1683 (let ((service "org.gnome.Rhythmbox3")
1684 (path "/org/gnome/Rhythmbox3/PlayQueue")
1685 (interface "org.gnome.Rhythmbox3.PlayQueue"))
1686 (dbus-call-method :session service path interface
1687 "AddToQueue" (rhythmbox-song-uri song))))
1688
1689 (defvar counsel-rhythmbox-history nil
1690 "History for `counsel-rhythmbox'.")
1691
1692 (defun counsel-rhythmbox-current-song ()
1693 "Return the currently playing song title."
1694 (ignore-errors
1695 (let* ((entry (dbus-get-property
1696 :session
1697 "org.mpris.MediaPlayer2.rhythmbox"
1698 "/org/mpris/MediaPlayer2"
1699 "org.mpris.MediaPlayer2.Player"
1700 "Metadata"))
1701 (artist (caar (cadr (assoc "xesam:artist" entry))))
1702 (album (cl-caadr (assoc "xesam:album" entry)))
1703 (title (cl-caadr (assoc "xesam:title" entry))))
1704 (format "%s - %s - %s" artist album title))))
1705
1706 ;;;###autoload
1707 (defun counsel-rhythmbox ()
1708 "Choose a song from the Rhythmbox library to play or enqueue."
1709 (interactive)
1710 (unless (require 'helm-rhythmbox nil t)
1711 (error "Please install `helm-rhythmbox'"))
1712 (unless rhythmbox-library
1713 (rhythmbox-load-library)
1714 (while (null rhythmbox-library)
1715 (sit-for 0.1)))
1716 (ivy-read "Rhythmbox: "
1717 (helm-rhythmbox-candidates)
1718 :history 'counsel-rhythmbox-history
1719 :preselect (counsel-rhythmbox-current-song)
1720 :action
1721 '(1
1722 ("p" helm-rhythmbox-play-song "Play song")
1723 ("e" counsel-rhythmbox-enqueue-song "Enqueue song"))
1724 :caller 'counsel-rhythmbox))
1725
1726 ;;** `counsel-mode'
1727 (defvar counsel-mode-map
1728 (let ((map (make-sparse-keymap)))
1729 (dolist (binding
1730 '((execute-extended-command . counsel-M-x)
1731 (describe-bindings . counsel-descbinds)
1732 (describe-function . counsel-describe-function)
1733 (describe-variable . counsel-describe-variable)
1734 (find-file . counsel-find-file)
1735 (imenu . counsel-imenu)
1736 (load-library . counsel-load-library)
1737 (load-theme . counsel-load-theme)
1738 (yank-pop . counsel-yank-pop)))
1739 (define-key map (vector 'remap (car binding)) (cdr binding)))
1740 map)
1741 "Map for `counsel-mode'. Remaps built-in functions to counsel
1742 replacements.")
1743
1744 ;;;###autoload
1745 (define-minor-mode counsel-mode
1746 "Toggle Counsel mode on or off.
1747 Turn Counsel mode on if ARG is positive, off otherwise. Counsel
1748 mode remaps built-in emacs functions that have counsel
1749 replacements. "
1750 :group 'ivy
1751 :global t
1752 :keymap counsel-mode-map
1753 :lighter " counsel")
1754
1755 (provide 'counsel)
1756
1757 ;;; counsel.el ends here