Если я не ошибаюсь, представленные решения (с использованием Perl и Vim) не работают должным образом, если среди последних слов, подлежащих замене, есть какие-либо замены. В частности, ни одно из решений не работает для первого примера: «i» будет заменено на «in», которое затем будет некорректно заменено на «ni», а затем последующими правилами вернется к «i», в то время как оно должно остаться как в".
Замены не могут считаться независимыми и применяться последовательно; они должны применяться параллельно.
В Emacs вы можете сделать это:
М-х параллельная замена ,
и в командной строке введите
i in in ni ni i .
Замена будет происходить между курсором и концом буфера или в области, если она выбрана.
(При условии, что у вас есть это определение в вашем ~/.emacs.d/init.el
: -)
(require 'cl)
(defun parallel-replace (plist &optional start end)
(interactive
`(,(loop with input = (read-from-minibuffer "Replace: ")
with limit = (length input)
for (item . index) = (read-from-string input 0)
then (read-from-string input index)
collect (prin1-to-string item t) until (<= limit index))
,@(if (use-region-p) `(,(region-beginning) ,(region-end)))))
(let* ((alist (loop for (key val . tail) on plist by #'cddr
collect (cons key val)))
(matcher (regexp-opt (mapcar #'car alist) 'words)))
(save-excursion
(goto-char (or start (point)))
(while (re-search-forward matcher (or end (point-max)) t)
(replace-match (cdr (assoc-string (match-string 0) alist)))))))
Edit (2013-08-20):
Несколько улучшений:
- Для особого случая, когда даны только два элемента, вместо этого выполнить своп (т. Е. Заменить друг друга);
- Запросите подтверждение для каждой замены таким же образом, как и
query-replace
.
(require 'cl)
(defun parallel-query-replace (plist &optional delimited start end)
"Replace every occurrence of the (2n)th token of PLIST in
buffer with the (2n+1)th token; if only two tokens are provided,
replace them with each other (ie, swap them).
If optional second argument DELIMITED is nil, match words
according to syntax-table; otherwise match symbols.
When called interactively, PLIST is input as space separated
tokens, and DELIMITED as prefix arg."
(interactive
`(,(loop with input = (read-from-minibuffer "Replace: ")
with limit = (length input)
for j = 0 then i
for (item . i) = (read-from-string input j)
collect (prin1-to-string item t) until (<= limit i))
,current-prefix-arg
,@(if (use-region-p) `(,(region-beginning) ,(region-end)))))
(let* ((alist (cond ((= (length plist) 2) (list plist (reverse plist)))
((loop for (key val . tail) on plist by #'cddr
collect (list (prin1-to-string key t) val)))))
(matcher (regexp-opt (mapcar #'car alist)
(if delimited 'words 'symbols)))
(to-spec `(replace-eval-replacement replace-quote
(cadr (assoc-string (match-string 0) ',alist
case-fold-search)))))
(query-replace-regexp matcher to-spec nil start end)))