;; Author: Oleh Krehel <ohwoeowho@gmail.com>
;; URL: https://github.com/abo-abo/swiper
-;; Version: 0.4.0
+;; Version: 0.6.0
;; Package-Requires: ((emacs "24.1"))
;; Keywords: matching
'((t (:inherit isearch-fail)))
"Face for `swiper' matches modulo 3.")
+(define-obsolete-face-alias 'swiper-minibuffer-match-face-1
+ 'ivy-minibuffer-match-face-1 "0.6.0")
+
+(define-obsolete-face-alias 'swiper-minibuffer-match-face-2
+ 'ivy-minibuffer-match-face-2 "0.6.0")
+
+(define-obsolete-face-alias 'swiper-minibuffer-match-face-3
+ 'ivy-minibuffer-match-face-3 "0.6.0")
+
+(define-obsolete-face-alias 'swiper-minibuffer-match-face-4
+ 'ivy-minibuffer-match-face-4 "0.6.0")
+
(defface swiper-line-face
'((t (:inherit highlight)))
"Face for current `swiper' line.")
(let ((map (make-sparse-keymap)))
(define-key map (kbd "M-q") 'swiper-query-replace)
(define-key map (kbd "C-l") 'swiper-recenter-top-bottom)
+ (define-key map (kbd "C-'") 'swiper-avy)
+ (define-key map (kbd "C-7") 'swiper-mc)
+ (define-key map (kbd "C-c C-f") 'swiper-toggle-face-matching)
map)
"Keymap for swiper.")
(let* ((enable-recursive-minibuffers t)
(from (ivy--regex ivy-text))
(to (query-replace-read-to from "Query replace" t)))
- (delete-minibuffer-contents)
- (ivy-set-action (lambda ()
- (with-selected-window swiper--window
- (perform-replace from to
- t t nil))))
(swiper--cleanup)
- (exit-minibuffer))))
+ (ivy-exit-with-action
+ (lambda (_)
+ (with-ivy-window
+ (move-beginning-of-line 1)
+ (perform-replace from to
+ t t nil)))))))
+
+(defvar avy-background)
+(defvar avy-all-windows)
+(defvar avy-style)
+(defvar avy-keys)
+(declare-function avy--regex-candidates "ext:avy")
+(declare-function avy--process "ext:avy")
+(declare-function avy--overlay-post "ext:avy")
+(declare-function avy-action-goto "ext:avy")
+(declare-function avy--done "ext:avy")
+(declare-function avy--make-backgrounds "ext:avy")
+(declare-function avy-window-list "ext:avy")
+(declare-function avy-read "ext:avy")
+(declare-function avy-read-de-bruijn "ext:avy")
+(declare-function avy-tree "ext:avy")
+(declare-function avy-push-mark "ext:avy")
+(declare-function avy--remove-leading-chars "ext:avy")
+
+;;;###autoload
+(defun swiper-avy ()
+ "Jump to one of the current swiper candidates."
+ (interactive)
+ (unless (string= ivy-text "")
+ (let* ((avy-all-windows nil)
+ (candidates (append
+ (with-ivy-window
+ (avy--regex-candidates
+ (ivy--regex ivy-text)))
+ (save-excursion
+ (save-restriction
+ (narrow-to-region (window-start) (window-end))
+ (goto-char (point-min))
+ (forward-line)
+ (let ((cands))
+ (while (< (point) (point-max))
+ (push (cons (1+ (point))
+ (selected-window))
+ cands)
+ (forward-line))
+ cands)))))
+ (candidate (unwind-protect
+ (prog2
+ (avy--make-backgrounds
+ (append (avy-window-list)
+ (list (ivy-state-window ivy-last))))
+ (if (eq avy-style 'de-bruijn)
+ (avy-read-de-bruijn
+ candidates avy-keys)
+ (avy-read (avy-tree candidates avy-keys)
+ #'avy--overlay-post
+ #'avy--remove-leading-chars))
+ (avy-push-mark))
+ (avy--done))))
+ (if (window-minibuffer-p (cdr candidate))
+ (progn
+ (ivy-set-index (- (line-number-at-pos (car candidate)) 2))
+ (ivy--exhibit)
+ (ivy-done)
+ (ivy-call))
+ (ivy-quit-and-run
+ (avy-action-goto (caar candidate)))))))
+
+(declare-function mc/create-fake-cursor-at-point "ext:multiple-cursors-core")
+(declare-function multiple-cursors-mode "ext:multiple-cursors-core")
-(defvar swiper--window nil
- "Store the current window.")
+;;;###autoload
+(defun swiper-mc ()
+ (interactive)
+ (unless (require 'multiple-cursors nil t)
+ (error "multiple-cursors isn't installed"))
+ (let ((cands (nreverse ivy--old-cands)))
+ (unless (string= ivy-text "")
+ (ivy-exit-with-action
+ (lambda (_)
+ (let (cand)
+ (while (setq cand (pop cands))
+ (swiper--action cand)
+ (when cands
+ (mc/create-fake-cursor-at-point))))
+ (multiple-cursors-mode 1))))))
(defun swiper-recenter-top-bottom (&optional arg)
- "Call (`recenter-top-bottom' ARG) in `swiper--window'."
+ "Call (`recenter-top-bottom' ARG)."
(interactive "P")
- (with-selected-window swiper--window
+ (with-ivy-window
(recenter-top-bottom arg)))
(defun swiper-font-lock-ensure ()
"Ensure the entired buffer is highlighted."
(unless (or (derived-mode-p 'magit-mode)
+ (bound-and-true-p magit-blame-mode)
(memq major-mode '(package-menu-mode
gnus-summary-mode
gnus-article-mode
gnus-group-mode
- emms-playlist-mode erc-mode
+ emms-playlist-mode
+ emms-stream-mode
+ erc-mode
org-agenda-mode
dired-mode
jabber-chat-mode
- elfeed-search-mode)))
+ elfeed-search-mode
+ elfeed-show-mode
+ fundamental-mode
+ Man-mode
+ woman-mode
+ mu4e-view-mode
+ mu4e-headers-mode
+ help-mode
+ debbugs-gnu-mode
+ occur-mode
+ occur-edit-mode
+ bongo-mode
+ eww-mode
+ twittering-mode
+ w3m-mode)))
(unless (> (buffer-size) 100000)
(if (fboundp 'font-lock-ensure)
(font-lock-ensure)
(defvar swiper--width nil
"Store the amount of digits needed for the longest line nubmer.")
-(defun swiper--candidates ()
- "Return a list of this buffer lines."
+(defvar swiper-use-visual-line nil
+ "When non-nil, use `line-move' instead of `forward-line'.")
+
+(declare-function outline-show-all "outline")
+
+(defun swiper--candidates (&optional numbers-width)
+ "Return a list of this buffer lines.
+
+NUMBERS-WIDTH, when specified, is used for line numbers width
+spec, instead of calculating it as the log of the buffer line
+count."
+ (if (and visual-line-mode
+ ;; super-slow otherwise
+ (< (buffer-size) 20000))
+ (progn
+ (when (eq major-mode 'org-mode)
+ (require 'outline)
+ (if (fboundp 'outline-show-all)
+ (outline-show-all)
+ (show-all)))
+ (setq swiper-use-visual-line t))
+ (setq swiper-use-visual-line nil))
(let ((n-lines (count-lines (point-min) (point-max))))
(unless (zerop n-lines)
- (setq swiper--width (1+ (floor (log n-lines 10))))
+ (setq swiper--width (or numbers-width
+ (1+ (floor (log n-lines 10)))))
(setq swiper--format-spec
- (format "%%-%dd %%s" swiper--width))
+ (format "%%-%dd " swiper--width))
(let ((line-number 0)
+ (advancer (if swiper-use-visual-line
+ (lambda (arg) (line-move arg t))
+ #'forward-line))
candidates)
(save-excursion
(goto-char (point-min))
(swiper-font-lock-ensure)
(while (< (point) (point-max))
- (push (format swiper--format-spec
- (cl-incf line-number)
- (buffer-substring
- (line-beginning-position)
- (line-end-position)))
- candidates)
- (forward-line 1))
+ (let ((str (concat
+ " "
+ (replace-regexp-in-string
+ "\t" " "
+ (if swiper-use-visual-line
+ (buffer-substring
+ (save-excursion
+ (beginning-of-visual-line)
+ (point))
+ (save-excursion
+ (end-of-visual-line)
+ (point)))
+ (buffer-substring
+ (point)
+ (line-end-position)))))))
+ (when (eq major-mode 'twittering-mode)
+ (remove-text-properties 0 (length str) '(field) str))
+ (put-text-property 0 1 'display
+ (format swiper--format-spec
+ (cl-incf line-number))
+ str)
+ (push str candidates))
+ (funcall advancer 1))
(nreverse candidates))))))
(defvar swiper--opoint 1
(interactive)
(swiper--ivy initial-input))
-(defvar swiper--anchor nil
- "A line number to which the search should be anchored.")
-
-(defvar swiper--len 0
- "The last length of input for which an anchoring was made.")
+(declare-function evil-jumper--set-jump "ext:evil-jumper")
(defun swiper--init ()
"Perform initialization common to both completion methods."
- (deactivate-mark)
(setq swiper--opoint (point))
- (setq swiper--len 0)
- (setq swiper--anchor (line-number-at-pos))
- (setq swiper--window (selected-window))
- (setq ivy--regex-function
- (cdr (assoc t ivy-re-builders-alist))))
+ (when (bound-and-true-p evil-jumper-mode)
+ (evil-jumper--set-jump)))
+
+(defun swiper--re-builder (str)
+ "Transform STR into a swiper regex.
+This is the regex used in the minibuffer, since the candidates
+there have line numbers. In the buffer, `ivy--regex' should be used."
+ (cond
+ ((equal str "")
+ "")
+ ((equal str "^")
+ (setq ivy--subexps 0)
+ ".")
+ ((string-match "^\\^" str)
+ (setq ivy--old-re "")
+ (let ((re (ivy--regex-plus (substring str 1))))
+ (if (zerop ivy--subexps)
+ (prog1 (format "^ ?\\(%s\\)" re)
+ (setq ivy--subexps 1))
+ (format "^ %s" re))))
+ (t
+ (ivy--regex-plus str))))
+
+(defvar swiper-history nil
+ "History for `swiper'.")
+
+(defvar swiper-invocation-face nil
+ "The face at the point of invocation of `swiper'.")
(defun swiper--ivy (&optional initial-input)
"`isearch' with an overview using `ivy'.
When non-nil, INITIAL-INPUT is the initial search pattern."
(interactive)
- (unless (eq (length (help-function-arglist 'ivy-read)) 4)
- (warn "You seem to be using the outdated stand-alone \"ivy\" package.
-Please remove it and update the \"swiper\" package."))
(swiper--init)
+ (setq swiper-invocation-face
+ (plist-get (text-properties-at (point)) 'face))
(let ((candidates (swiper--candidates))
- (preselect (format
- swiper--format-spec
- (line-number-at-pos)
- (regexp-quote
- (buffer-substring-no-properties
- (line-beginning-position)
- (line-end-position)))))
+ (preselect
+ (if swiper-use-visual-line
+ (count-screen-lines
+ (point-min)
+ (save-excursion (beginning-of-visual-line) (point)))
+ (1- (line-number-at-pos))))
+ (minibuffer-allow-text-properties t)
res)
(unwind-protect
- (setq res (ivy-read
- (replace-regexp-in-string
- "%s" "pattern: " swiper--format-spec)
- candidates
- :initial-input initial-input
- :keymap swiper-map
- :preselect preselect
- :require-match t
- :update-fn #'swiper--update-input-ivy
- :unwind #'swiper--cleanup))
- (if (null ivy-exit)
- (goto-char swiper--opoint)
- (swiper--action res ivy-text)))))
+ (setq res
+ (ivy-read
+ "Swiper: "
+ candidates
+ :initial-input initial-input
+ :keymap swiper-map
+ :preselect preselect
+ :require-match t
+ :update-fn #'swiper--update-input-ivy
+ :unwind #'swiper--cleanup
+ :action #'swiper--action
+ :re-builder #'swiper--re-builder
+ :history 'swiper-history
+ :caller 'swiper))
+ (unless res
+ (goto-char swiper--opoint)))))
+
+(defun swiper-toggle-face-matching ()
+ "Toggle matching only the candidates with `swiper-invocation-face'."
+ (interactive)
+ (setf (ivy-state-matcher ivy-last)
+ (if (ivy-state-matcher ivy-last)
+ nil
+ #'swiper--face-matcher))
+ (setq ivy--old-re nil))
+
+(defun swiper--face-matcher (regexp candidates)
+ "Return REGEXP-matching CANDIDATES.
+Matched candidates should have `swiper-invocation-face'."
+ (cl-remove-if-not
+ (lambda (x)
+ (and
+ (string-match regexp x)
+ (let ((s (match-string 0 x))
+ (i 0))
+ (while (and (< i (length s))
+ (text-property-any
+ i (1+ i)
+ 'face swiper-invocation-face
+ s))
+ (cl-incf i))
+ (eq i (length s)))))
+ candidates))
(defun swiper--ensure-visible ()
"Remove overlays hiding point."
(defun swiper--update-input-ivy ()
"Called when `ivy' input is updated."
- (swiper--cleanup)
- (let* ((re (funcall ivy--regex-function ivy-text))
- (str ivy--current)
- (num (if (string-match "^[0-9]+" str)
- (string-to-number (match-string 0 str))
- 0)))
- (with-selected-window swiper--window
- (goto-char (point-min))
- (when (cl-plusp num)
+ (with-ivy-window
+ (swiper--cleanup)
+ (when (> (length ivy--current) 0)
+ (let* ((re (funcall ivy--regex-function ivy-text))
+ (re (if (stringp re) re (caar re)))
+ (str (get-text-property 0 'display ivy--current))
+ (num (if (string-match "^[0-9]+" str)
+ (string-to-number (match-string 0 str))
+ 0)))
(goto-char (point-min))
- (forward-line (1- num))
- (isearch-range-invisible (line-beginning-position)
- (line-end-position))
- (unless (and (>= (point) (window-start))
- (<= (point) (window-end swiper--window t)))
- (recenter)))
- (swiper--add-overlays re))))
-
-(defun swiper--add-overlays (re &optional beg end)
+ (when (cl-plusp num)
+ (goto-char (point-min))
+ (if swiper-use-visual-line
+ (line-move (1- num))
+ (forward-line (1- num)))
+ (if (and (equal ivy-text "")
+ (>= swiper--opoint (line-beginning-position))
+ (<= swiper--opoint (line-end-position)))
+ (goto-char swiper--opoint)
+ (re-search-forward re (line-end-position) t))
+ (isearch-range-invisible (line-beginning-position)
+ (line-end-position))
+ (unless (and (>= (point) (window-start))
+ (<= (point) (window-end (ivy-state-window ivy-last) t)))
+ (recenter)))
+ (swiper--add-overlays re)))))
+
+(defun swiper--add-overlays (re &optional beg end wnd)
"Add overlays for RE regexp in visible part of the current buffer.
-BEG and END, when specified, are the point bounds."
- (let ((ov (make-overlay
- (line-beginning-position)
- (1+ (line-end-position)))))
+BEG and END, when specified, are the point bounds.
+WND, when specified is the window."
+ (setq wnd (or wnd (ivy-state-window ivy-last)))
+ (let ((ov (if visual-line-mode
+ (make-overlay
+ (save-excursion
+ (beginning-of-visual-line)
+ (point))
+ (save-excursion
+ (end-of-visual-line)
+ (point)))
+ (make-overlay
+ (line-beginning-position)
+ (1+ (line-end-position))))))
(overlay-put ov 'face 'swiper-line-face)
- (overlay-put ov 'window swiper--window)
- (push ov swiper--overlays))
- (let* ((wh (window-height))
- (beg (or beg (save-excursion
- (forward-line (- wh))
- (point))))
- (end (or end (save-excursion
- (forward-line wh)
- (point)))))
- (when (>= (length re) swiper-min-highlight)
- (save-excursion
- (goto-char beg)
- ;; RE can become an invalid regexp
- (while (and (ignore-errors (re-search-forward re end t))
- (> (- (match-end 0) (match-beginning 0)) 0))
- (let ((i 0))
- (while (<= i ivy--subexps)
- (when (match-beginning i)
- (let ((overlay (make-overlay (match-beginning i)
- (match-end i)))
- (face
- (cond ((zerop ivy--subexps)
- (cadr swiper-faces))
- ((zerop i)
- (car swiper-faces))
- (t
- (nth (1+ (mod (+ i 2) (1- (length swiper-faces))))
- swiper-faces)))))
- (push overlay swiper--overlays)
- (overlay-put overlay 'face face)
- (overlay-put overlay 'window swiper--window)
- (overlay-put overlay 'priority i)))
- (cl-incf i))))))))
-
-(defun swiper--action (x input)
- "Goto line X and search for INPUT."
+ (overlay-put ov 'window wnd)
+ (push ov swiper--overlays)
+ (let* ((wh (window-height))
+ (beg (or beg (save-excursion
+ (forward-line (- wh))
+ (point))))
+ (end (or end (save-excursion
+ (forward-line wh)
+ (point)))))
+ (when (>= (length re) swiper-min-highlight)
+ (save-excursion
+ (goto-char beg)
+ ;; RE can become an invalid regexp
+ (while (and (ignore-errors (re-search-forward re end t))
+ (> (- (match-end 0) (match-beginning 0)) 0))
+ (let ((i 0))
+ (while (<= i ivy--subexps)
+ (when (match-beginning i)
+ (let ((overlay (make-overlay (match-beginning i)
+ (match-end i)))
+ (face
+ (cond ((zerop ivy--subexps)
+ (cadr swiper-faces))
+ ((zerop i)
+ (car swiper-faces))
+ (t
+ (nth (1+ (mod (+ i 2) (1- (length swiper-faces))))
+ swiper-faces)))))
+ (push overlay swiper--overlays)
+ (overlay-put overlay 'face face)
+ (overlay-put overlay 'window wnd)
+ (overlay-put overlay 'priority i)))
+ (cl-incf i)))))))))
+
+(defun swiper--action (x)
+ "Goto line X."
(if (null x)
(user-error "No candidates")
- (goto-char (point-min))
- (forward-line (1- (read x)))
- (re-search-forward
- (funcall ivy--regex-function input) (line-end-position) t)
- (swiper--ensure-visible)
- (when (/= (point) swiper--opoint)
- (unless (and transient-mark-mode mark-active)
- (push-mark swiper--opoint t)
- (message "Mark saved where search started")))))
+ (with-ivy-window
+ (unless (equal (current-buffer)
+ (ivy-state-buffer ivy-last))
+ (switch-to-buffer (ivy-state-buffer ivy-last)))
+ (goto-char (point-min))
+ (funcall (if swiper-use-visual-line
+ #'line-move
+ #'forward-line)
+ (1- (read (get-text-property 0 'display x))))
+ (re-search-forward
+ (ivy--regex ivy-text) (line-end-position) t)
+ (swiper--ensure-visible)
+ (when (/= (point) swiper--opoint)
+ (unless (and transient-mark-mode mark-active)
+ (when (eq ivy-exit 'done)
+ (push-mark swiper--opoint t)
+ (message "Mark saved where search started")))))))
+
+;; (define-key isearch-mode-map (kbd "C-o") 'swiper-from-isearch)
+(defun swiper-from-isearch ()
+ "Invoke `swiper' from isearch."
+ (interactive)
+ (let ((query (if isearch-regexp
+ isearch-string
+ (regexp-quote isearch-string))))
+ (isearch-exit)
+ (swiper query)))
+
+(defvar swiper-multi-buffers nil
+ "Store the current list of buffers.")
+
+(defvar swiper-multi-candidates nil
+ "Store the list of candidates for `swiper-multi'.")
+
+(defun swiper-multi-prompt ()
+ (format "Buffers (%s): "
+ (mapconcat #'identity swiper-multi-buffers ", ")))
+
+(defun swiper-multi ()
+ "Select one or more buffers.
+Run `swiper' for those buffers."
+ (interactive)
+ (setq swiper-multi-buffers nil)
+ (ivy-read (swiper-multi-prompt)
+ 'internal-complete-buffer
+ :action 'swiper-multi-action-1)
+ (ivy-read "Swiper: " swiper-multi-candidates
+ :action 'swiper-multi-action-2
+ :unwind #'swiper--cleanup
+ :caller 'swiper-multi))
+
+(defun swiper-all ()
+ "Run `swiper' for all opened buffers."
+ (interactive)
+ (ivy-read "Swiper: " (swiper--multi-candidates
+ (cl-remove-if-not
+ #'buffer-file-name
+ (buffer-list)))
+ :action 'swiper-multi-action-2
+ :unwind #'swiper--cleanup
+ :caller 'swiper-multi))
+
+(defun swiper--multi-candidates (buffers)
+ (let* ((ww (window-width))
+ (res nil)
+ (column-2 (apply #'max
+ (mapcar
+ (lambda (b)
+ (length (buffer-name b)))
+ buffers)))
+ (column-1 (- ww 4 column-2 1)))
+ (dolist (buf buffers)
+ (with-current-buffer buf
+ (setq res
+ (append
+ (mapcar
+ (lambda (s)
+ (setq s (concat (ivy--truncate-string s column-1) " "))
+ (let ((len (length s)))
+ (put-text-property
+ (1- len) len 'display
+ (concat
+ (make-string
+ (- ww (string-width s) (length (buffer-name)) 3)
+ ?\ )
+ (buffer-name))
+ s)
+ s))
+ (swiper--candidates 4))
+ res))
+ nil))
+ res))
+
+(defun swiper-multi-action-1 (x)
+ (if (member x swiper-multi-buffers)
+ (progn
+ (setq swiper-multi-buffers (delete x swiper-multi-buffers)))
+ (unless (equal x "")
+ (setq swiper-multi-buffers (append swiper-multi-buffers (list x)))))
+ (let ((prompt (swiper-multi-prompt)))
+ (setf (ivy-state-prompt ivy-last) prompt)
+ (setq ivy--prompt (concat "%-4d " prompt)))
+ (cond ((memq this-command '(ivy-done
+ ivy-alt-done
+ ivy-immediate-done))
+ (setq swiper-multi-candidates
+ (swiper--multi-candidates
+ (mapcar #'get-buffer swiper-multi-buffers))))
+ ((eq this-command 'ivy-call)
+ (delete-minibuffer-contents))))
+
+(defun swiper-multi-action-2 (x)
+ (let ((buf-space (get-text-property (1- (length x)) 'display x)))
+ (with-ivy-window
+ (when (string-match "\\` *\\([^ ]+\\)\\'" buf-space)
+ (switch-to-buffer (match-string 1 buf-space))
+ (goto-char (point-min))
+ (forward-line (1- (read (get-text-property 0 'display x))))
+ (re-search-forward
+ (ivy--regex ivy-text)
+ (line-end-position) t)
+ (unless (eq ivy-exit 'done)
+ (swiper--cleanup)
+ (swiper--add-overlays (ivy--regex ivy-text)))))))
(provide 'swiper)