Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make swiper-query-replace a standalone command #2777

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 85 additions & 43 deletions swiper.el
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ If the input is empty, select the previous history element instead."
map)
"Keymap for swiper.")

(defun swiper--regex-plain (str)
"Use STR literally as an ivy regex."
(let ((regex (or (ivy--regex-p str) (regexp-quote str))))
astoff marked this conversation as resolved.
Show resolved Hide resolved
(setq ivy--subexps (regexp-opt-depth regex))
regex))

(defvar swiper--query-replace-overlays nil)

(defun swiper--query-replace-updatefn ()
Expand Down Expand Up @@ -193,49 +199,85 @@ If the input is empty, select the previous history element instead."
(unless (> (match-end 0) (match-beginning 0))
(forward-char)))))))

(defun swiper-query-replace ()
"Start `query-replace' with string to replace from last search string."
(interactive)
(cond ((null (window-minibuffer-p))
(user-error "Should only be called in the minibuffer through `swiper-map'"))
((string= "" ivy-text)
(user-error "Empty input"))
(t
(swiper--query-replace-setup)
(unwind-protect
(let* ((enable-recursive-minibuffers t)
(from (ivy-re-to-str ivy-regex))
(groups (number-sequence 1 ivy--subexps))
(default
(list
(mapconcat (lambda (i) (format "\\%d" i)) groups " ")
(format "\\,(concat %s)"
(if (<= ivy--subexps 1)
"\\&"
(mapconcat
(lambda (i) (format "\\%d" i))
groups
" \" \" ")))))
(to
(query-replace-compile-replacement
(ivy-read
(format "Query replace %s with: " from) nil
:def default
:caller 'swiper-query-replace)
t)))
(swiper--cleanup)
(ivy-exit-with-action
(lambda (_)
(with-ivy-window
(move-beginning-of-line 1)
(let ((inhibit-read-only t))
(perform-replace from to
t t nil))))))
(swiper--query-replace-cleanup)))))

(ivy-configure 'swiper-query-replace
:update-fn #'swiper--query-replace-updatefn)
(put 'swiper-query-replace 'no-counsel-M-x t)
;;;###autoload
(defun swiper-query-replace (&optional initial-regexp initial-replacement start end)
"Read a regexp with Swiper and start a query replace operation.
INITIAL-REGEXP and INITIAL-REPLACEMENT are the initial inputs of
the respective prompts. When called interactively with a prefix
argument, the values of the previous query replace are used.

START and END specify the region to operate on. When called
interactively from outside the minibuffer, the current region is
used if active.

This command can also be called from within a Swiper session. In
this case, it doesn't ask for a regexp, and instead uses the
current Swiper input."
(interactive (list (when current-prefix-arg (caar query-replace-defaults))
(when current-prefix-arg (cdar query-replace-defaults))
(when (use-region-p) (region-beginning))
(when (use-region-p) (region-end))))
(barf-if-buffer-read-only)
(if (window-minibuffer-p)
(ivy-exit-with-action
(lambda (x)
(swiper-isearch-action x)
(swiper-query-replace t)))
(when start
(deactivate-mark) ;; TODO: maybe add a fake active region as overlay
(goto-char start))
(when (string-or-null-p initial-regexp)
(let ((ivy-fixed-height-minibuffer t)
(cursor-in-non-selected-windows nil)
(swiper-min-highlight 1))
(ivy-read
"Swiper query replace: "
#'swiper-isearch-function
:initial-input initial-regexp
:keymap swiper-isearch-map
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives a "reference to free variable swiper-isearch-map" byte-compiler warning, which probably means you need to define that variable before this function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've replaced this with swiper-map. Then M-n will not quote the query, but at least no large reorganizing of the code is needed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know which is better, but FWIW moving a defvar verbatim doesn't qualify as "large reorganizing of the code".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True; but then a random piece of swiper-isearch will be outside of the corresponding ;;*-section of the file. I was assuming this is undesired.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rules are made to be broken ;).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or rather, there are always exceptions that prove the rule.

:dynamic-collection t
:require-match t
:action #'swiper-isearch-action
:re-builder #'swiper--regex-plain
:history query-replace-from-history-variable
:extra-props (list :fname (buffer-file-name))
:caller 'swiper-isearch)))
(when (string= "" ivy-text) (user-error "Empty input"))
(swiper--delayed-add-overlays)
(swiper--add-cursor-overlay (ivy-state-window ivy-last))
(swiper--query-replace-setup)
(unwind-protect
(let* ((from (ivy-re-to-str ivy-regex))
(ivy-count-format "")
(to
(query-replace-compile-replacement
(ivy-read
(format "Query replace %s with: " from) nil
:initial-input initial-replacement
:history query-replace-to-history-variable
;; TODO: M-n here could insert (map-elt query-replace-defaults from)
:update-fn #'swiper--query-replace-updatefn)
t)))
(swiper--cleanup)
(add-to-history 'query-replace-defaults (cons from to))
(with-ivy-window
(perform-replace from to t t nil nil nil
(or start
(if (looking-back from (line-beginning-position))
(match-beginning 0)
(point)))
(or end (point-max)))))
(swiper--cleanup)
(swiper--query-replace-cleanup))))

(ivy-add-actions 'swiper '(("q" (lambda (x)
(swiper--action x)
(swiper-query-replace t))
"query replace")))
(ivy-add-actions 'swiper-isearch '(("q" (lambda (x)
(swiper-isearch-action x)
(swiper-query-replace t))
"query replace")))
basil-conto marked this conversation as resolved.
Show resolved Hide resolved

(defvar inhibit-message)

Expand Down