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