X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/63f349821c88ccbb6379ac8f0df08bb8f1bea518..e7f41bbc2713b940a91b313cfcd2143ed2f7f452:/swiper.el diff --git a/swiper.el b/swiper.el index 216a50a0e..90e25b5f3 100644 --- a/swiper.el +++ b/swiper.el @@ -4,7 +4,7 @@ ;; Author: Oleh Krehel ;; URL: https://github.com/abo-abo/swiper -;; Version: 0.6.0 +;; Version: 0.7.0 ;; Package-Requires: ((emacs "24.1")) ;; Keywords: matching @@ -61,18 +61,6 @@ '((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.") @@ -81,7 +69,9 @@ swiper-match-face-2 swiper-match-face-3 swiper-match-face-4) - "List of `swiper' faces for group matches.") + "List of `swiper' faces for group matches." + :group 'ivy-faces + :type 'list) (defcustom swiper-min-highlight 2 "Only highlight matches for regexps at least this long." @@ -104,15 +94,20 @@ (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-ivy-window - (move-beginning-of-line 1) - (perform-replace from to - t t nil)))) + (to (minibuffer-with-setup-hook + (lambda () + (setq minibuffer-default + (if (string-match "\\`\\\\_<\\(.*\\)\\\\_>\\'" ivy-text) + (match-string 1 ivy-text) + ivy-text))) + (read-from-minibuffer (format "Query replace %s with: " from))))) (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) @@ -154,17 +149,17 @@ (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)) + (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 @@ -178,23 +173,19 @@ (declare-function mc/create-fake-cursor-at-point "ext:multiple-cursors-core") (declare-function multiple-cursors-mode "ext:multiple-cursors-core") -;;;###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-set-action + (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))) - (setq ivy-exit 'done) - (exit-minibuffer)))) + (multiple-cursors-mode 1)))))) (defun swiper-recenter-top-bottom (&optional arg) "Call (`recenter-top-bottom' ARG)." @@ -202,35 +193,49 @@ (with-ivy-window (recenter-top-bottom arg))) +(defvar swiper-font-lock-exclude + '(package-menu-mode + gnus-summary-mode + gnus-article-mode + gnus-group-mode + emms-playlist-mode + emms-stream-mode + erc-mode + org-agenda-mode + dired-mode + jabber-chat-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 + bongo-library-mode + bongo-playlist-mode + eww-mode + twittering-mode + vc-dir-mode + rcirc-mode + sauron-mode + w3m-mode) + "List of major-modes that are incompatible with font-lock-ensure.") + +(defun swiper-font-lock-ensure-p () + "Return non-nil if we should font-lock-ensure." + (or (derived-mode-p 'magit-mode) + (bound-and-true-p magit-blame-mode) + (memq major-mode swiper-font-lock-exclude))) + (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 - emms-stream-mode - erc-mode - org-agenda-mode - dired-mode - jabber-chat-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 - w3m-mode))) - (unless (> (buffer-size) 100000) + (unless (swiper-font-lock-ensure-p) + (unless (or (> (buffer-size) 100000) (null font-lock-mode)) (if (fboundp 'font-lock-ensure) (font-lock-ensure) (with-no-warnings (font-lock-fontify-buffer)))))) @@ -239,21 +244,30 @@ "Store the current candidates format spec.") (defvar swiper--width nil - "Store the amount of digits needed for the longest line nubmer.") + "Store the number of digits needed for the longest line nubmer.") (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." - (setq swiper-use-visual-line - (and (not (eq major-mode 'org-mode)) - visual-line-mode - (< (buffer-size) 20000))) +NUMBERS-WIDTH, when specified, is used for width spec of line +numbers; replaces calculating the width from 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) + (with-no-warnings + (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 (or numbers-width @@ -273,13 +287,18 @@ count." " " (replace-regexp-in-string "\t" " " - (buffer-substring - (point) - (if swiper-use-visual-line + (if swiper-use-visual-line + (buffer-substring (save-excursion - (end-of-visual-line) + (beginning-of-visual-line) (point)) + (save-excursion + (end-of-visual-line) + (point))) + (buffer-substring + (point) (line-end-position))))))) + (remove-text-properties 0 (length str) '(field) str) (put-text-property 0 1 'display (format swiper--format-spec (cl-incf line-number)) @@ -296,35 +315,89 @@ count." "`isearch' with an overview. When non-nil, INITIAL-INPUT is the initial search pattern." (interactive) - (swiper--ivy initial-input)) + (swiper--ivy (swiper--candidates) initial-input)) + +(declare-function string-trim-right "subr-x") + +(defun swiper-occur (&optional revert) + "Generate a custom occur buffer for `swiper'. +When REVERT is non-nil, regenerate the current *ivy-occur* buffer." + (let* ((buffer (ivy-state-buffer ivy-last)) + (fname (propertize + (with-ivy-window + (if (buffer-file-name buffer) + (file-name-nondirectory + (buffer-file-name buffer)) + (buffer-name buffer))) + 'face + 'compilation-info)) + (cands (mapcar + (lambda (s) + (format "%s:%s:%s" + fname + (propertize + (string-trim-right + (get-text-property 0 'display s)) + 'face 'compilation-line-number) + (substring s 1))) + (if (null revert) + ivy--old-cands + (setq ivy--old-re nil) + (let ((ivy--regex-function 'swiper--re-builder)) + (ivy--filter + (progn (string-match "\"\\(.*\\)\"" (buffer-name)) + (match-string 1 (buffer-name))) + (with-current-buffer buffer + (swiper--candidates)))))))) + (unless (eq major-mode 'ivy-occur-grep-mode) + (ivy-occur-grep-mode) + (font-lock-mode -1)) + (insert (format "-*- mode:grep; default-directory: %S -*-\n\n\n" + default-directory)) + (insert (format "%d candidates:\n" (length cands))) + (ivy--occur-insert-lines + (mapcar + (lambda (cand) (concat "./" cand)) + cands)) + (goto-char (point-min)) + (forward-line 4))) + +(ivy-set-occur 'swiper 'swiper-occur) (declare-function evil-jumper--set-jump "ext:evil-jumper") +(defvar swiper--current-line nil) +(defvar swiper--current-match-start nil) + (defun swiper--init () "Perform initialization common to both completion methods." + (setq swiper--current-line nil) + (setq swiper--current-match-start nil) (setq swiper--opoint (point)) (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)))) +This is the regex used in the minibuffer where candidates have +line numbers. For the buffer, use `ivy--regex' instead." + (replace-regexp-in-string + "\t" " " + (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'.") @@ -332,36 +405,39 @@ there have line numbers. In the buffer, `ivy--regex' should be used." (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'. +(defun swiper--ivy (candidates &optional initial-input) + "Select one of CANDIDATES and move there. When non-nil, INITIAL-INPUT is the initial search pattern." (interactive) (swiper--init) (setq swiper-invocation-face (plist-get (text-properties-at (point)) 'face)) - (let ((candidates (swiper--candidates)) - (preselect - (if (bound-and-true-p visual-line-mode) + (let ((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)) + (minibuffer-allow-text-properties t) + res) (unwind-protect - (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) - (when (null ivy-exit) + (and + (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)) + (point)) + (unless res (goto-char swiper--opoint))))) (defun swiper-toggle-face-matching () @@ -393,7 +469,7 @@ Matched candidates should have `swiper-invocation-face'." (defun swiper--ensure-visible () "Remove overlays hiding point." - (let ((overlays (overlays-at (point))) + (let ((overlays (overlays-at (1- (point)))) ov expose) (while (setq ov (pop overlays)) (if (and (invisible-p (overlay-get ov 'invisible)) @@ -416,28 +492,38 @@ Matched candidates should have `swiper-invocation-face'." (with-ivy-window (swiper--cleanup) (when (> (length ivy--current) 0) - (let* ((re (funcall ivy--regex-function ivy-text)) + (let* ((re (replace-regexp-in-string + " " "\t" + (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)) - (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))) + (unless (eq this-command 'ivy-yank-word) + (when (cl-plusp num) + (unless (if swiper--current-line + (eq swiper--current-line num) + (eq (line-number-at-pos) 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) + (if (eq swiper--current-line num) + (when swiper--current-match-start + (goto-char swiper--current-match-start)) + (setq swiper--current-line num)) + (when (re-search-forward re (line-end-position) t) + (setq swiper--current-match-start (match-beginning 0)))) + (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) @@ -493,24 +579,32 @@ WND, when specified is the window." (defun swiper--action (x) "Goto line X." - (if (null x) - (user-error "No candidates") - (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) - (push-mark swiper--opoint t) - (message "Mark saved where search started")))))) + (let ((ln (1- (read (or (get-text-property 0 'display x) + (and (string-match ":\\([0-9]+\\):.*\\'" x) + (match-string-no-properties 1 x)))))) + (re (ivy--regex ivy-text))) + (if (null x) + (user-error "No candidates") + (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) + ln) + (re-search-forward re (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")))) + (add-to-history + 'regexp-search-ring + re + regexp-search-ring-max))))) ;; (define-key isearch-mode-map (kbd "C-o") 'swiper-from-isearch) (defun swiper-from-isearch () @@ -554,6 +648,8 @@ Run `swiper' for those buffers." (buffer-list))) :action 'swiper-multi-action-2 :unwind #'swiper--cleanup + :update-fn (lambda () + (swiper-multi-action-2 ivy--current)) :caller 'swiper-multi)) (defun swiper--multi-candidates (buffers)