X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/d8f79fa8b646b28add36b74a951f318560bfdae2..ae05765add1f3bc90421f033ff408fb93eba03a1:/ivy.el diff --git a/ivy.el b/ivy.el index 22b8ac69c..c4ff81419 100644 --- a/ivy.el +++ b/ivy.el @@ -4,7 +4,6 @@ ;; Author: Oleh Krehel ;; URL: https://github.com/abo-abo/swiper -;; Version: 0.2.3 ;; Package-Requires: ((emacs "24.1")) ;; Keywords: matching @@ -108,6 +107,8 @@ Only \"./\" and \"../\" apply here. They appear in reverse order." (define-key map (kbd "DEL") 'ivy-backward-delete-char) (define-key map (kbd "M-DEL") 'ivy-backward-kill-word) (define-key map (kbd "C-d") 'ivy-delete-char) + (define-key map (kbd "C-f") 'ivy-forward-char) + (define-key map (kbd "M-d") 'ivy-kill-word) (define-key map (kbd "M-<") 'ivy-beginning-of-buffer) (define-key map (kbd "M->") 'ivy-end-of-buffer) (define-key map (kbd "") 'ivy-beginning-of-buffer) @@ -120,8 +121,11 @@ Only \"./\" and \"../\" apply here. They appear in reverse order." (define-key map (kbd "C-M-n") 'ivy-next-line-and-call) (define-key map (kbd "C-M-p") 'ivy-previous-line-and-call) (define-key map (kbd "M-q") 'ivy-toggle-regexp-quote) + (define-key map (kbd "M-j") 'ivy-yank-word) + (define-key map (kbd "C-o") 'hydra-ivy/body) map) "Keymap used in the minibuffer.") +(autoload 'hydra-ivy/body "ivy-hydra" "" t) (defvar ivy-mode-map (let ((map (make-sparse-keymap))) @@ -196,6 +200,15 @@ When non-nil, it should contain one %d.") (defvar ivy--regex-function 'ivy--regex "Current function for building a regex.") +(defvar ivy--subexps 0 + "Number of groups in the current `ivy--regex'.") + +(defvar ivy--full-length nil + "When :dynamic-collection is non-nil, this can be the total amount of candidates.") + +(defvar ivy--old-text "" + "Store old `ivy-text' for dynamic completion.") + (defvar Info-current-file) (defmacro ivy-quit-and-run (&rest body) @@ -212,9 +225,9 @@ When non-nil, it should contain one %d.") "Insert TEXT and exit minibuffer." (if (and ivy--directory (not (eq (ivy-state-history ivy-last) 'grep-files-history))) - (insert (expand-file-name - text ivy--directory)) - (insert text)) + (insert (setq ivy--current (expand-file-name + text ivy--directory))) + (insert (setq ivy--current text))) (setq ivy-exit 'done) (exit-minibuffer)) @@ -251,6 +264,7 @@ Is is a cons cell, related to `tramp-get-completion-function'." domain))) (declare-function tramp-get-completion-function "tramp") +(declare-function Info-find-node "info") (defun ivy-alt-done (&optional arg) "Exit the minibuffer with the selected candidate. @@ -269,7 +283,17 @@ When ARG is t, exit with current text, ignoring the candidates." ivy--current ivy--directory)))))) (ivy--cd dir) (ivy--exhibit)) - ((string-match "^/\\([^/]+?\\):\\(?:\\(.*\\)@\\)?" ivy-text) + ((eq (ivy-state-collection ivy-last) 'Info-read-node-name-1) + (if (or (equal ivy--current "(./)") + (equal ivy--current "(../)")) + (ivy-quit-and-run + (ivy-read "Go to file: " 'read-file-name-internal + :action (lambda (x) + (Info-find-node + (expand-file-name x ivy--directory) + "Top")))) + (ivy-done))) + ((string-match "\\`/\\([^/]+?\\):\\(?:\\(.*\\)@\\)?" ivy-text) (let ((method (match-string 1 ivy-text)) (user (match-string 2 ivy-text)) res) @@ -279,7 +303,7 @@ When ARG is t, exit with current text, ignoring the candidates." (when user (dolist (x res) (setcar x user))) - (setq res (cl-delete-duplicates res :test 'equal)) + (setq res (cl-delete-duplicates res :test #'equal)) (let ((host (ivy-read "Find File: " (mapcar #'ivy-build-tramp-name res)))) (when host @@ -296,8 +320,8 @@ When ARG is t, exit with current text, ignoring the candidates." "Complete the minibuffer text as much as possible. If the text hasn't changed as a result, forward to `ivy-alt-done'." (interactive) - (if (and (eq (ivy-state-collection ivy-last) 'read-file-name-internal) - (string-match "^/" ivy-text)) + (if (and (eq (ivy-state-collection ivy-last) #'read-file-name-internal) + (string-match "\\`/" ivy-text)) (let ((default-directory ivy--directory)) (minibuffer-complete) (setq ivy-text (ivy--input)) @@ -305,7 +329,9 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." (= ivy--length 1)) (ivy--cd (expand-file-name ivy-text)))) (or (ivy-partial) - (ivy-alt-done)))) + (when (or (eq this-command last-command) + (eq ivy--length 1)) + (ivy-alt-done))))) (defun ivy-partial () "Complete the minibuffer text as much as possible." @@ -313,14 +339,20 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." (let* ((parts (or (split-string ivy-text " " t) (list ""))) (postfix (car (last parts))) (completion-ignore-case t) - (new (try-completion postfix + (startp (string-match "^\\^" postfix)) + (new (try-completion (if startp + (substring postfix 1) + postfix) (mapcar (lambda (str) (substring str (string-match postfix str))) ivy--old-cands)))) (cond ((eq new t) nil) - ((string= new ivy-text) nil) + ((string= new ivy-text) nil) (new (delete-region (minibuffer-prompt-end) (point-max)) - (setcar (last parts) new) + (setcar (last parts) + (if startp + (concat "^" new) + new)) (insert (mapconcat #'identity parts " ") (if ivy-tab-space " " "")) t)))) @@ -329,7 +361,7 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." "Exit the minibuffer with the current input." (interactive) (delete-minibuffer-contents) - (insert ivy-text) + (insert (setq ivy--current ivy-text)) (setq ivy-exit 'done) (exit-minibuffer)) @@ -343,7 +375,9 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." :require-match (ivy-state-require-match ivy-last) :initial-input ivy-text :history (ivy-state-history ivy-last) - :preselect (regexp-quote ivy--current) + :preselect (unless (eq (ivy-state-collection ivy-last) + 'read-file-name-internal) + (regexp-quote ivy--current)) :keymap (ivy-state-keymap ivy-last) :update-fn (ivy-state-update-fn ivy-last) :sort (ivy-state-sort ivy-last) @@ -353,37 +387,48 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." :matcher (ivy-state-matcher ivy-last) :dynamic-collection (ivy-state-dynamic-collection ivy-last))) +(defvar ivy-calling nil + "When non-nil, call the current action when `ivy--index' changes.") + +(defun ivy-set-index (index) + "Set `ivy--index' to INDEX." + (setq ivy--index index) + (when ivy-calling + (ivy--exhibit) + (ivy-call))) + (defun ivy-beginning-of-buffer () "Select the first completion candidate." (interactive) - (setq ivy--index 0)) + (ivy-set-index 0)) (defun ivy-end-of-buffer () "Select the last completion candidate." (interactive) - (setq ivy--index (1- ivy--length))) + (ivy-set-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)))) + (ivy-set-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))) + (ivy-set-index (max (- ivy--index ivy-height) + 0))) (defun ivy-next-line (&optional arg) "Move cursor vertically down ARG candidates." (interactive "p") (setq arg (or arg 1)) - (cl-incf ivy--index arg) - (when (>= ivy--index (1- ivy--length)) - (if ivy-wrap - (ivy-beginning-of-buffer) - (setq ivy--index (1- ivy--length))))) + (let ((index (+ ivy--index arg))) + (if (> index (1- ivy--length)) + (if ivy-wrap + (ivy-beginning-of-buffer) + (ivy-set-index (1- ivy--length))) + (ivy-set-index index)))) (defun ivy-next-line-or-history (&optional arg) "Move cursor vertically down ARG candidates. @@ -397,11 +442,12 @@ If the input is empty, select the previous history element instead." "Move cursor vertically up ARG candidates." (interactive "p") (setq arg (or arg 1)) - (cl-decf ivy--index arg) - (when (< ivy--index 0) - (if ivy-wrap - (ivy-end-of-buffer) - (setq ivy--index 0)))) + (let ((index (- ivy--index arg))) + (if (< index 0) + (if ivy-wrap + (ivy-end-of-buffer) + (ivy-set-index 0)) + (ivy-set-index index)))) (defun ivy-previous-line-or-history (arg) "Move cursor vertically up ARG candidates. @@ -411,21 +457,33 @@ If the input is empty, select the previous history element instead." (ivy-previous-history-element 1)) (ivy-previous-line arg)) +(defun ivy-toggle-calling () + "Flip `ivy-calling'" + (interactive) + (when (setq ivy-calling (not ivy-calling)) + (ivy-call))) + +(defun ivy-call () + "Call the current action without exiting completion." + (when (ivy-state-action ivy-last) + (with-selected-window (ivy-state-window ivy-last) + (funcall (ivy-state-action ivy-last) ivy--current)))) + (defun ivy-next-line-and-call (&optional arg) - "Move cursor vertically down ARG candidates." + "Move cursor vertically down ARG candidates. +Call the permanent action if possible." (interactive "p") (ivy-next-line arg) (ivy--exhibit) - (with-selected-window (ivy-state-window ivy-last) - (funcall (ivy-state-action ivy-last)))) + (ivy-call)) (defun ivy-previous-line-and-call (&optional arg) - "Move cursor vertically down ARG candidates." + "Move cursor vertically down ARG candidates. +Call the permanent action if possible." (interactive "p") (ivy-previous-line arg) (ivy--exhibit) - (with-selected-window (ivy-state-window ivy-last) - (funcall (ivy-state-action ivy-last)))) + (ivy-call)) (defun ivy-previous-history-element (arg) "Forward to `previous-history-element' with ARG." @@ -486,6 +544,18 @@ On error (read-only), call `ivy-on-del-error-function'." (unless (= (point) (line-end-position)) (delete-char arg))) +(defun ivy-forward-char (arg) + "Forward to `forward-char' ARG." + (interactive "p") + (unless (= (point) (line-end-position)) + (forward-char arg))) + +(defun ivy-kill-word (arg) + "Forward to `kill-word' ARG." + (interactive "p") + (unless (= (point) (line-end-position)) + (kill-word arg))) + (defun ivy-backward-kill-word () "Forward to `backward-kill-word'." (interactive) @@ -544,6 +614,11 @@ The matches will be filtered in a sequence, you can mix the regexps that should match and that should not match as you like.") +(defvar ivy-initial-inputs-alist + '((org-refile . "^") + (counsel-M-x . "^")) + "Command to initial input table.") + (defcustom ivy-sort-max-size 30000 "Sorting won't be done for collections larger than this." :type 'integer) @@ -559,9 +634,9 @@ Directories come first." (setq seq (delete "./" (delete "../" seq))) (when (eq (setq sort-fn (cdr (assoc 'read-file-name-internal ivy-sort-functions-alist))) - 'ivy-sort-file-function-default) + #'ivy-sort-file-function-default) (setq seq (mapcar (lambda (x) - (propertize x 'dirp (string-match-p "/$" x))) + (propertize x 'dirp (string-match-p "/\\'" x))) seq))) (when sort-fn (setq seq (cl-sort seq sort-fn))) @@ -594,7 +669,8 @@ UPDATE-FN is called each time the current candidate(s) is changed. When SORT is t, refer to `ivy-sort-functions-alist' for sorting. -ACTION is a lambda to call after a result was selected. +ACTION is a lambda to call after a result was selected. It should +take a single argument, usually a string. UNWIND is a lambda to call before exiting. @@ -604,6 +680,9 @@ MATCHER can completely override matching. DYNAMIC-COLLECTION is a function to call to update the list of candidates with each input." + (unless initial-input + (setq initial-input (cdr (assoc this-command + ivy-initial-inputs-alist)))) (setq ivy-last (make-ivy-state :prompt prompt @@ -633,6 +712,7 @@ candidates with each input." (setq ivy--regexp-quote 'regexp-quote) (setq ivy--old-text "") (setq ivy-text "") + (setq ivy-calling nil) (let (coll sort-fn) (cond ((eq collection 'Info-read-node-name-1) (if (equal Info-current-file "dir") @@ -640,7 +720,7 @@ candidates with each input." (mapcar (lambda (x) (format "(%s)" x)) (cl-delete-duplicates (all-completions "(" collection predicate) - :test 'equal))) + :test #'equal))) (setq coll (all-completions "" collection predicate)))) ((eq collection 'read-file-name-internal) (setq ivy--directory default-directory) @@ -649,7 +729,8 @@ candidates with each input." (ivy--sorted-files default-directory)) (when initial-input (unless (or require-match - (equal initial-input default-directory)) + (equal initial-input default-directory) + (equal initial-input "")) (setq coll (cons initial-input coll))) (setq initial-input nil))) ((eq collection 'internal-complete-buffer) @@ -673,10 +754,11 @@ candidates with each input." (<= (length coll) ivy-sort-max-size)) (setq coll (cl-sort (copy-sequence coll) sort-fn)))))) (when preselect - (unless (or require-match - (cl-find-if `(lambda (x) - (string-match ,(format "^%s" preselect) x)) - coll)) + (unless (or (and require-match + (not (eq collection 'internal-complete-buffer))) + (let ((re (format "\\`%s" (regexp-quote preselect)))) + (cl-find-if (lambda (x) (string-match re x)) + coll))) (setq coll (cons preselect coll)))) (setq ivy--index (or (and dynamic-collection @@ -721,7 +803,7 @@ candidates with each input." (when (setq unwind (ivy-state-unwind ivy-last)) (funcall unwind))) (when (setq action (ivy-state-action ivy-last)) - (funcall action))))) + (funcall action ivy--current))))) (defun ivy-completing-read (prompt collection &optional predicate require-match initial-input @@ -744,7 +826,9 @@ The history, defaults and input-method arguments are ignored for now." (ivy-read prompt collection :predicate predicate :require-match require-match - :initial-input initial-input + :initial-input (if (consp initial-input) + (car initial-input) + initial-input) :preselect (if (listp def) (car def) def) :history history :keymap nil @@ -779,19 +863,16 @@ Minibuffer bindings: (lambda (x) (string-match initial-input x)) candidates))) - (or (cl-position preselect candidates :test 'equal) + (or (cl-position preselect candidates :test #'equal) (cl-position-if (lambda (x) - (string-match preselect x)) + (string-match (regexp-quote preselect) x)) candidates))) ;;* Implementation ;;** Regex -(defvar ivy--subexps 0 - "Number of groups in the current `ivy--regex'.") - (defvar ivy--regex-hash - (make-hash-table :test 'equal) + (make-hash-table :test #'equal) "Store pre-computed regex.") (defun ivy--split (str) @@ -799,17 +880,30 @@ Minibuffer bindings: The remaining spaces stick to their left. This allows to \"quote\" N spaces by inputting N+1 spaces." (let ((len (length str)) - (start 0) - res s) - (while (and (string-match " +" str start) - (< start len)) - (setq s (substring str start (1- (match-end 0)))) + start0 + (start1 0) + res s + match-len) + (while (and (string-match " +" str start1) + (< start1 len)) + (setq match-len (- (match-end 0) (match-beginning 0))) + (if (= match-len 1) + (progn + (when start0 + (setq start1 start0) + (setq start0 nil)) + (push (substring str start1 (match-beginning 0)) res) + (setq start1 (match-end 0))) + (setq str (replace-match + (make-string (1- match-len) ?\ ) + nil nil str)) + (setq start0 (or start0 start1)) + (setq start1 (1- (match-end 0))))) + (if start0 + (push (substring str start0) res) + (setq s (substring str start1)) (unless (= (length s) 0) - (push s res)) - (setq start (match-end 0))) - (setq s (substring str start)) - (unless (= (length s) 0) - (push s res)) + (push s res))) (nreverse res))) (defun ivy--regex (str &optional greedy) @@ -830,7 +924,7 @@ When GREEDY is non-nil, join words in a greedy way." (setq ivy--subexps (length subs)) (mapconcat (lambda (x) - (if (string-match "^\\\\(.*\\\\)$" x) + (if (string-match "\\`\\\\(.*\\\\)\\'" x) x (format "\\(%s\\)" x))) subs @@ -853,7 +947,7 @@ Ignore the order of each group." (let ((all (mapconcat #'identity subs "\\|"))) (mapconcat (lambda (x) - (if (string-match "^\\\\(.*\\\\)$" x) + (if (string-match "\\`\\\\(.*\\\\)\\'" x) x (format "\\(%s\\)" x))) (make-list len all) @@ -877,6 +971,15 @@ Everything after \"!\" should not match." res))) (t (error "Unexpected: use only one !"))))) +(defun ivy--regex-fuzzy (str) + "Build a regex sequence from STR. +Insert .* between each char." + (if (string-match "\\`\\(\\^?\\)\\(.*?\\)\\(\\$?\\)\\'" str) + (concat (match-string 1 str) + (mapconcat #'string (string-to-list (match-string 2 str)) ".*") + (match-string 3 str)) + str)) + ;;** Rest (defun ivy--minibuffer-setup () "Setup ivy completion in the minibuffer." @@ -902,12 +1005,6 @@ Everything after \"!\" should not match." (goto-char (minibuffer-prompt-end)) (delete-region (line-end-position) (point-max)))) -(defvar ivy--full-length nil - "When :dynamic-collection is non-nil, this can be the total amount of candidates.") - -(defvar ivy--old-text "" - "Store old `ivy-text' for dynamic completion.") - (defun ivy--insert-prompt () "Update the prompt according to `ivy--prompt'." (when ivy--prompt @@ -915,7 +1012,7 @@ Everything after \"!\" should not match." counsel-find-symbol)) (setq ivy--prompt-extra "")) (let (head tail) - (if (string-match "\\(.*\\): $" ivy--prompt) + (if (string-match "\\(.*\\): \\'" ivy--prompt) (progn (setq head (match-string 1 ivy--prompt)) (setq tail ": ")) @@ -925,12 +1022,17 @@ Everything after \"!\" should not match." (std-props '(front-sticky t rear-nonsticky t field t read-only t)) (n-str (format - (concat head - ivy--prompt-extra - tail - (if ivy--directory - (abbreviate-file-name ivy--directory) - "")) + (concat + (if (and (bound-and-true-p minibuffer-depth-indicate-mode) + (> (minibuffer-depth) 1)) + (format "[%d] " (minibuffer-depth)) + "") + head + ivy--prompt-extra + tail + (if ivy--directory + (abbreviate-file-name ivy--directory) + "")) (or (and (ivy-state-dynamic-collection ivy-last) ivy--full-length) ivy--length)))) @@ -967,25 +1069,27 @@ Should be run via minibuffer `post-command-hook'." ;; while-no-input would cause annoying ;; "Waiting for process to die...done" message interruptions (let ((inhibit-message t)) - (while-no-input - (unless (equal ivy--old-text ivy-text) - (cl-letf ((store (ivy-state-dynamic-collection ivy-last)) - ((ivy-state-dynamic-collection ivy-last) nil)) - (setq ivy--all-candidates (funcall store ivy-text)))) - (ivy--insert-minibuffer (ivy--format ivy--all-candidates)))) + (unless (equal ivy--old-text ivy-text) + (while-no-input + ;; dynamic collection should take care of everything + (funcall (ivy-state-dynamic-collection ivy-last) ivy-text) + (setq ivy--old-text ivy-text))) + (unless (eq ivy--full-length -1) + (ivy--insert-minibuffer + (ivy--format ivy--all-candidates)))) (cond (ivy--directory - (if (string-match "/$" ivy-text) + (if (string-match "/\\'" ivy-text) (if (member ivy-text ivy--all-candidates) (ivy--cd (expand-file-name ivy-text ivy--directory)) - (when (string-match "//$" ivy-text) + (when (string-match "//\\'" ivy-text) (ivy--cd "/"))) - (if (string-match "~$" ivy-text) + (if (string-match "~\\'" ivy-text) (ivy--cd (expand-file-name "~/"))))) ((eq (ivy-state-collection ivy-last) 'internal-complete-buffer) - (when (or (and (string-match "^ " ivy-text) - (not (string-match "^ " ivy--old-text))) - (and (string-match "^ " ivy--old-text) - (not (string-match "^ " ivy-text)))) + (when (or (and (string-match "\\` " ivy-text) + (not (string-match "\\` " ivy--old-text))) + (and (string-match "\\` " ivy--old-text) + (not (string-match "\\` " ivy-text)))) (setq ivy--all-candidates (if (and (> (length ivy-text) 0) (eq (aref ivy-text 0) @@ -995,8 +1099,8 @@ Should be run via minibuffer `post-command-hook'." (setq ivy--old-re nil)))) (ivy--insert-minibuffer (ivy--format - (ivy--filter ivy-text ivy--all-candidates)))) - (setq ivy--old-text ivy-text)) + (ivy--filter ivy-text ivy--all-candidates))) + (setq ivy--old-text ivy-text))) (defun ivy--insert-minibuffer (text) "Insert TEXT into minibuffer with appropriate cleanup." @@ -1014,6 +1118,8 @@ Should be run via minibuffer `post-command-hook'." (forward-line 1) (insert text))))) +(declare-function colir-blend-face-background "ext:colir") + (defun ivy--add-face (str face) "Propertize STR with FACE. `font-lock-append-text-property' is used, since it's better than @@ -1033,8 +1139,7 @@ CANDIDATES are assumed to be static." (matcher (ivy-state-matcher ivy-last)) (cands (cond (matcher - (let ((ivy--old-re re)) - (cl-remove-if-not matcher candidates))) + (funcall matcher re candidates)) ((and (equal re ivy--old-re) ivy--old-cands) ivy--old-cands) @@ -1044,10 +1149,11 @@ CANDIDATES are assumed to be static." (not (string-match "\\\\" ivy--old-re)) (not (equal ivy--old-re "")) (memq (cl-search - (if (string-match "\\\\)$" ivy--old-re) + (if (string-match "\\\\)\\'" ivy--old-re) (substring ivy--old-re 0 -2) ivy--old-re) - re) '(0 2))) + re) + '(0 2))) (ignore-errors (cl-remove-if-not (lambda (x) (string-match re x)) @@ -1062,7 +1168,8 @@ CANDIDATES are assumed to be static." (if (cdr re) #'cl-remove-if-not #'cl-remove-if) - `(lambda (x) (string-match ,(car re) x)) + (let ((re (car re))) + (lambda (x) (string-match re x))) res)))) res)))) (tail (nthcdr ivy--index ivy--old-cands)) @@ -1072,11 +1179,11 @@ CANDIDATES are assumed to be static." (or (setq ivy--index (or (cl-position re cands - :test 'equal) + :test #'equal) (and ivy--directory (cl-position (concat re "/") cands - :test 'equal)))))) + :test #'equal)))))) (while (and tail (null idx)) ;; Compare with eq to handle equal duplicates in cands (setq idx (cl-position (pop tail) cands))) @@ -1084,7 +1191,7 @@ CANDIDATES are assumed to be static." (when (and (string= name "") (not (equal ivy--old-re ""))) (setq ivy--index (or (cl-position (ivy-state-preselect ivy-last) - cands :test 'equal) + cands :test #'equal) ivy--index))) (setq ivy--old-re (if cands re "")) (setq ivy--old-cands cands))) @@ -1129,7 +1236,7 @@ CANDS is a list of strings." (index (min ivy--index half-height (1- (length cands))))) (when ivy--directory (setq cands (mapcar (lambda (x) - (if (string-match-p "/$" x) + (if (string-match-p "/\\'" x) (propertize x 'face 'ivy-subdir) x)) cands))) @@ -1145,7 +1252,9 @@ CANDS is a list of strings." "Store the virtual buffers alist.") (defvar recentf-list) -(defvar ido-use-faces) + +(defface ivy-virtual '((t :inherit font-lock-builtin-face)) + "Face used by Ivy for matching virtual buffer names.") (defun ivy--virtual-buffers () "Adapted from `ido-add-virtual-buffers-to-list'." @@ -1170,11 +1279,10 @@ CANDS is a list of strings." (not (assoc name virtual-buffers)) (push (cons name head) virtual-buffers))) (when virtual-buffers - (if ido-use-faces - (dolist (comp virtual-buffers) - (put-text-property 0 (length (car comp)) - 'face 'ido-virtual - (car comp)))) + (dolist (comp virtual-buffers) + (put-text-property 0 (length (car comp)) + 'face 'ivy-virtual + (car comp))) (setq ivy--virtual-buffers (nreverse virtual-buffers)) (mapcar #'car ivy--virtual-buffers)))) @@ -1194,16 +1302,18 @@ When VIRTUAL is non-nil, add virtual buffers." (and virtual (ivy--virtual-buffers))))) -(defun ivy--switch-buffer-action () - "Finalizer for `ivy-switch-buffer'." - (if (zerop (length ivy--current)) +(defun ivy--switch-buffer-action (buffer) + "Switch to BUFFER. +BUFFER may be a string or nil." + (if (zerop (length buffer)) (switch-to-buffer ivy-text nil 'force-same-window) - (let ((virtual (assoc ivy--current ivy--virtual-buffers))) - (if virtual + (let ((virtual (assoc buffer ivy--virtual-buffers))) + (if (and virtual + (not (get-buffer buffer))) (find-file (cdr virtual)) (switch-to-buffer - ivy--current nil 'force-same-window))))) + buffer nil 'force-same-window))))) (defun ivy-switch-buffer () "Switch to another buffer." @@ -1211,8 +1321,28 @@ When VIRTUAL is non-nil, add virtual buffers." (if (not ivy-mode) (call-interactively 'switch-to-buffer) (ivy-read "Switch to buffer: " 'internal-complete-buffer - :action #'ivy--switch-buffer-action - :preselect (buffer-name (other-buffer (current-buffer)))))) + :preselect (buffer-name (other-buffer (current-buffer))) + :action #'ivy--switch-buffer-action))) + +(defun ivy-recentf () + "Find a file on `recentf-list'." + (interactive) + (ivy-read "Recentf: " recentf-list + :action #'find-file)) + +(defun ivy-yank-word () + "Pull next word from buffer into search string." + (interactive) + (let (amend) + (with-selected-window (ivy-state-window ivy-last) + (let ((pt (point)) + (le (line-end-position))) + (forward-word 1) + (if (> (point) le) + (goto-char pt) + (setq amend (buffer-substring-no-properties pt (point)))))) + (when amend + (insert amend)))) (provide 'ivy)