]> code.delx.au - gnu-emacs-elpa/blobdiff - swiper.el
Update index formatting logic
[gnu-emacs-elpa] / swiper.el
index 6970968b098564157df14908778b8282e89b5602..24be15349b7bcfba56b369844d4f95337cdd89ea 100644 (file)
--- a/swiper.el
+++ b/swiper.el
@@ -1,14 +1,14 @@
 ;;; swiper.el --- Isearch with an overview. Oh, man! -*- lexical-binding: t -*-
 
-;; Copyright (C) 2015 Oleh Krehel
+;; Copyright (C) 2015  Free Software Foundation, Inc.
 
 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
 ;; URL: https://github.com/abo-abo/swiper
-;; Version: 0.1.0
-;; Package-Requires: ((emacs "24.1") (ivy "0.1.0"))
+;; Version: 0.5.1
+;; Package-Requires: ((emacs "24.1"))
 ;; Keywords: matching
 
-;; This file is not part of GNU Emacs
+;; This file is part of GNU Emacs.
 
 ;; This file is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
 ;; candidates.  The search regex can be split into groups with a
 ;; space.  Each group is highlighted with a different face.
 ;;
-;; The overview back end can be either `helm' or `ivy'.
-;;
 ;; It can double as a quick `regex-builder', although only single
 ;; lines will be matched.
+;;
+;; It also provides `ivy-mode': a global minor mode that uses the
+;; matching back end of `swiper' for all matching on your system,
+;; including file matching. You can use it in place of `ido-mode'
+;; (can't have both on at once).
 
 ;;; Code:
 (require 'ivy)
   :group 'matching
   :prefix "swiper-")
 
-(defcustom swiper-completion-method 'helm
-  "Method to select a candidate from a list of strings."
-  :type '(choice
-          (const :tag "Helm" helm)
-          (const :tag "Ivy" ivy)))
-
 (defface swiper-match-face-1
-    '((t (:background "#FEEA89")))
-  "Face for `swiper' matches.")
+  '((t (:inherit isearch-lazy-highlight-face)))
+  "The background face for `swiper' matches.")
 
 (defface swiper-match-face-2
-  '((t (:background "#F9A35A")))
-  "Face for `swiper' matches.")
+  '((t (:inherit isearch)))
+  "Face for `swiper' matches modulo 1.")
 
 (defface swiper-match-face-3
-  '((t (:background "#fb7905")))
-  "Face for `swiper' matches.")
+  '((t (:inherit match)))
+  "Face for `swiper' matches modulo 2.")
 
 (defface swiper-match-face-4
-  '((t (:background "#F15C79")))
-  "Face for `swiper' matches.")
+  '((t (:inherit isearch-fail)))
+  "Face for `swiper' matches modulo 3.")
 
 (defface swiper-line-face
-  '((t (:background "#f3d3d3")))
+  '((t (:inherit highlight)))
   "Face for current `swiper' line.")
 
 (defcustom swiper-faces '(swiper-match-face-1
                           swiper-match-face-4)
   "List of `swiper' faces for group matches.")
 
+(defcustom swiper-min-highlight 2
+  "Only highlight matches for regexps at least this long."
+  :type 'integer)
+
+(defvar swiper-map
+  (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)
+    map)
+  "Keymap for swiper.")
+
 (defvar swiper--window nil
   "Store the current window.")
 
