1 ;;; iedit-lib.el --- APIs for editing multiple regions in the same way
4 ;; Copyright (C) 2010, 2011, 2012 Victor Ren
6 ;; Time-stamp: <2012-10-17 08:48:28 Victor Ren>
7 ;; Author: Victor Ren <victorhge@gmail.com>
8 ;; Keywords: occurrence region simultaneous rectangle refactoring
10 ;; X-URL: http://www.emacswiki.org/emacs/Iedit
11 ;; Compatibility: GNU Emacs: 22.x, 23.x, 24.x
13 ;; This file is not part of GNU Emacs, but it is distributed under
14 ;; the same terms as GNU Emacs.
16 ;; GNU Emacs is free software: you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation, either version 3 of the License, or
19 ;; (at your option) any later version.
21 ;; GNU Emacs is distributed in the hope that it will be useful,
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;; GNU General Public License for more details.
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
31 ;; This package is iedit APIs library that allow you to write your own minor mode.
34 ;; - Update comments for APIs
35 ;; - Add more easy access keys for whole occurrence
36 ;; - More APIs: extend occurrences,
40 (eval-when-compile (require 'cl))
43 "Edit multiple regions in the same way simultaneously."
48 (defface iedit-occurrence
49 '((t :inherit highlight))
50 "*Face used for the occurrences' default values."
53 (defcustom iedit-case-sensitive-default t
54 "If no-nil, matching is case sensitive."
58 (defcustom iedit-unmatched-lines-invisible-default nil
59 "If no-nil, hide lines that do not cover any occurrences by default."
63 (defcustom iedit-transient-mark-sensitive t
64 "If no-nil, Iedit mode is sensitive to the Transient Mark mode.
65 It means Iedit works as expected only when regions are
66 highlighted. If you want to use iedit without Transient Mark
71 (defvar iedit-occurrences-overlays nil
72 "The occurrences slot contains a list of overlays used to
73 indicate the position of each occurrence. In addition, the
74 occurrence overlay is used to provide a different face
75 configurable via `iedit-occurrence-face'. The list is sorted by
76 the position of overlays.")
78 (defvar iedit-case-sensitive iedit-case-sensitive-default
79 "This is buffer local variable.
80 If no-nil, matching is case sensitive.")
82 (defvar iedit-unmatched-lines-invisible nil
83 "This is buffer local variable which indicates whether
84 unmatched lines are hided.")
86 (defvar iedit-forward-success t
87 "This is buffer local variable which indicates the moving
88 forward or backward successful")
90 (defvar iedit-before-modification-string ""
91 "This is buffer local variable which is the buffer substring
92 that is going to be changed.")
94 (defvar iedit-before-modification-undo-list nil
95 "This is buffer local variable which is the buffer undo list before modification.")
97 ;; `iedit-occurrence-update' gets called twice when change==0 and occurrence
98 ;; is zero-width (beg==end)
99 ;; -- for front and back insertion.
100 (defvar iedit-skip-modification-once t
101 "Variable used to skip first modification hook run when
102 insertion against a zero-width occurrence.")
104 (defvar iedit-aborting nil
105 "This is buffer local variable which indicates Iedit mode is aborting.")
107 (defvar iedit-aborting-hook nil
108 "Functions to call before iedit-abort. Normally it should be mode exit function.")
110 (defvar iedit-post-undo-hook-installed nil
111 "This is buffer local variable which indicated if
112 `iedit-post-undo-hook' is installed in `post-command-hook'.")
114 (defvar iedit-buffering nil
115 "This is buffer local variable which indicates iedit-mode is
116 buffering, which means the modification to the current occurrence
117 is not applied to other occurrences when it is true.")
119 (defvar iedit-occurrence-context-lines 1
120 "The number of lines before or after the occurrence.")
122 (make-variable-buffer-local 'iedit-occurrences-overlays)
123 (make-variable-buffer-local 'iedit-unmatched-lines-invisible)
124 (make-local-variable 'iedit-case-sensitive)
125 (make-variable-buffer-local 'iedit-forward-success)
126 (make-variable-buffer-local 'iedit-before-modification-string)
127 (make-variable-buffer-local 'iedit-before-modification-undo-list)
128 (make-variable-buffer-local 'iedit-skip-modification-once)
129 (make-variable-buffer-local 'iedit-aborting)
130 (make-variable-buffer-local 'iedit-buffering)
131 (make-variable-buffer-local 'iedit-post-undo-hook-installed)
132 (make-variable-buffer-local 'iedit-occurrence-context-lines)
134 (defconst iedit-occurrence-overlay-name 'iedit-occurrence-overlay-name)
135 (defconst iedit-invisible-overlay-name 'iedit-invisible-overlay-name)
137 ;;; Define Iedit mode map
138 (defvar iedit-lib-keymap
139 (let ((map (make-sparse-keymap)))
140 ;; Default key bindings
141 (define-key map (kbd "TAB") 'iedit-next-occurrence)
142 (define-key map (kbd "<S-tab>") 'iedit-prev-occurrence)
143 (define-key map (kbd "<S-iso-lefttab>") 'iedit-prev-occurrence)
144 (define-key map (kbd "<backtab>") 'iedit-prev-occurrence)
145 (define-key map (kbd "C-'") 'iedit-toggle-unmatched-lines-visible)
147 "Keymap used while Iedit mode is enabled.")
149 (defvar iedit-occurrence-keymap-default
150 (let ((map (make-sparse-keymap)))
151 ;; (set-keymap-parent map iedit-lib-keymap)
152 (define-key map (kbd "M-U") 'iedit-upcase-occurrences)
153 (define-key map (kbd "M-L") 'iedit-downcase-occurrences)
154 (define-key map (kbd "M-R") 'iedit-replace-occurrences)
155 (define-key map (kbd "M-SPC") 'iedit-blank-occurrences)
156 (define-key map (kbd "M-D") 'iedit-delete-occurrences)
157 (define-key map (kbd "M-N") 'iedit-number-occurrences)
158 (define-key map (kbd "M-;") 'iedit-apply-global-modification)
159 (define-key map (kbd "M-B") 'iedit-toggle-buffering)
160 (define-key map (kbd "M-<") 'iedit-first-occurrence)
161 (define-key map (kbd "M->") 'iedit-last-occurrence)
162 (define-key map (kbd "C-?") 'iedit-help-for-occurrences)
164 "Default keymap used within occurrence overlays.")
166 (defvar iedit-occurrence-keymap 'iedit-occurrence-keymap-default
167 "Keymap used within occurrence overlays.
168 It should be set before occurrence overlay is created.")
169 (make-local-variable 'iedit-occurrence-keymap)
171 (defun iedit-help-for-occurrences ()
172 "Display `iedit-occurrence-keymap-default'"
174 (message (concat (substitute-command-keys "\\[iedit-upcase-occurrences]") "/"
175 (substitute-command-keys "\\[iedit-downcase-occurrences]") ":up/downcase "
176 (substitute-command-keys "\\[iedit-replace-occurrences]") ":replace "
177 (substitute-command-keys "\\[iedit-blank-occurrences]") ":blank "
178 (substitute-command-keys "\\[iedit-delete-occurrences]") ":delete "
179 (substitute-command-keys "\\[iedit-number-occurrences]") ":number "
180 (substitute-command-keys "\\[iedit-apply-global-modification]") ":redo "
181 (substitute-command-keys "\\[iedit-toggle-buffering]") ":buffering "
182 (substitute-command-keys "\\[iedit-first-occurrence]") "/"
183 (substitute-command-keys "\\[iedit-last-occurrence]") ":first/last "
186 (defun iedit-make-occurrences-overlays (occurrence-regexp beg end)
187 "Create occurrence overlays for `occurrence-regexp' in a region.
188 Return the number of occurrences."
189 (setq iedit-aborting nil)
190 (setq iedit-occurrences-overlays nil)
191 ;; Find and record each occurrence's markers and add the overlay to the occurrences
193 (case-fold-search (not iedit-case-sensitive)))
196 (while (re-search-forward occurrence-regexp end t)
197 (push (iedit-make-occurrence-overlay (match-beginning 0) (match-end 0))
198 iedit-occurrences-overlays)
199 (setq counter (1+ counter)))
200 (message "%d matches for \"%s\"" counter (iedit-printable occurrence-regexp))
202 (setq iedit-occurrences-overlays (nreverse iedit-occurrences-overlays))
203 (if iedit-unmatched-lines-invisible
204 (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))
207 (defun iedit-add-next-occurrence-overlay (occurrence-exp &optional point)
208 "Create next occurrence overlay for `occurrence-exp'."
209 (iedit-add-occurrence-overlay occurrence-exp point t))
211 (defun iedit-add-previous-occurrence-overlay (occurrence-exp &optional point)
212 "Create previous occurrence overlay for `occurrence-exp'."
213 (iedit-add-occurrence-overlay occurrence-exp point nil))
215 (defun iedit-add-occurrence-overlay (occurrence-exp point forward)
216 "Create next or previous occurrence overlay for `occurrence-exp'."
218 (setq point (point)))
219 (let ((case-fold-search (not iedit-case-sensitive)))
223 (re-search-forward occurrence-exp nil t)
224 (re-search-backward occurrence-exp nil t)))
225 (message "No matches.")
226 (if (or (iedit-find-overlay-at-point (match-beginning 0) 'iedit-occurrence-overlay-name)
227 (iedit-find-overlay-at-point (match-end 0) 'iedit-occurrence-overlay-name))
228 (error "Conflict region."))
229 (push (iedit-make-occurrence-overlay (match-beginning 0)
231 iedit-occurrences-overlays)
232 (sort iedit-occurrences-overlays
234 (< (overlay-start left) (overlay-start right))))
235 (message "Add one match for \"%s\"" (iedit-printable occurrence-exp))
236 (if iedit-unmatched-lines-invisible
237 (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))))
239 (defun iedit-add-region-as-occurrence (beg end)
240 "Add region as an occurrence.
241 The length of the region must the same as other occurrences if
245 (if (null iedit-occurrences-overlays)
247 (iedit-make-occurrence-overlay beg end)
248 iedit-occurrences-overlays)
249 (or (= (- end beg) (iedit-occurrence-string-length))
250 (error "Wrong region."))
251 (if (or (iedit-find-overlay-at-point beg 'iedit-occurrence-overlay-name)
252 (iedit-find-overlay-at-point end 'iedit-occurrence-overlay-name))
253 (error "Conflict region."))
254 (push (iedit-make-occurrence-overlay beg end)
255 iedit-occurrences-overlays)
256 (sort iedit-occurrences-overlays
258 (< (overlay-start left) (overlay-start right)))))) ;; todo test this function
260 (defun iedit-cleanup ()
261 "Clean up occurrence overlay, invisible overlay and local variables."
262 (remove-overlays nil nil iedit-occurrence-overlay-name t)
264 (setq iedit-occurrences-overlays nil)
265 (setq iedit-aborting nil)
266 (setq iedit-before-modification-string "")
267 (setq iedit-before-modification-undo-list nil))
269 (defun iedit-make-occurrence-overlay (begin end)
270 "Create an overlay for an occurrence in Iedit mode.
271 Add the properties for the overlay: a face used to display a
272 occurrence's default value, and modification hooks to update
273 occurrences if the user starts typing."
274 (let ((occurrence (make-overlay begin end (current-buffer) nil t)))
275 (overlay-put occurrence iedit-occurrence-overlay-name t)
276 (overlay-put occurrence 'face 'iedit-occurrence)
277 (overlay-put occurrence 'keymap iedit-occurrence-keymap)
278 (overlay-put occurrence 'insert-in-front-hooks '(iedit-occurrence-update))
279 (overlay-put occurrence 'insert-behind-hooks '(iedit-occurrence-update))
280 (overlay-put occurrence 'modification-hooks '(iedit-occurrence-update))
283 (defun iedit-make-unmatched-lines-overlay (begin end)
284 "Create an overlay for lines between two occurrences in Iedit mode."
285 (let ((unmatched-lines-overlay (make-overlay begin end (current-buffer) nil t)))
286 (overlay-put unmatched-lines-overlay iedit-invisible-overlay-name t)
287 (overlay-put unmatched-lines-overlay 'invisible 'iedit-invisible-overlay-name)
288 ;; (overlay-put unmatched-lines-overlay 'intangible t)
289 unmatched-lines-overlay))
291 (defun iedit-post-undo-hook ()
292 "Check if it is time to abort iedit after undo command is executed.
294 This is added to `post-command-hook' when undo command is executed
296 (if (iedit-same-length)
298 (run-hooks 'iedit-aborting-hook))
299 (remove-hook 'post-command-hook 'iedit-post-undo-hook t)
300 (setq iedit-post-undo-hook-installed nil))
302 (defun iedit-reset-aborting ()
303 "Turning Iedit mode off and reset `iedit-aborting'.
305 This is added to `post-command-hook' when aborting Iedit mode is
306 decided. `iedit-aborting-hook' is postponed after the current
307 command is executed for avoiding `iedit-occurrence-update' is
308 called for a removed overlay."
309 (run-hooks 'iedit-aborting-hook)
310 (remove-hook 'post-command-hook 'iedit-reset-aborting t)
311 (setq iedit-aborting nil))
313 ;; There are two ways to update all occurrence. One is to redefine all key
314 ;; stroke map for overlay, the other is to figure out three basic modification
315 ;; in the modification hook. This function chooses the latter.
316 (defun iedit-occurrence-update (occurrence after beg end &optional change)
317 "Update all occurrences.
318 This modification hook is triggered when a user edits any
319 occurrence and is responsible for updating all other occurrences.
320 Current supported edits are insertion, yank, deletion and
321 replacement. If this modification is going out of the
322 occurrence, it will abort Iedit mode."
324 ;; If the "undo" change make occurrences different, it is going to mess up
325 ;; occurrences. So a check will be done after undo command is executed.
326 (when (not iedit-post-undo-hook-installed)
327 (add-hook 'post-command-hook 'iedit-post-undo-hook nil t)
328 (setq iedit-post-undo-hook-installed t))
329 (when (and (not iedit-aborting ))
330 ;; before modification
332 (if (or (< beg (overlay-start occurrence))
333 (> end (overlay-end occurrence)))
334 (progn (setq iedit-aborting t) ; abort iedit-mode
335 (add-hook 'post-command-hook 'iedit-reset-aborting nil t))
336 (setq iedit-before-modification-string
337 (buffer-substring-no-properties beg end))
338 ;; Check if this is called twice before modification. When inserting
339 ;; into zero-width occurrence or between two conjoined occurrences,
340 ;; both insert-in-front-hooks and insert-behind-hooks will be
341 ;; called. Two calls will make `iedit-skip-modification-once' true.
342 (setq iedit-skip-modification-once (not iedit-skip-modification-once)))
343 ;; after modification
344 (when (not iedit-buffering)
345 (if iedit-skip-modification-once
346 ;; Skip the first hook
347 (setq iedit-skip-modification-once nil)
348 (setq iedit-skip-modification-once t)
349 (when (or (eq 0 change) ;; insertion
350 (eq beg end) ;; deletion
351 (not (string= iedit-before-modification-string
352 (buffer-substring-no-properties beg end))))
353 (let ((inhibit-modification-hooks t) ; todo: extract this as a function
354 (offset (- beg (overlay-start occurrence)))
355 (value (buffer-substring-no-properties beg end)))
359 (dolist (another-occurrence iedit-occurrences-overlays)
360 (let* ((beginning (+ (overlay-start another-occurrence) offset))
361 (ending (+ beginning (- end beg))))
362 (when (not (eq another-occurrence occurrence))
363 (goto-char beginning)
364 (insert-and-inherit value)
365 ;; todo: reconsider this change Quick fix for
366 ;; multi-occur occur-edit-mode: multi-occur depend on
367 ;; after-change-functions to update original
368 ;; buffer. Since inhibit-modification-hooks is set to
369 ;; non-nil, after-change-functions hooks are not going
370 ;; to be called for the changes of other occurrences.
371 ;; So run the hook here.
372 (run-hook-with-args 'after-change-functions
376 (iedit-move-conjoined-overlays another-occurrence)))
378 (dolist (another-occurrence (remove occurrence iedit-occurrences-overlays))
379 (let ((beginning (+ (overlay-start another-occurrence) offset)))
380 (delete-region beginning (+ beginning change))
381 (unless (eq beg end) ;; replacement
382 (goto-char beginning)
383 (insert-and-inherit value))
384 (run-hook-with-args 'after-change-functions
386 (+ beginning (- beg end))
389 (defun iedit-next-occurrence ()
390 "Move forward to the next occurrence in the `iedit'.
391 If the point is already in the last occurrences, you are asked to type
392 another `iedit-next-occurrence', it starts again from the
393 beginning of the buffer."
396 (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
398 (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name)))
399 (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name))
400 (if (/= pos (point-max))
401 (setq iedit-forward-success t)
402 (if (and iedit-forward-success in-occurrence)
403 (progn (message "This is the last occurrence.")
404 (setq iedit-forward-success nil))
406 (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
407 (setq pos (point-min))
408 (setq pos (next-single-char-property-change
410 'iedit-occurrence-overlay-name)))
411 (setq iedit-forward-success t)
412 (message "Located the first occurrence."))))
413 (when iedit-forward-success
416 (defun iedit-prev-occurrence ()
417 "Move backward to the previous occurrence in the `iedit'.
418 If the point is already in the first occurrences, you are asked to type
419 another `iedit-prev-occurrence', it starts again from the end of
423 (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
425 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
426 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name))
427 ;; At the start of the first occurrence
428 (if (or (and (eq pos (point-min))
429 (not (get-char-property (point-min) 'iedit-occurrence-overlay-name)))
430 (and (eq (point) (point-min))
432 (if (and iedit-forward-success in-occurrence)
433 (progn (message "This is the first occurrence.")
434 (setq iedit-forward-success nil))
436 (setq pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name))
437 (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
438 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
439 (setq iedit-forward-success t)
440 (message "Located the last occurrence.")))
441 (setq iedit-forward-success t))
442 (when iedit-forward-success
445 (defun iedit-first-occurrence ()
446 "Move to the first occurrence."
448 (let ((pos (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
450 (next-single-char-property-change
451 (point-min) 'iedit-occurrence-overlay-name))))
452 (setq iedit-forward-success t)
454 (message "Located the first occurrence.")))
456 (defun iedit-last-occurrence ()
457 "Move to the last occurrence."
459 (let ((pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name)))
460 (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
461 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
462 (setq iedit-forward-success t)
464 (message "Located the last occurrence.")))
466 (defun iedit-toggle-unmatched-lines-visible (&optional arg)
467 "Toggle whether to display unmatched lines.
468 A prefix ARG specifies how many lines before and after the
469 occurrences are not hided; negative is treated the same as zero.
471 If no prefix argument, the prefix argument last time or default
472 value of `iedit-occurrence-context-lines' is used for this time."
476 (progn (setq iedit-unmatched-lines-invisible (not iedit-unmatched-lines-invisible))
477 (if iedit-unmatched-lines-invisible
478 (iedit-hide-unmatched-lines iedit-occurrence-context-lines)
480 ;; reset invisible lines
481 (setq arg (prefix-numeric-value arg))
484 (unless (and iedit-unmatched-lines-invisible
485 (= arg iedit-occurrence-context-lines))
486 (when iedit-unmatched-lines-invisible
487 (remove-overlays nil nil iedit-invisible-overlay-name t))
488 (setq iedit-occurrence-context-lines arg)
489 (setq iedit-unmatched-lines-invisible t)
490 (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))
492 (defun iedit-show-all()
494 (setq line-move-ignore-invisible nil)
495 (remove-from-invisibility-spec '(iedit-invisible-overlay-name . t))
496 (remove-overlays nil nil iedit-invisible-overlay-name t))
498 (defun iedit-hide-unmatched-lines (context-lines)
499 "Hide unmatched lines using invisible overlay.
500 This function depends on the order of `iedit-occurrences-overlays'. TODO"
501 (let ((prev-occurrence-end 1)
502 (unmatched-lines nil))
504 (dolist (overlay iedit-occurrences-overlays)
505 (goto-char (overlay-start overlay))
506 (forward-line (- context-lines))
507 (let ((line-beginning (line-beginning-position)))
508 (if (> line-beginning prev-occurrence-end)
509 (push (list prev-occurrence-end (1- line-beginning)) unmatched-lines)))
510 (goto-char (overlay-end overlay))
511 (forward-line context-lines)
512 (setq prev-occurrence-end (line-end-position)))
513 (if (< prev-occurrence-end (point-max))
514 (push (list prev-occurrence-end (point-max)) unmatched-lines))
515 (when unmatched-lines
516 (set (make-local-variable 'line-move-ignore-invisible) t)
517 (add-to-invisibility-spec '(iedit-invisible-overlay-name . t))
518 (dolist (unmatch unmatched-lines)
519 (iedit-make-unmatched-lines-overlay (car unmatch) (cadr unmatch)))))))
521 ;;;; functions for overlay keymap
522 (defun iedit-apply-on-occurrences (function &rest args)
523 "Call function for each occurrence."
524 (let ((inhibit-modification-hooks t))
526 (dolist (occurrence iedit-occurrences-overlays)
527 (apply function (overlay-start occurrence) (overlay-end occurrence) args)))))
529 (defun iedit-upcase-occurrences ()
530 "Covert occurrences to upper case."
532 (iedit-apply-on-occurrences 'upcase-region))
534 (defun iedit-downcase-occurrences()
535 "Covert occurrences to lower case."
537 (iedit-apply-on-occurrences 'downcase-region))
539 (defun iedit-replace-occurrences(to-string)
540 "Replace occurrences with STRING.
541 This function preserves case."
542 (interactive "*sReplace with: ")
543 (let* ((ov (iedit-find-current-occurrence-overlay))
544 (offset (- (point) (overlay-start ov)))
545 (from-string (downcase (buffer-substring-no-properties
548 (iedit-apply-on-occurrences
549 (lambda (beg end from-string to-string)
551 (search-forward from-string end)
552 (replace-match to-string nil))
553 from-string to-string)
554 (goto-char (+ (overlay-start ov) offset))))
556 (defun iedit-blank-occurrences()
557 "Replace occurrences with blank spaces."
559 (let* ((ov (car iedit-occurrences-overlays))
560 (offset (- (point) (overlay-start ov)))
561 (count (- (overlay-end ov) (overlay-start ov))))
562 (iedit-apply-on-occurrences
564 (delete-region beg end)
566 (insert-and-inherit (make-string count 32))))
567 (goto-char (+ (overlay-start ov) offset))))
569 (defun iedit-delete-occurrences()
570 "Delete occurrences."
572 (iedit-apply-on-occurrences 'delete-region))
574 ;; todo: add cancel buffering function
575 (defun iedit-toggle-buffering ()
577 This is intended to improve iedit's response time. If the number
578 of occurrences are huge, it might be slow to update all the
579 occurrences for each key stoke. When buffering is on,
580 modification is only applied to the current occurrence and will
581 be applied to other occurrences when buffering is off."
584 (iedit-stop-buffering)
585 (iedit-start-buffering))
586 (message (concat "Modification Buffering "
591 (defun iedit-start-buffering ()
593 (setq iedit-buffering t)
594 (setq iedit-before-modification-string (iedit-current-occurrence-string))
595 (setq iedit-before-modification-undo-list buffer-undo-list)
596 (message "Start buffering editing...")
597 ;; (setq iedit-mode (propertize
598 ;; (concat " Iedit-B:" (number-to-string (length iedit-occurrences-overlays)))
599 ;; 'face 'font-lock-warning-face))
600 ;; (force-mode-line-update)
603 (defun iedit-stop-buffering ()
604 "Stop buffering and apply the modification to other occurrences.
605 If current point is not at any occurrence, the buffered
606 modification is not going to be applied to other occurrences."
607 (let ((ov (iedit-find-current-occurrence-overlay)))
609 (let* ((beg (overlay-start ov))
610 (end (overlay-end ov))
611 (modified-string (buffer-substring-no-properties beg end))
612 (offset (- (point) beg)) ;; delete-region moves cursor
613 (inhibit-modification-hooks t))
614 (when (not (string= iedit-before-modification-string modified-string))
616 ;; Rollback the current modification and buffer-undo-list. This is
617 ;; to avoid the inconsistency if user undoes modifications
618 (delete-region beg end)
620 (insert-and-inherit iedit-before-modification-string)
621 (setq buffer-undo-list iedit-before-modification-undo-list)
622 (dolist (occurrence iedit-occurrences-overlays) ; todo:extract as a function
623 (let ((beginning (overlay-start occurrence))
624 (ending (overlay-end occurrence)))
625 (delete-region beginning ending)
626 (unless (eq beg end) ;; replacement
627 (goto-char beginning)
628 (insert-and-inherit modified-string))
629 (iedit-move-conjoined-overlays occurrence))))
630 (goto-char (+ (overlay-start ov) offset))))))
631 (setq iedit-buffering nil)
632 ;; (setq iedit-mode (propertize
633 ;; (concat (if iedit-rectangle " Iedit-RECT:" " Iedit:")
634 ;; (number-to-string (length iedit-occurrences-overlays)))
635 ;; 'face 'font-lock-warning-face))
636 ;; (force-mode-line-update)
637 (message "Buffered modification applied.")
638 (setq iedit-before-modification-undo-list nil))
640 (defun iedit-move-conjoined-overlays (occurrence)
641 "This function keeps overlays conjoined after modification.
642 After modification, conjoined overlays may be overlapped."
643 (let ((beginning (overlay-start occurrence))
644 (ending (overlay-end occurrence)))
645 (unless (= beginning (point-min))
646 (let ((previous-overlay (iedit-find-overlay-at-point
648 'iedit-occurrence-overlay-name)))
649 (if previous-overlay ; two conjoined occurrences
650 (move-overlay previous-overlay
651 (overlay-start previous-overlay)
653 (unless (= ending (point-max))
654 (let ((next-overlay (iedit-find-overlay-at-point
656 'iedit-occurrence-overlay-name)))
657 (if next-overlay ; two conjoined occurrences
658 (move-overlay next-overlay ending (overlay-end next-overlay)))))))
660 (defvar iedit-number-line-counter 1
661 "Occurrence number for 'iedit-number-occurrences.")
663 (defun iedit-default-occurrence-number-format (start-at)
666 (length (int-to-string
667 (1- (+ (length iedit-occurrences-overlays) start-at)))))
670 (defun iedit-number-occurrences (start-at &optional format-string)
671 "Insert numbers in front of the occurrences.
672 START-AT, if non-nil, should be a number from which to begin
673 counting. FORMAT, if non-nil, should be a format string to pass
674 to `format-string' along with the line count. When called
675 interactively with a prefix argument, prompt for START-AT and
678 (if current-prefix-arg
679 (let* ((start-at (read-number "Number to count from: " 1)))
681 (read-string "Format string: "
682 (iedit-default-occurrence-number-format
685 (unless format-string
686 (setq format-string (iedit-default-occurrence-number-format start-at)))
687 (let ((iedit-number-occurrence-counter start-at)
688 (inhibit-modification-hooks t))
690 (dolist (occurrence iedit-occurrences-overlays)
691 (goto-char (overlay-start occurrence))
692 (insert (format format-string iedit-number-occurrence-counter))
693 (iedit-move-conjoined-overlays occurrence)
694 (setq iedit-number-occurrence-counter
695 (1+ iedit-number-occurrence-counter))))))
699 (defun iedit-find-current-occurrence-overlay ()
700 "Return the current occurrence overlay at point or point - 1.
701 This function is supposed to be called in overlay keymap."
702 (or (iedit-find-overlay-at-point (point) 'iedit-occurrence-overlay-name)
703 (iedit-find-overlay-at-point (1- (point)) 'iedit-occurrence-overlay-name)))
705 (defun iedit-find-overlay-at-point (point property)
706 "Return the overlay with PROPERTY at POINT."
707 (let ((overlays (overlays-at point))
709 (while (and overlays (not found))
710 (let ((overlay (car overlays)))
711 (if (overlay-get overlay property)
713 (setq overlays (cdr overlays)))))
716 (defun iedit-same-column ()
717 "Return t if all occurrences are at the same column."
719 (let ((column (progn (goto-char (overlay-start (car iedit-occurrences-overlays)))
721 (overlays (cdr iedit-occurrences-overlays))
723 (while (and overlays same)
724 (let ((overlay (car overlays)))
725 (if (/= (progn (goto-char (overlay-start overlay))
729 (setq overlays (cdr overlays)))))
732 (defun iedit-same-length ()
733 "Return t if all occurrences are the same length."
735 (let ((length (iedit-occurrence-string-length))
736 (overlays (cdr iedit-occurrences-overlays))
738 (while (and overlays same)
739 (let ((ov (car overlays)))
740 (if (/= (- (overlay-end ov) (overlay-start ov))
743 (setq overlays (cdr overlays)))))
746 ;; This function might be called out of any occurrence
747 (defun iedit-current-occurrence-string ()
748 "Return current occurrence string.
749 Return nil if occurrence string is empty string."
750 (let* ((ov (or (iedit-find-current-occurrence-overlay)
751 (car iedit-occurrences-overlays)))
752 (beg (overlay-start ov))
753 (end (overlay-end ov)))
754 (if (and ov (/= beg end))
755 (buffer-substring-no-properties beg end)
758 (defun iedit-occurrence-string-length ()
759 "Return the length of current occurrence string."
760 (let ((ov (car iedit-occurrences-overlays)))
761 (- (overlay-end ov) (overlay-start ov))))
763 (defun iedit-find-overlay (beg end property &optional exclusive)
764 "Return a overlay with property in region, or out of the region if EXCLUSIVE is not nil."
766 (or (iedit-find-overlay-in-region (point-min) beg property)
767 (iedit-find-overlay-in-region end (point-max) property))
768 (iedit-find-overlay-in-region beg end property)))
770 (defun iedit-find-overlay-in-region (beg end property)
771 "Return a overlay with property in region."
772 (let ((overlays (overlays-in beg end))
774 (while (and overlays (not found))
775 (let ((overlay (car overlays)))
776 (if (and (overlay-get overlay property)
777 (>= (overlay-start overlay) beg)
778 (<= (overlay-end overlay) end))
780 (setq overlays (cdr overlays)))))
783 (defun iedit-cleanup-occurrences-overlays (beg end &optional inclusive)
784 "Remove deleted overlays from list `iedit-occurrences-overlays'."
786 (remove-overlays beg end iedit-occurrence-overlay-name t)
787 (remove-overlays (point-min) beg iedit-occurrence-overlay-name t)
788 (remove-overlays end (point-max) iedit-occurrence-overlay-name t))
790 (dolist (overlay iedit-occurrences-overlays)
791 (if (overlay-buffer overlay)
792 (push overlay overlays)))
793 (setq iedit-occurrences-overlays (nreverse overlays))))
795 (defun iedit-printable (string)
796 "Return a omitted substring that is not longer than 50.
797 STRING is already `regexp-quote'ed"
798 (let ((first-newline-index (string-match "$" string))
799 (length (length string)))
800 (if (and first-newline-index
801 (/= first-newline-index length))
802 (if (< first-newline-index 50)
803 (concat (substring string 0 first-newline-index) "...")
804 (concat (substring string 0 50) "..."))
806 (concat (substring string 0 50) "...")
809 (defun iedit-region-active ()
810 "Return t if region is active and not empty.
811 If variable `iedit-transient-mark-sensitive' is t, active region
812 means `transient-mark-mode' is on and mark is active. Otherwise,
813 it just means mark is active."
814 (and (if iedit-transient-mark-sensitive
817 mark-active (not (equal (mark) (point)))))
819 (defun iedit-barf-if-lib-active()
820 "Signal error if `iedit-occurrences-overlays' is not nil."
821 (or (null iedit-occurrences-overlays )
822 (error "Iedit lib is active.")))
826 ;;; iedit-lib.el ends here
828 ;; LocalWords: iedit el MERCHANTABILITY kbd isearch todo ert Lindberg Tassilo
829 ;; LocalWords: eval rect defgroup defcustom boolean defvar assq alist nconc
830 ;; LocalWords: substring cadr keymap defconst purecopy bkm defun princ prev
831 ;; LocalWords: iso lefttab backtab upcase downcase concat setq autoload arg
832 ;; LocalWords: refactoring propertize cond goto nreverse progn rotatef eq elp
833 ;; LocalWords: dolist pos unmatch args ov sReplace iedit's cdr quote'ed