lotsa changes and inclusion of elpy
[emacs.git] / .emacs.d / elisp / iedit / 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: <2013-10-07 10:53:50 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 (defface iedit-read-only-occurrence
54 '((t :inherit region))
55 "*Face used for the read-only occurrences' default values."
56 :group 'iedit)
57
58 (defcustom iedit-case-sensitive-default t
59 "If no-nil, matching is case sensitive."
60 :type 'boolean
61 :group 'iedit)
62
63 (defcustom iedit-unmatched-lines-invisible-default nil
64 "If no-nil, hide lines that do not cover any occurrences by default."
65 :type 'boolean
66 :group 'iedit)
67
68 (defcustom iedit-transient-mark-sensitive t
69 "If no-nil, Iedit mode is sensitive to the Transient Mark mode.
70 It means Iedit works as expected only when regions are
71 highlighted. If you want to use iedit without Transient Mark
72 mode, set it as nil."
73 :type 'boolean
74 :group 'iedit)
75
76 (defcustom iedit-overlay-priority 200
77 "The priority of the overlay used to indicate matches."
78 :type 'integer
79 :group 'iedit)
80
81 (defvar iedit-occurrences-overlays nil
82 "The occurrences slot contains a list of overlays used to
83 indicate the position of each editable occurrence. In addition, the
84 occurrence overlay is used to provide a different face
85 configurable via `iedit-occurrence'.")
86
87 (defvar iedit-read-only-occurrences-overlays nil
88 "The occurrences slot contains a list of overlays used to
89 indicate the position of each read-only occurrence. In addition, the
90 occurrence overlay is used to provide a different face
91 configurable via `iedit-ready-only-occurrence'.")
92
93 (defvar iedit-case-sensitive iedit-case-sensitive-default
94 "This is buffer local variable.
95 If no-nil, matching is case sensitive.")
96
97 (defvar iedit-unmatched-lines-invisible nil
98 "This is buffer local variable which indicates whether
99 unmatched lines are hided.")
100
101 (defvar iedit-forward-success t
102 "This is buffer local variable which indicates the moving
103 forward or backward successful")
104
105 (defvar iedit-before-modification-string ""
106 "This is buffer local variable which is the buffer substring
107 that is going to be changed.")
108
109 (defvar iedit-before-modification-undo-list nil
110 "This is buffer local variable which is the buffer undo list before modification.")
111
112 ;; `iedit-occurrence-update-hook' gets called twice when change==0 and
113 ;; occurrence is zero-width (beg==end) -- for front and back insertion.
114 (defvar iedit-skip-modification-once t
115 "Variable used to skip first modification hook run when
116 insertion against a zero-width occurrence.")
117
118 (defvar iedit-aborting nil
119 "This is buffer local variable which indicates Iedit mode is aborting.")
120
121 (defvar iedit-aborting-hook nil
122 "Functions to call before iedit-abort. Normally it should be mode exit function.")
123
124 (defvar iedit-post-undo-hook-installed nil
125 "This is buffer local variable which indicated if
126 `iedit-post-undo-hook' is installed in `post-command-hook'.")
127
128 (defvar iedit-buffering nil
129 "This is buffer local variable which indicates iedit-mode is
130 buffering, which means the modification to the current occurrence
131 is not applied to other occurrences when it is true.")
132
133 (defvar iedit-occurrence-context-lines 1
134 "The number of lines before or after the occurrence.")
135
136 (make-variable-buffer-local 'iedit-occurrences-overlays)
137 (make-variable-buffer-local 'iedit-read-only-occurrences-overlays)
138 (make-variable-buffer-local 'iedit-unmatched-lines-invisible)
139 (make-local-variable 'iedit-case-sensitive)
140 (make-variable-buffer-local 'iedit-forward-success)
141 (make-variable-buffer-local 'iedit-before-modification-string)
142 (make-variable-buffer-local 'iedit-before-modification-undo-list)
143 (make-variable-buffer-local 'iedit-skip-modification-once)
144 (make-variable-buffer-local 'iedit-aborting)
145 (make-variable-buffer-local 'iedit-buffering)
146 (make-variable-buffer-local 'iedit-post-undo-hook-installed)
147 (make-variable-buffer-local 'iedit-occurrence-context-lines)
148
149 (defconst iedit-occurrence-overlay-name 'iedit-occurrence-overlay-name)
150 (defconst iedit-invisible-overlay-name 'iedit-invisible-overlay-name)
151
152 ;;; Define Iedit mode map
153 (defvar iedit-lib-keymap
154 (let ((map (make-sparse-keymap)))
155 ;; Default key bindings
156 (define-key map (kbd "TAB") 'iedit-next-occurrence)
157 (define-key map (kbd "<S-tab>") 'iedit-prev-occurrence)
158 (define-key map (kbd "<S-iso-lefttab>") 'iedit-prev-occurrence)
159 (define-key map (kbd "<backtab>") 'iedit-prev-occurrence)
160 (define-key map (kbd "C-'") 'iedit-toggle-unmatched-lines-visible)
161 map)
162 "Keymap used while Iedit mode is enabled.")
163
164 (defvar iedit-occurrence-keymap-default
165 (let ((map (make-sparse-keymap)))
166 ;; (set-keymap-parent map iedit-lib-keymap)
167 (define-key map (kbd "M-U") 'iedit-upcase-occurrences)
168 (define-key map (kbd "M-L") 'iedit-downcase-occurrences)
169 (define-key map (kbd "M-R") 'iedit-replace-occurrences)
170 (define-key map (kbd "M-SPC") 'iedit-blank-occurrences)
171 (define-key map (kbd "M-D") 'iedit-delete-occurrences)
172 (define-key map (kbd "M-N") 'iedit-number-occurrences)
173 (define-key map (kbd "M-B") 'iedit-toggle-buffering)
174 (define-key map (kbd "M-<") 'iedit-goto-first-occurrence)
175 (define-key map (kbd "M->") 'iedit-goto-last-occurrence)
176 (define-key map (kbd "C-?") 'iedit-help-for-occurrences)
177 map)
178 "Default keymap used within occurrence overlays.")
179
180 (defvar iedit-occurrence-keymap 'iedit-occurrence-keymap-default
181 "Keymap used within occurrence overlays.
182 It should be set before occurrence overlay is created.")
183 (make-local-variable 'iedit-occurrence-keymap)
184
185 (defun iedit-help-for-occurrences ()
186 "Display `iedit-occurrence-keymap-default'"
187 (interactive)
188 (message (concat (substitute-command-keys "\\[iedit-upcase-occurrences]") "/"
189 (substitute-command-keys "\\[iedit-downcase-occurrences]") ":up/downcase "
190 (substitute-command-keys "\\[iedit-replace-occurrences]") ":replace "
191 (substitute-command-keys "\\[iedit-blank-occurrences]") ":blank "
192 (substitute-command-keys "\\[iedit-delete-occurrences]") ":delete "
193 (substitute-command-keys "\\[iedit-number-occurrences]") ":number "
194 (substitute-command-keys "\\[iedit-toggle-buffering]") ":buffering "
195 (substitute-command-keys "\\[iedit-goto-first-occurrence]") "/"
196 (substitute-command-keys "\\[iedit-goto-last-occurrence]") ":first/last "
197 )))
198
199 (defun iedit-make-occurrences-overlays (occurrence-regexp beg end)
200 "Create occurrence overlays for `occurrence-regexp' in a region.
201 Return the number of occurrences."
202 (setq iedit-aborting nil)
203 (setq iedit-occurrences-overlays nil)
204 (setq iedit-read-only-occurrences-overlays nil)
205 ;; Find and record each occurrence's markers and add the overlay to the occurrences
206 (let ((counter 0)
207 (case-fold-search (not iedit-case-sensitive)))
208 (save-excursion
209 (save-window-excursion
210 (goto-char end)
211 ;; todo: figure out why re-search-forward is slow without "recenter"
212 (recenter)
213 (goto-char beg)
214 (while (re-search-forward occurrence-regexp end t)
215 (let ((beginning (match-beginning 0))
216 (ending (match-end 0)))
217 (if (text-property-not-all beginning ending 'read-only nil)
218 (push (iedit-make-read-only-occurrence-overlay beginning ending)
219 iedit-read-only-occurrences-overlays)
220 (push (iedit-make-occurrence-overlay beginning ending)
221 iedit-occurrences-overlays))
222 (setq counter (1+ counter))))
223 (when (/= 0 counter)
224 (if iedit-unmatched-lines-invisible
225 (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))
226 counter)))
227
228 (defun iedit-add-next-occurrence-overlay (occurrence-exp &optional point)
229 "Create next occurrence overlay for `occurrence-exp'."
230 (iedit-add-occurrence-overlay occurrence-exp point t))
231
232 (defun iedit-add-previous-occurrence-overlay (occurrence-exp &optional point)
233 "Create previous occurrence overlay for `occurrence-exp'."
234 (iedit-add-occurrence-overlay occurrence-exp point nil))
235
236 (defun iedit-add-occurrence-overlay (occurrence-exp point forward)
237 "Create next or previous occurrence overlay for `occurrence-exp'."
238 (or point
239 (setq point (point)))
240 (let ((case-fold-search (not iedit-case-sensitive)))
241 (save-excursion
242 (goto-char point)
243 (if (not (if forward
244 (re-search-forward occurrence-exp nil t)
245 (re-search-backward occurrence-exp nil t)))
246 (message "No match")
247 (if (or (iedit-find-overlay-at-point (match-beginning 0) 'iedit-occurrence-overlay-name)
248 (iedit-find-overlay-at-point (match-end 0) 'iedit-occurrence-overlay-name))
249 (error "Conflict region"))
250 (push (iedit-make-occurrence-overlay (match-beginning 0)
251 (match-end 0))
252 iedit-occurrences-overlays)
253 (message "Add one match for \"%s\"" (iedit-printable occurrence-exp))
254 (if iedit-unmatched-lines-invisible
255 (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))))
256
257 (defun iedit-add-region-as-occurrence (beg end)
258 "Add region as an occurrence.
259 The length of the region must the same as other occurrences if
260 there are."
261 (or (= beg end)
262 (error "No region"))
263 (if (null iedit-occurrences-overlays)
264 (push
265 (iedit-make-occurrence-overlay beg end)
266 iedit-occurrences-overlays)
267 (or (= (- end beg) (iedit-occurrence-string-length))
268 (error "Wrong region"))
269 (if (or (iedit-find-overlay-at-point beg 'iedit-occurrence-overlay-name)
270 (iedit-find-overlay-at-point end 'iedit-occurrence-overlay-name))
271 (error "Conflict region"))
272 (push (iedit-make-occurrence-overlay beg end)
273 iedit-occurrences-overlays)
274 )) ;; todo test this function
275
276 (defun iedit-cleanup ()
277 "Clean up occurrence overlay, invisible overlay and local variables."
278 (remove-overlays nil nil iedit-occurrence-overlay-name t)
279 (iedit-show-all)
280 (setq iedit-occurrences-overlays nil)
281 (setq iedit-read-only-occurrences-overlays nil)
282 (setq iedit-aborting nil)
283 (setq iedit-before-modification-string "")
284 (setq iedit-before-modification-undo-list nil))
285
286 (defun iedit-make-occurrence-overlay (begin end)
287 "Create an overlay for an occurrence in Iedit mode.
288 Add the properties for the overlay: a face used to display a
289 occurrence's default value, and modification hooks to update
290 occurrences if the user starts typing."
291 (let ((occurrence (make-overlay begin end (current-buffer) nil t)))
292 (overlay-put occurrence iedit-occurrence-overlay-name t)
293 (overlay-put occurrence 'face 'iedit-occurrence)
294 (overlay-put occurrence 'keymap iedit-occurrence-keymap)
295 (overlay-put occurrence 'insert-in-front-hooks '(iedit-occurrence-update-hook))
296 (overlay-put occurrence 'insert-behind-hooks '(iedit-occurrence-update-hook))
297 (overlay-put occurrence 'modification-hooks '(iedit-occurrence-update-hook))
298 (overlay-put occurrence 'priority iedit-overlay-priority)
299 occurrence))
300
301 (defun iedit-make-read-only-occurrence-overlay (begin end)
302 "Create an overlay for an read-only occurrence in Iedit mode."
303 (let ((occurrence (make-overlay begin end (current-buffer) nil t)))
304 (overlay-put occurrence iedit-occurrence-overlay-name t)
305 (overlay-put occurrence 'face 'iedit-read-only-occurrence)
306 occurrence))
307
308 (defun iedit-make-unmatched-lines-overlay (begin end)
309 "Create an overlay for lines between two occurrences in Iedit mode."
310 (let ((unmatched-lines-overlay (make-overlay begin end (current-buffer) nil t)))
311 (overlay-put unmatched-lines-overlay iedit-invisible-overlay-name t)
312 (overlay-put unmatched-lines-overlay 'invisible 'iedit-invisible-overlay-name)
313 ;; (overlay-put unmatched-lines-overlay 'intangible t)
314 unmatched-lines-overlay))
315
316 (defun iedit-post-undo-hook ()
317 "Check if it is time to abort iedit after undo command is executed.
318
319 This is added to `post-command-hook' when undo command is executed
320 in occurrences."
321 (if (iedit-same-length)
322 nil
323 (run-hooks 'iedit-aborting-hook))
324 (remove-hook 'post-command-hook 'iedit-post-undo-hook t)
325 (setq iedit-post-undo-hook-installed nil))
326
327 (defun iedit-reset-aborting ()
328 "Turning Iedit mode off and reset `iedit-aborting'.
329
330 This is added to `post-command-hook' when aborting Iedit mode is
331 decided. `iedit-aborting-hook' is postponed after the current
332 command is executed for avoiding `iedit-occurrence-update-hook'
333 is called for a removed overlay."
334 (run-hooks 'iedit-aborting-hook)
335 (remove-hook 'post-command-hook 'iedit-reset-aborting t)
336 (setq iedit-aborting nil))
337
338 ;; There are two ways to update all occurrences. One is to redefine all key
339 ;; stroke map for overlay, the other is to figure out three basic modification
340 ;; in the modification hook. This function chooses the latter.
341 (defun iedit-occurrence-update-hook (occurrence after beg end &optional change)
342 "Update all occurrences.
343 This modification hook is triggered when a user edits any
344 occurrence and is responsible for updating all other
345 occurrences. Refer to `modification-hooks' for more details.
346 Current supported edits are insertion, yank, deletion and
347 replacement. If this modification is going out of the
348 occurrence, it will abort Iedit mode."
349 (if undo-in-progress
350 ;; If the "undo" change make occurrences different, it is going to mess up
351 ;; occurrences. So a check will be done after undo command is executed.
352 (when (not iedit-post-undo-hook-installed)
353 (add-hook 'post-command-hook 'iedit-post-undo-hook nil t)
354 (setq iedit-post-undo-hook-installed t))
355 (when (not iedit-aborting)
356 ;; before modification
357 (if (null after)
358 (if (or (< beg (overlay-start occurrence))
359 (> end (overlay-end occurrence)))
360 (progn (setq iedit-aborting t) ; abort iedit-mode
361 (add-hook 'post-command-hook 'iedit-reset-aborting nil t))
362 (setq iedit-before-modification-string
363 (buffer-substring-no-properties beg end))
364 ;; Check if this is called twice before modification. When inserting
365 ;; into zero-width occurrence or between two conjoined occurrences,
366 ;; both insert-in-front-hooks and insert-behind-hooks will be
367 ;; called. Two calls will make `iedit-skip-modification-once' true.
368 (setq iedit-skip-modification-once (not iedit-skip-modification-once)))
369 ;; after modification
370 (when (not iedit-buffering)
371 (if iedit-skip-modification-once
372 ;; Skip the first hook
373 (setq iedit-skip-modification-once nil)
374 (setq iedit-skip-modification-once t)
375 (when (or (eq 0 change) ;; insertion
376 (eq beg end) ;; deletion
377 (not (string= iedit-before-modification-string
378 (buffer-substring-no-properties beg end))))
379 (iedit-update-occurrences occurrence after beg end change))))))))
380
381 (defun iedit-update-occurrences (occurrence after beg end &optional change)
382 ""
383 (let ((inhibit-modification-hooks t)
384 (offset (- beg (overlay-start occurrence)))
385 (value (buffer-substring-no-properties beg end)))
386 (save-excursion
387 ;; insertion or yank
388 (if (= 0 change)
389 (dolist (another-occurrence iedit-occurrences-overlays)
390 (let* ((beginning (+ (overlay-start another-occurrence) offset))
391 (ending (+ beginning (- end beg))))
392 (when (not (eq another-occurrence occurrence))
393 (goto-char beginning)
394 (insert-and-inherit value)
395 ;; todo: reconsider this change Quick fix for
396 ;; multi-occur occur-edit-mode: multi-occur depend on
397 ;; after-change-functions to update original
398 ;; buffer. Since inhibit-modification-hooks is set to
399 ;; non-nil, after-change-functions hooks are not going
400 ;; to be called for the changes of other occurrences.
401 ;; So run the hook here.
402 (run-hook-with-args 'after-change-functions
403 beginning
404 ending
405 change))
406 (iedit-move-conjoined-overlays another-occurrence)))
407 ;; deletion
408 (dolist (another-occurrence (remove occurrence iedit-occurrences-overlays))
409 (let ((beginning (+ (overlay-start another-occurrence) offset)))
410 (delete-region beginning (+ beginning change))
411 (unless (eq beg end) ;; replacement
412 (goto-char beginning)
413 (insert-and-inherit value))
414 (run-hook-with-args 'after-change-functions
415 beginning
416 (+ beginning (- beg end))
417 change)))))))
418
419 (defun iedit-next-occurrence ()
420 "Move forward to the next occurrence in the `iedit'.
421 If the point is already in the last occurrences, you are asked to type
422 another `iedit-next-occurrence', it starts again from the
423 beginning of the buffer."
424 (interactive)
425 (let ((pos (point))
426 (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
427 (when in-occurrence
428 (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name)))
429 (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name))
430 (if (/= pos (point-max))
431 (setq iedit-forward-success t)
432 (if (and iedit-forward-success in-occurrence)
433 (progn (message "This is the last occurrence.")
434 (setq iedit-forward-success nil))
435 (progn
436 (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
437 (setq pos (point-min))
438 (setq pos (next-single-char-property-change
439 (point-min)
440 'iedit-occurrence-overlay-name)))
441 (setq iedit-forward-success t)
442 (message "Located the first occurrence."))))
443 (when iedit-forward-success
444 (goto-char pos))))
445
446 (defun iedit-prev-occurrence ()
447 "Move backward to the previous occurrence in the `iedit'.
448 If the point is already in the first occurrences, you are asked to type
449 another `iedit-prev-occurrence', it starts again from the end of
450 the buffer."
451 (interactive)
452 (let ((pos (point))
453 (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
454 (when in-occurrence
455 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
456 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name))
457 ;; At the start of the first occurrence
458 (if (or (and (eq pos (point-min))
459 (not (get-char-property (point-min) 'iedit-occurrence-overlay-name)))
460 (and (eq (point) (point-min))
461 in-occurrence))
462 (if (and iedit-forward-success in-occurrence)
463 (progn (message "This is the first occurrence.")
464 (setq iedit-forward-success nil))
465 (progn
466 (setq pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name))
467 (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
468 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
469 (setq iedit-forward-success t)
470 (message "Located the last occurrence.")))
471 (setq iedit-forward-success t))
472 (when iedit-forward-success
473 (goto-char pos))))
474
475 (defun iedit-goto-first-occurrence ()
476 "Move to the first occurrence."
477 (interactive)
478 (goto-char (iedit-first-occurrence))
479 (setq iedit-forward-success t)
480 (message "Located the first occurrence."))
481
482 (defun iedit-first-occurrence ()
483 "return the position of the first occurrence."
484 (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
485 (point-min)
486 (next-single-char-property-change
487 (point-min) 'iedit-occurrence-overlay-name)))
488
489 (defun iedit-goto-last-occurrence ()
490 "Move to the last occurrence."
491 (interactive)
492 (goto-char (iedit-last-occurrence))
493 (setq iedit-forward-success t)
494 (message "Located the last occurrence."))
495
496 (defun iedit-last-occurrence ()
497 "return the position of the last occurrence."
498 (let ((pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name)))
499 (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
500 (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
501 pos))
502
503 (defun iedit-toggle-unmatched-lines-visible (&optional arg)
504 "Toggle whether to display unmatched lines.
505 A prefix ARG specifies how many lines before and after the
506 occurrences are not hided; negative is treated the same as zero.
507
508 If no prefix argument, the prefix argument last time or default
509 value of `iedit-occurrence-context-lines' is used for this time."
510 (interactive "P")
511 (if (null arg)
512 ;; toggle visible
513 (progn (setq iedit-unmatched-lines-invisible (not iedit-unmatched-lines-invisible))
514 (if iedit-unmatched-lines-invisible
515 (iedit-hide-unmatched-lines iedit-occurrence-context-lines)
516 (iedit-show-all)))
517 ;; reset invisible lines
518 (setq arg (prefix-numeric-value arg))
519 (if (< arg 0)
520 (setq arg 0))
521 (unless (and iedit-unmatched-lines-invisible
522 (= arg iedit-occurrence-context-lines))
523 (when iedit-unmatched-lines-invisible
524 (remove-overlays nil nil iedit-invisible-overlay-name t))
525 (setq iedit-occurrence-context-lines arg)
526 (setq iedit-unmatched-lines-invisible t)
527 (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))
528
529 (defun iedit-show-all()
530 "Show hided lines."
531 (setq line-move-ignore-invisible nil)
532 (remove-from-invisibility-spec '(iedit-invisible-overlay-name . t))
533 (remove-overlays nil nil iedit-invisible-overlay-name t))
534
535 (defun iedit-hide-unmatched-lines (context-lines)
536 "Hide unmatched lines using invisible overlay."
537 (let ((prev-occurrence-end 1)
538 (unmatched-lines nil))
539 (save-excursion
540 (goto-char (iedit-first-occurrence))
541 (while (/= (point) (point-max))
542 ;; Now at the beginning of an occurrence
543 (let ((current-start (point)))
544 (forward-line (- context-lines))
545 (let ((line-beginning (line-beginning-position)))
546 (if (> line-beginning prev-occurrence-end)
547 (push (list prev-occurrence-end (1- line-beginning)) unmatched-lines)))
548 ;; goto the end of the occurrence
549 (goto-char (next-single-char-property-change current-start 'iedit-occurrence-overlay-name)))
550 (let ((current-end (point)))
551 (forward-line context-lines)
552 (setq prev-occurrence-end (1+ (line-end-position)))
553 ;; goto the beginning of next occurrence
554 (goto-char (next-single-char-property-change current-end 'iedit-occurrence-overlay-name))))
555 (if (< prev-occurrence-end (point-max))
556 (push (list prev-occurrence-end (point-max)) unmatched-lines))
557 (when unmatched-lines
558 (set (make-local-variable 'line-move-ignore-invisible) t)
559 (add-to-invisibility-spec '(iedit-invisible-overlay-name . t))
560 (dolist (unmatch unmatched-lines)
561 (iedit-make-unmatched-lines-overlay (car unmatch) (cadr unmatch)))))
562 unmatched-lines))
563
564 ;;;; functions for overlay keymap
565 (defun iedit-apply-on-occurrences (function &rest args)
566 "Call function for each occurrence."
567 (let ((inhibit-modification-hooks t))
568 (save-excursion
569 (dolist (occurrence iedit-occurrences-overlays)
570 (apply function (overlay-start occurrence) (overlay-end occurrence) args)))))
571
572 (defun iedit-upcase-occurrences ()
573 "Covert occurrences to upper case."
574 (interactive "*")
575 (iedit-barf-if-buffering)
576 (iedit-apply-on-occurrences 'upcase-region))
577
578 (defun iedit-downcase-occurrences()
579 "Covert occurrences to lower case."
580 (interactive "*")
581 (iedit-barf-if-buffering)
582 (iedit-apply-on-occurrences 'downcase-region))
583
584 (defun iedit-replace-occurrences(to-string)
585 "Replace occurrences with STRING.
586 This function preserves case."
587 (interactive "*sReplace with: ")
588 (iedit-barf-if-buffering)
589 (let* ((ov (iedit-find-current-occurrence-overlay))
590 (offset (- (point) (overlay-start ov)))
591 (from-string (downcase (buffer-substring-no-properties
592 (overlay-start ov)
593 (overlay-end ov)))))
594 (iedit-apply-on-occurrences
595 (lambda (beg end from-string to-string)
596 (goto-char beg)
597 (search-forward from-string end)
598 (replace-match to-string nil))
599 from-string to-string)
600 (goto-char (+ (overlay-start ov) offset))))
601
602 (defun iedit-blank-occurrences()
603 "Replace occurrences with blank spaces."
604 (interactive "*")
605 (iedit-barf-if-buffering)
606 (let* ((ov (car iedit-occurrences-overlays))
607 (offset (- (point) (overlay-start ov)))
608 (count (- (overlay-end ov) (overlay-start ov))))
609 (iedit-apply-on-occurrences
610 (lambda (beg end )
611 (delete-region beg end)
612 (goto-char beg)
613 (insert-and-inherit (make-string count 32))))
614 (goto-char (+ (overlay-start ov) offset))))
615
616 (defun iedit-delete-occurrences()
617 "Delete occurrences."
618 (interactive "*")
619 (iedit-barf-if-buffering)
620 (iedit-apply-on-occurrences 'delete-region))
621
622 ;; todo: add cancel buffering function
623 (defun iedit-toggle-buffering ()
624 "Toggle buffering.
625 This is intended to improve iedit's response time. If the number
626 of occurrences are huge, it might be slow to update all the
627 occurrences for each key stoke. When buffering is on,
628 modification is only applied to the current occurrence and will
629 be applied to other occurrences when buffering is off."
630 (interactive "*")
631 (if iedit-buffering
632 (iedit-stop-buffering)
633 (iedit-start-buffering))
634 (message (concat "Modification Buffering "
635 (if iedit-buffering
636 "started."
637 "stopped."))))
638
639 (defun iedit-start-buffering ()
640 "Start buffering."
641 (setq iedit-buffering t)
642 (setq iedit-before-modification-string (iedit-current-occurrence-string))
643 (setq iedit-before-modification-undo-list buffer-undo-list)
644 (message "Start buffering editing..."))
645
646 (defun iedit-stop-buffering ()
647 "Stop buffering and apply the modification to other occurrences.
648 If current point is not at any occurrence, the buffered
649 modification is not going to be applied to other occurrences."
650 (let ((ov (iedit-find-current-occurrence-overlay)))
651 (when ov
652 (let* ((beg (overlay-start ov))
653 (end (overlay-end ov))
654 (modified-string (buffer-substring-no-properties beg end))
655 (offset (- (point) beg)) ;; delete-region moves cursor
656 (inhibit-modification-hooks t))
657 (when (not (string= iedit-before-modification-string modified-string))
658 (save-excursion
659 ;; Rollback the current modification and buffer-undo-list. This is
660 ;; to avoid the inconsistency if user undoes modifications
661 (delete-region beg end)
662 (goto-char beg)
663 (insert-and-inherit iedit-before-modification-string)
664 (setq buffer-undo-list iedit-before-modification-undo-list)
665 (dolist (occurrence iedit-occurrences-overlays) ; todo:extract as a function
666 (let ((beginning (overlay-start occurrence))
667 (ending (overlay-end occurrence)))
668 (delete-region beginning ending)
669 (unless (eq beg end) ;; replacement
670 (goto-char beginning)
671 (insert-and-inherit modified-string))
672 (iedit-move-conjoined-overlays occurrence))))
673 (goto-char (+ (overlay-start ov) offset))))))
674 (setq iedit-buffering nil)
675 (message "Buffered modification applied.")
676 (setq iedit-before-modification-undo-list nil))
677
678 (defun iedit-move-conjoined-overlays (occurrence)
679 "This function keeps overlays conjoined after modification.
680 After modification, conjoined overlays may be overlapped."
681 (let ((beginning (overlay-start occurrence))
682 (ending (overlay-end occurrence)))
683 (unless (= beginning (point-min))
684 (let ((previous-overlay (iedit-find-overlay-at-point
685 (1- beginning)
686 'iedit-occurrence-overlay-name)))
687 (if previous-overlay ; two conjoined occurrences
688 (move-overlay previous-overlay
689 (overlay-start previous-overlay)
690 beginning))))
691 (unless (= ending (point-max))
692 (let ((next-overlay (iedit-find-overlay-at-point
693 ending
694 'iedit-occurrence-overlay-name)))
695 (if next-overlay ; two conjoined occurrences
696 (move-overlay next-overlay ending (overlay-end next-overlay)))))))
697
698 (defvar iedit-number-line-counter 1
699 "Occurrence number for 'iedit-number-occurrences.")
700
701 (defun iedit-default-occurrence-number-format (start-at)
702 (concat "%"
703 (int-to-string
704 (length (int-to-string
705 (1- (+ (length iedit-occurrences-overlays) start-at)))))
706 "d "))
707
708 (defun iedit-number-occurrences (start-at &optional format-string)
709 "Insert numbers in front of the occurrences.
710 START-AT, if non-nil, should be a number from which to begin
711 counting. FORMAT, if non-nil, should be a format string to pass
712 to `format-string' along with the line count. When called
713 interactively with a prefix argument, prompt for START-AT and
714 FORMAT."
715 (interactive
716 (if current-prefix-arg
717 (let* ((start-at (read-number "Number to count from: " 1)))
718 (list start-at
719 (read-string "Format string: "
720 (iedit-default-occurrence-number-format
721 start-at))))
722 (list 1 nil)))
723 (iedit-barf-if-buffering)
724 (unless format-string
725 (setq format-string (iedit-default-occurrence-number-format start-at)))
726 (let ((iedit-number-occurrence-counter start-at)
727 (inhibit-modification-hooks t))
728 (save-excursion
729 (goto-char (iedit-first-occurrence))
730 (while (/= (point) (point-max))
731 (insert (format format-string iedit-number-occurrence-counter))
732 (iedit-move-conjoined-overlays (iedit-find-current-occurrence-overlay))
733 (setq iedit-number-occurrence-counter
734 (1+ iedit-number-occurrence-counter))
735 (goto-char (next-single-char-property-change (point) 'iedit-occurrence-overlay-name))
736 (goto-char (next-single-char-property-change (point) 'iedit-occurrence-overlay-name))))))
737
738
739 ;;; help functions
740 (defun iedit-find-current-occurrence-overlay ()
741 "Return the current occurrence overlay at point or point - 1.
742 This function is supposed to be called in overlay keymap."
743 (or (iedit-find-overlay-at-point (point) 'iedit-occurrence-overlay-name)
744 (iedit-find-overlay-at-point (1- (point)) 'iedit-occurrence-overlay-name)))
745
746 (defun iedit-find-overlay-at-point (point property)
747 "Return the overlay with PROPERTY at POINT."
748 (let ((overlays (overlays-at point))
749 found)
750 (while (and overlays (not found))
751 (let ((overlay (car overlays)))
752 (if (overlay-get overlay property)
753 (setq found overlay)
754 (setq overlays (cdr overlays)))))
755 found))
756
757 (defun iedit-same-column ()
758 "Return t if all occurrences are at the same column."
759 (save-excursion
760 (let ((column (progn (goto-char (overlay-start (car iedit-occurrences-overlays)))
761 (current-column)))
762 (overlays (cdr iedit-occurrences-overlays))
763 (same t))
764 (while (and overlays same)
765 (let ((overlay (car overlays)))
766 (if (/= (progn (goto-char (overlay-start overlay))
767 (current-column))
768 column)
769 (setq same nil)
770 (setq overlays (cdr overlays)))))
771 same)))
772
773 (defun iedit-same-length ()
774 "Return t if all occurrences are the same length."
775 (save-excursion
776 (let ((length (iedit-occurrence-string-length))
777 (overlays (cdr iedit-occurrences-overlays))
778 (same t))
779 (while (and overlays same)
780 (let ((ov (car overlays)))
781 (if (/= (- (overlay-end ov) (overlay-start ov))
782 length)
783 (setq same nil)
784 (setq overlays (cdr overlays)))))
785 same)))
786
787 ;; This function might be called out of any occurrence
788 (defun iedit-current-occurrence-string ()
789 "Return current occurrence string.
790 Return nil if occurrence string is empty string."
791 (let ((ov (or (iedit-find-current-occurrence-overlay)
792 (car iedit-occurrences-overlays))))
793 (if ov
794 (let ((beg (overlay-start ov))
795 (end (overlay-end ov)))
796 (if (/= beg end)
797 (buffer-substring-no-properties beg end)
798 nil))
799 nil)))
800
801 (defun iedit-occurrence-string-length ()
802 "Return the length of current occurrence string."
803 (let ((ov (car iedit-occurrences-overlays)))
804 (- (overlay-end ov) (overlay-start ov))))
805
806 (defun iedit-find-overlay (beg end property &optional exclusive)
807 "Return a overlay with property in region, or out of the region if EXCLUSIVE is not nil."
808 (if exclusive
809 (or (iedit-find-overlay-in-region (point-min) beg property)
810 (iedit-find-overlay-in-region end (point-max) property))
811 (iedit-find-overlay-in-region beg end property)))
812
813 (defun iedit-find-overlay-in-region (beg end property)
814 "Return a overlay with property in region."
815 (let ((overlays (overlays-in beg end))
816 found)
817 (while (and overlays (not found))
818 (let ((overlay (car overlays)))
819 (if (and (overlay-get overlay property)
820 (>= (overlay-start overlay) beg)
821 (<= (overlay-end overlay) end))
822 (setq found overlay)
823 (setq overlays (cdr overlays)))))
824 found))
825
826 (defun iedit-cleanup-occurrences-overlays (beg end &optional inclusive)
827 "Remove deleted overlays from list `iedit-occurrences-overlays'."
828 (if inclusive
829 (remove-overlays beg end iedit-occurrence-overlay-name t)
830 (remove-overlays (point-min) beg iedit-occurrence-overlay-name t)
831 (remove-overlays end (point-max) iedit-occurrence-overlay-name t))
832 (let (overlays)
833 (dolist (overlay iedit-occurrences-overlays)
834 (if (overlay-buffer overlay)
835 (push overlay overlays)))
836 (setq iedit-occurrences-overlays overlays)))
837
838 (defun iedit-printable (string)
839 "Return a omitted substring that is not longer than 50.
840 STRING is already `regexp-quote'ed"
841 (let ((first-newline-index (string-match "$" string))
842 (length (length string)))
843 (if (and first-newline-index
844 (/= first-newline-index length))
845 (if (< first-newline-index 50)
846 (concat (substring string 0 first-newline-index) "...")
847 (concat (substring string 0 50) "..."))
848 (if (> length 50)
849 (concat (substring string 0 50) "...")
850 string))))
851
852 (defun iedit-char-at-bol (&optional N)
853 "Get char position of the beginning of the current line. If `N'
854 is given, move forward (or backward) that many lines (using
855 `forward-line') and get the char position at the beginning of
856 that line."
857 (save-excursion
858 (forward-line (if N N 0))
859 (point)))
860
861 (defun iedit-char-at-eol (&optional N)
862 "Get char position of the end of the current line. If `N' is
863 given, move forward (or backward) that many lines (using
864 `forward-line') and get the char position at the end of that
865 line."
866 (save-excursion
867 (forward-line (if N N 0))
868 (end-of-line)
869 (point)))
870
871 (defun iedit-region-active ()
872 "Return t if region is active and not empty.
873 If variable `iedit-transient-mark-sensitive' is t, active region
874 means `transient-mark-mode' is on and mark is active. Otherwise,
875 it just means mark is active."
876 (and (if iedit-transient-mark-sensitive
877 transient-mark-mode
878 t)
879 mark-active (not (equal (mark) (point)))))
880
881 (defun iedit-barf-if-lib-active()
882 "Signal error if Iedit lib is active."
883 (or (and (null iedit-occurrences-overlays)
884 (null iedit-read-only-occurrences-overlays))
885 (error "Iedit lib is active")))
886
887 (defun iedit-barf-if-buffering()
888 "Signal error if Iedit lib is buffering."
889 (or (null iedit-buffering)
890 (error "Iedit is buffering")))
891
892 (provide 'iedit-lib)
893
894 ;;; iedit-lib.el ends here
895
896 ;; LocalWords: iedit el MERCHANTABILITY kbd isearch todo ert Lindberg Tassilo
897 ;; LocalWords: eval rect defgroup defcustom boolean defvar assq alist nconc
898 ;; LocalWords: substring cadr keymap defconst purecopy bkm defun princ prev
899 ;; LocalWords: iso lefttab backtab upcase downcase concat setq autoload arg
900 ;; LocalWords: refactoring propertize cond goto nreverse progn rotatef eq elp
901 ;; LocalWords: dolist pos unmatch args ov sReplace iedit's cdr quote'ed