-(defalias 'swiper-font-lock-ensure
-    (if (fboundp 'font-lock-ensure)
-        'font-lock-ensure
-      'font-lock-fontify-buffer))
+(defun swiper-query-replace ()
+  "Start `query-replace' with string to replace from last search string."
+  (interactive)
+  (if (null (window-minibuffer-p))
+      (user-error "Should only be called in the minibuffer through `swiper-map'")
+    (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
+                          (move-beginning-of-line 1)
+                          (perform-replace from to
+                                           t t nil))))
+      (swiper--cleanup)
+      (exit-minibuffer))))
+
+(defvar avy-background)
+(defvar avy-all-windows)
+(declare-function avy--regex-candidates "ext:avy")
+(declare-function avy--process "ext:avy")
+(declare-function avy--overlay-post "ext:avy")
+(declare-function avy--goto "ext:avy")
+
+;;;###autoload
+(defun swiper-avy ()
+  "Jump to one of the current swiper candidates."
+  (interactive)
+  (unless (string= ivy-text "")
+    (with-selected-window (ivy-state-window ivy-last)
+      (let* ((avy-all-windows nil)
+             (candidates
+              (avy--regex-candidates
+               (ivy--regex ivy-text)))
+             (avy-background nil)
+             (candidate
+              (avy--process candidates #'avy--overlay-post)))
+        (ivy-quit-and-run
+         (avy--goto candidate))))))
+
+(defun swiper-recenter-top-bottom (&optional arg)
+  "Call (`recenter-top-bottom' ARG) in `swiper--window'."
+  (interactive "P")
+  (with-selected-window swiper--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
+                                 org-agenda-mode
+                                 dired-mode
+                                 jabber-chat-mode
+                                 elfeed-search-mode
+                                 fundamental-mode)))
+    (unless (> (buffer-size) 100000)
+      (if (fboundp 'font-lock-ensure)
+          (font-lock-ensure)
+        (with-no-warnings (font-lock-fontify-buffer))))))
+
+(defvar swiper--format-spec ""
+  "Store the current candidates format spec.")
+
+(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."
-  (let* ((line-width (1+ (floor (log (count-lines
-                                      (point-min) (point-max))
-                                     10))))
-         (fspec (format "%%-%dd %%s" line-width))
-         (line-number 0)
-         candidates)
-    (save-excursion
-      (goto-char (point-min))
-      (swiper-font-lock-ensure)
-      (while (< (point) (point-max))
-        (push (format fspec
-                      (cl-incf line-number)
-                      (buffer-substring
-                       (line-beginning-position)
-                       (line-end-position)))
-              candidates)
-        (zerop (forward-line 1)))
-      (nreverse candidates))))
-
-(defvar swiper-helm-keymap
-  (let ((map (make-sparse-keymap)))
-    (define-key map (kbd "C-s") 'helm-next-line)
-    (define-key map (kbd "C-r") 'helm-previous-line)
-    map)
-  "Allows you to go to next and previous hit isearch-style.")
+  (let ((n-lines (count-lines (point-min) (point-max))))
+    (unless (zerop n-lines)
+      (setq swiper--width (1+ (floor (log n-lines 10))))
+      (setq swiper--format-spec
+            (format "%%-%dd %%s" swiper--width))
+      (let ((line-number 0)
+            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))
+          (nreverse candidates))))))
+
+(defvar swiper--opoint 1
+  "The point when `swiper' starts.")
 
 ;;;###autoload
