'((t (:inherit highlight)))
"Face used by Ivy for highlighting first match.")
+(defface ivy-subdir
+ '((t (:weight bold)))
+ "Face used by Ivy for highlighting subdirs in the alternatives.")
+
(defcustom ivy-height 10
"Number of lines for the minibuffer window."
:type 'integer)
This is usually meant as a quick exit out of the minibuffer."
:type 'function)
+(defcustom ivy-extra-directories '("../" "./")
+ "Add this to the front of the list when completing file names.
+Only \"./\" and \"../\" apply here. They appear in reverse order."
+ :type 'list)
+
;;* User Visible
;;** Keymap
(require 'delsel)
(define-key map (kbd "M-n") 'ivy-next-history-element)
(define-key map (kbd "M-p") 'ivy-previous-history-element)
(define-key map (kbd "C-g") 'minibuffer-keyboard-quit)
+ (define-key map (kbd "C-v") 'ivy-scroll-up-command)
+ (define-key map (kbd "M-v") 'ivy-scroll-down-command)
map)
"Keymap used in the minibuffer.")
(defvar ivy--directory nil
"Current directory when completing file names.")
+(defvar ivy--length 0
+ "Store the amount of viable candidates.")
+
+(defvar ivy-text ""
+ "Store the user's string as it is typed in.")
+
+(defvar ivy--current ""
+ "Current candidate.")
+
+(defvar ivy--index 0
+ "Store the index of the current candidate.")
+
+(defvar ivy-exit nil
+ "Store 'done if the completion was successfully selected.
+Otherwise, store nil.")
+
+(defvar ivy--action nil
+ "Store a function to call at the end of `ivy--read'.")
+
+(defvar ivy--all-candidates nil
+ "Store the candidates passed to `ivy-read'.")
+
+(defvar ivy--default nil
+ "Default initial input.")
+
+(defvar ivy--update-fn nil
+ "Current function to call when current candidate(s) update.")
+
+(defvar ivy--prompt nil
+ "Store the format-style prompt.
+When non-nil, it should contain one %d.")
+
+(defvar ivy--old-re nil
+ "Store the old regexp.")
+
+(defvar ivy--old-cands nil
+ "Store the candidates matched by `ivy--old-re'.")
+
;;** Commands
(defun ivy-done ()
"Exit the minibuffer with the selected candidate."
(interactive)
(delete-minibuffer-contents)
- (if (zerop ivy--length)
- (when (memq ivy-require-match '(nil confirm confirm-after-completion))
- (insert ivy-text)
- (setq ivy-exit 'done))
- (if ivy--directory
- (insert (expand-file-name ivy--current ivy--directory))
- (insert ivy--current))
- (setq ivy-exit 'done))
+ (cond (ivy--directory
+ (insert
+ (cond ((string= ivy-text "")
+ (if (equal ivy--current "./")
+ ivy--directory
+ ivy--current))
+ ((zerop ivy--length)
+ (expand-file-name ivy-text ivy--directory))
+ (t
+ (expand-file-name ivy--current ivy--directory))))
+ (setq ivy-exit 'done))
+ ((zerop ivy--length)
+ (when (memq ivy-require-match
+ '(nil confirm confirm-after-completion))
+ (insert ivy-text)
+ (setq ivy-exit 'done)))
+ (t
+ (insert ivy--current)
+ (setq ivy-exit 'done)))
(exit-minibuffer))
(defun ivy-alt-done ()
"Exit the minibuffer with the selected candidate."
(interactive)
- (if (and ivy--directory
- (file-directory-p
- (expand-file-name ivy--current ivy--directory)))
- (progn
- (delete-minibuffer-contents)
- (setq ivy--directory
- (expand-file-name ivy--current ivy--directory))
- (setq ivy--old-cands nil)
- (setq ivy--all-candidates
- (let ((default-directory ivy--directory))
- (all-completions "" 'read-file-name-internal)))
-
- (ivy--exhibit))
- (ivy-done)))
+ (let (dir)
+ (cond ((and ivy--directory
+ (= 0 ivy--index)
+ (= 0 (length ivy-text)))
+ (ivy-done))
+
+ ((and ivy--directory
+ (plusp ivy--length)
+ (file-directory-p
+ (setq dir (expand-file-name
+ ivy--current ivy--directory))))
+ (ivy--cd dir)
+ (ivy--exhibit))
+
+ (t
+ (ivy-done)))))
(defun ivy-beginning-of-buffer ()
"Select the first completion candidate."
(interactive)
(setq ivy--index (1- ivy--length)))
+(defun ivy-scroll-up-command ()
+ "Scroll the candidates upward by the minibuffer height."
+ (interactive)
+ (setq ivy--index (min (+ ivy--index ivy-height)
+ (1- ivy--length))))
+
+(defun ivy-scroll-down-command ()
+ "Scroll the candidates downward by the minibuffer height."
+ (interactive)
+ (setq ivy--index (max (- ivy--index ivy-height)
+ 0)))
+
(defun ivy-next-line (&optional arg)
"Move cursor vertically down ARG candidates."
(interactive "p")
(next-history-element arg)
(move-end-of-line 1))
+(defun ivy--cd (dir)
+ "When completing file names, move to directory DIR."
+ (if (null ivy--directory)
+ (error "Unexpected")
+ (setq ivy--old-cands nil)
+ (setq ivy--all-candidates
+ (ivy--sorted-files (setq ivy--directory dir)))
+ (setq ivy-text "")
+ (delete-minibuffer-contents)))
+
(defun ivy-backward-delete-char ()
"Forward to `backward-delete-char'.
On error (read-only), call `ivy-on-del-error-function'."
(interactive)
(if (and ivy--directory (= (minibuffer-prompt-end) (point)))
(progn
- (setq ivy--old-cands nil)
- (setq ivy--all-candidates
- (let ((default-directory (setq ivy--directory
- (file-name-directory
- (directory-file-name ivy--directory)))))
- (all-completions "" 'read-file-name-internal)))
+ (ivy--cd (file-name-directory
+ (directory-file-name ivy--directory)))
(ivy--exhibit))
(condition-case nil
(backward-delete-char 1)
(when ivy-on-del-error-function
(funcall ivy-on-del-error-function))))))
+(defun ivy-sort-file-function-default (x y)
+ "Compare two files X and Y.
+Prioritize directories."
+ (if (get-text-property 0 'dirp x)
+ (if (get-text-property 0 'dirp y)
+ (string< x y)
+ t)
+ (if (get-text-property 0 'dirp y)
+ nil
+ (string< x y))))
+
+(defvar ivy-sort-file-function 'ivy-sort-file-function-default
+ "The function that compares file names.
+It should take two string arguments and return nil and non-nil.")
+
+(defun ivy--sorted-files (dir)
+ "Return the list of files in DIR.
+Directories come first."
+ (let* ((default-directory dir)
+ (seq (all-completions "" 'read-file-name-internal)))
+ (if (equal dir "/")
+ seq
+ (setq seq (delete "./" (delete "../" seq)))
+ (when (eq ivy-sort-file-function 'ivy-sort-file-function-default)
+ (setq seq (mapcar (lambda (x)
+ (propertize x 'dirp (string-match-p "/$" x)))
+ (delete "./" (delete "../" seq)))))
+ (setq seq (cl-sort seq ivy-sort-file-function))
+ (dolist (dir ivy-extra-directories)
+ (push dir seq))
+ seq)))
+
;;** Entry Point
(defun ivy-read (prompt collection
&optional predicate initial-input keymap preselect update-fn)
UPDATE-FN is called each time the current candidate(s) is changed."
(setq ivy--directory nil)
- (cond ((or (functionp collection)
+ (cond ((eq collection 'Info-read-node-name-1)
+ (if (equal Info-current-file "dir")
+ (setq collection
+ (mapcar (lambda (x) (format "(%s)" x))
+ (cl-delete-duplicates
+ (all-completions "(" collection predicate)
+ :test 'equal)))
+ (setq collection (all-completions "" collection predicate))))
+ ((eq collection 'read-file-name-internal)
+ (setq ivy--directory default-directory)
+ (setq initial-input nil)
+ (setq collection
+ (ivy--sorted-files default-directory)))
+ ((or (functionp collection)
(vectorp collection))
- (when (eq collection 'read-file-name-internal)
- (setq ivy--directory default-directory)
- (setq initial-input nil))
(setq collection (all-completions "" collection predicate)))
((hash-table-p collection)
(error "Hash table as a collection unsupported"))
((listp (car collection))
(setq collection (all-completions "" collection predicate))))
+ (when preselect
+ (unless (or ivy-require-match
+ (all-completions preselect collection))
+ (setq collection (cons preselect collection))))
(cl-case (length collection)
(0 nil)
(1 (car collection))
"Toggle Ivy mode on or off.
With ARG, turn Ivy mode on if arg is positive, off otherwise.
Turning on Ivy mode will set `completing-read-function' to
-`ivy-completing-read'."
+`ivy-completing-read'.
+
+\\{ivy-minibuffer-map}"
:group 'ivy
:global t
:lighter " ivy"
(setq completing-read-function 'ivy-completing-read)
(setq completing-read-function 'completing-read-default)))
-(defvar ivy--action nil
- "Store a function to call at the end of `ivy--read'.")
-
(defun ivy--preselect-index (candidates initial-input preselect)
"Return the index in CANDIDATES filtered by INITIAL-INPUT for PRESELECT."
(when initial-input
(lambda (x)
(string-match initial-input x))
candidates)))
- (cl-position-if
- (lambda (x)
- (string-match preselect x))
- candidates))
-
-(defvar ivy-text ""
- "Stores the user's string as it is typed in.")
-
-(defvar ivy-exit nil
- "Store 'done if the completion was successfully selected.
-Otherwise, store nil.")
+ (or (cl-position preselect candidates :test 'equal)
+ (cl-position-if
+ (lambda (x)
+ (string-match preselect x))
+ candidates)))
;;* Implementation
;;** Regex
;; show completions with empty input
(ivy--exhibit))
-(defvar ivy--all-candidates nil
- "Store the candidates passed to `ivy-read'.")
-
-(defvar ivy--index 0
- "Store the index of the current candidate.")
-
-(defvar ivy--length 0
- "Store the amount of viable candidates.")
-
-(defvar ivy--current ""
- "Current candidate.")
-
-(defvar ivy--default nil
- "Default initial input.")
-
-(defvar ivy--update-fn nil
- "Current function to call when current candidate(s) update.")
-
(defun ivy--input ()
"Return the current minibuffer input."
;; assume one-line minibuffer input
(goto-char (minibuffer-prompt-end))
(delete-region (line-end-position) (point-max))))
-(defvar ivy--prompt nil
- "Store the format-style prompt.
-When non-nil, it should contain one %d.")
-
(defun ivy--insert-prompt ()
"Update the prompt according to `ivy--prompt'."
(when ivy--prompt
Should be run via minibuffer `post-command-hook'."
(setq ivy-text (ivy--input))
(ivy--cleanup)
+ (when ivy--directory
+ (if (string-match "/$" ivy-text)
+ (if (member ivy-text ivy--all-candidates)
+ (ivy--cd (expand-file-name ivy-text ivy--directory))
+ (ivy--cd "/"))
+ (if (string-match "~$" ivy-text)
+ (ivy--cd (expand-file-name "~/")))))
(let ((text (ivy-completions
ivy-text
ivy--all-candidates))
(forward-line 1)
(insert text)))))
-(defvar ivy--old-re nil
- "Store the old regexp.")
-
-(defvar ivy--old-cands nil
- "Store the candidates matched by `ivy--old-re'.")
-
(defun ivy--add-face (str face)
"Propertize STR with FACE.
`font-lock-append-text-property' is used, since it's better than
build a regex.
CANDIDATES is a list of strings."
(let* ((re (ivy--regex name))
- (cands (if (and (equal re ivy--old-re)
- ivy--old-cands)
- ivy--old-cands
- (ignore-errors
- (cl-remove-if-not
- (lambda (x) (string-match re x))
- candidates))))
+ (cands (cond ((and (equal re ivy--old-re)
+ ivy--old-cands)
+ ivy--old-cands)
+ ((and ivy--old-re
+ (not (equal ivy--old-re ""))
+ (eq 0 (cl-search ivy--old-re re)))
+ (ignore-errors
+ (cl-remove-if-not
+ (lambda (x) (string-match re x))
+ ivy--old-cands)))
+ (t
+ (ignore-errors
+ (cl-remove-if-not
+ (lambda (x) (string-match re x))
+ candidates)))))
(tail (nthcdr ivy--index ivy--old-cands))
(ww (window-width))
idx)
(end (min (+ start (1- ivy-height)) ivy--length))
(cands (cl-subseq cands start end))
(index (min ivy--index half-height (1- (length cands)))))
+ (when ivy--directory
+ (setq cands (mapcar (lambda (x)
+ (if (string-match-p "/$" x)
+ (propertize x 'face 'ivy-subdir)
+ x))
+ cands)))
(setq ivy--current (copy-sequence (nth index cands)))
(setf (nth index cands)
(ivy--add-face ivy--current 'ivy-current-match))