]> git.donarmstrong.com Git - lib.git/blob - emacs_el/iedit-lib.el
fix missing ) for org-mode
[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-10-17 08:48:28 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                       (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
385                                           beginning
386                                           (+ beginning (- beg end))
387                                           change)))))))))))))
388
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."
394   (interactive)
395   (let ((pos (point))
396         (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
397     (when in-occurrence
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))
405         (progn
406           (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
407               (setq pos (point-min))
408             (setq pos (next-single-char-property-change
409                        (point-min)
410                        'iedit-occurrence-overlay-name)))
411           (setq iedit-forward-success t)
412           (message "Located the first occurrence."))))
413     (when iedit-forward-success
414       (goto-char pos))))
415
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
420 the buffer."
421   (interactive)
422   (let ((pos (point))
423         (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
424     (when in-occurrence
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))
431                  in-occurrence))
432         (if (and iedit-forward-success in-occurrence)
433             (progn (message "This is the first occurrence.")
434                    (setq iedit-forward-success nil))
435           (progn
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
443       (goto-char pos))))
444
445 (defun iedit-first-occurrence ()
446   "Move to the first occurrence."
447   (interactive)
448   (let ((pos (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
449                  (point-min)
450                (next-single-char-property-change
451                 (point-min) 'iedit-occurrence-overlay-name))))
452     (setq iedit-forward-success t)
453     (goto-char pos)
454     (message "Located the first occurrence.")))
455
456 (defun iedit-last-occurrence ()
457   "Move to the last occurrence."
458   (interactive)
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)
463     (goto-char pos)
464     (message "Located the last occurrence.")))
465
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.
470
471 If no prefix argument, the prefix argument last time or default
472 value of `iedit-occurrence-context-lines' is used for this time."
473   (interactive "P")
474   (if (null arg)
475       ;; toggle visible
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)
479                (iedit-show-all)))
480     ;; reset invisible lines
481     (setq arg (prefix-numeric-value arg))
482     (if (< arg 0)
483         (setq arg 0))
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))))
491
492 (defun iedit-show-all()
493   "Show hided lines."
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))
497
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))
503     (save-excursion
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)))))))
520
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))
525       (save-excursion
526         (dolist (occurrence iedit-occurrences-overlays)
527           (apply function (overlay-start occurrence) (overlay-end occurrence) args)))))
528
529 (defun iedit-upcase-occurrences ()
530   "Covert occurrences to upper case."
531   (interactive "*")
532   (iedit-apply-on-occurrences 'upcase-region))
533
534 (defun iedit-downcase-occurrences()
535   "Covert occurrences to lower case."
536   (interactive "*")
537   (iedit-apply-on-occurrences 'downcase-region))
538
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
546                                  (overlay-start ov)
547                                  (overlay-end ov)))))
548     (iedit-apply-on-occurrences
549      (lambda (beg end from-string to-string)
550        (goto-char beg)
551        (search-forward from-string end)
552        (replace-match to-string nil))
553      from-string to-string)
554     (goto-char (+ (overlay-start ov) offset))))
555
556 (defun iedit-blank-occurrences()
557   "Replace occurrences with blank spaces."
558   (interactive "*")
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
563      (lambda (beg end )
564        (delete-region beg end)
565        (goto-char beg)
566        (insert-and-inherit (make-string count 32))))
567     (goto-char (+ (overlay-start ov) offset))))
568
569 (defun iedit-delete-occurrences()
570   "Delete occurrences."
571   (interactive "*")
572   (iedit-apply-on-occurrences 'delete-region))
573
574 ;; todo: add cancel buffering function
575 (defun iedit-toggle-buffering ()
576   "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."
582   (interactive "*")
583   (if iedit-buffering
584       (iedit-stop-buffering)
585     (iedit-start-buffering))
586   (message (concat "Modification Buffering "
587                    (if iedit-buffering
588                        "started."
589                      "stopped."))))
590
591 (defun iedit-start-buffering ()
592   "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)
601   )
602
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)))
608     (when ov
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))
615           (save-excursion
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)
619             (goto-char beg)
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))
639
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
647                                (1- beginning)
648                                'iedit-occurrence-overlay-name)))
649         (if previous-overlay ; two conjoined occurrences
650             (move-overlay previous-overlay
651                           (overlay-start previous-overlay)
652                           beginning))))
653     (unless (= ending (point-max))
654       (let ((next-overlay (iedit-find-overlay-at-point
655                            ending
656                            'iedit-occurrence-overlay-name)))
657         (if next-overlay ; two conjoined occurrences
658             (move-overlay next-overlay ending (overlay-end next-overlay)))))))
659
660 (defvar iedit-number-line-counter 1
661   "Occurrence number for 'iedit-number-occurrences.")
662
663 (defun iedit-default-occurrence-number-format (start-at)
664   (concat "%"
665           (int-to-string
666            (length (int-to-string
667                     (1- (+ (length iedit-occurrences-overlays) start-at)))))
668           "d "))
669
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
676 FORMAT."
677   (interactive
678    (if current-prefix-arg
679        (let* ((start-at (read-number "Number to count from: " 1)))
680          (list start-at
681                (read-string "Format string: "
682                             (iedit-default-occurrence-number-format
683                              start-at))))
684      (list 1 nil)))
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))
689     (save-excursion
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))))))
696
697
698 ;;; help functions
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)))
704
705 (defun iedit-find-overlay-at-point (point property)
706   "Return the overlay with PROPERTY at POINT."
707   (let ((overlays (overlays-at point))
708         found)
709     (while (and overlays (not found))
710       (let ((overlay (car overlays)))
711         (if (overlay-get overlay property)
712             (setq found overlay)
713           (setq overlays (cdr overlays)))))
714     found))
715
716 (defun iedit-same-column ()
717   "Return t if all occurrences are at the same column."
718   (save-excursion
719     (let ((column (progn (goto-char (overlay-start (car iedit-occurrences-overlays)))
720                          (current-column)))
721           (overlays (cdr  iedit-occurrences-overlays))
722           (same t))
723       (while (and overlays same)
724         (let ((overlay (car overlays)))
725           (if (/= (progn (goto-char (overlay-start overlay))
726                          (current-column))
727                   column)
728               (setq same nil)
729             (setq overlays (cdr overlays)))))
730       same)))
731
732 (defun iedit-same-length ()
733   "Return t if all occurrences are the same length."
734   (save-excursion
735     (let ((length (iedit-occurrence-string-length))
736           (overlays (cdr iedit-occurrences-overlays))
737           (same t))
738       (while (and overlays same)
739         (let ((ov (car overlays)))
740           (if (/= (- (overlay-end ov) (overlay-start ov))
741                   length)
742               (setq same nil)
743             (setq overlays (cdr overlays)))))
744       same)))
745
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)
756       nil)))
757
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))))
762
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."
765   (if exclusive
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)))
769
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))
773         found)
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))
779             (setq found overlay)
780           (setq overlays (cdr overlays)))))
781     found))
782
783 (defun iedit-cleanup-occurrences-overlays (beg end &optional inclusive)
784   "Remove deleted overlays from list `iedit-occurrences-overlays'."
785   (if inclusive
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))
789   (let (overlays)
790     (dolist (overlay iedit-occurrences-overlays)
791       (if (overlay-buffer overlay)
792           (push overlay overlays)))
793     (setq iedit-occurrences-overlays (nreverse overlays))))
794
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) "..."))
805       (if (> length 50)
806           (concat (substring string 0 50) "...")
807         string))))
808
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
815            transient-mark-mode
816          t)
817        mark-active (not (equal (mark) (point)))))
818
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.")))
823
824 (provide 'iedit-lib)
825
826 ;;; iedit-lib.el ends here
827
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