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