]> git.donarmstrong.com Git - lib.git/blob - emacs_el/iedit-lib.el
add iedit
[lib.git] / emacs_el / iedit-lib.el
1 ;;; iedit-lib.el --- APIs for editing multiple regions in the same way
2 ;;; simultaneously.
3
4 ;; Copyright (C) 2010, 2011, 2012 Victor Ren
5
6 ;; Time-stamp: <2012-09-07 16:28:18 Victor Ren>
7 ;; Author: Victor Ren <victorhge@gmail.com>
8 ;; Keywords: occurrence region simultaneous rectangle refactoring
9 ;; Version: 0.97
10 ;; X-URL: http://www.emacswiki.org/emacs/Iedit
11 ;; Compatibility: GNU Emacs: 22.x, 23.x, 24.x
12
13 ;; This file is not part of GNU Emacs, but it is distributed under
14 ;; the same terms as GNU Emacs.
15
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.
20
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.
25
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/>.
28
29 ;;; Commentary:
30
31 ;; This package is iedit APIs library that allow you to write your own minor mode.
32
33 ;;; todo:
34 ;; - Update comments for APIs
35 ;; - Add more easy access keys for whole occurrence
36 ;; - More APIs: extend occurrences,
37
38 ;;; Code:
39
40 (eval-when-compile (require 'cl))
41
42 (defgroup iedit nil
43   "Edit multiple regions in the same way simultaneously."
44   :prefix "iedit-"
45   :group 'replace
46   :group 'convenience)
47
48 (defface iedit-occurrence
49   '((t :inherit highlight))
50   "*Face used for the occurrences' default values."
51   :group 'iedit)
52
53 (defcustom iedit-case-sensitive-default t
54   "If no-nil, matching is case sensitive."
55   :type 'boolean
56   :group 'iedit)
57
58 (defcustom iedit-unmatched-lines-invisible-default nil
59   "If no-nil, hide lines that do not cover any occurrences by default."
60   :type 'boolean
61   :group 'iedit)
62
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
67 mode, set it as nil."
68   :type 'boolean
69   :group 'iedit)
70
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.")
77
78 (defvar iedit-case-sensitive iedit-case-sensitive-default
79   "This is buffer local variable.
80 If no-nil, matching is case sensitive.")
81
82 (defvar iedit-unmatched-lines-invisible nil
83   "This is buffer local variable which indicates whether
84 unmatched lines are hided.")
85
86 (defvar iedit-forward-success t
87   "This is buffer local variable which indicates the moving
88 forward or backward successful")
89
90 (defvar iedit-before-modification-string ""
91   "This is buffer local variable which is the buffer substring
92 that is going to be changed.")
93
94 (defvar iedit-before-modification-undo-list nil
95   "This is buffer local variable which is the buffer undo list before modification.")
96
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.")
103
104 (defvar iedit-aborting nil
105   "This is buffer local variable which indicates Iedit mode is aborting.")
106
107 (defvar iedit-aborting-hook nil
108   "Functions to call before iedit-abort.  Normally it should be mode exit function.")
109
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'.")
113
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.")
118
119 (defvar iedit-occurrence-context-lines 1
120   "The number of lines before or after the occurrence.")
121
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)
133
134 (defconst iedit-occurrence-overlay-name 'iedit-occurrence-overlay-name)
135 (defconst iedit-invisible-overlay-name 'iedit-invisible-overlay-name)
136
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)
146     map)
147   "Keymap used while Iedit mode is enabled.")
148
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)
163     map)
164   "Default keymap used within occurrence overlays.")
165
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)
170
171 (defun iedit-help-for-occurrences ()
172   "Display `iedit-occurrence-keymap-default'"
173   (interactive)
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 "
184                    )))
185
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
192   (let ((counter 0)
193         (case-fold-search (not iedit-case-sensitive)))
194     (save-excursion
195       (goto-char beg)
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))
201       (when (/= 0 counter)
202         (setq iedit-occurrences-overlays (nreverse iedit-occurrences-overlays))
203         (if iedit-unmatched-lines-invisible
204             (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))
205     counter))
206
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))
210
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))
214
215 (defun iedit-add-occurrence-overlay (occurrence-exp point forward)
216   "Create next or previous occurrence overlay for `occurrence-exp'."
217   (or point
218       (setq point (point)))
219   (let ((case-fold-search (not iedit-case-sensitive)))
220     (save-excursion
221       (goto-char point)
222       (if (not (if forward
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)
230                                              (match-end 0))
231               iedit-occurrences-overlays)
232         (sort iedit-occurrences-overlays
233               (lambda (left right)
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))))))
238
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
242 there are."
243   (or (= beg end)
244       (error "No region"))
245   (if (null iedit-occurrences-overlays)
246       (push
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
257           (lambda (left right)
258             (< (overlay-start left) (overlay-start right)))))) ;; todo test this function
259
260 (defun iedit-cleanup ()
261   "Clean up occurrence overlay, invisible overlay and local variables."
262   (remove-overlays nil nil iedit-occurrence-overlay-name t)
263   (iedit-show-all)
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))
268
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))
281     occurrence))
282
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))
290
291 (defun iedit-post-undo-hook ()
292   "Check if it is time to abort iedit after undo command is executed.
293
294 This is added to `post-command-hook' when undo command is executed
295 in occurrences."
296   (if (iedit-same-length)
297       nil
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))
301
302 (defun iedit-reset-aborting ()
303   "Turning Iedit mode off and reset `iedit-aborting'.
304
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))
312
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."
323   (if undo-in-progress
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
331     (if (null after)
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)))
356               (save-excursion
357                 ;; insertion or yank
358                 (if (eq 0 change)
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
373                                               beginning
374                                               ending
375                                               change))
376                         (iedit-move-conjoined-overlays another-occurrence)))
377                   ;; deletion
378                   (dolist (another-occurrence (remove occurrence iedit-occurrences-overlays))
379                     (let* ((beginning (+ (overlay-start another-occurrence) offset))
380                            (ending (+ beginning change)))
381                       (delete-region beginning ending)
382                       (unless (eq beg end) ;; replacement
383                         (goto-char beginning)
384                         (insert-and-inherit value))
385                       (run-hook-with-args 'after-change-functions
386                                           beginning
387                                           ending
388                                           change)))))))))))))
389
390 (defun iedit-next-occurrence ()
391   "Move forward to the next occurrence in the `iedit'.
392 If the point is already in the last occurrences, you are asked to type
393 another `iedit-next-occurrence', it starts again from the
394 beginning of the buffer."
395   (interactive)
396   (let ((pos (point))
397         (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
398     (when in-occurrence
399       (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name)))
400     (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name))
401     (if (/= pos (point-max))
402         (setq iedit-forward-success t)
403       (if (and iedit-forward-success in-occurrence)
404           (progn (message "This is the last occurrence.")
405                  (setq iedit-forward-success nil))
406         (progn
407           (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
408               (setq pos (point-min))
409             (setq pos (next-single-char-property-change
410                        (point-min)
411                        'iedit-occurrence-overlay-name)))
412           (setq iedit-forward-success t)
413           (message "Located the first occurrence."))))
414     (when iedit-forward-success
415       (goto-char pos))))
416
417 (defun iedit-prev-occurrence ()
418   "Move backward to the previous occurrence in the `iedit'.
419 If the point is already in the first occurrences, you are asked to type
420 another `iedit-prev-occurrence', it starts again from the end of
421 the buffer."
422   (interactive)
423   (let ((pos (point))
424         (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
425     (when in-occurrence
426       (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
427     (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name))
428     ;; At the start of the first occurrence
429     (if (or (and (eq pos (point-min))
430                  (not (get-char-property (point-min) 'iedit-occurrence-overlay-name)))
431             (and (eq (point) (point-min))
432                  in-occurrence))
433         (if (and iedit-forward-success in-occurrence)
434             (progn (message "This is the first occurrence.")
435                    (setq iedit-forward-success nil))
436           (progn
437             (setq pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name))
438             (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
439                 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
440             (setq iedit-forward-success t)
441             (message "Located the last occurrence.")))
442       (setq iedit-forward-success t))
443     (when iedit-forward-success
444       (goto-char pos))))
445
446 (defun iedit-first-occurrence ()
447   "Move to the first occurrence."
448   (interactive)
449   (let ((pos (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
450                  (point-min)
451                (next-single-char-property-change
452                 (point-min) 'iedit-occurrence-overlay-name))))
453     (setq iedit-forward-success t)
454     (goto-char pos)
455     (message "Located the first occurrence.")))
456
457 (defun iedit-last-occurrence ()
458   "Move to the last occurrence."
459   (interactive)
460   (let ((pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name)))
461     (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
462         (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
463     (setq iedit-forward-success t)
464     (goto-char pos)
465     (message "Located the last occurrence.")))
466
467 (defun iedit-toggle-unmatched-lines-visible (&optional arg)
468   "Toggle whether to display unmatched lines.
469 A prefix ARG specifies how many lines before and after the
470 occurrences are not hided; negative is treated the same as zero.
471
472 If no prefix argument, the prefix argument last time or default
473 value of `iedit-occurrence-context-lines' is used for this time."
474   (interactive "P")
475   (if (null arg)
476       ;; toggle visible
477       (progn (setq iedit-unmatched-lines-invisible (not iedit-unmatched-lines-invisible))
478              (if iedit-unmatched-lines-invisible
479                  (iedit-hide-unmatched-lines iedit-occurrence-context-lines)
480                (iedit-show-all)))
481     ;; reset invisible lines
482     (setq arg (prefix-numeric-value arg))
483     (if (< arg 0)
484         (setq arg 0))
485     (unless (and iedit-unmatched-lines-invisible
486                  (= arg iedit-occurrence-context-lines))
487       (when iedit-unmatched-lines-invisible
488         (remove-overlays nil nil iedit-invisible-overlay-name t))
489       (setq iedit-occurrence-context-lines arg)
490       (setq iedit-unmatched-lines-invisible t)
491       (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))
492
493 (defun iedit-show-all()
494   "Show hided lines."
495   (setq line-move-ignore-invisible nil)
496   (remove-from-invisibility-spec '(iedit-invisible-overlay-name . t))
497   (remove-overlays nil nil iedit-invisible-overlay-name t))
498
499 (defun iedit-hide-unmatched-lines (context-lines)
500   "Hide unmatched lines using invisible overlay.
501 This function depends on the order of `iedit-occurrences-overlays'. TODO"
502   (let ((prev-occurrence-end 1)
503         (unmatched-lines nil))
504     (save-excursion
505       (dolist (overlay iedit-occurrences-overlays)
506         (goto-char (overlay-start overlay))
507         (forward-line (- context-lines))
508         (let ((line-beginning (line-beginning-position)))
509           (if (> line-beginning prev-occurrence-end)
510               (push  (list prev-occurrence-end (1- line-beginning)) unmatched-lines)))
511         (goto-char (overlay-end overlay))
512         (forward-line context-lines)
513         (setq prev-occurrence-end (line-end-position)))
514       (if (< prev-occurrence-end (point-max))
515           (push (list prev-occurrence-end (point-max)) unmatched-lines))
516       (when unmatched-lines
517         (set (make-local-variable 'line-move-ignore-invisible) t)
518         (add-to-invisibility-spec '(iedit-invisible-overlay-name . t))
519         (dolist (unmatch unmatched-lines)
520           (iedit-make-unmatched-lines-overlay (car unmatch) (cadr unmatch)))))))
521
522 ;;;; functions for overlay keymap
523 (defun iedit-apply-on-occurrences (function &rest args)
524   "Call function for each occurrence."
525   (let ((inhibit-modification-hooks t))
526       (save-excursion
527         (dolist (occurrence iedit-occurrences-overlays)
528           (apply function (overlay-start occurrence) (overlay-end occurrence) args)))))
529
530 (defun iedit-upcase-occurrences ()
531   "Covert occurrences to upper case."
532   (interactive "*")
533   (iedit-apply-on-occurrences 'upcase-region))
534
535 (defun iedit-downcase-occurrences()
536   "Covert occurrences to lower case."
537   (interactive "*")
538   (iedit-apply-on-occurrences 'downcase-region))
539
540 (defun iedit-replace-occurrences(to-string)
541   "Replace occurrences with STRING.
542 This function preserves case."
543   (interactive "*sReplace with: ")
544   (let* ((ov (iedit-find-current-occurrence-overlay))
545          (offset (- (point) (overlay-start ov)))
546          (from-string (downcase (buffer-substring-no-properties
547                                  (overlay-start ov)
548                                  (overlay-end ov)))))
549     (iedit-apply-on-occurrences
550      (lambda (beg end from-string to-string)
551        (goto-char beg)
552        (search-forward from-string end)
553        (replace-match to-string nil))
554      from-string to-string)
555     (goto-char (+ (overlay-start ov) offset))))
556
557 (defun iedit-blank-occurrences()
558   "Replace occurrences with blank spaces."
559   (interactive "*")
560   (let* ((ov (car iedit-occurrences-overlays))
561          (offset (- (point) (overlay-start ov)))
562          (count (- (overlay-end ov) (overlay-start ov))))
563     (iedit-apply-on-occurrences
564      (lambda (beg end )
565        (delete-region beg end)
566        (goto-char beg)
567        (insert-and-inherit (make-string count 32))))
568     (goto-char (+ (overlay-start ov) offset))))
569
570 (defun iedit-delete-occurrences()
571   "Delete occurrences."
572   (interactive "*")
573   (iedit-apply-on-occurrences 'delete-region))
574
575 ;; todo: add cancel buffering function
576 (defun iedit-toggle-buffering ()
577   "Toggle buffering.
578 This is intended to improve iedit's response time.  If the number
579 of occurrences are huge, it might be slow to update all the
580 occurrences for each key stoke.  When buffering is on,
581 modification is only applied to the current occurrence and will
582 be applied to other occurrences when buffering is off."
583   (interactive "*")
584   (if iedit-buffering
585       (iedit-stop-buffering)
586     (iedit-start-buffering))
587   (message (concat "Modification Buffering "
588                    (if iedit-buffering
589                        "started."
590                      "stopped."))))
591
592 (defun iedit-start-buffering ()
593   "Start buffering."
594   (setq iedit-buffering t)
595   (setq iedit-before-modification-string (iedit-current-occurrence-string))
596   (setq iedit-before-modification-undo-list buffer-undo-list)
597   (message "Start buffering editing...")
598   ;; (setq iedit-mode (propertize
599   ;;                   (concat " Iedit-B:" (number-to-string (length iedit-occurrences-overlays)))
600   ;;                   'face 'font-lock-warning-face))
601   ;; (force-mode-line-update)
602   )
603
604 (defun iedit-stop-buffering ()
605   "Stop buffering and apply the modification to other occurrences.
606 If current point is not at any occurrence, the buffered
607 modification is not going to be applied to other occurrences."
608   (let ((ov (iedit-find-current-occurrence-overlay)))
609     (when ov
610       (let* ((beg (overlay-start ov))
611              (end (overlay-end ov))
612              (modified-string (buffer-substring-no-properties beg end))
613              (offset (- (point) beg)) ;; delete-region moves cursor
614              (inhibit-modification-hooks t))
615         (when (not (string= iedit-before-modification-string modified-string))
616           (save-excursion
617             ;; Rollback the current modification and buffer-undo-list. This is
618             ;; to avoid the inconsistency if user undoes modifications
619             (delete-region beg end)
620             (goto-char beg)
621             (insert-and-inherit iedit-before-modification-string)
622             (setq buffer-undo-list iedit-before-modification-undo-list)
623             (dolist (occurrence iedit-occurrences-overlays) ; todo:extract as a function
624               (let ((beginning (overlay-start occurrence))
625                     (ending (overlay-end occurrence)))
626                 (delete-region beginning ending)
627                 (unless (eq beg end) ;; replacement
628                   (goto-char beginning)
629                   (insert-and-inherit modified-string))
630                 (iedit-move-conjoined-overlays occurrence))))
631           (goto-char (+ (overlay-start ov) offset))))))
632   (setq iedit-buffering nil)
633   ;; (setq iedit-mode (propertize
634   ;;                   (concat (if iedit-rectangle " Iedit-RECT:" " Iedit:")
635   ;;                           (number-to-string (length iedit-occurrences-overlays)))
636   ;;                   'face 'font-lock-warning-face))
637   ;; (force-mode-line-update)
638   (message "Buffered modification applied.")
639   (setq iedit-before-modification-undo-list nil))
640
641 (defun iedit-move-conjoined-overlays (occurrence)
642   "This function keeps overlays conjoined after modification.
643 After modification, conjoined overlays may be overlapped."
644   (let ((beginning (overlay-start occurrence))
645         (ending (overlay-end occurrence)))
646     (unless (= beginning (point-min))
647       (let ((previous-overlay (iedit-find-overlay-at-point
648                                (1- beginning)
649                                'iedit-occurrence-overlay-name)))
650         (if previous-overlay ; two conjoined occurrences
651             (move-overlay previous-overlay
652                           (overlay-start previous-overlay)
653                           beginning))))
654     (unless (= ending (point-max))
655       (let ((next-overlay (iedit-find-overlay-at-point
656                            ending
657                            'iedit-occurrence-overlay-name)))
658         (if next-overlay ; two conjoined occurrences
659             (move-overlay next-overlay ending (overlay-end next-overlay)))))))
660
661 (defvar iedit-number-line-counter 1
662   "Occurrence number for 'iedit-number-occurrences.")
663
664 (defun iedit-default-occurrence-number-format (start-at)
665   (concat "%"
666           (int-to-string
667            (length (int-to-string
668                     (1- (+ (length iedit-occurrences-overlays) start-at)))))
669           "d "))
670
671 (defun iedit-number-occurrences (start-at &optional format-string)
672   "Insert numbers in front of the occurrences.
673 START-AT, if non-nil, should be a number from which to begin
674 counting.  FORMAT, if non-nil, should be a format string to pass
675 to `format-string' along with the line count.  When called
676 interactively with a prefix argument, prompt for START-AT and
677 FORMAT."
678   (interactive
679    (if current-prefix-arg
680        (let* ((start-at (read-number "Number to count from: " 1)))
681          (list start-at
682                (read-string "Format string: "
683                             (iedit-default-occurrence-number-format
684                              start-at))))
685      (list 1 nil)))
686   (unless format-string
687     (setq format-string (iedit-default-occurrence-number-format start-at)))
688   (let ((iedit-number-occurrence-counter start-at)
689         (inhibit-modification-hooks t))
690     (save-excursion
691       (dolist (occurrence iedit-occurrences-overlays)
692         (goto-char (overlay-start occurrence))
693         (insert (format format-string iedit-number-occurrence-counter))
694         (iedit-move-conjoined-overlays occurrence)
695         (setq iedit-number-occurrence-counter
696               (1+ iedit-number-occurrence-counter))))))
697
698
699 ;;; help functions
700 (defun iedit-find-current-occurrence-overlay ()
701   "Return the current occurrence overlay  at point or point - 1.
702 This function is supposed to be called in overlay keymap."
703   (or (iedit-find-overlay-at-point (point) 'iedit-occurrence-overlay-name)
704       (iedit-find-overlay-at-point (1- (point)) 'iedit-occurrence-overlay-name)))
705
706 (defun iedit-find-overlay-at-point (point property)
707   "Return the overlay with PROPERTY at POINT."
708   (let ((overlays (overlays-at point))
709         found)
710     (while (and overlays (not found))
711       (let ((overlay (car overlays)))
712         (if (overlay-get overlay property)
713             (setq found overlay)
714           (setq overlays (cdr overlays)))))
715     found))
716
717 (defun iedit-same-column ()
718   "Return t if all occurrences are at the same column."
719   (save-excursion
720     (let ((column (progn (goto-char (overlay-start (car iedit-occurrences-overlays)))
721                          (current-column)))
722           (overlays (cdr  iedit-occurrences-overlays))
723           (same t))
724       (while (and overlays same)
725         (let ((overlay (car overlays)))
726           (if (/= (progn (goto-char (overlay-start overlay))
727                          (current-column))
728                   column)
729               (setq same nil)
730             (setq overlays (cdr overlays)))))
731       same)))
732
733 (defun iedit-same-length ()
734   "Return t if all occurrences are the same length."
735   (save-excursion
736     (let ((length (iedit-occurrence-string-length))
737           (overlays (cdr iedit-occurrences-overlays))
738           (same t))
739       (while (and overlays same)
740         (let ((ov (car overlays)))
741           (if (/= (- (overlay-end ov) (overlay-start ov))
742                   length)
743               (setq same nil)
744             (setq overlays (cdr overlays)))))
745       same)))
746
747 ;; This function might be called out of any occurrence
748 (defun iedit-current-occurrence-string ()
749   "Return current occurrence string.
750 Return nil if occurrence string is empty string."
751   (let* ((ov (or (iedit-find-current-occurrence-overlay)
752                  (car iedit-occurrences-overlays)))
753          (beg (overlay-start ov))
754          (end (overlay-end ov)))
755     (if (and ov (/=  beg end))
756         (buffer-substring-no-properties beg end)
757       nil)))
758
759 (defun iedit-occurrence-string-length ()
760   "Return the length of current occurrence string."
761   (let ((ov (car iedit-occurrences-overlays)))
762     (- (overlay-end ov) (overlay-start ov))))
763
764 (defun iedit-find-overlay (beg end property &optional exclusive)
765   "Return a overlay with property in region, or out of the region if EXCLUSIVE is not nil."
766   (if exclusive
767       (or (iedit-find-overlay-in-region (point-min) beg property)
768           (iedit-find-overlay-in-region end (point-max) property))
769     (iedit-find-overlay-in-region beg end property)))
770
771 (defun iedit-find-overlay-in-region (beg end property)
772   "Return a overlay with property in region."
773   (let ((overlays (overlays-in beg end))
774         found)
775     (while (and overlays (not found))
776       (let ((overlay (car overlays)))
777         (if (and (overlay-get overlay property)
778                  (>= (overlay-start overlay) beg)
779                  (<= (overlay-end overlay) end))
780             (setq found overlay)
781           (setq overlays (cdr overlays)))))
782     found))
783
784 (defun iedit-cleanup-occurrences-overlays (beg end &optional inclusive)
785   "Remove deleted overlays from list `iedit-occurrences-overlays'."
786   (if inclusive
787       (remove-overlays beg end iedit-occurrence-overlay-name t)
788     (remove-overlays (point-min) beg iedit-occurrence-overlay-name t)
789     (remove-overlays end (point-max) iedit-occurrence-overlay-name t))
790   (let (overlays)
791     (dolist (overlay iedit-occurrences-overlays)
792       (if (overlay-buffer overlay)
793           (push overlay overlays)))
794     (setq iedit-occurrences-overlays (nreverse overlays))))
795
796 (defun iedit-printable (string)
797   "Return a omitted substring that is not longer than 50.
798 STRING is already `regexp-quote'ed"
799   (let ((first-newline-index (string-match "$" string))
800         (length (length string)))
801     (if (and first-newline-index
802              (/= first-newline-index length))
803         (if (< first-newline-index 50)
804             (concat (substring string 0 first-newline-index) "...")
805           (concat (substring string 0 50) "..."))
806       (if (> length 50)
807           (concat (substring string 0 50) "...")
808         string))))
809
810 (defun iedit-region-active ()
811   "Return t if region is active and not empty.
812 If variable `iedit-transient-mark-sensitive' is t, active region
813 means `transient-mark-mode' is on and mark is active. Otherwise,
814 it just means mark is active."
815   (and (if iedit-transient-mark-sensitive
816            transient-mark-mode
817          t)
818        mark-active (not (equal (mark) (point)))))
819
820 (defun iedit-barf-if-lib-active()
821   "Signal error if `iedit-occurrences-overlays' is not nil."
822   (or (null iedit-occurrences-overlays )
823       (error "Iedit lib is active.")))
824
825 (provide 'iedit-lib)
826
827 ;;; iedit-lib.el ends here
828
829 ;;  LocalWords:  iedit el MERCHANTABILITY kbd isearch todo ert Lindberg Tassilo
830 ;;  LocalWords:  eval rect defgroup defcustom boolean defvar assq alist nconc
831 ;;  LocalWords:  substring cadr keymap defconst purecopy bkm defun princ prev
832 ;;  LocalWords:  iso lefttab backtab upcase downcase concat setq autoload arg
833 ;;  LocalWords:  refactoring propertize cond goto nreverse progn rotatef eq elp
834 ;;  LocalWords:  dolist pos unmatch args ov sReplace iedit's cdr quote'ed