Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 1c410fc2b8
Fetching contributors…

Cannot retrieve contributors at this time

421 lines (353 sloc) 16.396 kB
;;; iedit.el --- Edit multiple regions in the same way simultaneously.
;; Copyright (C) 2010, 2011, 2012 Victor Ren
;; Time-stamp: <2012-08-10 10:00:31 Victor Ren>
;; Author: Victor Ren <victorhge@gmail.com>
;; Keywords: occurrence region simultaneous rectangle refactoring
;; Version: 0.97
;; X-URL: http://www.emacswiki.org/emacs/Iedit
;; Compatibility: GNU Emacs: 22.x, 23.x, 24.x
;; This file is not part of GNU Emacs, but it is distributed under
;; the same terms as GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This package is an Emacs minor mode and allows you to edit one occurrence of
;; some text in a buffer (possibly narrowed) or region, and simultaneously have
;; other occurrences edited in the same way.
;;
;; Normal scenario of iedit-mode is like:
;;
;; - Highlight certain contents - by press C-;
;; All occurrences of a symbol, string or a rectangle in the buffer or a
;; region may be highlighted corresponding to current mark, point and prefix
;; argument. Refer to the document of `iedit-mode' for details.
;;
;; - Edit one of the occurrences
;; The change is applied to other occurrences simultaneously.
;;
;; - Finish - by pressing C-; again
;;
;; This package also provides rectangle support with *visible rectangle*
;; highlighting, which is similar with cua mode rectangle support, but still
;; quite different.
;; You can also use Iedit mode as a quick way to temporarily show only the
;; buffer lines that match the current text being edited. This gives you the
;; effect of a temporary `keep-lines' or `occur'. To get this effect, hit C-'
;; when in Iedit mode - it toggles hiding non-matching lines.
;;
;; Renaming refactoring is convenient in Iedit mode
;;
;; - The symbol under point is selected as occurrence by default and only
;; complete symbols are matched
;; - With digit prefix argument 0, only symbols in current function are matched
;; - Restricting symbols in current region can be done by pressing C-; again
;; - Last renaming refactoring is remembered and can be applied to other buffers
;; later
;;
;; There are also some other facilities you may never think about. Refer to the
;; document of function `iedit-mode' (C-h f iedit-mode RET) for more details.
;; The code was developed and fully tested on Gnu Emacs 24.0.93, partially
;; tested on Gnu Emacs 22. If you have any compatible problem, please let me
;; know.
;;; todo:
;; - Add more easy access keys for whole occurrence
;;; Contributors
;; Adam Lindberg <eproxus@gmail.com> added a case sensitivity option that can be toggled.
;; Tassilo Horn <tassilo@member.fsf.org> added an option to match only complete
;; words, not inside words
;; Le Wang <l26wang@gmail.com> proposed to match only complete symbols, not
;; inside symbols, contributed rectangle support
;;; Code:
(eval-when-compile (require 'cl))
(require 'rect) ;; kill rectangle
(require 'iedit-lib)
(defgroup iedit nil
"Edit multiple regions in the same way simultaneously."
:prefix "iedit-"
:group 'replace
:group 'convenience)
(defcustom iedit-current-symbol-default t
"If no-nil, use current symbol by default for the occurrence."
:type 'boolean
:group 'iedit)
(defcustom iedit-only-at-symbol-boundaries t
"If no-nil, matches have to start and end at symbol boundaries.
For example, when invoking command `iedit-mode' on the \"in\" in the
sentence \"The king in the castle...\", the \"king\" is not
edited."
:type 'boolean
:group 'iedit)
(defvar iedit-mode-hook nil
"Function(s) to call after starting up an iedit.")
(defvar iedit-mode-end-hook nil
"Function(s) to call after terminating an iedit.")
(defvar iedit-mode nil) ;; Name of the minor mode
(defvar iedit-only-complete-symbol-local nil
"This is buffer local variable which indicates the occurrence
only matches complete symbol.")
(defvar iedit-only-complete-symbol-global nil
"This is global variable which indicates the last global occurrence
only matches complete symbol.")
(defvar iedit-last-occurrence-local nil
"This is buffer local variable which is the occurrence when
Iedit mode is turned off last time.")
(defvar iedit-last-occurrence-global nil
"This is global variable which is the occurrence when
Iedit mode is turned off last time.")
(defvar iedit-last-initial-string-global nil
"This is a global variable which is the last initial occurrence string.")
(defvar iedit-initial-string-local nil
"This is buffer local variable which is the initial string to start Iedit mode.")
(make-variable-buffer-local 'iedit-mode)
(make-variable-buffer-local 'iedit-only-complete-symbol-local)
(make-variable-buffer-local 'iedit-last-occurrence-local)
(or (assq 'iedit-mode minor-mode-alist)
(nconc minor-mode-alist
(list '(iedit-mode iedit-mode))))
(defun iedit-describe-mode ()
"Display documentation of Iedit mode."
(interactive)
(let (same-window-buffer-names same-window-regexps)
(describe-function 'iedit-mode)))
;;; Default key bindings:
(define-key global-map (kbd "C-;") 'iedit-mode)
(define-key isearch-mode-map (kbd "C-;") 'iedit-mode)
(define-key esc-map (kbd "C-;") 'iedit-execute-last-modification)
(define-key help-map (kbd "C-;") 'iedit-mode-toggle-on-function)
;; Avoid to restore Iedit mode when restoring desktop
(add-to-list 'desktop-minor-mode-handlers
'(iedit-mode . nil))
;;; Define iedit help map.
(eval-when-compile (require 'help-macro))
(defvar iedit-mode-keymap
(let ((map (make-sparse-keymap)))
(set-keymap-parent map iedit-occurrence-keymap)
(define-key map (kbd "M-H") 'iedit-restrict-function)
(define-key map (kbd "M-C") 'iedit-toggle-case-sensitive)
map)
"Keymap used within overlays in Iedit mode.")
;;; Define Iedit mode map
(or (assq 'iedit-mode minor-mode-map-alist)
(setq minor-mode-map-alist
(cons (cons 'iedit-mode iedit-mode-keymap) minor-mode-map-alist)))
;; Avoid to restore Iedit mode when restoring desktop
(add-to-list 'desktop-minor-mode-handlers
'(iedit-mode . nil))
;;;###autoload
(defun iedit-mode (&optional arg)
"Toggle Iedit mode.
This command behaves differently, depending on the mark, point,
prefix argument and variable `iedit-transient-mark-sensitive'.
If Iedit mode is off, turn Iedit mode on.
When Iedit mode is turned on, all the occurrences of the current
region in the buffer (possibly narrowed) or a region are
highlighted. If one occurrence is modified, the change are
propagated to all other occurrences simultaneously.
If region is not active, the current symbol (returns from
`current-word') is used as the occurrence by default. The
occurrences of the current symbol, but not include occurrences
that are part of other symbols, are highlighted. If you still
want to match all the occurrences, even though they are parts of
other symbols, you may have to mark the symbol first.
In the above two situations, with digit prefix argument 0, only
occurrences in current function are matched. This is good for
renaming refactoring in programming.
You can also switch to Iedit mode from isearch mode directly. The
current search string is used as occurrence. All occurrences of
the current search string are highlighted.
With an universal prefix argument, the occurrence when Iedit mode
is turned off last time in current buffer is used as occurrence.
This is intended to recover last Iedit mode which is turned off.
If region active, Iedit mode is limited within the current
region.
With repeated universal prefix argument, the occurrence when
Iedit mode is turned off last time (might be in other buffer) is used
as occurrence. If region active, Iedit mode is limited within
the current region.
If Iedit mode is on and region is active, Iedit mode is
restricted in the region, e.g. the occurrences outside of the region
is excluded.
If Iedit mode is on and region is active, with an universal
prefix argument, Iedit mode is restricted outside of the region,
e.g. the occurrences in the region is excluded.
Turn off Iedit mode in other situations.
Commands:
\\{iedit-occurrence-keymap}"
(interactive "P")
(if iedit-mode
(progn
(iedit-mode-on-action arg)
(setq iedit-only-complete-symbol-global iedit-only-complete-symbol-local))
(iedit-barf-if-lib-active)
(let (occurrence
complete-symbol
(beg (if (eq major-mode 'occur-edit-mode) ; skip the first occurrence
(next-single-char-property-change 1 'read-only)
(point-min)))
(end (point-max)))
(cond ((and arg
(= 4 (prefix-numeric-value arg))
iedit-last-occurrence-local)
(setq occurrence iedit-last-occurrence-local)
(setq complete-symbol iedit-only-complete-symbol-local))
((and arg
(= 16 (prefix-numeric-value arg))
iedit-last-initial-string-global)
(setq occurrence iedit-last-initial-string-global)
(setq complete-symbol iedit-only-complete-symbol-global))
((iedit-region-active)
(setq occurrence (buffer-substring-no-properties
(mark) (point))))
((and isearch-mode (not (string= isearch-string "")))
(setq occurrence (buffer-substring-no-properties
(point) isearch-other-end))
(isearch-exit))
((and iedit-current-symbol-default (current-word t))
(setq occurrence (current-word))
(when iedit-only-at-symbol-boundaries
(setq complete-symbol t)))
(t (error "No candidate of the occurrence, cannot enable Iedit mode")))
(when arg
(if (= 0 (prefix-numeric-value arg))
(save-excursion
(mark-defun)
(setq beg (region-beginning))
(setq end (region-end)))
(when (iedit-region-active)
(setq beg (region-beginning))
(setq end (region-end)))))
(setq iedit-only-complete-symbol-local complete-symbol)
(set-mark nil)
(setq iedit-case-sensitive-local iedit-case-sensitive-default)
(iedit-start occurrence beg end))))
(defun iedit-start (occurrence-exp beg end)
"Start Iedit mode for the OCCURRENCE-EXP in the current buffer."
(setq iedit-unmatched-lines-invisible iedit-unmatched-lines-invisible-default)
(setq iedit-initial-string-local occurrence-exp)
(iedit-refresh occurrence-exp beg end)
(run-hooks 'iedit-mode-hook)
(add-hook 'kbd-macro-termination-hook 'iedit-done nil t)
(add-hook 'change-major-mode-hook 'iedit-done nil t)
(add-hook 'iedit-aborting-hook 'iedit-done nil t))
(defun iedit-refresh (occurrence-exp beg end)
"Refresh Iedit mode."
(setq occurrence-exp (regexp-quote occurrence-exp))
(when iedit-only-complete-symbol-local
(setq occurrence-exp (concat "\\_<" occurrence-exp "\\_>")))
(setq iedit-mode
(propertize
(concat " Iedit:"
(number-to-string
(iedit-make-occurrences-overlays occurrence-exp beg end)))
'face
'font-lock-warning-face))
(force-mode-line-update))
(defun iedit-done ()
"Exit Iedit mode.
Save the current occurrence string locally and globally. Save
the initial string globally."
(when iedit-buffering
(iedit-stop-buffering))
(setq iedit-last-occurrence-local (iedit-current-occurrence-string))
(setq iedit-last-occurrence-global iedit-last-occurrence-local)
(setq iedit-last-initial-string-global iedit-initial-string-local)
(setq iedit-case-sensitive-global iedit-case-sensitive-local)
(iedit-cleanup)
(setq iedit-mode nil)
(force-mode-line-update)
(remove-hook 'kbd-macro-termination-hook 'iedit-done t)
(remove-hook 'change-major-mode-hook 'iedit-done t)
(remove-hook 'iedit-aborting-hook 'iedit-done t)
(run-hooks 'iedit-mode-end-hook))
(defun iedit-mode-on-action (&optional arg)
"Turn off Iedit mode or restrict it in a region if region is active."
(if (iedit-region-active)
;; Restrict iedit-mode
(let ((beg (region-beginning))
(end (region-end)))
(if (null (iedit-find-overlay beg end 'iedit-occurrence-overlay-name arg))
(iedit-done)
(iedit-restrict-region beg end arg)
(iedit-first-occurrence)))
(iedit-done)))
;;;###autoload
(defun iedit-mode-toggle-on-function ()
"Toggle Iedit mode on current function."
(interactive)
(iedit-mode 0))
(defun iedit-execute-last-modification (&optional arg)
"Apply last modification in Iedit mode to the current buffer or an active region."
(interactive "*P")
(or (and iedit-last-initial-string-global
(not (string= iedit-last-initial-string-global iedit-last-occurrence-global)))
(error "No modification available"))
(let ((occurrence-exp (regexp-quote iedit-last-initial-string-global))
(replacement iedit-last-occurrence-global)
(case-fold-search (not iedit-case-sensitive-global))
beg end)
(when case-fold-search
(setq occurrence-exp (downcase occurrence-exp))
(setq replacement (downcase replacement)))
(if iedit-only-complete-symbol-global
(setq occurrence-exp (concat "\\_<" occurrence-exp "\\_>")))
(when (iedit-region-active)
(setq beg (region-beginning))
(setq end (region-end)))
(perform-replace occurrence-exp replacement t t nil nil nil beg end)))
(defun iedit-apply-global-modification ()
"Apply last global modification."
(interactive "*")
(if (and iedit-last-initial-string-global
(string= iedit-initial-string-local iedit-last-initial-string-global)
(not (string= iedit-last-initial-string-global iedit-last-occurrence-global)))
(iedit-replace-occurrences iedit-last-occurrence-global)
(message "No global modification available.")))
(defun iedit-restrict-function(&optional arg)
"Restricting Iedit mode in current function."
(interactive "P")
(save-excursion
(mark-defun)
(iedit-restrict-region (region-beginning) (region-end) arg)))
(defun iedit-restrict-region (beg end &optional inclusive)
"Restricting Iedit mode in a region."
(when iedit-buffering
(iedit-stop-buffering))
(setq iedit-last-occurrence-local (iedit-current-occurrence-string))
(set-mark nil)
(iedit-show-all)
(iedit-cleanup-occurrences-overlays beg end inclusive)
(if iedit-unmatched-lines-invisible
(iedit-hide-unmatched-lines iedit-occurrence-context-lines))
(setq iedit-mode (propertize
(concat " Iedit:" (number-to-string
(length iedit-occurrences-overlays)))
'face 'font-lock-warning-face))
(force-mode-line-update))
(defun iedit-toggle-case-sensitive ()
"Toggle case-sensitive matching occurrences.
Todo: how about region"
(interactive)
(setq iedit-case-sensitive-local (not iedit-case-sensitive-local))
(if iedit-buffering
(iedit-stop-buffering))
(setq iedit-last-occurrence-local (iedit-current-occurrence-string))
(when iedit-last-occurrence-local
(remove-overlays nil nil iedit-occurrence-overlay-name t)
(iedit-show-all)
(iedit-refresh iedit-last-occurrence-local (point-min) (point-max))))
(provide 'iedit)
;;; iedit.el ends here
;; LocalWords: iedit el MERCHANTABILITY kbd isearch todo ert Lindberg Tassilo
;; LocalWords: eval rect defgroup defcustom boolean defvar assq alist nconc
;; LocalWords: substring cadr keymap defconst purecopy bkm defun princ prev
;; LocalWords: iso lefttab backtab upcase downcase concat setq autoload arg
;; LocalWords: refactoring propertize cond goto nreverse progn rotatef eq elp
;; LocalWords: dolist pos unmatch args ov sReplace iedit's cdr quote'ed
Jump to Line
Something went wrong with that request. Please try again.