(defgroup ivy-faces nil
"Font-lock faces for `ivy'."
- :group 'ivy)
+ :group 'ivy
+ :group 'faces)
(defface ivy-current-match
'((((class color) (background light))
:background "#e99ce8" :weight bold)
(((class color) (background dark))
:background "#777777" :weight bold))
- "Face for `ivy' minibuffer matches modulo 1.")
+ "Face for `ivy' minibuffer matches numbered 1 modulo 3.")
(defface ivy-minibuffer-match-face-3
'((((class color) (background light))
:background "#bbbbff" :weight bold)
(((class color) (background dark))
:background "#7777ff" :weight bold))
- "Face for `ivy' minibuffer matches modulo 2.")
+ "Face for `ivy' minibuffer matches numbered 2 modulo 3.")
(defface ivy-minibuffer-match-face-4
'((((class color) (background light))
:background "#ffbbff" :weight bold)
(((class color) (background dark))
:background "#8a498a" :weight bold))
- "Face for `ivy' minibuffer matches modulo 3.")
+ "Face for `ivy' minibuffer matches numbered 3 modulo 3.")
(defface ivy-confirm-face
'((t :foreground "ForestGreen" :inherit minibuffer-prompt))
'((t :foreground "red" :inherit minibuffer-prompt))
"Face used by Ivy for a match required prompt.")
-(setcdr (assoc load-file-name custom-current-group-alist) 'ivy)
-
(defface ivy-subdir
'((t (:inherit 'dired-directory)))
"Face used by Ivy for highlighting subdirs in the alternatives.")
'((t :inherit font-lock-builtin-face))
"Face used by Ivy for matching virtual buffer names.")
+(setcdr (assoc load-file-name custom-current-group-alist) 'ivy)
+
(defcustom ivy-height 10
"Number of lines for the minibuffer window."
:type 'integer)
(define-key map (kbd "C-'") 'ivy-avy)
(define-key map (kbd "C-M-a") 'ivy-read-action)
(define-key map (kbd "C-c C-o") 'ivy-occur)
+ (define-key map (kbd "C-c C-a") 'ivy-toggle-ignore)
+ (define-key map (kbd "C-h m") 'ivy-help)
map)
"Keymap used in the minibuffer.")
(autoload 'hydra-ivy/body "ivy-hydra" "" t)
(defsubst ivy-set-action (action)
(setf (ivy-state-action ivy-last) action))
+(defun ivy-thing-at-point ()
+ "Return a string that corresponds to the current thing at point."
+ (or
+ (thing-at-point 'url)
+ (let (s)
+ (cond ((stringp (setq s (thing-at-point 'symbol)))
+ (if (string-match "\\`[`']?\\(.*?\\)'?\\'" s)
+ (match-string 1 s)
+ s))
+ ((looking-at "(+\\(\\(?:\\sw\\|\\s_\\)+\\)\\_>")
+ (match-string-no-properties 1))
+ (t
+ "")))))
+
(defvar ivy-history nil
"History list of candidates entered in the minibuffer.
This variable is set by `ivy-read' and used by `ivy--set-candidates'.")
+(defvar ivy-use-ignore t
+ "Store policy for user-configured candidate filtering.")
+
(defvar ivy--default nil
"Default initial input.")
"Move cursor vertically down ARG candidates.
If the input is empty, select the previous history element instead."
(interactive "p")
- (when (string= ivy-text "")
- (ivy-previous-history-element 1))
- (ivy-next-line arg))
+ (if (string= ivy-text "")
+ (ivy-previous-history-element 1)
+ (ivy-next-line arg)))
(defun ivy-previous-line (&optional arg)
"Move cursor vertically up ARG candidates."
(when (setq ivy-calling (not ivy-calling))
(ivy-call)))
+(defun ivy-toggle-ignore ()
+ "Toggle user-configured candidate filtering."
+ (interactive)
+ (setq ivy-use-ignore (null ivy-use-ignore))
+ ;; invalidate cache
+ (setq ivy--old-cands nil))
+
(defun ivy--get-action (state)
"Get the action function from STATE."
(let ((action (ivy-state-action state)))
(defun ivy-next-history-element (arg)
"Forward to `next-history-element' with ARG."
(interactive "p")
- (next-history-element arg)
+ (if (and (= minibuffer-history-position 0)
+ (equal ivy-text ""))
+ (progn
+ (insert ivy--default)
+ (when (and (with-ivy-window (derived-mode-p 'prog-mode))
+ (> (point) (minibuffer-prompt-end)))
+ (undo-boundary)
+ (insert "\\_>")
+ (goto-char (minibuffer-prompt-end))
+ (insert "\\_<")
+ (forward-char (+ 2 (length ivy--default)))))
+ (next-history-element arg))
(ivy--cd-maybe)
(move-end-of-line 1)
(ivy--maybe-scroll-history))
:type
'(alist
:key-type (choice
- (const :tag "All other functions" t)
- (symbol :tag "Function"))
+ (const :tag "Fall-through" t)
+ (symbol :tag "Collection"))
:value-type (choice
- (const :tag "plain sort" string-lessp)
- (const :tag "file sort" ivy-sort-file-function-default)
- (const :tag "no sort" nil)))
+ (const :tag "Plain sort" string-lessp)
+ (const :tag "File sort" ivy-sort-file-function-default)
+ (const :tag "No sort" nil)))
:group 'ivy)
(defvar ivy-index-functions-alist
CALLER is a symbol to uniquely identify the caller to `ivy-read'.
It is used, along with COLLECTION, to determine which
customizations apply to the current completion session."
- (let ((extra-actions (append (plist-get ivy--actions-list t)
- (plist-get ivy--actions-list this-command))))
+ (let ((extra-actions (delete-dups
+ (append (plist-get ivy--actions-list t)
+ (plist-get ivy--actions-list this-command)
+ (plist-get ivy--actions-list caller)))))
(when extra-actions
(setq action
(cond ((functionp action)
("o" ,action "default")
,@extra-actions))
((null action)
- (cons 1 extra-actions))
+ `(1
+ ("o" identity "default")
+ ,@extra-actions))
(t
(delete-dups (append action extra-actions)))))))
(let ((extra-sources (plist-get ivy--sources-list caller)))
(setq ivy--full-length nil)
(setq ivy-text "")
(setq ivy-calling nil)
+ (setq ivy-use-ignore t)
(let (coll sort-fn)
(cond ((eq collection 'Info-read-node-name-1)
(if (equal Info-current-file "dir")
(equal initial-input default-directory)
(equal initial-input ""))
(setq coll (cons initial-input coll)))
- (unless (ivy-state-action ivy-last)
+ (unless (and (ivy-state-action ivy-last)
+ (not (equal (ivy--get-action ivy-last) 'identity)))
(setq initial-input nil))))
((eq collection 'internal-complete-buffer)
(setq coll (ivy--buffer-list "" ivy-use-virtual-buffers)))
(byte-code-function-p collection)
(vectorp collection)
(and (consp collection) (listp (car collection)))
- (hash-table-p collection))
+ (hash-table-p collection)
+ (and (listp collection) (symbolp (car collection))))
(setq coll (all-completions "" collection predicate)))
(t
(setq coll collection)))
(setq ivy-exit nil)
(setq ivy--default
(if (region-active-p)
- (prog1 (buffer-substring
- (region-beginning)
- (region-end))
- (deactivate-mark))
- (or
- (thing-at-point 'url)
- (thing-at-point 'symbol)
- "")))
+ (buffer-substring
+ (region-beginning)
+ (region-end))
+ (ivy-thing-at-point)))
(setq ivy--prompt
(cond ((string-match "%.*d" prompt)
prompt)
This interface conforms to `completing-read' and can be used for
`completing-read-function'.
-PROMPT is a string to prompt with; normally it ends in a colon and a space.
-COLLECTION can be a list of strings, an alist, an obarray or a hash table.
+PROMPT is a string that normally ends in a colon and a space.
+COLLECTION is either a list of strings, an alist, an obarray, or a hash table.
PREDICATE limits completion to a subset of COLLECTION.
-REQUIRE-MATCH is specified with a boolean value. See `completing-read'.
-INITIAL-INPUT is a string that can be inserted into the minibuffer initially.
+REQUIRE-MATCH is a boolean value. See `completing-read'.
+INITIAL-INPUT is a string inserted into the minibuffer initially.
HISTORY is a list of previously selected inputs.
DEF is the default value.
INHERIT-INPUT-METHOD is currently ignored."
"An Ivy function suitable for `completion-in-region-function'."
(let* ((enable-recursive-minibuffers t)
(str (buffer-substring-no-properties start end))
+ (completion-ignore-case case-fold-search)
(comps
(completion-all-completions str collection predicate (- end start))))
(if (null comps)
preselect)
((cl-position preselect candidates :test #'equal))
((stringp preselect)
- (let ((re (regexp-quote preselect)))
+ (let ((re preselect))
(cl-position-if
(lambda (x)
(string-match re x))
(setq ivy--subexps (length subs))
(mapconcat
(lambda (x)
- (if (string-match "\\`\\\\(.*\\\\)\\'" x)
+ (if (string-match "\\`\\\\([^?].*\\\\)\\'" x)
x
(format "\\(%s\\)" x)))
subs
(setq ivy--subexps (length (match-string 2 str))))
str))
+(defcustom ivy-fixed-height-minibuffer nil
+ "When non nil, fix the height of the minibuffer during ivy
+completion at `ivy-height'. This effectively sets the minimum
+height at this level and tries to ensure that it does not change
+depending on the number of candidates."
+ :group 'ivy
+ :type 'boolean)
+
;;** Rest
(defun ivy--minibuffer-setup ()
"Setup ivy completion in the minibuffer."
(when (display-graphic-p)
(setq truncate-lines t))
(setq-local max-mini-window-height ivy-height)
+ (when ivy-fixed-height-minibuffer
+ (set-window-text-height (selected-window) ivy-height))
(add-hook 'post-command-hook #'ivy--exhibit nil t)
;; show completions with empty input
(ivy--exhibit))
(defun ivy--resize-minibuffer-to-fit ()
"Resize the minibuffer window size to fit the text in the minibuffer."
- (with-selected-window (minibuffer-window)
- (if (fboundp 'window-text-pixel-size)
- (let ((text-height (cdr (window-text-pixel-size)))
- (body-height (window-body-height nil t)))
+ (unless (frame-root-window-p (minibuffer-window))
+ (with-selected-window (minibuffer-window)
+ (if (fboundp 'window-text-pixel-size)
+ (let ((text-height (cdr (window-text-pixel-size)))
+ (body-height (window-body-height nil t)))
+ (when (> text-height body-height)
+ ;; Note: the size increment needs to be at least frame-char-height,
+ ;; otherwise resizing won't do anything.
+ (let ((delta (max (- text-height body-height) (frame-char-height))))
+ (window-resize nil delta nil t t))))
+ (let ((text-height (count-screen-lines))
+ (body-height (window-body-height)))
(when (> text-height body-height)
- ;; Note: the size increment needs to be at least frame-char-height,
- ;; otherwise resizing won't do anything.
- (let ((delta (max (- text-height body-height) (frame-char-height))))
- (window-resize nil delta nil t t))))
- (let ((text-height (count-screen-lines))
- (body-height (window-body-height)))
- (when (> text-height body-height)
- (window-resize nil (- text-height body-height) nil t))))))
+ (window-resize nil (- text-height body-height) nil t)))))))
(declare-function colir-blend-face-background "ext:colir")
(setq ivy--all-candidates res)))
(defcustom ivy-sort-matches-functions-alist '((t . nil))
- "An alist of functions used to sort the matching candidates.
+ "An alist of functions for sorting matching candidates.
-This is different from `ivy-sort-functions-alist', which is used
-to sort the whole collection only once. The functions taken from
-here are instead used on each input change, but they are used
-only on already matching candidates, not on all of them.
+Unlike `ivy-sort-functions-alist', which is used to sort the
+whole collection only once, this alist of functions are used to
+sort only matching candidates after each change in input.
-The alist KEY is a collection function or t to match previously
-not matched collection functions.
+The alist KEY is either a collection function or t to match
+previously unmatched collection functions.
The alist VAL is a sorting function with the signature of
-`ivy--prefix-sort'.")
+`ivy--prefix-sort'."
+ :type '(alist
+ :key-type (choice
+ (const :tag "Fall-through" t)
+ (symbol :tag "Collection"))
+ :value-type
+ (choice
+ (const :tag "Don't sort" nil)
+ (const :tag "Put prefix matches ahead" 'ivy--prefix-sort)
+ (function :tag "Custom sort function"))))
(defun ivy--sort-files-by-date (_name candidates)
"Re-soft CANDIDATES according to file modification date."
ivy-minibuffer-match-face-2
ivy-minibuffer-match-face-3
ivy-minibuffer-match-face-4)
- "List of `ivy' faces for minibuffer group matches.")
+ "List of `ivy' faces for minibuffer group matches."
+ :type '(repeat :tag "Faces"
+ (choice
+ (const ivy-minibuffer-match-face-1)
+ (const ivy-minibuffer-match-face-2)
+ (const ivy-minibuffer-match-face-3)
+ (const ivy-minibuffer-match-face-4)
+ (face :tag "Other face"))))
(defvar ivy-flx-limit 200
"Used to conditionally turn off flx sorting.
-When the amount of matching candidates is larger than this
-number, no sorting will be done.")
+
+When the amount of matching candidates exceeds this limit, then
+no sorting is done.")
(defun ivy--flx-sort (name cands)
"Sort according to closeness to string NAME the string list CANDS."
(defun ivy--buffer-list (str &optional virtual)
"Return the buffers that match STR.
When VIRTUAL is non-nil, add virtual buffers."
- (cl-remove-if
- (lambda (buf)
- (cl-find-if
- (lambda (regexp)
- (string-match regexp buf))
- ivy-ignore-buffers))
- (delete-dups
- (append
- (mapcar
- (lambda (x)
- (if (with-current-buffer x
- (file-remote-p
- (abbreviate-file-name default-directory)))
- (propertize x 'face 'ivy-remote)
- x))
- (all-completions str 'internal-complete-buffer))
- (and virtual
- (ivy--virtual-buffers))))))
+ (delete-dups
+ (append
+ (mapcar
+ (lambda (x)
+ (if (with-current-buffer x
+ (file-remote-p
+ (abbreviate-file-name default-directory)))
+ (propertize x 'face 'ivy-remote)
+ x))
+ (all-completions str 'internal-complete-buffer))
+ (and virtual
+ (ivy--virtual-buffers)))))
(defun ivy--switch-buffer-action (buffer)
"Switch to BUFFER.
ivy--rename-buffer-action
"rename")))
+(defun ivy--switch-buffer-matcher (regexp candidates)
+ "Return REGEXP-matching CANDIDATES.
+Skip buffers that match `ivy-ignore-buffers'."
+ (let ((res (ivy--re-filter regexp candidates)))
+ (if (or (null ivy-use-ignore)
+ (null ivy-ignore-buffers))
+ res
+ (or (cl-remove-if
+ (lambda (buf)
+ (cl-find-if
+ (lambda (regexp)
+ (string-match regexp buf))
+ ivy-ignore-buffers))
+ res)
+ res))))
+
;;;###autoload
(defun ivy-switch-buffer ()
"Switch to another buffer."
(call-interactively 'switch-to-buffer)
(let ((this-command 'ivy-switch-buffer))
(ivy-read "Switch to buffer: " 'internal-complete-buffer
+ :matcher #'ivy--switch-buffer-matcher
:preselect (buffer-name (other-buffer (current-buffer)))
:action #'ivy--switch-buffer-action
- :keymap ivy-switch-buffer-map))))
+ :keymap ivy-switch-buffer-map
+ :caller 'ivy-switch-buffer))))
;;;###autoload
(defun ivy-switch-buffer-other-window ()
(ivy-read "Switch to buffer in other window: " 'internal-complete-buffer
:preselect (buffer-name (other-buffer (current-buffer)))
:action #'ivy--switch-buffer-other-window-action
- :keymap ivy-switch-buffer-map))
+ :keymap ivy-switch-buffer-map
+ :caller 'ivy-switch-buffer-other-window))
;;;###autoload
(defun ivy-recentf ()
:action
(lambda (f)
(with-ivy-window
- (find-file f)))))
+ (find-file f)))
+ :caller 'ivy-recentf))
(defun ivy-yank-word ()
"Pull next word from buffer into search string."
\\{ivy-occur-grep-mode-map}")
-(defvar counsel-git-grep-cmd)
+(defvar ivy--occurs-list nil
+ "A list of custom occur generators per command.")
+
+(defun ivy-set-occur (cmd occur)
+ "Assign CMD a custom OCCUR function."
+ (setq ivy--occurs-list
+ (plist-put ivy--occurs-list cmd occur)))
+
+(defun ivy--occur-insert-lines (cands)
+ (dolist (str cands)
+ (add-text-properties
+ 0 (length str)
+ `(mouse-face
+ highlight
+ help-echo "mouse-1: call ivy-action")
+ str)
+ (insert str "\n")))
(defun ivy-occur ()
"Stop completion and put the current matches into a new buffer.
There is no limit on the number of *ivy-occur* buffers."
(interactive)
- (let ((buffer
- (generate-new-buffer
- (format "*ivy-occur%s \"%s\"*"
- (let (caller)
- (if (setq caller (ivy-state-caller ivy-last))
- (concat " " (prin1-to-string caller))
- ""))
- ivy-text)))
- (do-grep (eq (ivy-state-caller ivy-last) 'counsel-git-grep)))
- (with-current-buffer buffer
- (if do-grep
- (progn
- (setq ivy--old-cands
- (split-string
- (shell-command-to-string
- (format counsel-git-grep-cmd
- (if (stringp ivy--old-re)
- ivy--old-re
- (caar ivy--old-re))))
- "\n"
- t))
- (ivy-occur-grep-mode))
- (ivy-occur-mode))
- (setf (ivy-state-text ivy-last) ivy-text)
- (setq ivy-occur-last ivy-last)
- (setq-local ivy--directory ivy--directory)
- (let ((inhibit-read-only t))
- (erase-buffer)
- (when do-grep
- ;; Need precise number of header lines for `wgrep' to work.
- (insert (format "-*- mode:grep; default-directory: %S -*-\n\n\n"
- default-directory)))
- (insert (format "%d candidates:\n" (length ivy--old-cands)))
- (dolist (cand ivy--old-cands)
- (let ((str (if do-grep
- (concat "./" cand)
- (concat " " cand))))
- (add-text-properties
- 0 (length str)
- `(mouse-face
- highlight
- help-echo "mouse-1: call ivy-action")
- str)
- (insert str "\n")))))
- (ivy-exit-with-action
- `(lambda (_) (pop-to-buffer ,buffer)))))
+ (if (not (window-minibuffer-p))
+ (user-error "No completion session is active")
+ (let* ((caller (ivy-state-caller ivy-last))
+ (occur-fn (plist-get ivy--occurs-list caller))
+ (buffer
+ (generate-new-buffer
+ (format "*ivy-occur%s \"%s\"*"
+ (if caller
+ (concat " " (prin1-to-string caller))
+ "")
+ ivy-text))))
+ (with-current-buffer buffer
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (if occur-fn
+ (funcall occur-fn)
+ (ivy-occur-mode)
+ (insert (format "%d candidates:\n" (length ivy--old-cands)))
+ (ivy--occur-insert-lines
+ (mapcar
+ (lambda (cand) (concat " " cand))
+ ivy--old-cands))))
+ (setf (ivy-state-text ivy-last) ivy-text)
+ (setq ivy-occur-last ivy-last)
+ (setq-local ivy--directory ivy--directory))
+ (ivy-exit-with-action
+ `(lambda (_) (pop-to-buffer ,buffer))))))
(declare-function wgrep-change-to-wgrep-mode "ext:wgrep")
(run-at-time 0.5 nil 'swiper--cleanup))
(pulse-momentary-highlight-one-line (point)))))))
+(defvar ivy-help-file (let ((default-directory
+ (if load-file-name
+ (file-name-directory load-file-name)
+ default-directory)))
+ (if (file-exists-p "ivy-help.org")
+ (expand-file-name "ivy-help.org")
+ (if (file-exists-p "doc/ivy-help.org")
+ (expand-file-name "doc/ivy-help.org"))))
+ "The file for `ivy-help'.")
+
+(defun ivy-help ()
+ "Help for `ivy'."
+ (interactive)
+ (let ((buf (get-buffer "*Ivy Help*")))
+ (unless buf
+ (setq buf (get-buffer-create "*Ivy Help*"))
+ (with-current-buffer buf
+ (insert-file-contents ivy-help-file)
+ (org-mode)
+ (view-mode)
+ (goto-char (point-min))))
+ (if (eq this-command 'ivy-help)
+ (switch-to-buffer buf)
+ (with-ivy-window
+ (pop-to-buffer buf)))
+ (view-mode)
+ (goto-char (point-min))))
+
(provide 'ivy)
;;; ivy.el ends here