32

Is it possible to perform a replace-string operation inside of a rectangular region in emacs? If so, how?

user545424
  • 15,713
  • 11
  • 56
  • 70

4 Answers4

30

If you enable CUA selection mode:

M-x cua-selection-mode RET

or permanently in your init file:

(cua-selection-mode 1)

You can then use its advanced rectangle editing facilities.

(or if you're a cua-mode user, then you don't need to do that.)

  • C-RET to mark a corner.
  • Move point to opposite corner.
  • M-r to do a regexp replace within the marked rectangle.
  • C-RET to unmark/exit rectangle editing.

For documentation, look for the "CUA rectangle support" heading in the commentary in M-x find-library RET cua-base RET

If you don't want to use the cua rectangle facilities for some reason (perhaps if you really need replace-string specifically), a custom function using apply-on-rectangle would be pretty simply to put together.

Edit: Actually, slightly more complex than I had expected, but most of the code is the interactive spec and the support for the 'delimited' prefix argument behaviour (both based on replace-string).

Edit 2: I decided this was worth doing in a more fully-featured manner:

The following provides C-xrM-% and C-xrC-M-%, which (hopefully) act the way you would expect.

(require 'rect)

(defun my-search-replace-in-rectangle
  (start end search-pattern replacement search-function literal)
  "Replace all instances of SEARCH-PATTERN (as found by SEARCH-FUNCTION)
with REPLACEMENT, in each line of the rectangle established by the START
and END buffer positions.

SEARCH-FUNCTION should take the same BOUND and NOERROR arguments as
`search-forward' and `re-search-forward'.

The LITERAL argument is passed to `replace-match' during replacement.

If `case-replace' is nil, do not alter case of replacement text."
  (apply-on-rectangle
   (lambda (start-col end-col search-function search-pattern replacement)
     (move-to-column start-col)
     (let ((bound (min (+ (point) (- end-col start-col))
                       (line-end-position)))
           (fixedcase (not case-replace)))
       (while (funcall search-function search-pattern bound t)
         (replace-match replacement fixedcase literal))))
   start end search-function search-pattern replacement))

(defun my-replace-regexp-rectangle-read-args (regexp-flag)
  "Interactively read arguments for `my-replace-regexp-rectangle'
or `my-replace-string-rectangle' (depending upon REGEXP-FLAG)."
  (let ((args (query-replace-read-args
               (concat "Replace"
                       (if current-prefix-arg " word" "")
                       (if regexp-flag " regexp" " string"))
               regexp-flag)))
    (list (region-beginning) (region-end)
          (nth 0 args) (nth 1 args) (nth 2 args))))

(defun my-replace-regexp-rectangle
  (start end regexp to-string &optional delimited)
  "Perform a regexp search and replace on each line of a rectangle
established by START and END (interactively, the marked region),
similar to `replace-regexp'.

Optional arg DELIMITED (prefix arg if interactive), if non-nil, means
replace only matches surrounded by word boundaries.

If `case-replace' is nil, do not alter case of replacement text."
  (interactive (my-replace-regexp-rectangle-read-args t))
  (when delimited
    (setq regexp (concat "\\b" regexp "\\b")))
  (my-search-replace-in-rectangle
   start end regexp to-string 're-search-forward nil))

(defun my-replace-string-rectangle
  (start end from-string to-string &optional delimited)
  "Perform a string search and replace on each line of a rectangle
established by START and END (interactively, the marked region),
similar to `replace-string'.

Optional arg DELIMITED (prefix arg if interactive), if non-nil, means
replace only matches surrounded by word boundaries.

If `case-replace' is nil, do not alter case of replacement text."
  (interactive (my-replace-regexp-rectangle-read-args nil))
  (let ((search-function 'search-forward))
    (when delimited
      (setq search-function 're-search-forward
            from-string (concat "\\b" (regexp-quote from-string) "\\b")))
    (my-search-replace-in-rectangle
     start end from-string to-string search-function t)))

(global-set-key (kbd "C-x r M-%") 'my-replace-string-rectangle)
(global-set-key (kbd "C-x r C-M-%") 'my-replace-regexp-rectangle)
phils
  • 71,335
  • 11
  • 153
  • 198
  • 1
    Thanks! Unfortunately, it seems I can only mark a corner with `C-RET` in the emacs GUI, not in terminal mode. – user545424 Jun 21 '12 at 16:47
  • That'll be due to your terminal not sending that sequence. You may find an xterm more successful (see also http://stackoverflow.com/questions/4337837/send-c-to-emacs-in-vt100-xterm-terminal-mac-os-xs-terminal). I've also updated my answer with more fully-featured code for the non-CUA approach. – phils Jun 23 '12 at 14:41
  • Feature request: search the rectangle. I have a huge column and I need to check does it have anything else but `DPP`. I use some complex regexp -- and if the column is really big -- I get regexp stack overflow. – Adobe Nov 03 '12 at 12:56
  • Just a note for others who might be confused like me - M-r is bound to cua-replace-in-rectangle only when a selected rectangle is active. – Michael Paulukonis Mar 20 '15 at 13:55
  • Thank you for the lisp, it is just working out of the box! – hell_ical_vortex Jul 22 '16 at 05:53
6

In recent Emacs (e.g. Emacs 25) you can do this out of the box using M-% (query-replace) or C-M-% (query-replace-regexp).

Use M-x rectangle-mark-mode to create the rectangular region. Use C-x C-x if necessary, to put point before mark. Then just query-replace.

They didn't, however, make the same change in Emacs 25 for replace-string. You can do that, if you like, just by adding argument region-noncontiguous-p the same way it's done in query-replace. The code is simple:

(defun replace-string (from-string to-string &optional delimited start end backward
                       region-noncontiguous-p)
  "..."
  (declare (interactive-only
            "use `search-forward' and `replace-match' instead."))
  (interactive
   (let ((common
          (query-replace-read-args
           (concat "Replace"
                   (if current-prefix-arg
                       (if (eq current-prefix-arg '-) " backward" " word")
                     "")
                   " string"
                   (if (use-region-p) " in region" ""))
           nil)))
     (list (nth 0 common) (nth 1 common) (nth 2 common)
           (if (use-region-p) (region-beginning))
           (if (use-region-p) (region-end))
           (nth 3 common)
           (if (use-region-p) (region-noncontiguous-p)))))
  (perform-replace
    from-string to-string nil nil delimited nil nil start end backward region-noncontiguous-p))

Alternatively, you can just download library replace+.el and use the version of replace-string from there. It does what you want.

FWIW, I've just filed Emacs bug#27897 to get this feature added to replace-string and several other commands in the same library, replace.el.

Drew
  • 29,895
  • 7
  • 74
  • 104
  • There is also an anzu bug (which might be replacing your version of `query-replace`) related to this: https://github.com/syohex/emacs-anzu/issues/69 – Micah Elliott Sep 06 '17 at 22:28
1

For evil users, you can use my package evil-visual-replace to get query-replace and replace-regexp inside evil visual blocks.

pyrocrasty
  • 717
  • 8
  • 11
0

If you are in CUA mode, you can use cua-replace-in-rectangle bound to 'M-r'.

Mario Giovinazzo
  • 431
  • 2
  • 12
  • But this only replaces first occurrence of the thing (default vim replace-regexp behavior). – Adobe Nov 22 '17 at 10:36