X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/7a0b24302e9f0d04ce775120da2520eb9de66fa0..0b51e8e407488b1855c4cacae90560ccb9142a46:/ivy.el diff --git a/ivy.el b/ivy.el index 5e59717ec..091e575e9 100644 --- a/ivy.el +++ b/ivy.el @@ -93,15 +93,15 @@ "Face used by Ivy for a match required prompt.") (defface ivy-subdir - '((t (:inherit 'dired-directory))) + '((t :inherit dired-directory)) "Face used by Ivy for highlighting subdirs in the alternatives.") (defface ivy-modified-buffer - '((t :inherit 'default)) + '((t :inherit default)) "Face used by Ivy for highlighting modified file visiting buffers.") (defface ivy-remote - '((t (:foreground "#110099"))) + '((t :foreground "#110099")) "Face used by Ivy for highlighting remotes in the alternatives.") (defface ivy-virtual @@ -124,6 +124,10 @@ Set this to \"(%d/%d) \" to display both the index and the count." (const :tag "Count matches and show current match" "(%d/%d) ") string)) +(defcustom ivy-add-newline-after-prompt nil + "When non-nil, add a newline after the `ivy-read' prompt." + :type 'boolean) + (defcustom ivy-wrap nil "When non-nil, wrap around after the first and the last candidate." :type 'boolean) @@ -168,6 +172,21 @@ Only \"./\" and \"../\" apply here. They appear in reverse order." (setq ivy--actions-list (plist-put ivy--actions-list cmd actions))) +(defvar ivy--display-transformers-list nil + "A list of str->str transformers per command.") + +(defun ivy-set-display-transformer (cmd transformer) + "Set CMD a displayed candidate TRANSFORMER. + +It's a lambda that takes a string one of the candidates in the +collection and returns a string for display, the same candidate +plus some extra information. + +This lambda is called only on the `ivy-height' candidates that +are about to be displayed, not on the whole collection." + (setq ivy--display-transformers-list + (plist-put ivy--display-transformers-list cmd transformer))) + (defvar ivy--sources-list nil "A list of extra sources per command.") @@ -204,25 +223,23 @@ Example: (define-key map (kbd "C-j") 'ivy-alt-done) (define-key map (kbd "C-M-j") 'ivy-immediate-done) (define-key map (kbd "TAB") 'ivy-partial-or-done) - (define-key map (kbd "C-n") 'ivy-next-line) - (define-key map (kbd "C-p") 'ivy-previous-line) - (define-key map (kbd "") 'ivy-next-line) - (define-key map (kbd "") 'ivy-previous-line) + (define-key map [remap next-line] 'ivy-next-line) + (define-key map [remap previous-line] 'ivy-previous-line) (define-key map (kbd "C-s") 'ivy-next-line-or-history) (define-key map (kbd "C-r") 'ivy-reverse-i-search) (define-key map (kbd "SPC") 'self-insert-command) (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 [remap backward-kill-word] 'ivy-backward-kill-word) + (define-key map [remap delete-char] 'ivy-delete-char) + (define-key map [remap forward-char] 'ivy-forward-char) + (define-key map [remap kill-word] 'ivy-kill-word) + (define-key map [remap beginning-of-buffer] 'ivy-beginning-of-buffer) + (define-key map [remap end-of-buffer] 'ivy-end-of-buffer) (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) + (define-key map [remap scroll-up-command] 'ivy-scroll-up-command) + (define-key map [remap scroll-down-command] 'ivy-scroll-down-command) (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) @@ -231,14 +248,14 @@ Example: (define-key map (kbd "C-o") 'hydra-ivy/body) (define-key map (kbd "M-o") 'ivy-dispatching-done) (define-key map (kbd "C-M-o") 'ivy-dispatching-call) - (define-key map (kbd "C-k") 'ivy-kill-line) + (define-key map [remap kill-line] 'ivy-kill-line) (define-key map (kbd "S-SPC") 'ivy-restrict-to-matches) - (define-key map (kbd "M-w") 'ivy-kill-ring-save) + (define-key map [remap kill-ring-save] 'ivy-kill-ring-save) (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) + (define-key map [remap describe-mode] 'ivy-help) map) "Keymap used in the minibuffer.") (autoload 'hydra-ivy/body "ivy-hydra" "" t) @@ -269,6 +286,8 @@ Example: matcher ;; When this is non-nil, call it for each input change to get new candidates dynamic-collection + ;; A lambda that transforms candidates only for display + display-transformer-fn caller) (defvar ivy-last (make-ivy-state) @@ -284,6 +303,8 @@ This should eventually become a stack so that you could use "Return a string that corresponds to the current thing at point." (or (thing-at-point 'url) + (and (eq (ivy-state-collection ivy-last) 'read-file-name-internal) + (ffap-file-at-point)) (let (s) (cond ((stringp (setq s (thing-at-point 'symbol))) (if (string-match "\\`[`']?\\(.*?\\)'?\\'" s) @@ -370,6 +391,18 @@ When non-nil, it should contain at least one %d.") (defvar Info-current-file) +(eval-and-compile + (unless (fboundp 'defvar-local) + (defmacro defvar-local (var val &optional docstring) + "Define VAR as a buffer-local variable with default value VAL." + (declare (debug defvar) (doc-string 3)) + (list 'progn (list 'defvar var val docstring) + (list 'make-variable-buffer-local (list 'quote var))))) + (unless (fboundp 'setq-local) + (defmacro setq-local (var val) + "Set variable VAR to value VAL in current buffer." + (list 'set (list 'make-local-variable (list 'quote var)) val)))) + (defmacro ivy-quit-and-run (&rest body) "Quit the minibuffer and run BODY afterwards." `(progn @@ -503,8 +536,7 @@ When ARG is t, exit with current text, ignoring the candidates." (ivy--directory (ivy--directory-done)) ((eq (ivy-state-collection ivy-last) 'Info-read-node-name-1) - (if (or (equal ivy--current "(./)") - (equal ivy--current "(../)")) + (if (member ivy--current '("(./)" "(../)")) (ivy-quit-and-run (ivy-read "Go to file: " 'read-file-name-internal :action (lambda (x) @@ -523,23 +555,10 @@ When ARG is t, exit with current text, ignoring the candidates." (setq dir (concat ivy-text ivy--directory)) (ivy--cd dir) (ivy--exhibit)) - ((or - (and - (not (equal ivy-text "")) - (ignore-errors - (file-directory-p - (setq dir - (file-name-as-directory - (expand-file-name - ivy-text ivy--directory)))))) - (and - (not (string= ivy--current "./")) - (cl-plusp ivy--length) - (ignore-errors - (file-directory-p - (setq dir (file-name-as-directory - (expand-file-name - ivy--current ivy--directory))))))) + ((and + (> ivy--length 0) + (not (string= ivy--current "./")) + (setq dir (ivy-expand-file-if-directory ivy--current))) (ivy--cd dir) (ivy--exhibit)) ((or (and (equal ivy--directory "/") @@ -581,6 +600,18 @@ When ARG is t, exit with current text, ignoring the candidates." (t (ivy-done))))) +(defun ivy-expand-file-if-directory (file-name) + "Expand FILE-NAME as directory. +When this directory doesn't exist, return nil." + (when (stringp file-name) + (let ((full-name + ;; Ignore host name must not match method "ssh" + (ignore-errors + (file-name-as-directory + (expand-file-name file-name ivy--directory))))) + (when (and full-name (file-directory-p full-name)) + full-name)))) + (defcustom ivy-tab-space nil "When non-nil, `ivy-partial-or-done' should insert a space." :type 'boolean) @@ -593,13 +624,12 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." (or (and (equal ivy--directory "/") (string-match "\\`[^/]+:.*\\'" ivy-text)) (string-match "\\`/" ivy-text))) - (let ((default-directory ivy--directory)) + (let ((default-directory ivy--directory) + dir) (minibuffer-complete) (setq ivy-text (ivy--input)) - (when (file-directory-p - (expand-file-name ivy-text ivy--directory)) - (ivy--cd (file-name-as-directory - (expand-file-name ivy-text ivy--directory))))) + (when (setq dir (ivy-expand-file-if-directory ivy-text)) + (ivy--cd dir))) (or (ivy-partial) (when (or (eq this-command last-command) (eq ivy--length 1)) @@ -647,30 +677,32 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." (defun ivy-resume () "Resume the last completion session." (interactive) - (when (eq (ivy-state-caller ivy-last) 'swiper) - (switch-to-buffer (ivy-state-buffer ivy-last))) - (with-current-buffer (ivy-state-buffer ivy-last) - (ivy-read - (ivy-state-prompt ivy-last) - (ivy-state-collection ivy-last) - :predicate (ivy-state-predicate ivy-last) - :require-match (ivy-state-require-match ivy-last) - :initial-input ivy-text - :history (ivy-state-history ivy-last) - :preselect (unless (eq (ivy-state-collection ivy-last) - 'read-file-name-internal) - ivy--current) - :keymap (ivy-state-keymap ivy-last) - :update-fn (ivy-state-update-fn ivy-last) - :sort (ivy-state-sort ivy-last) - :action (ivy-state-action ivy-last) - :unwind (ivy-state-unwind ivy-last) - :re-builder (ivy-state-re-builder ivy-last) - :matcher (ivy-state-matcher ivy-last) - :dynamic-collection (ivy-state-dynamic-collection ivy-last) - :caller (ivy-state-caller ivy-last)))) - -(defvar ivy-calling nil + (if (null (ivy-state-action ivy-last)) + (user-error "The last session isn't compatible with `ivy-resume'") + (when (eq (ivy-state-caller ivy-last) 'swiper) + (switch-to-buffer (ivy-state-buffer ivy-last))) + (with-current-buffer (ivy-state-buffer ivy-last) + (ivy-read + (ivy-state-prompt ivy-last) + (ivy-state-collection ivy-last) + :predicate (ivy-state-predicate ivy-last) + :require-match (ivy-state-require-match ivy-last) + :initial-input ivy-text + :history (ivy-state-history ivy-last) + :preselect (unless (eq (ivy-state-collection ivy-last) + 'read-file-name-internal) + ivy--current) + :keymap (ivy-state-keymap ivy-last) + :update-fn (ivy-state-update-fn ivy-last) + :sort (ivy-state-sort ivy-last) + :action (ivy-state-action ivy-last) + :unwind (ivy-state-unwind ivy-last) + :re-builder (ivy-state-re-builder ivy-last) + :matcher (ivy-state-matcher ivy-last) + :dynamic-collection (ivy-state-dynamic-collection ivy-last) + :caller (ivy-state-caller ivy-last))))) + +(defvar-local ivy-calling nil "When non-nil, call the current action when `ivy--index' changes.") (defun ivy-set-index (index) @@ -878,7 +910,10 @@ Call the permanent action if possible." (progn (insert ivy--default) (when (and (with-ivy-window (derived-mode-p 'prog-mode)) + (eq (ivy-state-caller ivy-last) 'swiper) (not (file-exists-p ivy--default)) + (not (ffap-url-p ivy--default)) + (not (ivy-state-dynamic-collection ivy-last)) (> (point) (minibuffer-prompt-end))) (undo-boundary) (insert "\\_>") @@ -1049,9 +1084,10 @@ On error (read-only), call `ivy-on-del-error-function'." (avy--process (nreverse candidates) (avy--style-fn avy-style))))) - (ivy-set-index (- (line-number-at-pos candidate) 2)) - (ivy--exhibit) - (ivy-done))) + (when (numberp candidate) + (ivy-set-index (- (line-number-at-pos candidate) 2)) + (ivy--exhibit) + (ivy-done)))) (defun ivy-sort-file-function-default (x y) "Compare two files X and Y. @@ -1089,7 +1125,8 @@ See also `ivy-sort-max-size'." :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 "No sort" nil) + (function :tag "Custom function"))) :group 'ivy) (defvar ivy-index-functions-alist @@ -1241,7 +1278,11 @@ customizations apply to the current completion session." (list (car source) (funcall (car source))) ivy--extra-candidates)))))) (setq ivy--extra-candidates '((original-source))))) - (let ((recursive-ivy-last (and (active-minibuffer-window) ivy-last))) + (let ((recursive-ivy-last (and (active-minibuffer-window) ivy-last)) + (transformer-fn + (plist-get ivy--display-transformers-list + (or caller (and (functionp collection) + collection))))) (setq ivy-last (make-ivy-state :prompt prompt @@ -1261,6 +1302,7 @@ customizations apply to the current completion session." :re-builder re-builder :matcher matcher :dynamic-collection dynamic-collection + :display-transformer-fn transformer-fn :caller caller)) (ivy--reset-state ivy-last) (prog1 @@ -1366,7 +1408,7 @@ This is useful for recursive `ivy-read'." (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))) + (setq coll (ivy--buffer-list "" ivy-use-virtual-buffers predicate))) (dynamic-collection (setq coll (funcall collection ivy-text))) ((or (functionp collection) @@ -1388,6 +1430,7 @@ This is useful for recursive `ivy-read'." (if (and (setq sort-fn (cdr (assoc t ivy-sort-functions-alist))) (<= (length coll) ivy-sort-max-size)) (setq coll (cl-sort (copy-sequence coll) sort-fn)))))) + (setq coll (ivy--set-candidates coll)) (when preselect (unless (or (and require-match (not (eq collection 'internal-complete-buffer))) @@ -1446,7 +1489,7 @@ This is useful for recursive `ivy-read'." (ivy--directory prompt) (t - nil))) + prompt))) (setf (ivy-state-initial-input ivy-last) initial-input))) ;;;###autoload @@ -1492,7 +1535,8 @@ INHERIT-INPUT-METHOD is currently ignored." :history history :keymap nil :sort - (let ((sort (assoc this-command ivy-sort-functions-alist))) + (let ((sort (or (assoc this-command ivy-sort-functions-alist) + (assoc t ivy-sort-functions-alist)))) (if sort (cdr sort) t))))) @@ -1751,6 +1795,7 @@ depending on the number of candidates." (set (make-local-variable 'minibuffer-default-add-function) (lambda () (list ivy--default))) + (set (make-local-variable 'inhibit-field-text-motion) nil) (when (display-graphic-p) (setq truncate-lines t)) (setq-local max-mini-window-height ivy-height) @@ -1818,6 +1863,8 @@ depending on the number of candidates." (window-width)) (setq n-str (concat n-str "\n" d-str)) (setq n-str (concat n-str d-str))) + (when ivy-add-newline-after-prompt + (setq n-str (concat n-str "\n"))) (let ((regex (format "\\([^\n]\\{%d\\}\\)[^\n]" (window-width)))) (while (string-match regex n-str) (setq n-str (replace-match (concat (match-string 1 n-str) "\n") nil t n-str 1)))) @@ -2153,7 +2200,7 @@ Prefix matches to NAME are put ahead of the list." (not (and (require 'flx nil 'noerror) (eq ivy--regex-function 'ivy--regex-fuzzy) (< (length cands) 200))) - + ivy--old-cands (cl-position (nth ivy--index ivy--old-cands) cands)) (funcall func re-str cands)))) @@ -2190,16 +2237,26 @@ Prefix matches to NAME are put ahead of the list." res))))) (defun ivy-recompute-index-swiper-async (_re-str cands) - (let ((tail (nthcdr ivy--index ivy--old-cands)) - idx) - (if (and tail ivy--old-cands (not (equal "^" ivy--old-re))) - (progn - (while (and tail (null idx)) - ;; Compare with `equal', since the collection is re-created - ;; each time with `split-string' - (setq idx (cl-position (pop tail) cands :test #'equal))) - (or idx 0)) - ivy--index))) + (if (null ivy--old-cands) + (let ((ln (with-ivy-window + (line-number-at-pos)))) + (or + ;; closest to current line going forwards + (cl-position-if (lambda (x) + (>= (string-to-number x) ln)) + cands) + ;; closest to current line going backwards + (1- (length cands)))) + (let ((tail (nthcdr ivy--index ivy--old-cands)) + idx) + (if (and tail ivy--old-cands (not (equal "^" ivy--old-re))) + (progn + (while (and tail (null idx)) + ;; Compare with `equal', since the collection is re-created + ;; each time with `split-string' + (setq idx (cl-position (pop tail) cands :test #'equal))) + (or idx 0)) + ivy--index)))) (defun ivy-recompute-index-zero (_re-str _cands) 0) @@ -2278,49 +2335,47 @@ This string is inserted into the minibuffer." (- (length str) 3))) "...") str)) -(defun ivy--format-function-generic (selected-fn other-fn cand-pairs separator) +(defun ivy--format-function-generic (selected-fn other-fn strs separator) "Transform CAND-PAIRS into a string for minibuffer. -SELECTED-FN and OTHER-FN each take two string arguments. +SELECTED-FN and OTHER-FN each take one string argument. SEPARATOR is used to join the candidates." (let ((i -1)) (mapconcat - (lambda (pair) - (let ((str (car pair)) - (extra (cdr pair)) - (curr (eq (cl-incf i) ivy--index))) + (lambda (str) + (let ((curr (eq (cl-incf i) ivy--index))) (if curr - (funcall selected-fn str extra) - (funcall other-fn str extra)))) - cand-pairs + (funcall selected-fn str) + (funcall other-fn str)))) + strs separator))) -(defun ivy-format-function-default (cand-pairs) +(defun ivy-format-function-default (cands) "Transform CAND-PAIRS into a string for minibuffer." (ivy--format-function-generic - (lambda (str extra) - (concat (ivy--add-face str 'ivy-current-match) extra)) - #'concat - cand-pairs + (lambda (str) + (ivy--add-face str 'ivy-current-match)) + #'identity + cands "\n")) -(defun ivy-format-function-arrow (cand-pairs) +(defun ivy-format-function-arrow (cands) "Transform CAND-PAIRS into a string for minibuffer." (ivy--format-function-generic - (lambda (str extra) - (concat "> " (ivy--add-face str 'ivy-current-match) extra)) - (lambda (str extra) - (concat " " str extra)) - cand-pairs + (lambda (str) + (concat "> " (ivy--add-face str 'ivy-current-match))) + (lambda (str) + (concat " " str)) + cands "\n")) -(defun ivy-format-function-line (cand-pairs) +(defun ivy-format-function-line (cands) "Transform CAND-PAIRS into a string for minibuffer." (ivy--format-function-generic - (lambda (str extra) - (ivy--add-face (concat str extra "\n") 'ivy-current-match)) - (lambda (str extra) - (concat str extra "\n")) - cand-pairs + (lambda (str) + (ivy--add-face (concat str "\n") 'ivy-current-match)) + (lambda (str) + (concat str "\n")) + cands "")) (defun ivy-add-face-text-property (start end face str) @@ -2367,6 +2422,16 @@ SEPARATOR is used to join the candidates." (cl-incf i)))))) str)) +(ivy-set-display-transformer + 'counsel-find-file 'ivy-read-file-transformer) +(ivy-set-display-transformer + 'read-file-name-internal 'ivy-read-file-transformer) + +(defun ivy-read-file-transformer (str) + (if (string-match-p "/\\'" str) + (propertize str 'face 'ivy-subdir) + str)) + (defun ivy--format (cands) "Return a string for CANDS suitable for display in the minibuffer. CANDS is a list of strings." @@ -2380,28 +2445,17 @@ CANDS is a list of strings." (end (min (+ start (1- ivy-height)) ivy--length)) (start (max 0 (min start (- end (1- ivy-height))))) (cands (cl-subseq cands start end)) - (index (- ivy--index start))) - (cond (ivy--directory - (setq cands (mapcar (lambda (x) - (if (string-match-p "/\\'" x) - (propertize x 'face 'ivy-subdir) - x)) - cands))) - ((eq (ivy-state-collection ivy-last) 'internal-complete-buffer) - (setq cands (mapcar (lambda (x) - (let ((b (get-buffer x))) - (if (and b - (buffer-file-name b) - (buffer-modified-p b)) - (propertize x 'face 'ivy-modified-buffer) - x))) - cands)))) + (index (- ivy--index start)) + transformer-fn) (setq ivy--current (copy-sequence (nth index cands))) + (when (setq transformer-fn (ivy-state-display-transformer-fn ivy-last)) + (with-ivy-window + (setq cands (mapcar transformer-fn cands)))) (let* ((ivy--index index) - (cand-pairs (mapcar - (lambda (cand) - (cons (ivy--format-minibuffer-line cand) nil)) cands)) - (res (concat "\n" (funcall ivy-format-function cand-pairs)))) + (cands (mapcar + #'ivy--format-minibuffer-line + cands)) + (res (concat "\n" (funcall ivy-format-function cands)))) (put-text-property 0 (length res) 'read-only nil res) res)))) @@ -2451,11 +2505,11 @@ CANDS is a list of strings." (setq ivy--virtual-buffers (nreverse virtual-buffers)) (mapcar #'car ivy--virtual-buffers)))) -(defcustom ivy-ignore-buffers nil - "List of regexps matching buffer names to ignore." - :type '(repeat regexp)) +(defcustom ivy-ignore-buffers '("\\` ") + "List of regexps or functions matching buffer names to ignore." + :type '(repeat (choice regexp function))) -(defun ivy--buffer-list (str &optional virtual) +(defun ivy--buffer-list (str &optional virtual predicate) "Return the buffers that match STR. When VIRTUAL is non-nil, add virtual buffers." (delete-dups @@ -2467,7 +2521,7 @@ When VIRTUAL is non-nil, add virtual buffers." (abbreviate-file-name default-directory))) (propertize x 'face 'ivy-remote) x)) - (all-completions str 'internal-complete-buffer)) + (all-completions str 'internal-complete-buffer predicate)) (and virtual (ivy--virtual-buffers))))) @@ -2528,25 +2582,45 @@ Skip buffers that match `ivy-ignore-buffers'." (or (cl-remove-if (lambda (buf) (cl-find-if - (lambda (regexp) - (string-match regexp buf)) + (lambda (f-or-r) + (if (functionp f-or-r) + (funcall f-or-r buf) + (string-match-p f-or-r buf))) ivy-ignore-buffers)) res) res)))) +(ivy-set-display-transformer + 'ivy-switch-buffer 'ivy-switch-buffer-transformer) +(ivy-set-display-transformer + 'internal-complete-buffer 'ivy-switch-buffer-transformer) + +(defun ivy-switch-buffer-transformer (str) + (let ((b (get-buffer str))) + (if (and b + (buffer-file-name b) + (buffer-modified-p b)) + (propertize str 'face 'ivy-modified-buffer) + str))) + +(defun ivy-switch-buffer-occur () + "Occur function for `ivy-switch-buffer' that uses `ibuffer'." + (let* ((cand-regexp + (concat "\\(" (mapconcat #'regexp-quote ivy--old-cands "\\|") "\\)")) + (new-qualifier `((name . ,cand-regexp)))) + (ibuffer nil (buffer-name) new-qualifier))) + ;;;###autoload (defun ivy-switch-buffer () "Switch to another buffer." (interactive) - (if (not ivy-mode) - (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 - :caller 'ivy-switch-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 + :caller 'ivy-switch-buffer))) ;;;###autoload (defun ivy-switch-buffer-other-window () @@ -2647,17 +2721,45 @@ buffer would modify `ivy-last'.") (let ((map (make-sparse-keymap))) (define-key map [mouse-1] 'ivy-occur-click) (define-key map (kbd "RET") 'ivy-occur-press) - (define-key map (kbd "j") 'next-line) - (define-key map (kbd "k") 'previous-line) + (define-key map (kbd "j") 'ivy-occur-next-line) + (define-key map (kbd "k") 'ivy-occur-previous-line) (define-key map (kbd "h") 'backward-char) (define-key map (kbd "l") 'forward-char) - (define-key map (kbd "g") 'ivy-occur-press) + (define-key map (kbd "f") 'ivy-occur-press) + (define-key map (kbd "g") 'ivy-occur-revert-buffer) (define-key map (kbd "a") 'ivy-occur-read-action) (define-key map (kbd "o") 'ivy-occur-dispatch) + (define-key map (kbd "c") 'ivy-occur-toggle-calling) (define-key map (kbd "q") 'quit-window) map) "Keymap for Ivy Occur mode.") +(defun ivy-occur-toggle-calling () + "Toggle `ivy-calling'." + (interactive) + (if (setq ivy-calling (not ivy-calling)) + (progn + (setq mode-name "Ivy-Occur [calling]") + (ivy-occur-press)) + (setq mode-name "Ivy-Occur")) + (force-mode-line-update)) + +(defun ivy-occur-next-line (&optional arg) + "Move the cursor down ARG lines. +When `ivy-calling' isn't nil, call `ivy-occur-press'." + (interactive "p") + (forward-line arg) + (when ivy-calling + (ivy-occur-press))) + +(defun ivy-occur-previous-line (&optional arg) + "Move the cursor up ARG lines. +When `ivy-calling' isn't nil, call `ivy-occur-press'." + (interactive "p") + (forward-line (- arg)) + (when ivy-calling + (ivy-occur-press))) + (define-derived-mode ivy-occur-mode fundamental-mode "Ivy-Occur" "Major mode for output from \\[ivy-occur]. @@ -2682,6 +2784,9 @@ buffer would modify `ivy-last'.") (setq ivy--occurs-list (plist-put ivy--occurs-list cmd occur))) +(ivy-set-occur 'ivy-switch-buffer 'ivy-switch-buffer-occur) +(ivy-set-occur 'ivy-switch-buffer-other-window 'ivy-switch-buffer-occur) + (defun ivy--occur-insert-lines (cands) (dolist (str cands) (add-text-properties @@ -2730,6 +2835,30 @@ There is no limit on the number of *ivy-occur* buffers." (ivy-exit-with-action `(lambda (_) (pop-to-buffer ,buffer)))))) +(defun ivy-occur-revert-buffer () + "Refresh the buffer making it up-to date with the collection. + +Currently only works for `swiper'. In that specific case, the +*ivy-occur* buffer becomes nearly useless as the orignal buffer +is updated, since the line numbers no longer match. + +Calling this function is as if you called `ivy-occur' on the +updated original buffer." + (interactive) + (let ((caller (ivy-state-caller ivy-occur-last)) + (ivy-last ivy-occur-last)) + (cond ((eq caller 'swiper) + (let ((buffer (ivy-state-buffer ivy-occur-last))) + (unless (buffer-live-p buffer) + (error "buffer was killed")) + (let ((inhibit-read-only t)) + (erase-buffer) + (funcall (plist-get ivy--occurs-list caller) t)))) + ((memq caller '(counsel-git-grep counsel-grep counsel-ag)) + (let ((inhibit-read-only t)) + (erase-buffer) + (funcall (plist-get ivy--occurs-list caller))))))) + (declare-function wgrep-change-to-wgrep-mode "ext:wgrep") (defun ivy-wgrep-change-to-wgrep-mode () @@ -2774,10 +2903,19 @@ EVENT gives the mouse position." (defun ivy-occur-press () "Execute action for the current candidate." (interactive) - (require 'pulse) (when (save-excursion (beginning-of-line) (looking-at "\\(?:./\\| \\)\\(.*\\)$")) + (when (memq (ivy-state-caller ivy-occur-last) + '(swiper counsel-git-grep counsel-grep counsel-ag + counsel-describe-function counsel-describe-variable)) + (let ((window (ivy-state-window ivy-occur-last))) + (when (or (null (window-live-p window)) + (equal window (selected-window))) + (save-selected-window + (setf (ivy-state-window ivy-occur-last) + (display-buffer (ivy-state-buffer ivy-occur-last) + 'display-buffer-pop-up-window)))))) (let* ((ivy-last ivy-occur-last) (ivy-text (ivy-state-text ivy-last)) (str (buffer-substring @@ -2801,8 +2939,7 @@ EVENT gives the mouse position." (line-beginning-position) (line-end-position) (selected-window)) - (run-at-time 0.5 nil 'swiper--cleanup)) - (pulse-momentary-highlight-one-line (point))))))) + (run-at-time 0.5 nil 'swiper--cleanup))))))) (defvar ivy-help-file (let ((default-directory (if load-file-name