X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/4e0c3f1132f16039e909301590661fb36063bf15..e7f41bbc2713b940a91b313cfcd2143ed2f7f452:/swiper.el diff --git a/swiper.el b/swiper.el index 09c266ebf..90e25b5f3 100644 --- a/swiper.el +++ b/swiper.el @@ -4,7 +4,7 @@ ;; Author: Oleh Krehel ;; URL: https://github.com/abo-abo/swiper -;; Version: 0.5.1 +;; Version: 0.7.0 ;; Package-Requires: ((emacs "24.1")) ;; Keywords: matching @@ -61,35 +61,6 @@ '((t (:inherit isearch-fail))) "Face for `swiper' matches modulo 3.") -(defface swiper-minibuffer-match-face-1 - '((((class color) (background light)) - :background "#d3d3d3") - (((class color) (background dark)) - :background "#555555")) - "The background face for `swiper' minibuffer matches." - :group 'function-args-faces) - -(defface swiper-minibuffer-match-face-2 - '((((class color) (background light)) - :background "#e99ce8" :weight bold) - (((class color) (background dark)) - :background "#777777" :weight bold)) - "Face for `swiper' minibuffer matches modulo 1.") - -(defface swiper-minibuffer-match-face-3 - '((((class color) (background light)) - :background "#bbbbff" :weight bold) - (((class color) (background dark)) - :background "#7777ff" :weight bold)) - "Face for `swiper' minibuffer matches modulo 2.") - -(defface swiper-minibuffer-match-face-4 - '((((class color) (background light)) - :background "#ffbbff" :weight bold) - (((class color) (background dark)) - :background "#8a498a" :weight bold)) - "Face for `swiper' minibuffer matches modulo 3.") - (defface swiper-line-face '((t (:inherit highlight))) "Face for current `swiper' line.") @@ -98,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." @@ -109,6 +82,8 @@ (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.") @@ -119,22 +94,37 @@ (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) +(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 () @@ -159,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 @@ -180,31 +170,72 @@ (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") + +(defun swiper-mc () + (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)." (interactive "P") (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 erc-mode - org-agenda-mode - dired-mode - jabber-chat-mode - elfeed-search-mode - fundamental-mode - Man-mode - woman-mode - mu4e-view-mode - mu4e-headers-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)))))) @@ -213,17 +244,38 @@ "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.") -(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 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 (1+ (floor (log n-lines 10)))) + (setq swiper--width (or numbers-width + (1+ (floor (log n-lines 10))))) (setq swiper--format-spec (format "%%-%dd " swiper--width)) (let ((line-number 0) - (advancer (if visual-line-mode + (advancer (if swiper-use-visual-line (lambda (arg) (line-move arg t)) #'forward-line)) candidates) @@ -231,13 +283,22 @@ (goto-char (point-min)) (swiper-font-lock-ensure) (while (< (point) (point-max)) - (let ((str (concat " " (buffer-substring - (point) - (if visual-line-mode - (save-excursion - (end-of-visual-line) - (point)) - (line-end-position)))))) + (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))))))) + (remove-text-properties 0 (length str) '(field) str) (put-text-property 0 1 'display (format swiper--format-spec (cl-incf line-number)) @@ -254,74 +315,161 @@ "`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))) -(defvar swiper--anchor nil - "A line number to which the search should be anchored.") +(ivy-set-occur 'swiper 'swiper-occur) -(defvar swiper--len 0 - "The last length of input for which an anchoring was made.") +(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)) - (setq swiper--len 0) - (setq swiper--anchor (line-number-at-pos))) + (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 "^") - ".") - ((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)))) +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'.") -(defun swiper--ivy (&optional initial-input) - "`isearch' with an overview using `ivy'. +(defvar swiper-invocation-face nil + "The face at the point of invocation of `swiper'.") + +(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) - (let ((candidates (swiper--candidates)) - (preselect (buffer-substring-no-properties - (line-beginning-position) - (line-end-position))) + (setq swiper-invocation-face + (plist-get (text-properties-at (point)) 'face)) + (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) 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))))) + (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 () + "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." - (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)) @@ -344,33 +492,45 @@ When non-nil, INITIAL-INPUT is the initial search pattern." (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 visual-line-mode - (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) +(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." +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 @@ -383,7 +543,7 @@ BEG and END, when specified, are the point bounds." (line-beginning-position) (1+ (line-end-position)))))) (overlay-put ov 'face 'swiper-line-face) - (overlay-put ov 'window (ivy-state-window ivy-last)) + (overlay-put ov 'window wnd) (push ov swiper--overlays) (let* ((wh (window-height)) (beg (or beg (save-excursion @@ -413,26 +573,38 @@ BEG and END, when specified, are the point bounds." swiper-faces))))) (push overlay swiper--overlays) (overlay-put overlay 'face face) - (overlay-put overlay 'window (ivy-state-window ivy-last)) + (overlay-put overlay 'window wnd) (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)) - (funcall (if visual-line-mode - #'line-move - #'forward-line) - (1- (read (get-text-property 0 'display 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"))))) +(defun swiper--action (x) + "Goto line X." + (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 () @@ -459,13 +631,57 @@ BEG and END, when specified, are the point bounds." Run `swiper' for those buffers." (interactive) (setq swiper-multi-buffers nil) - (setq swiper-multi-candidates 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)) + :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 + :update-fn (lambda () + (swiper-multi-action-2 ivy--current)) + :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) @@ -479,31 +695,9 @@ Run `swiper' for those buffers." (cond ((memq this-command '(ivy-done ivy-alt-done ivy-immediate-done)) - (let ((ww (window-width))) - (dolist (buf swiper-multi-buffers) - (with-current-buffer buf - (setq swiper-multi-candidates - (append - (mapcar - (lambda (s) - (setq s (concat s " ")) - (let ((len (length s))) - (put-text-property - (1- len) len 'display - (concat - (make-string - (max - (- ww - (string-width s) - (length (buffer-name)) - 1) - 0) - ?\ ) - (buffer-name)) - s) - s)) - (swiper--candidates)) - swiper-multi-candidates)))))) + (setq swiper-multi-candidates + (swiper--multi-candidates + (mapcar #'get-buffer swiper-multi-buffers)))) ((eq this-command 'ivy-call) (delete-minibuffer-contents)))) @@ -513,7 +707,7 @@ Run `swiper' for those buffers." (when (string-match "\\` *\\([^ ]+\\)\\'" buf-space) (switch-to-buffer (match-string 1 buf-space)) (goto-char (point-min)) - (forward-line (1- (read x))) + (forward-line (1- (read (get-text-property 0 'display x)))) (re-search-forward (ivy--regex ivy-text) (line-end-position) t)