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