]> code.delx.au - gnu-emacs-elpa/blob - ivy.el
`counsel-git-grep' can now handle huge git repos
[gnu-emacs-elpa] / ivy.el
1 ;;; ivy.el --- Incremental Vertical completYon -*- 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.2.3
8 ;; Package-Requires: ((emacs "24.1"))
9 ;; Keywords: 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 ;; This package provides `ivy-read' as an alternative to
29 ;; `completing-read' and similar functions.
30 ;;
31 ;; There's no intricate code to determine the best candidate.
32 ;; Instead, the user can navigate to it with `ivy-next-line' and
33 ;; `ivy-previous-line'.
34 ;;
35 ;; The matching is done by splitting the input text by spaces and
36 ;; re-building it into a regex.
37 ;; So "for example" is transformed into "\\(for\\).*\\(example\\)".
38
39 ;;; Code:
40 (require 'cl-lib)
41
42 ;;* Customization
43 (defgroup ivy nil
44 "Incremental vertical completion."
45 :group 'convenience)
46
47 (defface ivy-current-match
48 '((t (:inherit highlight)))
49 "Face used by Ivy for highlighting first match.")
50
51 (defface ivy-subdir
52 '((t (:weight bold)))
53 "Face used by Ivy for highlighting subdirs in the alternatives.")
54
55 (defcustom ivy-height 10
56 "Number of lines for the minibuffer window."
57 :type 'integer)
58
59 (defcustom ivy-count-format "%-4d "
60 "The style of showing the current candidate count for `ivy-read'.
61 Set this to nil if you don't want the count."
62 :type 'string)
63
64 (defcustom ivy-wrap nil
65 "Whether to wrap around after the first and last candidate."
66 :type 'boolean)
67
68 (defcustom ivy-on-del-error-function 'minibuffer-keyboard-quit
69 "The handler for when `ivy-backward-delete-char' throws.
70 This is usually meant as a quick exit out of the minibuffer."
71 :type 'function)
72
73 (defcustom ivy-extra-directories '("../" "./")
74 "Add this to the front of the list when completing file names.
75 Only \"./\" and \"../\" apply here. They appear in reverse order."
76 :type 'list)
77
78 ;;* User Visible
79 ;;** Keymap
80 (require 'delsel)
81 (defvar ivy-minibuffer-map
82 (let ((map (make-sparse-keymap)))
83 (define-key map (kbd "C-m") 'ivy-done)
84 (define-key map (kbd "C-j") 'ivy-alt-done)
85 (define-key map (kbd "C-n") 'ivy-next-line)
86 (define-key map (kbd "C-p") 'ivy-previous-line)
87 (define-key map (kbd "<down>") 'ivy-next-line)
88 (define-key map (kbd "<up>") 'ivy-previous-line)
89 (define-key map (kbd "C-s") 'ivy-next-line-or-history)
90 (define-key map (kbd "C-r") 'ivy-previous-line-or-history)
91 (define-key map (kbd "SPC") 'self-insert-command)
92 (define-key map (kbd "DEL") 'ivy-backward-delete-char)
93 (define-key map (kbd "M-<") 'ivy-beginning-of-buffer)
94 (define-key map (kbd "M->") 'ivy-end-of-buffer)
95 (define-key map (kbd "<left>") 'ivy-beginning-of-buffer)
96 (define-key map (kbd "<right>") 'ivy-end-of-buffer)
97 (define-key map (kbd "M-n") 'ivy-next-history-element)
98 (define-key map (kbd "M-p") 'ivy-previous-history-element)
99 (define-key map (kbd "C-g") 'minibuffer-keyboard-quit)
100 (define-key map (kbd "C-v") 'ivy-scroll-up-command)
101 (define-key map (kbd "M-v") 'ivy-scroll-down-command)
102 map)
103 "Keymap used in the minibuffer.")
104
105 (defvar ivy-history nil
106 "History list of candidates entered in the minibuffer.
107
108 Maximum length of the history list is determined by the value
109 of `history-length', which see.")
110
111 (defvar ivy-require-match t
112 "Store require-match. See `completing-read'.")
113
114 (defvar ivy--directory nil
115 "Current directory when completing file names.")
116
117 (defvar ivy--length 0
118 "Store the amount of viable candidates.")
119
120 (defvar ivy-text ""
121 "Store the user's string as it is typed in.")
122
123 (defvar ivy--current ""
124 "Current candidate.")
125
126 (defvar ivy--index 0
127 "Store the index of the current candidate.")
128
129 (defvar ivy-exit nil
130 "Store 'done if the completion was successfully selected.
131 Otherwise, store nil.")
132
133 (defvar ivy--action nil
134 "Store a function to call at the end of `ivy--read'.")
135
136 (defvar ivy--all-candidates nil
137 "Store the candidates passed to `ivy-read'.")
138
139 (defvar ivy--default nil
140 "Default initial input.")
141
142 (defvar ivy--update-fn nil
143 "Current function to call when current candidate(s) update.")
144
145 (defvar ivy--prompt nil
146 "Store the format-style prompt.
147 When non-nil, it should contain one %d.")
148
149 (defvar ivy--old-re nil
150 "Store the old regexp.")
151
152 (defvar ivy--old-cands nil
153 "Store the candidates matched by `ivy--old-re'.")
154
155 ;;** Commands
156 (defun ivy-done ()
157 "Exit the minibuffer with the selected candidate."
158 (interactive)
159 (delete-minibuffer-contents)
160 (cond (ivy--directory
161 (insert
162 (cond ((string= ivy-text "")
163 (if (equal ivy--current "./")
164 ivy--directory
165 ivy--current))
166 ((zerop ivy--length)
167 (expand-file-name ivy-text ivy--directory))
168 (t
169 (expand-file-name ivy--current ivy--directory))))
170 (setq ivy-exit 'done))
171 ((zerop ivy--length)
172 (when (memq ivy-require-match
173 '(nil confirm confirm-after-completion))
174 (insert ivy-text)
175 (setq ivy-exit 'done)))
176 (t
177 (insert ivy--current)
178 (setq ivy-exit 'done)))
179 (exit-minibuffer))
180
181 (defun ivy-alt-done ()
182 "Exit the minibuffer with the selected candidate."
183 (interactive)
184 (let (dir)
185 (cond ((and ivy--directory
186 (= 0 ivy--index)
187 (= 0 (length ivy-text)))
188 (ivy-done))
189
190 ((and ivy--directory
191 (cl-plusp ivy--length)
192 (file-directory-p
193 (setq dir (expand-file-name
194 ivy--current ivy--directory))))
195 (ivy--cd dir)
196 (ivy--exhibit))
197
198 (t
199 (ivy-done)))))
200
201 (defun ivy-beginning-of-buffer ()
202 "Select the first completion candidate."
203 (interactive)
204 (setq ivy--index 0))
205
206 (defun ivy-end-of-buffer ()
207 "Select the last completion candidate."
208 (interactive)
209 (setq ivy--index (1- ivy--length)))
210
211 (defun ivy-scroll-up-command ()
212 "Scroll the candidates upward by the minibuffer height."
213 (interactive)
214 (setq ivy--index (min (+ ivy--index ivy-height)
215 (1- ivy--length))))
216
217 (defun ivy-scroll-down-command ()
218 "Scroll the candidates downward by the minibuffer height."
219 (interactive)
220 (setq ivy--index (max (- ivy--index ivy-height)
221 0)))
222
223 (defun ivy-next-line (&optional arg)
224 "Move cursor vertically down ARG candidates."
225 (interactive "p")
226 (setq arg (or arg 1))
227 (cl-incf ivy--index arg)
228 (when (>= ivy--index (1- ivy--length))
229 (if ivy-wrap
230 (ivy-beginning-of-buffer)
231 (setq ivy--index (1- ivy--length)))))
232
233 (defun ivy-next-line-or-history (&optional arg)
234 "Move cursor vertically down ARG candidates.
235 If the input is empty, select the previous history element instead."
236 (interactive "p")
237 (when (string= ivy-text "")
238 (ivy-previous-history-element 1))
239 (ivy-next-line arg))
240
241 (defun ivy-previous-line (&optional arg)
242 "Move cursor vertically up ARG candidates."
243 (interactive "p")
244 (setq arg (or arg 1))
245 (cl-decf ivy--index arg)
246 (when (< ivy--index 0)
247 (if ivy-wrap
248 (ivy-end-of-buffer)
249 (setq ivy--index 0))))
250
251 (defun ivy-previous-line-or-history (arg)
252 "Move cursor vertically up ARG candidates.
253 If the input is empty, select the previous history element instead."
254 (interactive "p")
255 (when (string= ivy-text "")
256 (ivy-previous-history-element 1))
257 (ivy-previous-line arg))
258
259 (defun ivy-previous-history-element (arg)
260 "Forward to `previous-history-element' with ARG."
261 (interactive "p")
262 (previous-history-element arg)
263 (move-end-of-line 1))
264
265 (defun ivy-next-history-element (arg)
266 "Forward to `next-history-element' with ARG."
267 (interactive "p")
268 (next-history-element arg)
269 (move-end-of-line 1))
270
271 (defun ivy--cd (dir)
272 "When completing file names, move to directory DIR."
273 (if (null ivy--directory)
274 (error "Unexpected")
275 (setq ivy--old-cands nil)
276 (setq ivy--all-candidates
277 (ivy--sorted-files (setq ivy--directory dir)))
278 (setq ivy-text "")
279 (delete-minibuffer-contents)))
280
281 (defun ivy-backward-delete-char ()
282 "Forward to `backward-delete-char'.
283 On error (read-only), call `ivy-on-del-error-function'."
284 (interactive)
285 (if (and ivy--directory (= (minibuffer-prompt-end) (point)))
286 (progn
287 (ivy--cd (file-name-directory
288 (directory-file-name ivy--directory)))
289 (ivy--exhibit))
290 (condition-case nil
291 (backward-delete-char 1)
292 (error
293 (when ivy-on-del-error-function
294 (funcall ivy-on-del-error-function))))))
295
296 (defun ivy-sort-file-function-default (x y)
297 "Compare two files X and Y.
298 Prioritize directories."
299 (if (get-text-property 0 'dirp x)
300 (if (get-text-property 0 'dirp y)
301 (string< x y)
302 t)
303 (if (get-text-property 0 'dirp y)
304 nil
305 (string< x y))))
306
307 (defvar ivy-sort-file-function 'ivy-sort-file-function-default
308 "The function that compares file names.
309 It should take two string arguments and return nil and non-nil.")
310
311 (defun ivy--sorted-files (dir)
312 "Return the list of files in DIR.
313 Directories come first."
314 (let* ((default-directory dir)
315 (seq (all-completions "" 'read-file-name-internal)))
316 (if (equal dir "/")
317 seq
318 (setq seq (delete "./" (delete "../" seq)))
319 (when (eq ivy-sort-file-function 'ivy-sort-file-function-default)
320 (setq seq (mapcar (lambda (x)
321 (propertize x 'dirp (string-match-p "/$" x)))
322 (delete "./" (delete "../" seq)))))
323 (setq seq (cl-sort seq ivy-sort-file-function))
324 (dolist (dir ivy-extra-directories)
325 (push dir seq))
326 seq)))
327
328 ;;** Entry Point
329 (defun ivy-read (prompt collection
330 &optional predicate initial-input keymap preselect update-fn)
331 "Read a string in the minibuffer, with completion.
332
333 PROMPT is a string to prompt with; normally it ends in a colon
334 and a space. When PROMPT contains %d, it will be updated with
335 the current number of matching candidates.
336 See also `ivy-count-format'.
337
338 COLLECTION is a list of strings.
339
340 If INITIAL-INPUT is non-nil, insert it in the minibuffer initially.
341
342 KEYMAP is composed together with `ivy-minibuffer-map'.
343
344 If PRESELECT is non-nil select the corresponding candidate out of
345 the ones that match INITIAL-INPUT.
346
347 UPDATE-FN is called each time the current candidate(s) is changed."
348 (setq ivy--directory nil)
349 (cond ((eq collection 'Info-read-node-name-1)
350 (if (equal Info-current-file "dir")
351 (setq collection
352 (mapcar (lambda (x) (format "(%s)" x))
353 (cl-delete-duplicates
354 (all-completions "(" collection predicate)
355 :test 'equal)))
356 (setq collection (all-completions "" collection predicate))))
357 ((eq collection 'read-file-name-internal)
358 (setq ivy--directory default-directory)
359 (setq initial-input nil)
360 (setq collection
361 (ivy--sorted-files default-directory)))
362 ((or (functionp collection)
363 (vectorp collection))
364 (setq collection (all-completions "" collection predicate)))
365 ((hash-table-p collection)
366 (error "Hash table as a collection unsupported"))
367 ((listp (car collection))
368 (setq collection (all-completions "" collection predicate))))
369 (when preselect
370 (unless (or ivy-require-match
371 (all-completions preselect collection))
372 (setq collection (cons preselect collection))))
373 (cl-case (length collection)
374 (0 nil)
375 (1 (car collection))
376 (t
377 (setq ivy--index (or
378 (and preselect
379 (ivy--preselect-index
380 collection initial-input preselect))
381 0))
382 (setq ivy--old-re nil)
383 (setq ivy--old-cands nil)
384 (setq ivy-text "")
385 (setq ivy--all-candidates collection)
386 (setq ivy--update-fn update-fn)
387 (setq ivy-exit nil)
388 (setq ivy--default (or (thing-at-point 'symbol) ""))
389 (setq ivy--prompt
390 (cond ((string-match "%.*d" prompt)
391 prompt)
392 ((string-match "%.*d" ivy-count-format)
393 (concat ivy-count-format prompt))
394 (ivy--directory
395 prompt)
396 (t
397 nil)))
398 (setq ivy--action nil)
399 (prog1
400 (unwind-protect
401 (minibuffer-with-setup-hook
402 #'ivy--minibuffer-setup
403 (let ((res (read-from-minibuffer
404 prompt
405 initial-input
406 (make-composed-keymap keymap ivy-minibuffer-map)
407 nil
408 'ivy-history)))
409 (when (eq ivy-exit 'done)
410 (pop ivy-history)
411 (setq ivy-history
412 (cons ivy-text (delete ivy-text ivy-history)))
413 res)))
414 (remove-hook 'post-command-hook #'ivy--exhibit))
415 (when ivy--action
416 (funcall ivy--action))))))
417
418 (defun ivy-completing-read (prompt collection
419 &optional predicate require-match initial-input
420 _history def _inherit-input-method)
421 "Read a string in the minibuffer, with completion.
422
423 This is an interface that conforms to `completing-read', so that
424 it can be used for `completing-read-function'.
425
426 PROMPT is a string to prompt with; normally it ends in a colon and a space.
427 COLLECTION can be a list of strings, an alist, an obarray or a hash table.
428 PREDICATE limits completion to a subset of COLLECTION.
429
430 REQUIRE-MATCH is stored into `ivy-require-match'. See `completing-read'.
431 INITIAL-INPUT is a string that can be inserted into the minibuffer initially.
432 _HISTORY is ignored for now.
433 DEF is the default value.
434 _INHERIT-INPUT-METHOD is ignored for now.
435
436 The history, defaults and input-method arguments are ignored for now."
437 (when (listp def)
438 (setq def (car def)))
439 (setq ivy-require-match require-match)
440 (ivy-read prompt collection predicate initial-input nil def))
441
442 ;;;###autoload
443 (define-minor-mode ivy-mode
444 "Toggle Ivy mode on or off.
445 With ARG, turn Ivy mode on if arg is positive, off otherwise.
446 Turning on Ivy mode will set `completing-read-function' to
447 `ivy-completing-read'.
448
449 \\{ivy-minibuffer-map}"
450 :group 'ivy
451 :global t
452 :lighter " ivy"
453 (if ivy-mode
454 (setq completing-read-function 'ivy-completing-read)
455 (setq completing-read-function 'completing-read-default)))
456
457 (defun ivy--preselect-index (candidates initial-input preselect)
458 "Return the index in CANDIDATES filtered by INITIAL-INPUT for PRESELECT."
459 (when initial-input
460 (setq candidates
461 (cl-remove-if-not
462 (lambda (x)
463 (string-match initial-input x))
464 candidates)))
465 (or (cl-position preselect candidates :test 'equal)
466 (cl-position-if
467 (lambda (x)
468 (string-match preselect x))
469 candidates)))
470
471 ;;* Implementation
472 ;;** Regex
473 (defvar ivy--subexps 0
474 "Number of groups in the current `ivy--regex'.")
475
476 (defvar ivy--regex-hash
477 (make-hash-table :test 'equal)
478 "Store pre-computed regex.")
479
480 (defun ivy--regex (str)
481 "Re-build regex from STR in case it has a space."
482 (let ((hashed (gethash str ivy--regex-hash)))
483 (if hashed
484 (prog1 (cdr hashed)
485 (setq ivy--subexps (car hashed)))
486 (cdr (puthash str
487 (let ((subs (split-string str " +" t)))
488 (if (= (length subs) 1)
489 (cons
490 (setq ivy--subexps 0)
491 (car subs))
492 (cons
493 (setq ivy--subexps (length subs))
494 (mapconcat
495 (lambda (x) (format "\\(%s\\)" x))
496 subs
497 ".*"))))
498 ivy--regex-hash)))))
499
500 ;;** Rest
501 (defun ivy--minibuffer-setup ()
502 "Setup ivy completion in the minibuffer."
503 (set (make-local-variable 'completion-show-inline-help) nil)
504 (set (make-local-variable 'minibuffer-default-add-function)
505 (lambda ()
506 (list ivy--default)))
507 (use-local-map (make-composed-keymap ivy-minibuffer-map
508 (current-local-map)))
509 (setq-local max-mini-window-height ivy-height)
510 (add-hook 'post-command-hook #'ivy--exhibit nil t)
511 ;; show completions with empty input
512 (ivy--exhibit))
513
514 (defun ivy--input ()
515 "Return the current minibuffer input."
516 ;; assume one-line minibuffer input
517 (buffer-substring-no-properties
518 (minibuffer-prompt-end)
519 (line-end-position)))
520
521 (defun ivy--cleanup ()
522 "Delete the displayed completion candidates."
523 (save-excursion
524 (goto-char (minibuffer-prompt-end))
525 (delete-region (line-end-position) (point-max))))
526
527 (defvar ivy--dynamic-function nil
528 "When this is non-nil, call it for each input change to get new candidates.")
529
530 (defvar ivy--full-length nil
531 "When `ivy--dynamic-function' is non-nil, this can be the total amount of candidates.")
532
533 (defvar ivy--old-text nil
534 "Store old `ivy-text' for dynamic completion.")
535
536 (defun ivy--insert-prompt ()
537 "Update the prompt according to `ivy--prompt'."
538 (when ivy--prompt
539 (let ((inhibit-read-only t)
540 (n-str
541 (format
542 (if ivy--directory
543 (concat ivy--prompt (abbreviate-file-name ivy--directory))
544 ivy--prompt)
545 (or (and ivy--dynamic-function
546 ivy--full-length)
547 ivy--length))))
548 (save-excursion
549 (goto-char (point-min))
550 (delete-region (point-min) (minibuffer-prompt-end))
551 (set-text-properties
552 0 (length n-str)
553 '(front-sticky t rear-nonsticky t field t read-only t face minibuffer-prompt)
554 n-str)
555 (insert n-str))
556 ;; get out of the prompt area
557 (constrain-to-field nil (point-max)))))
558
559 (defun ivy--exhibit ()
560 "Insert Ivy completions display.
561 Should be run via minibuffer `post-command-hook'."
562 (setq ivy-text (ivy--input))
563 (if ivy--dynamic-function
564 ;; while-no-input would cause annoying
565 ;; "Waiting for process to die...done" message interruptions
566 (progn
567 (unless (equal ivy--old-text ivy-text)
568 (let ((store ivy--dynamic-function)
569 (ivy--dynamic-function nil))
570 (setq ivy--all-candidates (funcall store ivy-text)))
571 (setq ivy--old-text ivy-text))
572 (ivy--insert-minibuffer (ivy--format ivy--all-candidates)))
573 (when ivy--directory
574 (if (string-match "/$" ivy-text)
575 (if (member ivy-text ivy--all-candidates)
576 (ivy--cd (expand-file-name ivy-text ivy--directory))
577 (ivy--cd "/"))
578 (if (string-match "~$" ivy-text)
579 (ivy--cd (expand-file-name "~/")))))
580 (ivy--insert-minibuffer
581 (ivy--format
582 (ivy--filter ivy-text ivy--all-candidates)))))
583
584 (defun ivy--insert-minibuffer (text)
585 (ivy--cleanup)
586 (let ((buffer-undo-list t)
587 deactivate-mark)
588 (when ivy--update-fn
589 (funcall ivy--update-fn))
590 (ivy--insert-prompt)
591 ;; Do nothing if while-no-input was aborted.
592 (when (stringp text)
593 (save-excursion
594 (forward-line 1)
595 (insert text)))))
596
597 (defun ivy--add-face (str face)
598 "Propertize STR with FACE.
599 `font-lock-append-text-property' is used, since it's better than
600 `propertize' or `add-face-text-property' in this case."
601 (font-lock-append-text-property 0 (length str) 'face face str)
602 str)
603
604 (defun ivy--filter (name candidates)
605 "Return the matches for NAME for CANDIDATES.
606 CANDIDATES are assumed to be static."
607 (let* ((re (ivy--regex name))
608 (cands (cond ((and (equal re ivy--old-re)
609 ivy--old-cands)
610 ivy--old-cands)
611 ((and ivy--old-re
612 (not (equal ivy--old-re ""))
613 (memq (cl-search
614 (if (string-match "\\\\)$" ivy--old-re)
615 (substring ivy--old-re 0 -2)
616 ivy--old-re)
617 re) '(0 2)))
618 (ignore-errors
619 (cl-remove-if-not
620 (lambda (x) (string-match re x))
621 ivy--old-cands)))
622 (t
623 (ignore-errors
624 (cl-remove-if-not
625 (lambda (x) (string-match re x))
626 candidates)))))
627 (tail (nthcdr ivy--index ivy--old-cands))
628 idx)
629 (when (and tail ivy--old-cands)
630 (unless (and (not (equal re ivy--old-re))
631 (setq ivy--index (cl-position re cands :test 'equal)))
632 (while (and tail (null idx))
633 ;; Compare with eq to handle equal duplicates in cands
634 (setq idx (cl-position (pop tail) cands)))
635 (setq ivy--index (or idx 0))))
636 (setq ivy--old-re re)
637 (setq ivy--old-cands cands)))
638
639 (defun ivy--format (cands)
640 "Return a string for CANDS suitable for display in the minibuffer.
641 CANDS is a list of strings."
642 (setq ivy--length (length cands))
643 (when (>= ivy--index ivy--length)
644 (setq ivy--index (max (1- ivy--length) 0)))
645 (if (null cands)
646 ""
647 (let* ((half-height (/ ivy-height 2))
648 (start (max 0 (- ivy--index half-height)))
649 (end (min (+ start (1- ivy-height)) ivy--length))
650 (cands (cl-subseq cands start end))
651 (index (min ivy--index half-height (1- (length cands)))))
652 (when ivy--directory
653 (setq cands (mapcar (lambda (x)
654 (if (string-match-p "/$" x)
655 (propertize x 'face 'ivy-subdir)
656 x))
657 cands)))
658 (setq ivy--current (copy-sequence (nth index cands)))
659 (setf (nth index cands)
660 (ivy--add-face ivy--current 'ivy-current-match))
661 (let* ((ww (window-width))
662 (res (concat "\n" (mapconcat
663 (lambda (s)
664 (if (> (length s) ww)
665 (concat (substring s 0 (- ww 3)) "...")
666 s))
667 cands "\n"))))
668 (put-text-property 0 (length res) 'read-only nil res)
669 res))))
670
671 (provide 'ivy)
672
673 ;;; ivy.el ends here