-(defun swiper ()
-  "`isearch' with an overview."
+(defun swiper (&optional initial-input)
+  "`isearch' with an overview.
+When non-nil, INITIAL-INPUT is the initial search pattern."
   (interactive)
-  (if (and (eq 'swiper-completion-method 'helm)
-           (featurep 'helm))
-      (swiper--helm)
-    (swiper--ivy)))
+  (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.")
 
 (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)))
 
-(defun swiper--ivy ()
-  "`isearch' with an overview using `ivy'."
-  (interactive)
-  (ido-mode -1)
-  (swiper--init)
-  (unwind-protect
-       (let ((res (ivy-read "pattern: "
-                            (swiper--candidates)
-                            #'swiper--update-input-ivy)))
-         (goto-char (point-min))
-         (forward-line (1- (read res)))
-         (re-search-forward
-          (ivy--regex ivy-text)
-          (line-end-position)
-          t))
-    (ido-mode 1)
-    (swiper--cleanup)))
-
-(defun swiper--helm ()
-  "`isearch' with an overview using `helm'."
+(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 "^")
+     ".")
+    ((string-match "^\\^" str)
+     (setq ivy--old-re "")
+     (let ((re (ivy--regex-plus (substring str 1))))
+       (format "^[0-9][0-9 ]\\{%d\\}%s"
+               swiper--width
+               (if (zerop ivy--subexps)
+                   (prog1 (format "\\(%s\\)" re)
+                     (setq ivy--subexps 1))
+                 re))))
+    (t
+     (ivy--regex-plus str))))
+
+(defvar swiper-history nil
+  "History for `swiper'.")
+
+(defun swiper--ivy (&optional initial-input)
+  "`isearch' with an overview using `ivy'.
+When non-nil, INITIAL-INPUT is the initial search pattern."
   (interactive)
-  (require 'helm)
+  (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)
-  (unwind-protect
-       (let ((helm-display-function
-              (lambda (buf)
-                (when (one-window-p)
-                  (split-window-vertically))
-                (other-window 1)
-                (switch-to-buffer buf)))
-             helm-candidate-number-limit)
-         (helm :sources
-               `((name . ,(buffer-name))
-                 (init . (lambda ()
-                           (add-hook 'helm-move-selection-after-hook
-                                     #'swiper--update-sel)
-                           (add-hook 'helm-update-hook
-                                     #'swiper--update-input-helm)
-                           (add-hook 'helm-after-update-hook
-                                     #'swiper--reanchor)))
-                 (match-strict . (lambda (x)
-                                   (ignore-errors
-                                     (string-match (ivy--regex helm-input) x))))
-                 (candidates . ,(swiper--candidates))
-                 (filtered-candidate-transformer
-                  helm-fuzzy-highlight-matches)
-                 (action . swiper--action))
-               :keymap (make-composed-keymap
-                        swiper-helm-keymap
-                        helm-map)
-               :preselect
-               (format "^%d " swiper--anchor)
-               :buffer "*swiper*"))
-    ;; cleanup
-    (remove-hook 'helm-move-selection-after-hook #'swiper--update-sel)
-    (remove-hook 'helm-update-hook #'swiper--update-input-helm)
-    (remove-hook 'helm-after-update-hook #'swiper--reanchor)
-    (swiper--cleanup)))
-
-(defun swiper--cleanup ()
-  "Clean up the overlays."
-  (while swiper--overlays
-    (delete-overlay (pop swiper--overlays))))
+  (let ((candidates (swiper--candidates))
+        (preselect (format
+                    swiper--format-spec
+                    (line-number-at-pos)
+                    (buffer-substring-no-properties
+                     (line-beginning-position)
+                     (line-end-position))))
+        res)
+    (unwind-protect
+         (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
+                    :re-builder #'swiper--re-builder
+                    :history 'swiper-history))
+      (if (null ivy-exit)
+          (goto-char swiper--opoint)
+        (swiper--action res ivy-text)))))
+
+(defun swiper--ensure-visible ()
+  "Remove overlays hiding point."
+  (let ((overlays (overlays-at (point)))
+        ov expose)
+    (while (setq ov (pop overlays))
+      (if (and (invisible-p (overlay-get ov 'invisible))
+               (setq expose (overlay-get ov 'isearch-open-invisible)))
+          (funcall expose ov)))))
 
 (defvar swiper--overlays nil
   "Store overlays.")
 
-(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.")
-
-(defun swiper--update-input-helm ()
-  "Update selection."
-  (swiper--cleanup)
-  (with-selected-window swiper--window
-    (swiper--add-overlays
-     (ivy--regex helm-input)
-     (window-start swiper--window)
-     (window-end swiper--window t)))
-  (when (/= (length helm-input) swiper--len)
-    (setq swiper--len (length helm-input))
-    (swiper--reanchor)))
+(defun swiper--cleanup ()
+  "Clean up the overlays."
+  (while swiper--overlays
+    (delete-overlay (pop swiper--overlays)))
+  (save-excursion
+    (goto-char (point-min))
+    (isearch-clean-overlays)))
 
 (defun swiper--update-input-ivy ()
   "Called when `ivy' input is updated."
                 0)))
     (with-selected-window swiper--window
       (goto-char (point-min))
-      (when (plusp num)
-        (goto-char (point-min))
-        (forward-line (1- num))
-        (recenter))
-      (let ((ov (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))
-      (swiper--add-overlays
-       re
-       (window-start swiper--window)
-       (window-end swiper--window t)))))
-
-(defun swiper--add-overlays (re beg end)
-  "Add overlays for RE regexp in current buffer between BEG and END."
-  (when (> (length re) 1)
-    (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)
-                            (cl-caddr swiper-faces))
-                           ((zerop i)
-                            (car swiper-faces))
-                           (t
-                            (nth (1+ (mod (1- i) (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--binary (beg end)
-  "Find anchor between BEG and END."
-  (if (<= (- end beg) 10)
-      (let ((min 1000)
-            n
-            ln
-            d)
-        (goto-char (point-min))
-        (forward-line (1- beg))
-        (while (< beg end)
-          (beginning-of-line)
-          (setq n (read (current-buffer)))
-          (when (< (setq d (abs (- n swiper--anchor))) min)
-            (setq min d)
-            (setq ln beg))
-          (cl-incf beg)
-          (forward-line 1))
-        (goto-char (point-min))
-        (when ln
-          (forward-line (1- ln))))
-    (let ((mid (+ beg (/ (- end beg) 2))))
-      (goto-char (point-min))
-      (forward-line mid)
-      (beginning-of-line)
-      (let ((n (read (current-buffer))))
-        (if (> n swiper--anchor)
-            (swiper--binary beg mid)
-          (swiper--binary mid end))))))
-
-(defun swiper--update-sel ()
-  "Update selection."
-  (let* ((re (ivy--regex helm-input))
-         (str (buffer-substring-no-properties
-               (line-beginning-position)
-               (line-end-position)))
-         (num (if (string-match "^[0-9]+" str)
-                  (string-to-number (match-string 0 str))
-                0))
-         pt)
-    (when (> (length re) 0)
-      (with-selected-window swiper--window
+      (when (cl-plusp num)
         (goto-char (point-min))
         (forward-line (1- num))
-        (when (re-search-forward re (point-max) t)
-          (setq pt (match-beginning 0))))
-      (when pt
-        (with-selected-window
-            (helm-persistent-action-display-window)
-          (goto-char pt)
-          (recenter)
-          (swiper--update-input-helm))))
-    (with-selected-window swiper--window
-      (let ((ov (make-overlay
-                 (line-beginning-position)
-                 (1+ (line-end-position)))))
-        (overlay-put ov 'face 'swiper-line-face)
-        (push ov swiper--overlays)))))
-
-(defun swiper--reanchor ()
-  "Move to a valid match closest to `swiper--anchor'."
-  (with-helm-window
+        (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 swiper--window t)))
+          (recenter)))
+      (swiper--add-overlays re))))
+
+(defun swiper--add-overlays (re &optional beg end)
+  "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)))))
+    (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."
+  (if (null x)
+      (user-error "No candidates")
     (goto-char (point-min))
-    (if (re-search-forward (format "^%d " swiper--anchor) nil t)
-        nil
-      (forward-line 1)
-      (swiper--binary 2 (1+ (count-lines (point) (point-max)))))
-    (when (> (count-lines (point-min) (point-max)) 1)
-      (forward-line -1)
-      (helm-next-line 1))))
-
-(defun swiper--action (x)
-  "Goto line X."
-  (goto-char (point-min))
-  (forward-line (1- (read x)))
-  (re-search-forward
-   (ivy--regex helm-input) (line-end-position) t))
+    (forward-line (1- (read x)))
+    (re-search-forward
+     (ivy--regex 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")))))
 
 (provide 'swiper)