Function: evil-ex-substitute

evil-ex-substitute is an interactive and byte-compiled function defined in evil-commands.el.

Signature

(evil-ex-substitute BEG END &optional PATTERN REPLACEMENT FLAGS)

Documentation

The Ex substitute command.

[BEG,END]substitute/PATTERN/REPLACEMENT/FLAGS

Key Bindings

Source Code

;; Defined in ~/.emacs.d/elpa/evil-20251108.138/evil-commands.el
(evil-define-operator evil-ex-substitute
  (beg end pattern replacement flags)
  "The Ex substitute command.
\[BEG,END]substitute/PATTERN/REPLACEMENT/FLAGS"
  :repeat nil
  :jump t
  :move-point nil
  :motion evil-line
  (interactive "<r><s/>")
  (evil-ex-nohighlight)
  (unless pattern (user-error "No pattern given"))
  (setq replacement (or replacement "")
        flags (append flags)
        evil-ex-last-was-search nil
        evil-ex-substitute-pattern pattern
        evil-ex-substitute-replacement replacement
        evil-ex-substitute-flags flags)
  (let* ((inhibit-field-text-motion t)
         (count-only (memq ?n flags))
         (confirm (and (memq ?c flags) (not count-only)))
         (case-fold-search (evil-ex-pattern-ignore-case pattern))
         (case-replace case-fold-search)
         (regex (evil-ex-pattern-regex pattern))
         (nreplaced 0)
         (orig-point (point-marker))
         (last-point (point))
         (whole-line (evil-ex-pattern-whole-line pattern))
         (evil-ex-substitute-overlay (make-overlay (point) (point)))
         (end-marker (move-marker (make-marker) end))
         (use-reveal confirm)
         match-data
         reveal-open-spots
         transient-mark-mode)
    (unwind-protect
        (catch 'exit-search
          (isearch-update-ring (setq isearch-string regex) t)
          (evil-ex-hl-change 'evil-ex-substitute pattern)
          (overlay-put evil-ex-substitute-overlay 'face 'isearch)
          (overlay-put evil-ex-substitute-overlay 'priority 1001)
          (goto-char beg)
          (while (re-search-forward regex end-marker t)
            (unless (and query-replace-skip-read-only
                         (text-property-any (match-beginning 0) (match-end 0) 'read-only t))
              (let* ((match-beg (match-beginning 0))
                     (match-end (match-end 0))
                     (zero-length-match (= match-beg match-end))
                     match-contains-newline)
                (goto-char match-beg)
                (when (and (= match-beg end-marker) (> end-marker beg) (bolp))
                  ;; This line is not included due to range being exclusive
                  (throw 'exit-search nil))
                (setq match-data (match-data t match-data)
                      match-contains-newline (search-forward "\n" match-end t))
                (goto-char match-end)
                (if confirm
                    (let* ((next-replacement
                            (if (stringp replacement) replacement
                              (funcall (car replacement) (cdr replacement) nreplaced)))
                           (prompt
                            (format "Replace %s with %s (y/n/a/q/l/^E/^Y)? "
                                    (match-string 0)
                                    (match-substitute-replacement
                                     next-replacement (not case-replace))))
                           (search-invisible t)
                           response)
                      (move-overlay evil-ex-substitute-overlay match-beg match-end)
                      ;; Simulate `reveal-mode'. `reveal-mode' uses
                      ;; `post-command-hook' but that won't work here.
                      (when use-reveal
                        (reveal-post-command))
                      (catch 'exit-read-char
                        (while (setq response (read-char prompt))
                          (when (member response '(?y ?a ?l))
                            (unless count-only
                              (set-match-data match-data)
                              (replace-match next-replacement (not case-replace)))
                            (cl-incf nreplaced)
                            (evil-ex-hl-set-region
                             'evil-ex-substitute
                             (line-beginning-position 2)
                             (evil-ex-hl-get-max 'evil-ex-substitute)))
                          (cl-case response
                            ((?y ?n) (throw 'exit-read-char nil))
                            (?a (setq confirm nil)
                                (throw 'exit-read-char nil))
                            ((?q ?l ?\C-\[) (throw 'exit-search nil))
                            (?\C-e (evil-scroll-line-down 1))
                            (?\C-y (evil-scroll-line-up 1))))))
                  (unless count-only
                    (let ((next-replacement
                           (if (stringp replacement) replacement
                             (funcall (car replacement) (cdr replacement) nreplaced))))
                      (set-match-data match-data)
                      (replace-match next-replacement (not case-replace))))
                  (cl-incf nreplaced))
                (setq last-point (point))
                (cond ((>= (point) end-marker)
                       ;; Don't want to perform multiple replacements at the end
                       ;; of the search region.
                       (throw 'exit-search nil))
                      ((and (not whole-line)
                            (not match-contains-newline))
                       (forward-line)
                       ;; forward-line just moves to the end of the line on the
                       ;; last line of the buffer.
                       (when (>= (point) end-marker) (throw 'exit-search nil)))
                      ;; For zero-length matches check to see if point won't
                      ;; move next time. This is a problem when matching the
                      ;; regexp "$" because we can enter an infinite loop,
                      ;; repeatedly matching the same character
                      ((and zero-length-match
                            (let ((pnt (point)))
                              (save-excursion
                                (and (re-search-forward regex end-marker t)
                                     (= pnt (point))))))
                       (when (eobp) (throw 'exit-search nil))
                       (forward-char)))))))
      (evil-ex-delete-hl 'evil-ex-substitute)
      (delete-overlay evil-ex-substitute-overlay)
      (goto-char (if count-only orig-point last-point))
      (move-marker orig-point nil)
      (move-marker end-marker nil)

      (when use-reveal
        (evil-revert-reveal reveal-open-spots)))

    (evil--ex-substitute-final-message nreplaced flags)

    (if (and (= nreplaced 0) evil-ex-point)
        (goto-char evil-ex-point)
      (evil-first-non-blank))))