11

I frequently find myself trying to replace e.g. foo with bar and bar with foo in a buffer.

The way I usually do it is either:

  • 3 query-replaces: aaa -> @@@, bbb -> aaa, @@@ -> bbb
  • give up on automation and just do the replacement by hand

I suppose that it is possible to get something working using the , syntax in query-replace-regexp to test if the matched string is aaa or bbb. But the resulting syntax would be too cumbersome, so I never really tried it.

The thing is, given how common a task it is, I think there must be a better, single-step, way of doing it, either built-in or in an existing package. Is there?

T. Verron
  • 4,233
  • 1
  • 22
  • 55
  • There may be a single-step way, but your first way is not bad, IMO. And if you choose the replacement string right then you can just do no-query replacement for the second pass. – Drew Sep 17 '16 at 13:48
  • @Drew For regular `query-replace`, I know that it is usually faster to `M-% str1 str2` than to move the point and do the change by hand. It's nice, because I don't have to waste time thinking about what will be fastest, even if there is only one occurrence of `str1`. Ideally, I'd like text inversion to be just as fast, so that I can use it without thinking about it as well. – T. Verron Sep 17 '16 at 14:00
  • Someone will provide a simple answer. You can no doubt do what you ask, e.g., by using a Lisp expression for the replacement, referencing the text to be replaced. E.g., use something similar to the swap idiom `(setq a (prog1 b (setq b a)))`. And there may even be a simpler way. – Drew Sep 17 '16 at 15:05
  • This is not a homework question, I know that I can write it if need be. But if it already exists... – T. Verron Sep 17 '16 at 15:16
  • I'm sure it's not a homework question, and it is a good question. Someone will no doubt provide a good answer. I don't have time to look into it now, myself. (I'm having trouble with SE at the moment - I had to switch just now to https, just to be able to add this comment.) – Drew Sep 17 '16 at 15:35
  • [plur.el](https://github.com/xuchunyang/plur) (by me) should be able to do the job, it introduces a new syntax, for example, in your case, `M-x plur-query-replace RET {foo,bar} RET {bar,foo} RET` will swap foo and bar. This package is not integrated very well with Emacs's isearch.el/replace.el. I rarely use it – xuchunyang Sep 18 '16 at 04:47
  • 2
    I don't find `\,(if \1 "b" "a")` especially ugly (against a regexp of `\(a\)\|b`), but still the words have to be typed twice, if that bothers you. – politza Sep 19 '16 at 17:01
  • @politza No it's not ugly, it's way smarter than what I had in mind. With longer words it'd require 2 groups and to remember once and for all if we should test for `\1` or `\2`, but still it's short and to the point. Care to make it an answer? – T. Verron Sep 20 '16 at 07:53
  • Why does it need 2 groups in this case, I don't see ? – politza Sep 20 '16 at 18:22
  • @politza `\(\(aaa\)\|bbb\)` , no ? – T. Verron Sep 21 '16 at 04:23
  • 2
    Why not `\(aaa\)\|bbb` ? – politza Sep 21 '16 at 05:05
  • @politza Hm, I thought that the "or" had priority and thus applied to only one character unless in a group. Never too late to learn, I guess. – T. Verron Sep 21 '16 at 06:36

2 Answers2

8

Here is a small command that will do this:

(defun query-swap-strings (from-string to-string &optional delimited start end)
  "Swap occurrences of FROM-STRING and TO-STRING."
  (interactive
   (let ((common
          (query-replace-read-args
           (concat "Query swap"
                   (if current-prefix-arg
                       (if (eq current-prefix-arg '-) " backward" " word")
                     "")
                   (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)))))
  (perform-replace
   (concat "\\(" (regexp-quote from-string) "\\)\\|" (regexp-quote to-string))
   `(replace-eval-replacement replace-quote (if (match-string 1) ,to-string ,from-string))
   t t delimited nil nil start end))
link0ff
  • 1,081
  • 5
  • 14
  • Thank you, that's way more elaborate than something I would have written myself! Maybe you could elaborate on the implemented features? I see that with prefix argument you go backwards, that it performs the replace only in region if needed... Or is it just a rewriting of `query-replace` for the inversion, so that it retains all its features? It doesn't match 100% the definition in my emacs, but I'm still with 24.5... – T. Verron Sep 20 '16 at 07:58
  • @T.Verron This is just a standard body from `query-replace` family of functions from the latest version, so it retains all features. To the end of the function I added my recommended way of swapping strings mentioned in Emacs manual. – link0ff Sep 20 '16 at 08:26
  • I probably accepted too fast. The function only works here (emacs 24.3) if I comment out `backward` and `region-noncontiguous-p` from the call to `perform-replace`. Is it a general problem, or does it mean that the interface to `perform-replace` has changed between versions? – T. Verron Oct 14 '16 at 07:30
  • Yes, the interface has changed with these 2 arguments added in the latest version, so they don't exist in earlier ones, – link0ff Oct 14 '16 at 07:35
  • This is somewhat a problem. I don't always control what version of emacs I am using... I guess a test for `emacs-version` at runtime would do it, but maybe there is a more natural way to define this function, for example wrapping around `query-replace-regexp`? – T. Verron Oct 14 '16 at 07:51
  • You can just remove new arguments, and it will work in all versions. – link0ff Oct 14 '16 at 07:59
  • 2
    This solution also seems to be fine now. – Name Oct 14 '16 at 08:10
8

Install plur

and run the command plur-query-replace

and input {foo,bar} and its replacement {bar,foo}

Hit y to replace the occurrences as desired.

There are also non-interactive, and isearch-like, variants of this command.

InHarmsWay
  • 1,309
  • 7
  • 8
  • Thanks! plur's purpose is also something I have sometimes been looking for, I will definitely have a look at it. My only hesitation stems from the comment of the author of the package above... `;)` – T. Verron Sep 20 '16 at 07:55
  • 1
    `plur` requires `emacs 24.4`. – T. Verron Oct 14 '16 at 08:41