]> code.delx.au - gnu-emacs-elpa/blob - counsel.el
counsel-locate should use '' for the regex
[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 ;; Version: 0.1.0
8 ;; Package-Requires: ((emacs "24.1") (swiper "0.4.0"))
9 ;; Keywords: completion, matching
10
11 ;; This file is part of GNU Emacs.
12
13 ;; This file is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 3, or (at your option)
16 ;; any later version.
17
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22
23 ;; For a full copy of the GNU General Public License
24 ;; see <http://www.gnu.org/licenses/>.
25
26 ;;; Commentary:
27 ;;
28 ;; Just call one of the interactive functions in this file to complete
29 ;; the corresponding thing using `ivy'.
30 ;;
31 ;; Currently available: Elisp symbols, Clojure symbols, Git files.
32
33 ;;; Code:
34
35 (require 'swiper)
36
37 (defvar counsel-completion-beg nil
38 "Completion bounds start.")
39
40 (defvar counsel-completion-end nil
41 "Completion bounds end.")
42
43 ;;;###autoload
44 (defun counsel-el ()
45 "Elisp completion at point."
46 (interactive)
47 (let* ((bnd (unless (and (looking-at ")")
48 (eq (char-before) ?\())
49 (bounds-of-thing-at-point
50 'symbol)))
51 (str (if bnd
52 (buffer-substring-no-properties
53 (car bnd)
54 (cdr bnd))
55 ""))
56 (ivy-height 7)
57 (funp (eq (char-before (car bnd)) ?\())
58 symbol-names)
59 (if bnd
60 (progn
61 (setq counsel-completion-beg
62 (move-marker (make-marker) (car bnd)))
63 (setq counsel-completion-end
64 (move-marker (make-marker) (cdr bnd))))
65 (setq counsel-completion-beg nil)
66 (setq counsel-completion-end nil))
67 (if (string= str "")
68 (mapatoms
69 (lambda (x)
70 (when (symbolp x)
71 (push (symbol-name x) symbol-names))))
72 (setq symbol-names
73 (all-completions str obarray
74 (and funp
75 (lambda (x)
76 (or (functionp x)
77 (macrop x)
78 (special-form-p x)))))))
79 (ivy-read "Symbol name: " symbol-names
80 :predicate (and funp #'functionp)
81 :initial-input str
82 :action #'counsel--el-action)))
83
84 (defun counsel--el-action (symbol)
85 "Insert SYMBOL, erasing the previous one."
86 (when (stringp symbol)
87 (when counsel-completion-beg
88 (delete-region
89 counsel-completion-beg
90 counsel-completion-end))
91 (setq counsel-completion-beg
92 (move-marker (make-marker) (point)))
93 (insert symbol)
94 (setq counsel-completion-end
95 (move-marker (make-marker) (point)))))
96
97 (defvar counsel-describe-map
98 (let ((map (make-sparse-keymap)))
99 (define-key map (kbd "C-.") #'counsel-find-symbol)
100 (define-key map (kbd "C-,") #'counsel--info-lookup-symbol)
101 map))
102
103 (defun counsel-find-symbol ()
104 "Jump to the definition of the current symbol."
105 (interactive)
106 (ivy-set-action #'counsel--find-symbol)
107 (ivy-done))
108
109 (defun counsel--info-lookup-symbol ()
110 "Lookup the current symbol in the info docs."
111 (interactive)
112 (ivy-set-action #'counsel-info-lookup-symbol)
113 (ivy-done))
114
115 (defun counsel--find-symbol (x)
116 "Find symbol definition that corresponds to string X."
117 (let ((full-name (get-text-property 0 'full-name x)))
118 (if full-name
119 (find-library full-name)
120 (let ((sym (read x)))
121 (cond ((boundp sym)
122 (find-variable sym))
123 ((fboundp sym)
124 (find-function sym))
125 ((or (featurep sym)
126 (locate-library
127 (prin1-to-string sym)))
128 (find-library
129 (prin1-to-string sym)))
130 (t
131 (error "Couldn't fild definition of %s"
132 sym)))))))
133
134 (defvar counsel-describe-symbol-history nil
135 "History for `counsel-describe-variable' and `counsel-describe-function'.")
136
137 (defun counsel-symbol-at-point ()
138 "Return current symbol at point as a string."
139 (let ((s (thing-at-point 'symbol)))
140 (and (stringp s)
141 (if (string-match "\\`[`']?\\(.*\\)'?\\'" s)
142 (match-string 1 s)
143 s))))
144
145 (defun counsel-variable-list ()
146 "Return the list of all currently bound variables."
147 (let (cands)
148 (mapatoms
149 (lambda (vv)
150 (when (or (get vv 'variable-documentation)
151 (and (boundp vv) (not (keywordp vv))))
152 (push (symbol-name vv) cands))))
153 cands))
154
155 ;;;###autoload
156 (defun counsel-describe-variable ()
157 "Forward to `describe-variable'."
158 (interactive)
159 (let ((enable-recursive-minibuffers t))
160 (ivy-read
161 "Describe variable: "
162 (counsel-variable-list)
163 :keymap counsel-describe-map
164 :preselect (counsel-symbol-at-point)
165 :history 'counsel-describe-symbol-history
166 :require-match t
167 :sort t
168 :action (lambda (x)
169 (describe-variable
170 (intern x))))))
171
172 (ivy-set-actions
173 'counsel-describe-variable
174 '(("i" counsel-info-lookup-symbol "info")
175 ("d" counsel--find-symbol "definition")))
176
177 (ivy-set-actions
178 'counsel-describe-function
179 '(("i" counsel-info-lookup-symbol "info")
180 ("d" counsel--find-symbol "definition")))
181
182 ;;;###autoload
183 (defun counsel-describe-function ()
184 "Forward to `describe-function'."
185 (interactive)
186 (let ((enable-recursive-minibuffers t))
187 (ivy-read "Describe function: "
188 (let (cands)
189 (mapatoms
190 (lambda (x)
191 (when (fboundp x)
192 (push (symbol-name x) cands))))
193 cands)
194 :keymap counsel-describe-map
195 :preselect (counsel-symbol-at-point)
196 :history 'counsel-describe-symbol-history
197 :require-match t
198 :sort t
199 :action (lambda (x)
200 (describe-function
201 (intern x))))))
202
203 (defvar info-lookup-mode)
204 (declare-function info-lookup->completions "info-look")
205 (declare-function info-lookup->mode-value "info-look")
206 (declare-function info-lookup-select-mode "info-look")
207 (declare-function info-lookup-change-mode "info-look")
208 (declare-function info-lookup "info-look")
209
210 ;;;###autoload
211 (defun counsel-info-lookup-symbol (symbol &optional mode)
212 "Forward to (`info-describe-symbol' SYMBOL MODE) with ivy completion."
213 (interactive
214 (progn
215 (require 'info-look)
216 (let* ((topic 'symbol)
217 (mode (cond (current-prefix-arg
218 (info-lookup-change-mode topic))
219 ((info-lookup->mode-value
220 topic (info-lookup-select-mode))
221 info-lookup-mode)
222 ((info-lookup-change-mode topic))))
223 (completions (info-lookup->completions topic mode))
224 (enable-recursive-minibuffers t)
225 (value (ivy-read
226 "Describe symbol: "
227 (mapcar #'car completions)
228 :sort t)))
229 (list value info-lookup-mode))))
230 (require 'info-look)
231 (info-lookup 'symbol symbol mode))
232
233 ;;;###autoload
234 (defun counsel-unicode-char ()
235 "Insert a Unicode character at point."
236 (interactive)
237 (let ((minibuffer-allow-text-properties t))
238 (ivy-read "Unicode name: "
239 (mapcar (lambda (x)
240 (propertize
241 (format "% -60s%c" (car x) (cdr x))
242 'result (cdr x)))
243 (ucs-names))
244 :action (lambda (char)
245 (insert-char (get-text-property 0 'result char))))))
246
247 (declare-function cider-sync-request:complete "ext:cider-client")
248 ;;;###autoload
249 (defun counsel-clj ()
250 "Clojure completion at point."
251 (interactive)
252 (counsel--generic
253 (lambda (str)
254 (mapcar
255 #'cl-caddr
256 (cider-sync-request:complete str ":same")))))
257
258 ;;;###autoload
259 (defun counsel-git ()
260 "Find file in the current Git repository."
261 (interactive)
262 (let* ((default-directory (locate-dominating-file
263 default-directory ".git"))
264 (cands (split-string
265 (shell-command-to-string
266 "git ls-files --full-name --")
267 "\n"
268 t))
269 (action (lambda (x) (find-file x))))
270 (ivy-read "Find file: " cands
271 :action action)))
272
273 (defvar counsel--git-grep-dir nil
274 "Store the base git directory.")
275
276 (defvar counsel--git-grep-count nil
277 "Store the line count in current repository.")
278
279 (defun counsel-more-chars (n)
280 "Return two fake candidates prompting for at least N input."
281 (list ""
282 (format "%d chars more" (- n (length ivy-text)))))
283
284 (defun counsel-git-grep-function (string &optional _pred &rest _unused)
285 "Grep in the current git repository for STRING."
286 (if (and (> counsel--git-grep-count 20000)
287 (< (length string) 3))
288 (counsel-more-chars 3)
289 (let* ((default-directory counsel--git-grep-dir)
290 (cmd (format "git --no-pager grep --full-name -n --no-color -i -e %S"
291 (ivy--regex string t))))
292 (if (<= counsel--git-grep-count 20000)
293 (split-string (shell-command-to-string cmd) "\n" t)
294 (counsel--gg-candidates (ivy--regex string))
295 nil))))
296
297 (defvar counsel-git-grep-map
298 (let ((map (make-sparse-keymap)))
299 (define-key map (kbd "C-l") 'counsel-git-grep-recenter)
300 map))
301
302 (defun counsel-git-grep-recenter ()
303 (interactive)
304 (with-ivy-window
305 (counsel-git-grep-action ivy--current)
306 (recenter-top-bottom)))
307
308 (defun counsel-git-grep-action (x)
309 (when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" x)
310 (with-ivy-window
311 (let ((file-name (match-string-no-properties 1 x))
312 (line-number (match-string-no-properties 2 x)))
313 (find-file (expand-file-name file-name counsel--git-grep-dir))
314 (goto-char (point-min))
315 (forward-line (1- (string-to-number line-number)))
316 (re-search-forward (ivy--regex ivy-text t) (line-end-position) t)
317 (unless (eq ivy-exit 'done)
318 (swiper--cleanup)
319 (swiper--add-overlays (ivy--regex ivy-text)))))))
320
321 (defvar counsel-git-grep-history nil
322 "History for `counsel-git-grep'.")
323
324 ;;;###autoload
325 (defun counsel-git-grep (&optional initial-input)
326 "Grep for a string in the current git repository.
327 INITIAL-INPUT can be given as the initial minibuffer input."
328 (interactive)
329 (setq counsel--git-grep-dir
330 (locate-dominating-file default-directory ".git"))
331 (if (null counsel--git-grep-dir)
332 (error "Not in a git repository")
333 (setq counsel--git-grep-count (counsel--gg-count "" t))
334 (ivy-read "git grep: " 'counsel-git-grep-function
335 :initial-input initial-input
336 :matcher #'counsel-git-grep-matcher
337 :dynamic-collection (> counsel--git-grep-count 20000)
338 :keymap counsel-git-grep-map
339 :action #'counsel-git-grep-action
340 :unwind #'swiper--cleanup
341 :history 'counsel-git-grep-history)))
342
343 (defcustom counsel-find-file-at-point nil
344 "When non-nil, add file-at-point to the list of candidates."
345 :type 'boolean
346 :group 'ivy)
347
348 (declare-function ffap-guesser "ffap")
349
350 (defvar counsel-find-file-map (make-sparse-keymap))
351
352 ;;;###autoload
353 (defun counsel-find-file ()
354 "Forward to `find-file'."
355 (interactive)
356 (ivy-read "Find file: " 'read-file-name-internal
357 :matcher #'counsel--find-file-matcher
358 :action
359 (lambda (x)
360 (with-ivy-window
361 (find-file (expand-file-name x ivy--directory))))
362 :preselect (when counsel-find-file-at-point
363 (require 'ffap)
364 (ffap-guesser))
365 :require-match 'confirm-after-completion
366 :history 'file-name-history
367 :keymap counsel-find-file-map))
368
369 (defcustom counsel-find-file-ignore-regexp nil
370 "A regexp of files to ignore while in `counsel-find-file'.
371 These files are un-ignored if `ivy-text' matches them.
372 The common way to show all files is to start `ivy-text' with a dot.
373 Possible value: \"\\(?:\\`[#.]\\)\\|\\(?:[#~]\\'\\)\"."
374 :group 'ivy)
375
376 (defun counsel--find-file-matcher (regexp candidates)
377 "Return REGEXP-matching CANDIDATES.
378 Skip some dotfiles unless `ivy-text' requires them."
379 (let ((res (cl-remove-if-not
380 (lambda (x)
381 (string-match regexp x))
382 candidates)))
383 (if (or (null counsel-find-file-ignore-regexp)
384 (string-match counsel-find-file-ignore-regexp ivy-text))
385 res
386 (cl-remove-if
387 (lambda (x)
388 (string-match counsel-find-file-ignore-regexp x))
389 res))))
390
391 (defun counsel-git-grep-matcher (regexp candidates)
392 (or (and (equal regexp ivy--old-re)
393 ivy--old-cands)
394 (prog1
395 (setq ivy--old-cands
396 (cl-remove-if-not
397 (lambda (x)
398 (ignore-errors
399 (when (string-match "^[^:]+:[^:]+:" x)
400 (setq x (substring x (match-end 0)))
401 (if (stringp regexp)
402 (string-match regexp x)
403 (let ((res t))
404 (dolist (re regexp)
405 (setq res
406 (and res
407 (ignore-errors
408 (if (cdr re)
409 (string-match (car re) x)
410 (not (string-match (car re) x)))))))
411 res)))))
412 candidates))
413 (setq ivy--old-re regexp))))
414
415 (defun counsel--async-command (cmd)
416 (let* ((counsel--process " *counsel*")
417 (proc (get-process counsel--process))
418 (buff (get-buffer counsel--process)))
419 (when proc
420 (delete-process proc))
421 (when buff
422 (kill-buffer buff))
423 (setq proc (start-process-shell-command
424 counsel--process
425 counsel--process
426 cmd))
427 (set-process-sentinel proc #'counsel--async-sentinel)))
428
429 (defun counsel--async-sentinel (process event)
430 (if (string= event "finished\n")
431 (progn
432 (with-current-buffer (process-buffer process)
433 (setq ivy--all-candidates (split-string (buffer-string) "\n" t))
434 (setq ivy--old-cands ivy--all-candidates))
435 (ivy--exhibit))
436 (if (string= event "exited abnormally with code 1\n")
437 (progn
438 (setq ivy--all-candidates '("Error"))
439 (setq ivy--old-cands ivy--all-candidates)
440 (ivy--exhibit)))))
441
442 (defun counsel-locate-action-extern (x)
443 "Use xdg-open shell command on X."
444 (call-process shell-file-name nil
445 nil nil
446 shell-command-switch
447 (format "%s %s"
448 (if (eq system-type 'darwin)
449 "open"
450 "xdg-open")
451 (shell-quote-argument x))))
452
453 (declare-function dired-jump "dired-x")
454 (defun counsel-locate-action-dired (x)
455 "Use `dired-jump' on X."
456 (dired-jump nil x))
457
458 (defvar counsel-locate-history nil
459 "History for `counsel-locate'.")
460
461 (defcustom counsel-locate-options (if (eq system-type 'darwin)
462 '("-i")
463 '("-i" "--regex"))
464 "Command line options for `locate`."
465 :group 'ivy
466 :type '(repeat string))
467
468 (ivy-set-actions
469 'counsel-locate
470 '(("x" counsel-locate-action-extern "xdg-open")
471 ("d" counsel-locate-action-dired "dired")))
472
473 (defun counsel-unquote-regex-parens (str)
474 (replace-regexp-in-string
475 "\\\\)" ")"
476 (replace-regexp-in-string
477 "\\\\(" "("
478 str)))
479
480 (defun counsel-locate-function (str &rest _u)
481 (if (< (length str) 3)
482 (counsel-more-chars 3)
483 (counsel--async-command
484 (format "locate %s '%s'"
485 (mapconcat #'identity counsel-locate-options " ")
486 (counsel-unquote-regex-parens
487 (ivy--regex str))))
488 '("" "working...")))
489
490 ;;;###autoload
491 (defun counsel-locate ()
492 "Call locate shell command."
493 (interactive)
494 (ivy-read "Locate: " #'counsel-locate-function
495 :dynamic-collection t
496 :history 'counsel-locate-history
497 :action (lambda (file)
498 (when file
499 (find-file file)))))
500
501 (defun counsel--generic (completion-fn)
502 "Complete thing at point with COMPLETION-FN."
503 (let* ((bnd (bounds-of-thing-at-point 'symbol))
504 (str (if bnd
505 (buffer-substring-no-properties
506 (car bnd) (cdr bnd))
507 ""))
508 (candidates (funcall completion-fn str))
509 (ivy-height 7)
510 (res (ivy-read (format "pattern (%s): " str)
511 candidates)))
512 (when (stringp res)
513 (when bnd
514 (delete-region (car bnd) (cdr bnd)))
515 (insert res))))
516
517 (defun counsel-directory-parent (dir)
518 "Return the directory parent of directory DIR."
519 (concat (file-name-nondirectory
520 (directory-file-name dir)) "/"))
521
522 (defun counsel-string-compose (prefix str)
523 "Make PREFIX the display prefix of STR though text properties."
524 (let ((str (copy-sequence str)))
525 (put-text-property
526 0 1 'display
527 (concat prefix (substring str 0 1))
528 str)
529 str))
530
531 ;;;###autoload
532 (defun counsel-load-library ()
533 "Load a selected the Emacs Lisp library.
534 The libraries are offered from `load-path'."
535 (interactive)
536 (let ((dirs load-path)
537 (suffix (concat (regexp-opt '(".el" ".el.gz") t) "\\'"))
538 (cands (make-hash-table :test #'equal))
539 short-name
540 old-val
541 dir-parent
542 res)
543 (dolist (dir dirs)
544 (when (file-directory-p dir)
545 (dolist (file (file-name-all-completions "" dir))
546 (when (string-match suffix file)
547 (unless (string-match "pkg.elc?$" file)
548 (setq short-name (substring file 0 (match-beginning 0)))
549 (if (setq old-val (gethash short-name cands))
550 (progn
551 ;; assume going up directory once will resolve name clash
552 (setq dir-parent (counsel-directory-parent (cdr old-val)))
553 (puthash short-name
554 (cons
555 (counsel-string-compose dir-parent (car old-val))
556 (cdr old-val))
557 cands)
558 (setq dir-parent (counsel-directory-parent dir))
559 (puthash (concat dir-parent short-name)
560 (cons
561 (propertize
562 (counsel-string-compose
563 dir-parent short-name)
564 'full-name (expand-file-name file dir))
565 dir)
566 cands))
567 (puthash short-name
568 (cons (propertize
569 short-name
570 'full-name (expand-file-name file dir))
571 dir) cands)))))))
572 (maphash (lambda (_k v) (push (car v) res)) cands)
573 (ivy-read "Load library: " (nreverse res)
574 :action (lambda (x)
575 (load-library
576 (get-text-property 0 'full-name x)))
577 :keymap counsel-describe-map)))
578
579 (defvar counsel-gg-state nil
580 "The current state of candidates / count sync.")
581
582 (defun counsel--gg-candidates (regex)
583 "Return git grep candidates for REGEX."
584 (setq counsel-gg-state -2)
585 (counsel--gg-count regex)
586 (let* ((default-directory counsel--git-grep-dir)
587 (counsel-gg-process " *counsel-gg*")
588 (proc (get-process counsel-gg-process))
589 (buff (get-buffer counsel-gg-process)))
590 (when proc
591 (delete-process proc))
592 (when buff
593 (kill-buffer buff))
594 (setq proc (start-process-shell-command
595 counsel-gg-process
596 counsel-gg-process
597 (format "git --no-pager grep --full-name -n --no-color -i -e %S | head -n 200"
598 regex)))
599 (set-process-sentinel
600 proc
601 #'counsel--gg-sentinel)))
602
603 (defun counsel--gg-sentinel (process event)
604 (if (string= event "finished\n")
605 (progn
606 (with-current-buffer (process-buffer process)
607 (setq ivy--all-candidates (split-string (buffer-string) "\n" t))
608 (setq ivy--old-cands ivy--all-candidates))
609 (when (= 0 (cl-incf counsel-gg-state))
610 (ivy--exhibit)))
611 (if (string= event "exited abnormally with code 1\n")
612 (progn
613 (setq ivy--all-candidates '("Error"))
614 (setq ivy--old-cands ivy--all-candidates)
615 (ivy--exhibit)))))
616
617 (defun counsel--gg-count (regex &optional no-async)
618 "Quickly and asynchronously count the amount of git grep REGEX matches.
619 When NO-ASYNC is non-nil, do it synchronously."
620 (let ((default-directory counsel--git-grep-dir)
621 (cmd (format "git grep -i -c '%s' | sed 's/.*:\\(.*\\)/\\1/g' | awk '{s+=$1} END {print s}'"
622 regex))
623 (counsel-ggc-process " *counsel-gg-count*"))
624 (if no-async
625 (string-to-number (shell-command-to-string cmd))
626 (let ((proc (get-process counsel-ggc-process))
627 (buff (get-buffer counsel-ggc-process)))
628 (when proc
629 (delete-process proc))
630 (when buff
631 (kill-buffer buff))
632 (setq proc (start-process-shell-command
633 counsel-ggc-process
634 counsel-ggc-process
635 cmd))
636 (set-process-sentinel
637 proc
638 #'(lambda (process event)
639 (when (string= event "finished\n")
640 (with-current-buffer (process-buffer process)
641 (setq ivy--full-length (string-to-number (buffer-string))))
642 (when (= 0 (cl-incf counsel-gg-state))
643 (ivy--exhibit)))))))))
644
645 (defun counsel--M-x-transformer (cmd)
646 "Add a binding to CMD if it's bound in the current window.
647 CMD is a command name."
648 (let ((binding (substitute-command-keys (format "\\[%s]" cmd))))
649 (setq binding (replace-regexp-in-string "C-x 6" "<f2>" binding))
650 (if (string-match "^M-x" binding)
651 cmd
652 (format "%s (%s)" cmd
653 (propertize binding 'face 'font-lock-keyword-face)))))
654
655 (defvar smex-initialized-p)
656 (defvar smex-ido-cache)
657 (declare-function smex-initialize "ext:smex")
658 (declare-function smex-detect-new-commands "ext:smex")
659 (declare-function smex-update "ext:smex")
660 (declare-function smex-rank "ext:smex")
661 (declare-function package-installed-p "package")
662
663 ;;;###autoload
664 (defun counsel-M-x (&optional initial-input)
665 "Ivy version of `execute-extended-command'.
666 Optional INITIAL-INPUT is the initial input in the minibuffer."
667 (interactive)
668 (unless initial-input
669 (setq initial-input (cdr (assoc this-command
670 ivy-initial-inputs-alist))))
671 (let* ((store ivy-format-function)
672 (ivy-format-function
673 (lambda (cands)
674 (funcall
675 store
676 (with-ivy-window
677 (mapcar #'counsel--M-x-transformer cands)))))
678 (cands obarray)
679 (pred 'commandp)
680 (sort t))
681 (when (or (featurep 'smex)
682 (package-installed-p 'smex))
683 (require 'smex)
684 (unless smex-initialized-p
685 (smex-initialize))
686 (smex-detect-new-commands)
687 (smex-update)
688 (setq cands smex-ido-cache)
689 (setq pred nil)
690 (setq sort nil))
691 (ivy-read "M-x " cands
692 :predicate pred
693 :require-match t
694 :history 'extended-command-history
695 :action
696 (lambda (cmd)
697 (when (featurep 'smex)
698 (smex-rank (intern cmd)))
699 (let ((prefix-arg current-prefix-arg))
700 (command-execute (intern cmd) 'record)))
701 :sort sort
702 :keymap counsel-describe-map
703 :initial-input initial-input)))
704
705 (declare-function powerline-reset "ext:powerline")
706
707 (defun counsel--load-theme-action (x)
708 "Disable current themes and load theme X."
709 (condition-case nil
710 (progn
711 (mapc #'disable-theme custom-enabled-themes)
712 (load-theme (intern x))
713 (when (fboundp 'powerline-reset)
714 (powerline-reset)))
715 (error "Problem loading theme %s" x)))
716
717 ;;;###autoload
718 (defun counsel-load-theme ()
719 "Forward to `load-theme'.
720 Usable with `ivy-resume', `ivy-next-line-and-call' and
721 `ivy-previous-line-and-call'."
722 (interactive)
723 (ivy-read "Load custom theme: "
724 (mapcar 'symbol-name
725 (custom-available-themes))
726 :action #'counsel--load-theme-action))
727
728 (defvar rhythmbox-library)
729 (declare-function rhythmbox-load-library "ext:helm-rhythmbox")
730 (declare-function dbus-call-method "dbus")
731 (declare-function rhythmbox-song-uri "ext:helm-rhythmbox")
732 (declare-function helm-rhythmbox-candidates "ext:helm-rhythmbox")
733
734 (defun counsel-rhythmbox-enqueue-song (song)
735 "Let Rhythmbox enqueue SONG."
736 (let ((service "org.gnome.Rhythmbox3")
737 (path "/org/gnome/Rhythmbox3/PlayQueue")
738 (interface "org.gnome.Rhythmbox3.PlayQueue"))
739 (dbus-call-method :session service path interface
740 "AddToQueue" (rhythmbox-song-uri song))))
741
742 (defvar counsel-rhythmbox-history nil
743 "History for `counsel-rhythmbox'.")
744
745 ;;;###autoload
746 (defun counsel-rhythmbox ()
747 "Choose a song from the Rhythmbox library to play or enqueue."
748 (interactive)
749 (unless (require 'helm-rhythmbox nil t)
750 (error "Please install `helm-rhythmbox'"))
751 (unless rhythmbox-library
752 (rhythmbox-load-library)
753 (while (null rhythmbox-library)
754 (sit-for 0.1)))
755 (ivy-read "Rhythmbox: "
756 (helm-rhythmbox-candidates)
757 :history 'counsel-rhythmbox-history
758 :action
759 '(1
760 ("p" helm-rhythmbox-play-song "Play song")
761 ("e" counsel-rhythmbox-enqueue-song "Enqueue song"))))
762
763 (defvar counsel-org-tags nil
764 "Store the current list of tags.")
765
766 (defvar org-outline-regexp)
767 (defvar org-indent-mode)
768 (defvar org-indent-indentation-per-level)
769 (defvar org-tags-column)
770 (declare-function org-get-tags-string "org")
771 (declare-function org-move-to-column "org")
772
773 (defun counsel-org-change-tags (tags)
774 (let ((current (org-get-tags-string))
775 (col (current-column))
776 level)
777 ;; Insert new tags at the correct column
778 (beginning-of-line 1)
779 (setq level (or (and (looking-at org-outline-regexp)
780 (- (match-end 0) (point) 1))
781 1))
782 (cond
783 ((and (equal current "") (equal tags "")))
784 ((re-search-forward
785 (concat "\\([ \t]*" (regexp-quote current) "\\)[ \t]*$")
786 (point-at-eol) t)
787 (if (equal tags "")
788 (delete-region
789 (match-beginning 0)
790 (match-end 0))
791 (goto-char (match-beginning 0))
792 (let* ((c0 (current-column))
793 ;; compute offset for the case of org-indent-mode active
794 (di (if (bound-and-true-p org-indent-mode)
795 (* (1- org-indent-indentation-per-level) (1- level))
796 0))
797 (p0 (if (equal (char-before) ?*) (1+ (point)) (point)))
798 (tc (+ org-tags-column (if (> org-tags-column 0) (- di) di)))
799 (c1 (max (1+ c0) (if (> tc 0) tc (- (- tc) (string-width tags)))))
800 (rpl (concat (make-string (max 0 (- c1 c0)) ?\ ) tags)))
801 (replace-match rpl t t)
802 (and c0 indent-tabs-mode (tabify p0 (point)))
803 tags)))
804 (t (error "Tags alignment failed")))
805 (org-move-to-column col)))
806
807 (defun counsel-org-tag-action (x)
808 (if (member x counsel-org-tags)
809 (progn
810 (setq counsel-org-tags (delete x counsel-org-tags)))
811 (setq counsel-org-tags (append counsel-org-tags (list x)))
812 (unless (member x ivy--all-candidates)
813 (setq ivy--all-candidates (append ivy--all-candidates (list x)))))
814 (let ((prompt (counsel-org-tag-prompt)))
815 (setf (ivy-state-prompt ivy-last) prompt)
816 (setq ivy--prompt (concat "%-4d " prompt)))
817 (cond ((memq this-command '(ivy-done ivy-alt-done))
818 (counsel-org-change-tags
819 (if counsel-org-tags
820 (format ":%s:"
821 (mapconcat #'identity counsel-org-tags ":"))
822 "")))
823 ((eq this-command 'ivy-call)
824 (delete-minibuffer-contents))))
825
826 (defun counsel-org-tag-prompt ()
827 (format "Tags (%s): "
828 (mapconcat #'identity counsel-org-tags ", ")))
829
830 (defvar org-setting-tags)
831 (defvar org-last-tags-completion-table)
832 (defvar org-tag-persistent-alist)
833 (defvar org-tag-alist)
834 (defvar org-complete-tags-always-offer-all-agenda-tags)
835
836 (declare-function org-at-heading-p "org")
837 (declare-function org-back-to-heading "org")
838 (declare-function org-get-buffer-tags "org")
839 (declare-function org-global-tags-completion-table "org")
840 (declare-function org-agenda-files "org")
841 (declare-function org-agenda-set-tags "org-agenda")
842
843 ;;;###autoload
844 (defun counsel-org-tag ()
845 "Add or remove tags in org-mode."
846 (interactive)
847 (save-excursion
848 (unless (org-at-heading-p)
849 (org-back-to-heading t))
850 (setq counsel-org-tags (split-string (org-get-tags-string) ":" t))
851 (let ((org-setting-tags t)
852 (org-last-tags-completion-table
853 (append org-tag-persistent-alist
854 (or org-tag-alist (org-get-buffer-tags))
855 (and
856 org-complete-tags-always-offer-all-agenda-tags
857 (org-global-tags-completion-table
858 (org-agenda-files))))))
859 (ivy-read (counsel-org-tag-prompt)
860 (lambda (str &rest _unused)
861 (delete-dups
862 (all-completions str 'org-tags-completion-function)))
863 :history 'org-tags-history
864 :action 'counsel-org-tag-action))))
865
866 ;;;###autoload
867 (defun counsel-org-tag-agenda ()
868 "Set tags for the current agenda item."
869 (interactive)
870 (let ((store (symbol-function 'org-set-tags)))
871 (unwind-protect
872 (progn
873 (fset 'org-set-tags
874 (symbol-function 'counsel-org-tag))
875 (org-agenda-set-tags nil nil))
876 (fset 'org-set-tags store))))
877
878 (defun counsel-ag-function (string &optional _pred &rest _unused)
879 "Grep in the current directory for STRING."
880 (if (< (length string) 3)
881 (counsel-more-chars 3)
882 (let ((regex (counsel-unquote-regex-parens (ivy--regex string))))
883 (counsel--async-command
884 (format "ag --noheading --nocolor %S" regex))
885 nil)))
886
887 (defun counsel-ag (&optional initial-input)
888 "Grep for a string in the current directory using ag.
889 INITIAL-INPUT can be given as the initial minibuffer input."
890 (interactive)
891 (setq counsel--git-grep-dir default-directory)
892 (ivy-read "ag: " 'counsel-ag-function
893 :initial-input initial-input
894 :dynamic-collection t
895 :history 'counsel-git-grep-history
896 :action #'counsel-git-grep-action
897 :unwind #'swiper--cleanup))
898
899 (defun counsel-recoll-function (string &optional _pred &rest _unused)
900 "Grep in the current directory for STRING."
901 (if (< (length string) 3)
902 (counsel-more-chars 3)
903 (counsel--async-command
904 (format "recoll -t -b '%s'" string))
905 nil))
906
907 ;; This command uses the recollq command line tool that comes together
908 ;; with the recoll (the document indexing database) source:
909 ;; http://www.lesbonscomptes.com/recoll/download.html
910 ;; You need to build it yourself (together with recoll):
911 ;; cd ./query && make && sudo cp recollq /usr/local/bin
912 ;; You can try the GUI version of recoll with:
913 ;; sudo apt-get install recoll
914 ;; Unfortunately, that does not install recollq.
915 (defun counsel-recoll (&optional initial-input)
916 "Search for a string in the recoll database.
917 You'll be given a list of files that match.
918 Selecting a file will launch `swiper' for that file.
919 INITIAL-INPUT can be given as the initial minibuffer input."
920 (interactive)
921 (ivy-read "recoll: " 'counsel-recoll-function
922 :initial-input initial-input
923 :dynamic-collection t
924 :history 'counsel-git-grep-history
925 :action (lambda (x)
926 (when (string-match "file://\\(.*\\)\\'" x)
927 (let ((file-name (match-string 1 x)))
928 (find-file file-name)
929 (unless (string-match "pdf$" x)
930 (swiper ivy-text)))))))
931
932 (provide 'counsel)
933
934 ;;; counsel.el ends here