]> git.donarmstrong.com Git - lib.git/blob - emacs_el/iedit.el
set the default for emacs lisp to tangle
[lib.git] / emacs_el / iedit.el
1 ;;; iedit.el --- Edit multiple regions in the same way simultaneously.
2
3 ;; Copyright (C) 2010, 2011, 2012 Victor Ren
4
5 ;; Time-stamp: <2012-10-22 14:14:53 Victor Ren>
6 ;; Author: Victor Ren <victorhge@gmail.com>
7 ;; Keywords: occurrence region simultaneous refactoring
8 ;; Version: 0.97
9 ;; X-URL: http://www.emacswiki.org/emacs/Iedit
10 ;; Compatibility: GNU Emacs: 22.x, 23.x, 24.x
11
12 ;; This file is not part of GNU Emacs, but it is distributed under
13 ;; the same terms as GNU Emacs.
14
15 ;; GNU Emacs is free software: you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation, either version 3 of the License, or
18 ;; (at your option) any later version.
19
20 ;; GNU Emacs is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 ;; GNU General Public License for more details.
24
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
27
28 ;;; Commentary:
29
30 ;; This package is an Emacs minor mode and allows you to edit one occurrence of
31 ;; some text in a buffer (possibly narrowed) or region, and simultaneously have
32 ;; other occurrences edited in the same way.
33 ;;
34 ;; Normal scenario of iedit-mode is like:
35 ;;
36 ;; - Highlight certain contents - by press C-;
37 ;;   All occurrences of a symbol, string in the buffer or a region may be
38 ;;   highlighted corresponding to current mark, point and prefix argument.
39 ;;   Refer to the document of `iedit-mode' for details.
40 ;;
41 ;; - Edit one of the occurrences
42 ;;   The change is applied to other occurrences simultaneously.
43 ;;
44 ;; - Finish - by pressing C-; again
45 ;;
46 ;; You can also use Iedit mode as a quick way to temporarily show only the
47 ;; buffer lines that match the current text being edited.  This gives you the
48 ;; effect of a temporary `keep-lines' or `occur'.  To get this effect, hit C-'
49 ;; when in Iedit mode - it toggles hiding non-matching lines.
50 ;;
51 ;; Renaming refactoring is convenient in Iedit mode
52 ;;
53 ;; - The symbol under point is selected as occurrence by default and only
54 ;;   complete symbols are matched
55 ;; - With digit prefix argument 0, only symbols in current function are matched
56 ;; - Restricting symbols in current region can be done by pressing C-; again
57 ;; - Last renaming refactoring is remembered and can be applied to other buffers
58 ;;   later
59 ;;
60 ;; There are also some other facilities you may never think about.  Refer to the
61 ;; document of function `iedit-mode' (C-h f iedit-mode RET) for more details.
62
63 ;; The code was developed and fully tested on Gnu Emacs 24.0.93, partially
64 ;; tested on Gnu Emacs 22. If you have any compatible problem, please let me
65 ;; know.
66
67 ;;; todo:
68 ;; - Add more easy access keys for whole occurrence
69
70 ;;; Contributors
71 ;; Adam Lindberg <eproxus@gmail.com> added a case sensitivity option that can be toggled.
72
73 ;; Tassilo Horn <tassilo@member.fsf.org> added an option to match only complete
74 ;; words, not inside words
75
76 ;; Le Wang <l26wang@gmail.com> proposed to match only complete symbols,  not
77 ;; inside symbols, contributed rectangle support
78
79 ;;; Code:
80
81 (eval-when-compile (require 'cl))
82 (require 'iedit-lib)
83
84 (defcustom iedit-current-symbol-default t
85   "If no-nil, use current symbol by default for the occurrence."
86   :type 'boolean
87   :group 'iedit)
88
89 (defcustom iedit-only-at-symbol-boundaries t
90   "If no-nil, matches have to start and end at symbol boundaries.
91 For example, when invoking command `iedit-mode' on the \"in\" in the
92   sentence \"The king in the castle...\", the \"king\" is not
93   edited."
94   :type 'boolean
95   :group 'iedit)
96
97 (defcustom iedit-toggle-key-default (kbd "C-;")
98   "If no-nil, the key is inserted into global-map, isearch-mode-map, esc-map and help-map."
99   :type 'vector
100   :group 'iedit)
101
102 (defvar iedit-mode-hook nil
103   "Function(s) to call after starting up an iedit.")
104
105 (defvar iedit-mode-end-hook nil
106   "Function(s) to call after terminating an iedit.")
107
108 (defvar iedit-mode nil) ;; Name of the minor mode
109
110 (defvar iedit-only-complete-symbol-local nil
111   "This is buffer local variable which indicates the occurrence
112 only matches complete symbol.")
113
114 (defvar iedit-only-complete-symbol-global nil
115   "This is global variable which indicates the last global occurrence
116 only matches complete symbol.")
117
118 (defvar iedit-last-occurrence-local nil
119   "This is buffer local variable which is the occurrence when
120 Iedit mode is turned off last time.")
121
122 (defvar iedit-last-occurrence-global nil
123   "This is global variable which is the occurrence when
124 Iedit mode is turned off last time.")
125
126 (defvar iedit-last-initial-string-global nil
127   "This is a global variable which is the last initial occurrence string.")
128
129 (defvar iedit-initial-string-local nil
130   "This is buffer local variable which is the initial string to start Iedit mode.")
131 (defvar iedit-initial-region nil
132   "This is buffer local variable which is the initial region
133 where Iedit mode is started from.")
134
135
136 (make-variable-buffer-local 'iedit-mode)
137 (make-variable-buffer-local 'iedit-only-complete-symbol-local)
138 (make-variable-buffer-local 'iedit-last-occurrence-local)
139 (make-variable-buffer-local 'iedit-initial-string-local)
140 (make-variable-buffer-local 'iedit-initial-region)
141
142
143 (or (assq 'iedit-mode minor-mode-alist)
144     (nconc minor-mode-alist
145            (list '(iedit-mode iedit-mode))))
146
147 ;;; Define iedit help map.
148 (eval-when-compile (require 'help-macro))
149
150 (defvar iedit-help-map
151   (let ((map (make-sparse-keymap)))
152     (define-key map (char-to-string help-char) 'iedit-help-for-help)
153     (define-key map [help] 'iedit-help-for-help)
154     (define-key map [f1] 'iedit-help-for-help)
155     (define-key map "?" 'iedit-help-for-help)
156     (define-key map "b" 'iedit-describe-bindings)
157     (define-key map "k" 'iedit-describe-key)
158     (define-key map "m" 'iedit-describe-mode)
159     (define-key map "q" 'help-quit)
160     map)
161   "Keymap for characters following the Help key for Iedit mode.")
162
163 (make-help-screen
164  iedit-help-for-help-internal
165  (purecopy "Type a help option: [bkm] or ?")
166  "You have typed %THIS-KEY%, the help character.  Type a Help option:
167 \(Type \\<help-map>\\[help-quit] to exit the Help command.)
168
169 b           Display all Iedit key bindings.
170 k KEYS      Display full documentation of Iedit key sequence.
171 m           Display documentation of Iedit mode.
172
173 You can't type here other help keys available in the global help map,
174 but outside of this help window when you type them in Iedit mode,
175 they exit Iedit mode before displaying global help."
176  iedit-help-map)
177
178 (defun iedit-help-for-help ()
179   "Display Iedit help menu."
180   (interactive)
181   (let (same-window-buffer-names same-window-regexps)
182     (iedit-help-for-help-internal)))
183
184 (defun iedit-describe-bindings ()
185   "Show a list of all keys defined in Iedit mode, and their definitions.
186 This is like `describe-bindings', but displays only Iedit keys."
187   (interactive)
188   (let (same-window-buffer-names
189         same-window-regexps
190         (keymap (substitute-command-keys "\\{iedit-mode-keymap}\\{iedit-mode-occurrence-keymap}")))
191     (with-help-window "*Help*"
192       (with-current-buffer standard-output
193         (princ "Iedit Mode Bindings: ")
194         (princ keymap)))))
195
196 (defun iedit-describe-key ()
197   "Display documentation of the function invoked by Iedit mode key."
198   (interactive)
199   (let (same-window-buffer-names same-window-regexps)
200     (call-interactively 'describe-key)))
201
202 (defun iedit-describe-mode ()
203   "Display documentation of Iedit mode."
204   (interactive)
205   (let (same-window-buffer-names same-window-regexps)
206     (describe-function 'iedit-mode)))
207
208 ;;; Default key bindings:
209 (when iedit-toggle-key-default
210   (define-key global-map iedit-toggle-key-default 'iedit-mode)
211   (define-key isearch-mode-map iedit-toggle-key-default 'iedit-mode-from-isearch)
212   (define-key esc-map iedit-toggle-key-default 'iedit-execute-last-modification)
213   (define-key help-map iedit-toggle-key-default 'iedit-mode-toggle-on-function))
214
215 ;; Avoid to restore Iedit mode when restoring desktop
216 (add-to-list 'desktop-minor-mode-handlers
217              '(iedit-mode . nil))
218
219 ;;; Define iedit help map.
220 (eval-when-compile (require 'help-macro))
221
222 (defvar iedit-mode-occurrence-keymap
223   (let ((map (make-sparse-keymap)))
224     (set-keymap-parent map iedit-occurrence-keymap-default)
225     (define-key map (kbd "M-H") 'iedit-restrict-function)
226     (define-key map (kbd "M-C") 'iedit-toggle-case-sensitive)
227     map)
228   "Keymap used within overlays in Iedit mode.")
229
230 (defvar iedit-mode-keymap
231   (let ((map (make-sparse-keymap)))
232     (set-keymap-parent map iedit-lib-keymap)
233     (define-key map (char-to-string help-char) iedit-help-map)
234     (define-key map [help] iedit-help-map)
235     (define-key map [f1] iedit-help-map)
236     map)
237   "Keymap used while Iedit mode is enabled.")
238
239 ;;; Define Iedit mode map
240 (or (assq 'iedit-mode minor-mode-map-alist)
241     (setq minor-mode-map-alist
242           (cons (cons 'iedit-mode iedit-mode-keymap) minor-mode-map-alist)))
243
244 ;; Avoid to restore Iedit mode when restoring desktop
245 (add-to-list 'desktop-minor-mode-handlers
246              '(iedit-mode . nil))
247
248 ;;;###autoload
249 (defun iedit-mode (&optional arg)
250   "Toggle Iedit mode.
251 This command behaves differently, depending on the mark, point,
252 prefix argument and variable `iedit-transient-mark-sensitive'.
253
254 If Iedit mode is off, turn Iedit mode on.
255
256 When Iedit mode is turned on, all the occurrences of the current
257 region in the buffer (possibly narrowed) or a region are
258 highlighted.  If one occurrence is modified, the change are
259 propagated to all other occurrences simultaneously.
260
261 If region is not active, the current symbol (returns from
262 `current-word') is used as the occurrence by default.  The
263 occurrences of the current symbol, but not include occurrences
264 that are part of other symbols, are highlighted.  If you still
265 want to match all the occurrences, even though they are parts of
266 other symbols, you may have to mark the symbol first.
267
268 In the above two situations, with digit prefix argument 0, only
269 occurrences in current function are matched.  This is good for
270 renaming refactoring in programming.
271
272 You can also switch to Iedit mode from isearch mode directly. The
273 current search string is used as occurrence.  All occurrences of
274 the current search string are highlighted.
275
276 With an universal prefix argument, the occurrence when Iedit mode
277 is turned off last time in current buffer is used as occurrence.
278 This is intended to recover last Iedit mode which is turned off.
279 If region active, Iedit mode is limited within the current
280 region.
281
282 With repeated universal prefix argument, the occurrence when
283 Iedit mode is turned off last time (might be in other buffer) is
284 used as occurrence.  If region active, Iedit mode is limited
285 within the current region.
286
287 If Iedit mode is on and region is active, Iedit mode is
288 restricted in the region, e.g. the occurrences outside of the
289 region is excluded.
290
291 If Iedit mode is on and region is active, with an universal
292 prefix argument, Iedit mode is restricted outside of the region,
293 e.g. the occurrences in the region is excluded.
294
295 Turn off Iedit mode in other situations.
296
297 Commands:
298 \\{iedit-mode-keymap}
299 Keymap used within overlays:
300 \\{iedit-mode-occurrence-keymap}"
301   (interactive "P")
302   (if iedit-mode
303       (progn
304         (iedit-mode-on-action arg)
305         (setq iedit-only-complete-symbol-global iedit-only-complete-symbol-local))
306     (iedit-barf-if-lib-active)
307     (let (occurrence
308           complete-symbol
309           (beg (if (eq major-mode 'occur-edit-mode) ; skip the first occurrence
310                    (next-single-char-property-change 1 'read-only)
311                  (point-min)))
312           (end (point-max)))
313       (cond ((and arg
314                   (= 4 (prefix-numeric-value arg))
315                   iedit-last-occurrence-local)
316              (setq occurrence iedit-last-occurrence-local)
317              (setq complete-symbol iedit-only-complete-symbol-local))
318             ((and arg
319                   (= 16 (prefix-numeric-value arg))
320                   iedit-last-initial-string-global)
321              (setq occurrence iedit-last-initial-string-global)
322              (setq complete-symbol iedit-only-complete-symbol-global))
323             ((iedit-region-active)
324              (setq occurrence  (buffer-substring-no-properties
325                                 (mark) (point))))
326             ((and iedit-current-symbol-default (current-word t))
327              (setq occurrence  (current-word))
328              (when iedit-only-at-symbol-boundaries
329                (setq complete-symbol t)))
330             (t (error "No candidate of the occurrence, cannot enable Iedit mode")))
331       (when arg
332         (if (= 0 (prefix-numeric-value arg))
333             (save-excursion
334               (mark-defun)
335               (setq beg (region-beginning))
336               (setq end (region-end)))
337           (when (iedit-region-active)
338             (setq beg (region-beginning))
339             (setq end (region-end)))))
340       (setq iedit-only-complete-symbol-local complete-symbol)
341       (setq mark-active nil)
342       (run-hooks 'deactivate-mark-hook)
343       (setq iedit-initial-string-local occurrence)
344       (iedit-start (iedit-regexp-quote occurrence) beg end))))
345
346 (defun iedit-mode-from-isearch (regexp)
347   "Start Iedit mode using last search string as the regexp."
348   (interactive
349    (let ((regexp (cond
350                   ((functionp isearch-word)
351                    (funcall isearch-word isearch-string))
352                   (isearch-word (word-search-regexp isearch-string))
353                   (isearch-regexp isearch-string)
354                   (t (regexp-quote isearch-string)))))
355      (list regexp)))
356   (if (or isearch-regexp isearch-word)
357       nil
358     (setq iedit-initial-string-local isearch-string))
359   (isearch-exit)
360   (setq mark-active nil)
361   (run-hooks 'deactivate-mark-hook)
362   (iedit-start regexp (point-min) (point-max))
363   ;; TODO: reconsider how to avoid the loop in iedit-same-length
364   (if (iedit-same-length)
365       nil
366     (iedit-done)
367     (message "Matches are not the same length.")))
368
369 (defun iedit-start (occurrence-regexp beg end)
370   "Start Iedit mode for the `occurrence-regexp' in the current buffer."
371   (setq iedit-unmatched-lines-invisible iedit-unmatched-lines-invisible-default)
372   (setq iedit-initial-region (list beg end))
373   (iedit-start2 occurrence-regexp beg end)
374   (run-hooks 'iedit-mode-hook)
375   (add-hook 'kbd-macro-termination-hook 'iedit-done nil t)
376   (add-hook 'change-major-mode-hook 'iedit-done nil t)
377   (add-hook 'iedit-aborting-hook 'iedit-done nil t))
378
379 (defun iedit-regexp-quote (exp)
380   "Return a regexp string."
381   (if iedit-only-complete-symbol-local
382       (concat "\\_<" (regexp-quote exp) "\\_>")
383     (regexp-quote exp)))
384
385 (defun iedit-start2 (occurrence-regexp beg end)
386   "Refresh Iedit mode."
387   (setq iedit-occurrence-keymap iedit-mode-occurrence-keymap)
388   (setq iedit-mode
389         (propertize
390          (concat " Iedit:"
391                  (number-to-string
392                   (iedit-make-occurrences-overlays occurrence-regexp beg end)))
393          'face
394          'font-lock-warning-face))
395   (force-mode-line-update))
396
397 (defun iedit-done ()
398   "Exit Iedit mode.
399 Save the current occurrence string locally and globally.  Save
400 the initial string globally."
401   (when iedit-buffering
402       (iedit-stop-buffering))
403   (setq iedit-last-occurrence-local (iedit-current-occurrence-string))
404   (setq iedit-last-occurrence-global iedit-last-occurrence-local)
405   (setq iedit-last-initial-string-global iedit-initial-string-local)
406
407   (iedit-cleanup)
408
409   (setq iedit-initial-string-local nil)
410   (setq iedit-mode nil)
411   (force-mode-line-update)
412   (remove-hook 'kbd-macro-termination-hook 'iedit-done t)
413   (remove-hook 'change-major-mode-hook 'iedit-done t)
414   (remove-hook 'iedit-aborting-hook 'iedit-done t)
415   (run-hooks 'iedit-mode-end-hook))
416
417 (defun iedit-mode-on-action (&optional arg)
418   "Turn off Iedit mode or restrict it in a region if region is active."
419   (if (iedit-region-active)
420       ;; Restrict iedit-mode
421       (let ((beg (region-beginning))
422             (end (region-end)))
423         (if (null (iedit-find-overlay beg end 'iedit-occurrence-overlay-name arg))
424             (iedit-done)
425           (iedit-restrict-region beg end arg)
426           (iedit-first-occurrence)))
427     (iedit-done)))
428
429
430 ;;;###autoload
431 (defun iedit-mode-toggle-on-function ()
432   "Toggle Iedit mode on current function."
433   (interactive)
434   (iedit-mode 0))
435
436 (defun iedit-execute-last-modification (&optional arg)
437   "Apply last modification in Iedit mode to the current buffer or an active region."
438   (interactive "*P")
439   (or (and iedit-last-initial-string-global
440            (not (string= iedit-last-initial-string-global iedit-last-occurrence-global)))
441       (error "No modification available"))
442   (let ((occurrence-exp (regexp-quote iedit-last-initial-string-global))
443         (replacement  iedit-last-occurrence-global)
444         (case-fold-search (not iedit-case-sensitive))
445         beg end)
446     (when case-fold-search
447       (setq occurrence-exp (downcase occurrence-exp))
448       (setq replacement (downcase replacement)))
449     (if iedit-only-complete-symbol-global
450         (setq occurrence-exp (concat "\\_<"  occurrence-exp "\\_>")))
451     (when (iedit-region-active)
452       (setq beg (region-beginning))
453       (setq end (region-end)))
454     (perform-replace occurrence-exp replacement t t nil nil nil beg end)))
455
456 (defun iedit-apply-global-modification ()
457   "Apply last global modification."
458   (interactive "*")
459   (if (and iedit-last-initial-string-global
460            (string= iedit-initial-string-local iedit-last-initial-string-global)
461            (not (string= iedit-last-initial-string-global iedit-last-occurrence-global)))
462       (iedit-replace-occurrences iedit-last-occurrence-global)
463     (message "No global modification available.")))
464
465
466 (defun iedit-restrict-function(&optional arg)
467   "Restricting Iedit mode in current function."
468   (interactive "P")
469   (save-excursion
470     (mark-defun)
471     (iedit-restrict-region (region-beginning) (region-end) arg))
472   (message "Restricted in current function, %d matches."
473            (length iedit-occurrences-overlays)))
474
475 (defun iedit-restrict-region (beg end &optional inclusive)
476   "Restricting Iedit mode in a region."
477   (when iedit-buffering
478     (iedit-stop-buffering))
479   (setq iedit-last-occurrence-local (iedit-current-occurrence-string))
480   (setq mark-active nil)
481   (run-hooks 'deactivate-mark-hook)
482   (iedit-show-all)
483   (iedit-cleanup-occurrences-overlays beg end inclusive)
484   (if iedit-unmatched-lines-invisible
485       (iedit-hide-unmatched-lines iedit-occurrence-context-lines))
486   (setq iedit-mode (propertize
487                     (concat " Iedit:" (number-to-string
488                                        (length iedit-occurrences-overlays)))
489                     'face 'font-lock-warning-face))
490   (force-mode-line-update))
491
492 (defun iedit-toggle-case-sensitive ()
493   "Toggle case-sensitive matching occurrences.
494 Todo: how about region"
495   (interactive)
496   (setq iedit-case-sensitive (not iedit-case-sensitive))
497   (if iedit-buffering
498       (iedit-stop-buffering))
499   (setq iedit-last-occurrence-local (iedit-current-occurrence-string))
500   (when iedit-last-occurrence-local
501     (remove-overlays nil nil iedit-occurrence-overlay-name t)
502     (iedit-show-all)
503     (iedit-start2 (iedit-regexp-quote iedit-last-occurrence-local)
504                   (car iedit-initial-region)
505                   (cadr iedit-initial-region))))
506
507 (provide 'iedit)
508
509 ;;; iedit.el ends here
510
511 ;;  LocalWords:  iedit el MERCHANTABILITY kbd isearch todo ert Lindberg Tassilo
512 ;;  LocalWords:  eval defgroup defcustom boolean defvar assq alist nconc
513 ;;  LocalWords:  substring cadr keymap defconst purecopy bkm defun princ prev
514 ;;  LocalWords:  iso lefttab backtab upcase downcase concat setq autoload arg
515 ;;  LocalWords:  refactoring propertize cond goto nreverse progn rotatef eq elp
516 ;;  LocalWords:  dolist pos unmatch args ov sReplace iedit's cdr quote'ed