Initial Commit
[packages] / xemacs-packages / edit-utils / wide-edit.el
1 ;; ------------------------------ COPYRIGHT NOTICE ------------------------------
2 ;; wide-edit.el version 1.1
3 ;; Author: Jesper K. Pedersen <blackie@kdab.net>
4 ;; Copyright Klaralvdalens Datakonsult AB.
5 ;; Home page: http://www.kdab.net/
6 ;;
7 ;; This program is free software; you can redistribute it and/or modify it
8 ;; under the terms of the GNU General Public License as published by the Free
9 ;; Software Foundation; either version 2 of the License, or (at your option)
10 ;; any later version.
11 ;;
12 ;; This program is distributed in the hope that it will be useful, but
13 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 ;; for more details.
16 ;;
17 ;; You should have received a copy of the GNU General Public License along
18 ;; with GNU Emacs.  If you did not, write to the Free Software Foundation,
19 ;; Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
20
21
22 ;; ------------------------------ DESCRIPTION ------------------------------
23 ;; wide-edit.el is for people who recognizes the following situation I had
24 ;; once:
25 ;;                                PROBLEM
26 ;;
27 ;; I needed to replace "bool" to "Boolean" in a number of files, A fast grep
28 ;; though the files showed me that there where approx 2000 matches. Of course,
29 ;; there where situation where the replacement should not be done.  For each
30 ;; match it was pretty simply to decide whether the replacement should be done
31 ;; or not. However, even replacing one instance every second using
32 ;; tags-query-replace, would take 20 minutes. I did it that way, but I had to
33 ;; pause every few minutes, because the trivial work lulled me to sleep.  Even
34 ;; though I paused often, it showed up that I've made at least 10 errors (which
35 ;; really only is 1 error for every 200 instance), and a rough estimate would
36 ;; say that fixing these errors later costed up to a whole man-week of work!
37 ;;
38 ;;                                SOLUTION
39 ;;
40 ;; The solution to the problem above is a major mode called wide-edit, which
41 ;; works on the output from grep. The fundamental thing about this major mode
42 ;; is that whatever you change in the output from grep, gets written back into
43 ;; the file the grep line talks about. This makes it possible to handle each
44 ;; situation in turn: "Lines matching the keyword "SIGNAL" should not be replace,
45 ;; lines which contains the word "ok", should very likely be replace" etc.
46 ;; 
47 ;;                                EXAMPLE
48 ;;
49 ;; A grep buffer may contain the following line:
50 ;; /home/joe/project/main.cpp:131: bool b;
51 ;;                                 ^^^^^^^
52 ;; The text marked is the content of line 131 in main.cpp
53 ;; If we now change the line so it says:
54 ;; /home/joe/project/main.cpp:131: Boolean b;
55 ;; Then this change is propagated back to the file main.cpp, so line 131 now
56 ;; says Boolean rather than bool.
57 ;;
58 ;;                              KEY BINDINGS
59 ;;
60 ;; In the wide-edit buffer functionality like search-and-replace, macros,
61 ;; rectangular command etc still works, which makes this superior to using
62 ;; tags-query-replace. In addition, there is a number of extra functions, that
63 ;; allows you to narrow to lines matching a regular expression, and only work
64 ;; on these lines. The bindings are as follows:
65 ;; 
66 ;; C-c m - marks lines where the content matches a given regular expression.
67 ;; C-c M - marks lines where the file name matches a given regular expression.
68 ;; C-c o - marks lines where the content do not match ( o for nOt )
69 ;; C-c O - marks lines where the file name do not match ( O for nOt )
70 ;; C-c a - mark all lines above current line
71 ;; C-c space - toggle mark on current line
72 ;; C-c c - mark lines which has been changed
73 ;; C-c i - inverse all marks
74 ;; C-c r - remove all marks
75 ;; 
76 ;; C-c n - only show lines with marks (narrow)
77 ;; C-c w - widen out from narrowing, ie. show all lines
78 ;; C-c backspace - delete marked lines (ie. delete from grep buffer, not from
79 ;;                 their  original place). This is a way to say, OK I've
80 ;;                 handled these lines.
81 ;;
82 ;; C-c u - undo changes made to the current line
83 ;; C-c h - delete info lines, these are typical lines telling about number of
84 ;;         matches etc.
85 ;;
86 ;;                              ONE WARNING
87 ;; 
88 ;; Due to technicalities in the implementation, lines which are hidden during
89 ;; narrowing (C-c n) are still visible to Emacs commands, such as 
90 ;; search-and-replace, and rectangular commands. This means that if you narrow
91 ;; you might experience that Emacs offers to replace text at the beginning of a
92 ;; line, where you don't see the pattern searched for.
93 ;; This makes the narow-widen feature less useful, but in most situation this
94 ;; is not an issue, given the movement commands works fine.
95 ;; Thus search-and-replace can be replaced with the following sequence of actions:
96 ;; - Narrow till each line in the buffer contain a match (You are allowed to
97 ;;   narrow in a narrowed buffer)
98 ;; - record a macro, which replace the match on the current line for example by
99 ;;   searching, and replacing text found. Next step of macro is to go to the
100 ;;   next line of the buffer.
101 ;; - run the macro on all the lines of the buffer.
102
103
104 ;; ------------------------------ INSTALLATION ------------------------------
105 ;; To use this file insert a line similar to the following to your .emacs file:
106 ;; (load "wide-edit.el)
107 ;;
108 ;; If you want wide-edit to start automatically after grep and igrep, insert
109 ;; the following line in your .emacs:
110 ;; (wide-edit-insinuate-grep 't)
111 ;;
112 ;; Running igrep or grep should now automatically start wide-edit-mode. If it 
113 ;; doesn't or you want to use this mode with output from another program,
114 ;; simply type M-x wide-edit-mode.
115 ;;
116 ;; wide-edit need to modify next-error a bit to make it work with wide-edit. 
117 ;; If you do not want it to do so, insert the following code after the load line:
118 ;; (wide-edit-insinuate-next-error nil)
119 ;;
120 ;; Often it is more usefull to look at the grep output in reverse, thus the last
121 ;; line is the first and visa verse (This is usefull if you jump to the buffer 
122 ;; and delete lines, which otherwise would make future jumps go to the wrong line)
123 ;; setting the variable wide-edit-initial-reverse will make wide-edit reverse the lines
124 ;; on start up. You set this variable by inserting the following line into your .emacs
125 ;; (setq wide-edit-initial-reverse 't)
126 ;; This functionality is also bound to C-c C-r, so you can reverse text as you work with the buffer.
127
128 ;; ---------------------------- code starts here ----------------------------
129
130 ;----------------------------------------------------------------------
131 ;                                 Mode Map
132 ;----------------------------------------------------------------------
133 (defvar wide-edit-mode-map nil)   ; Create a mode-specific keymap.
134 (defvar wide-edit-initial-reverse nil
135   "Set this variable to 't if you want wide-edit to reverse all lines on start up")
136
137 (if wide-edit-mode-map
138     ()              ; Do not change the keymap if it is already set up.
139   (setq wide-edit-mode-map (make-sparse-keymap))
140   (define-key wide-edit-mode-map [(control c) (h)] 'wide-edit-delete-info-lines)
141   (define-key wide-edit-mode-map [(control c) (c)] 'wide-edit-mark-changed-line)
142   (define-key wide-edit-mode-map [(control c) (m)] 'wide-edit-mark-content-matches)
143   (define-key wide-edit-mode-map [(control c) (o)] 'wide-edit-mark-content-non-matches)
144   (define-key wide-edit-mode-map [(control c) (M)] 'wide-edit-mark-filename-matches)
145   (define-key wide-edit-mode-map [(control c) (O)] 'wide-edit-mark-filename-non-matches)
146   (define-key wide-edit-mode-map [(control c) (n)] 'wide-edit-narrow-to-mark)
147   (define-key wide-edit-mode-map [(control c) (w)] 'wide-edit-widen)
148   (define-key wide-edit-mode-map [(control c) (space)] 'wide-edit-toggle-mark)
149   (define-key wide-edit-mode-map [(control c) (i)] 'wide-edit-inverse-all-marks)
150   (define-key wide-edit-mode-map [(control c) (r)] 'wide-edit-clear-marks)
151   (define-key wide-edit-mode-map [(control c) (backspace)] 'wide-edit-remove-marked)
152   (define-key wide-edit-mode-map [(control c) (a)] 'wide-edit-mark-above)
153   (define-key wide-edit-mode-map [(control c) (control r)] 'wide-edit-reverse-buffer)
154
155
156   (define-key wide-edit-mode-map [(up)] 'wide-edit-prev-line)
157   (define-key wide-edit-mode-map [(control p)] 'wide-edit-prev-line)
158   (define-key wide-edit-mode-map [(kp-up)] 'wide-edit-prev-line)
159
160   (define-key wide-edit-mode-map [(down)] 'wide-edit-next-line)
161   (define-key wide-edit-mode-map [(control n)] 'wide-edit-next-line)
162   (define-key wide-edit-mode-map [(kp-down)] 'wide-edit-next-line)
163
164   (define-key wide-edit-mode-map [(control a)] 'wide-edit-beginning-of-line)
165   (define-key wide-edit-mode-map [(kp-home)] 'wide-edit-beginning-of-line)
166   (define-key wide-edit-mode-map [(begin)] 'wide-edit-beginning-of-line)
167   (define-key wide-edit-mode-map [(kp-begin)] 'wide-edit-beginning-of-line)
168
169   (define-key wide-edit-mode-map [(return)] 'wide-edit-goto-content)
170   (define-key wide-edit-mode-map [(button2)] 'wide-edit-mouse-goto)
171   (define-key wide-edit-mode-map [(control x) (control s)] 'wide-edit-save-changes)
172   (define-key wide-edit-mode-map [(control c) (u)] 'wide-edit-undo-current-change)
173
174   )
175
176 ;----------------------------------------------------------------------
177 ;                                   Faces
178 ;----------------------------------------------------------------------
179 (make-face 'wide-edit-info-text-face
180   "Face used for info text is shown with (info text is text like 'grep command is ....)'")
181 (set-face-property 'wide-edit-info-text-face 'foreground "grey70")
182
183 (make-face 'wide-edit-marked-face
184   "Face used for marked lines")
185 (set-face-property 'wide-edit-marked-face 'foreground "black")
186 (set-face-property 'wide-edit-marked-face 'background "orange")
187
188 (make-face 'wide-edit-file-info-face
189    "Face used on the part saying 'File:line-no:'")
190 (set-face-property 'wide-edit-file-info-face 'foreground "orange")
191 (set-face-property 'wide-edit-file-info-face 'background "white")
192
193
194 (make-face 'wide-edit-unmarked-face
195   "Face used for marked lines")
196
197 ;----------------------------------------------------------------------
198 ;                                Mode Setup
199 ;----------------------------------------------------------------------
200 (defun wide-edit-mode ()
201   "Major mode for editing buffers which result from grep or similar commands
202   Special Commands: \\{wide-edit-map}"
203
204   (interactive)
205   (kill-all-local-variables)
206   (use-local-map wide-edit-mode-map)
207   (setq mode-name "Wide-Edit")           ; This name goes into the modeline.
208   (setq major-mode 'wide-edit-mode)      ; This is how `describe-mode'
209                                          ; finds the doc string to print.
210   (wide-edit-init-buffer wide-edit-initial-reverse)
211   (run-hooks 'wide-edit-mode-hook)  
212
213   (make-local-variable 'kill-buffer-hook)
214   (setq kill-buffer-hook 'wide-edit-ask-to-save)
215   (setq compilation-last-buffer (current-buffer))
216   (make-local-variable 'wide-edit-init)
217   (setq wide-edit-init 't)
218 )
219
220 (defun wide-edit-init-buffer (reverse)
221   "Sets up the buffer for wide-edit."
222   ;; Kill all existig extents
223   (let (ext (extents (extent-list)))
224     (while extents
225       (setq ext (car extents))
226       (setq extents (cdr extents))
227       (delete-extent ext)))
228   
229   ;; reverse text
230   (if reverse
231       (wide-edit-reverse-text))
232   
233   ;; set new extent
234   (let ((more 't))
235     (goto-char (point-min))
236     (while (and more (not (= (point) (point-max))))
237       (if (looking-at "\\([^: \n]+\\):\\([0-9]+\\):")
238           ;; A match line
239           (let* ((start (point))
240                  (end (progn (end-of-line) (point)))
241                  (mid (match-end 0))
242                  (ext1 (make-extent start mid))
243                  (ext2 (make-extent start (+ 1 end))))
244             (set-extent-properties ext1 
245                                    '(face wide-edit-file-info-face read-only t mark t))
246             (set-extent-properties ext2 
247                                    '(face wide-edit-unmarked-face matched-line t))
248             (set-extent-property ext2 'orig-content (buffer-substring mid end))
249             (set-extent-property ext2 'file (match-string 1))
250             (set-extent-property ext2 'line (match-string 2))
251             (set-extent-property ext2 'offset (- mid start))
252             (set-extent-property ext2 'co-extent ext1)
253             )
254         
255         ;; Not looking at a match line
256         (let* ((start (point))
257                (end (+ 1 (progn (end-of-line) (point))))
258                (extent (make-extent start end)))
259           (progn
260             (set-extent-property extent 'read-only 't)
261             (set-extent-face extent 'wide-edit-info-text-face)
262             (set-extent-property extent 'unmatched-line 't))
263           ))
264       (setq more (eq (forward-line) 0))
265       
266       ))
267   (set-buffer-modified-p nil))
268
269 (defadvice igrep (before wide-edit-igrep-advice dis)
270   (setq compilation-finish-function 'wide-edit-highlight-grep)
271
272   ;; Clean out *igrep* as wide-edit might in a previous run
273   ;; have made them partly read-only.
274   (let ((igrep (get-buffer "*igrep*")))
275     (if igrep
276         (kill-buffer igrep))))
277
278 (defadvice grep (before wide-edit-grep-advice dis)
279   (setq compilation-finish-function 'wide-edit-highlight-grep)
280
281   ;; Clean out *grep*, as wide-edit might in a previous run
282   ;; have made them partly read-only.
283   (let ((grep (get-buffer "*grep*")))
284     (if grep
285         (kill-buffer grep))))
286
287 (defadvice grep (before wide-edit-igrep-advice dis)
288   (setq compilation-finish-function 'wide-edit-highlight-grep))
289
290 (defun wide-edit-insinuate-grep (on)
291   (if on
292       (progn
293         (ad-enable-advice 'igrep 'before 'wide-edit-igrep-advice)
294         (ad-enable-advice 'grep 'before 'wide-edit-grep-advice))
295     (progn
296       (ad-disable-advice 'igrep 'before 'wide-edit-igrep-advice)
297       (ad-disable-advice 'grep 'before 'wide-edit-grep-advice)))
298   (ad-activate 'igrep)
299   (ad-activate 'grep))
300
301 (defun wide-edit-highlight-grep (buffer str)
302   (set-buffer buffer)
303   (wide-edit-mode)
304   (setq compilation-finish-function nil))
305
306 ;----------------------------------------------------------------------
307 ;                           Misc
308 ;----------------------------------------------------------------------
309 (defun wide-edit-delete-info-lines ()
310   "Delete lines which are not part of the grep search that set up this buffer.
311   These lines are typically telling about the grep command etc, but are not part of the grep result"
312   (interactive)
313   (let ((list (extent-list (current-buffer) (point-min) (point-max) nil 'unmatched-line 't ))
314         ext)
315     (while list
316       (setq ext (car list))
317       (setq list (cdr list))
318       (set-extent-property ext 'read-only nil)
319       (delete-region (extent-start-position ext) (extent-end-position ext)))))
320
321 (defun wide-edit-mark-changed-line ()
322   "Mark lines with changes"
323   (interactive)
324   (map-extents (lambda (ext mapargs)
325                  (set-extent-property ext 'mark (wide-edit-line-changed ext)) 
326                  nil)
327                (current-buffer) (point-min) (point-max) nil nil 'matched-line 't)
328   (wide-edit-highlight-marked)
329   )
330
331 (defun wide-edit-line-changed (ext)
332   "Returns whether there is any change for the given extent"
333   (let ((str (buffer-substring (+ (extent-property ext 'offset) (extent-start-position ext))
334                                   (- (extent-end-position ext) 1))))
335     (not (equal str (extent-property ext 'orig-content)))))
336
337 (defun wide-edit-undo-current-change ( &optional extent )
338   "Undo changes on the current line"
339   (interactive)
340   (let ((ext (if extent 
341                  extent 
342                (extent-at (point) (current-buffer) 'matched-line)))
343         start)
344     (if ext
345         (progn
346           (setq start (+ (extent-property ext 'offset) (extent-start-position ext)))
347           (delete-region start (- (extent-end-position ext) 1))
348           (goto-char start)
349           (insert (extent-property ext 'orig-content))))))
350
351 ;----------------------------------------------------------------------
352 ;                                  Saving
353 ;----------------------------------------------------------------------
354 (defun wide-edit-save-changes ()
355   (interactive)
356
357   (let ( (quit-now nil) 
358          (mark-all nil)
359          (replace-all nil)
360          (wide-edit-buffer (current-buffer))
361          orig-content new-content existing-content answer replace-this buffer ) 
362     (wide-edit-clear-marks)
363
364     (map-extents 
365      (lambda (ext mapargs)
366        (set-buffer wide-edit-buffer)
367        (setq orig-content (extent-property ext 'orig-content))
368        (setq new-content (buffer-substring (+ (extent-property ext 'offset) (extent-start-position ext))
369                                            (- (extent-end-position ext) 1)))
370
371        (if (wide-edit-line-changed ext)
372            (progn
373              (setq buffer (wide-edit-goto-pos ext nil))
374              (setq existing-content (buffer-substring (point) (progn (end-of-line) (point))))
375
376              (setq
377               replace-this nil
378               mark-this nil)
379
380              ;; Test if the content on the line to modify is equal to the original content
381              (if (or replace-all (equal existing-content orig-content))
382                  (setq replace-this 't)
383                (if (not mark-all)
384                    ;; content didn't match
385                    (progn
386                      (setq answer (wide-edit-replace-this-match ext existing-content new-content))
387                      (if (equal answer "y")
388                          (setq replace-this 't)
389                        (if (equal answer "n")
390                            (wide-edit-undo-current-change ext)
391                          (if (or (equal answer "q") (equal answer "\a"))
392                              (setq quit-now 't)
393                            (if (equal answer "m")
394                                (setq mark-this 't)
395                              (if (equal answer "M")
396                                  (setq mark-all 't)
397                                (if (equal answer "!")
398                                    (setq replace-all 't))))))))))
399                
400              (if quit-now
401                  't ; return 't stop the map-extents
402                
403                (progn
404                  (if (or replace-this replace-all)
405                      (progn
406                        (set-buffer buffer)
407                        (delete-region (progn (beginning-of-line) (point))
408                                       (progn (end-of-line) (point)))
409                        (insert new-content)
410                        (set-extent-property ext 'orig-content new-content))
411                    (if (or mark-this mark-all)
412                        (set-extent-property ext 'mark 't)))
413                  nil)))) ; return nil to continue map-extent
414          nil) ; return nil to continue map-extent
415        
416        (current-buffer) (point-min) (point-max) nil nil 'matched-line 't)
417      (set-buffer wide-edit-buffer)
418      (wide-edit-highlight-marked)
419      (set-buffer-modified-p nil)))
420   
421 (defun wide-edit-replace-this-match (ext existing replacement)
422   (let ((buffer (get-buffer-create "*Wide-Edit-Questions*"))
423         answer map i)
424     (set-buffer buffer)
425     (delete-region (point-min) (point-max))
426     (insert "The original file did not contain the text expected!\n")
427     (insert "-----\n")
428     (insert "Expected text:     ")
429     (insert (extent-property ext 'orig-content))
430     (insert "\nFound text:        ")
431     (insert existing)
432     (insert "\nReplacement text: ")
433     (insert replacement)
434     (insert "\n-----\n")
435     (insert "Here's your option:\n")
436     (insert "y - yes, save this change\n")
437     (insert "n - no, please undo this change in my wide-edit buffer\n")
438     (insert "m - mark this line, but do not save now\n")
439     (insert "M - mark all lines which do not match, and let me look at them afterwards (saving those which do match)\n")
440     (insert "! - override any difference - BE CAREFULL!\n")
441     (insert "q - quit")
442     (toggle-read-only 1)
443     (set-window-buffer (selected-window) buffer)
444
445     ;; Create keymap only containing ynmM and '!'
446     (setq map (make-keymap))
447     (setq i 32)
448     (while (< i 255)
449       (define-key map (int-char i) (lambda () (interactive) nil))
450       (setq i (+ i 1)))
451     (define-key map [(y)] 'self-insert-and-exit)
452     (define-key map [(n)] 'self-insert-and-exit)
453     (define-key map [(m)] 'self-insert-and-exit)
454     (define-key map [(M)] 'self-insert-and-exit)
455     (define-key map [(!)] 'self-insert-and-exit)
456     (define-key map [(control g)] 'self-insert-and-exit)
457     (define-key map [(q)] 'self-insert-and-exit)
458     
459     (setq answer (read-from-minibuffer "What to do (y,n,m,M,!,q) " nil map ))
460     (kill-buffer buffer)
461     
462     answer ))
463
464 (defun wide-edit-has-changes()
465   "returns 't if there are any changes"
466   (let ((changes nil))
467     (map-extents
468      (lambda (ext args)
469        (if (wide-edit-line-changed ext)
470            (progn
471              (setq changes 't)
472              't)
473          nil))
474        (current-buffer) (point-min) (point-max) nil nil 'matched-line 't)
475     changes))
476   
477 (defun wide-edit-ask-to-save ()
478   "Asks the user if he was to save changes - connected to save hook"
479   (if (wide-edit-has-changes)
480       (if (yes-or-no-p "Save unsaved changes? ")
481           (wide-edit-save-changes))))
482
483 ;----------------------------------------------------------------------
484 ;                            Mark functions
485 ;----------------------------------------------------------------------
486 (defun wide-edit-mark-content-matches ( arg )
487   "Mark lines which matches a regular expression on the content part.
488   The regular expression will be queried in the mini-buffer.
489
490   If this function is called without a prefix, all marks will be cleared before matching, and only 
491   lines which matches the regexp will be matched
492
493   If this functions is called with C-u as prefix, the query will only be done on those lines
494   which is already marked, thus this is an 'and' query.
495
496   If this function is called with C-u C-u as prefix, those lines which already has a mark, will continue
497   having a mark, and the query is called among the other lines. This this is an 'or' query."
498   (interactive "p")
499   (wide-edit-mark-or-unmark "Regexp to match on content: " '(lambda (x) x) arg nil))
500
501 (defun wide-edit-mark-content-non-matches ()
502   "Mark lines which do NOT match a regular expression on the content part.
503   The regular expression will be queried in the mini-buffer.
504   See wide-edit-mark-content-matches for a description of arguments."
505   (interactive)
506   (wide-edit-mark-or-unmark "Regexp NOT to match on content: " '(lambda (x) (not x)) arg nil))
507
508 (defun wide-edit-mark-filename-matches ( arg )
509   "Mark lines which matches a regular expression on the filename part of the line.
510   The regular expression will be queried in the mini-buffer.
511   See wide-edit-mark-content-matches for a description of arguments."
512   (interactive "p")
513   (wide-edit-mark-or-unmark "Regexp to match on file name: " '(lambda (x) x) arg t))
514
515 (defun wide-edit-mark-filename-non-matches ( arg )
516   "Mark lines which do not matches a regular expression on the filename part of the line.
517   The regular expression will be queried in the mini-buffer.
518   See wide-edit-mark-content-matches for a description of arguments."
519   (interactive "p")
520   (wide-edit-mark-or-unmark "Regexp to match on file name: " '(lambda (x) (not x)) arg t))
521
522 (defun wide-edit-mark-or-unmark ( question possible-not-fn arg match-files )
523   "mark lines which matches or doesn't match a regular expression."
524   "This function is a utility function for wide-edit-mark-matches and wide-edit-mark-non-matches
525    `question' is the question to ask the user
526    `possible-not-fn' is a function taking one argument and returning either the argument or the argument negated.
527    This function is used to control whether we are searching for matches or non-marches.
528    'arg' is the prefix argument to the function calling this - see description for wide-edit-mark-matches"
529   (save-excursion
530     (let ((regexp (read-from-minibuffer question))
531           (list (extent-list (current-buffer) (point-min) (point-max) nil 'matched-line 't ))
532           ext str pred-fn)
533
534       ( if (= arg 1)
535           (progn 
536             ;; clean prev matches
537             (wide-edit-clear-marks)
538             (setq pred-fn 'or)))
539       
540       (if (= arg 4)
541           ;; mark only those which matches both this and the previous query
542           (setq pred-fn 'and))
543       
544       (if (= arg 16)
545           ;; mark both previos marches and matches of this query
546           (setq pred-fn 'or))
547       
548       (while list
549         (setq ext (car list))
550         (setq list (cdr list))
551         (if match-files
552             (setq str (extent-property ext 'file))
553           (setq str (buffer-substring (+ (extent-property ext 'offset) (extent-start-position ext))
554                                       (- (extent-end-position ext) 1))))
555         (set-extent-face ext nil)
556         (set-extent-property ext 'mark  
557                              (eval `(,pred-fn (extent-property ext 'mark nil)
558                                               (funcall possible-not-fn (string-match regexp str))))))
559       (wide-edit-highlight-marked))))
560
561 (defun wide-edit-clear-marks ()
562   "remove all marks"
563   (interactive)
564   (map-extents (lambda (ext mapargs)
565                  (set-extent-property ext 'mark nil)
566                  nil)
567                (current-buffer) (point-min) (point-max) nil nil 'matched-line 't)
568   (wide-edit-highlight-marked))
569
570 (defun wide-edit-highlight-marked ()
571   "Highligt lines containing mark
572    This command should be called from any function modifying the marks"
573   (map-extents (lambda (ext mapargs)
574                  (if (extent-property ext 'mark)
575                      (set-extent-face ext 'wide-edit-marked-face)
576                    (set-extent-face ext 'wide-edit-unmarked-face))
577                  nil)
578                (current-buffer) (point-min) (point-max) nil nil 'matched-line 't))
579
580                      
581
582 (defun wide-edit-toggle-mark (arg)
583   "Toggle marks on line. With arg, that amount of lines are toggled"
584   (interactive "p")
585   (let ( (count (if arg arg 1)))
586     (while ( not (= count 0 ) )
587       (let ((ext (extent-at (point) (current-buffer) 'matched-line)))
588         (if ext
589             (set-extent-property ext 'mark (not (extent-property ext 'mark))))
590         (if ( > count 0 )
591             (progn
592               (setq count (- count 1) )
593               (wide-edit-next-line))
594           (progn
595             (setq count (+ count 1) )
596             (wide-edit-prev-line)))))
597     (wide-edit-highlight-marked)))
598       
599
600 (defun wide-edit-inverse-all-marks ()
601   "Inverse marks"
602   (interactive)
603   (map-extents (lambda (ext mapargs)
604                  (set-extent-property ext 'mark (not (extent-property ext 'mark)))
605                  nil) (current-buffer) (point-min) (point-max) nil nil 'matched-line 't)
606   (wide-edit-highlight-marked))
607
608 (defun wide-edit-mark-above ()
609   "Mark all lines above this one"
610   (interactive)
611   (let ((p (point)))
612     (map-extents (lambda (ext mapargs)
613                    (if ( < (extent-start-position ext) p )
614                        (set-extent-property ext 'mark 't))
615                  nil) (current-buffer) (point-min) (point-max) nil nil 'matched-line 't)
616     (wide-edit-highlight-marked)))
617   
618 ;----------------------------------------------------------------------
619 ;                   Narrow and widening
620 ;----------------------------------------------------------------------
621 (defun wide-edit-narrow-to-mark ()
622   "Show only lines containing the mark - use wide-edit-widen to see all lines again.
623    Note, you can narrow down several times, but only widen ones."
624   (interactive)
625   (map-extents (lambda (ext mapargs) 
626                  (if (not (extent-property ext 'mark nil))
627                      (progn
628                        (set-extent-property ext 'invisible 't)
629                        (set-extent-property ext 'read-only 't)))
630                  nil))
631   (wide-edit-clear-marks)
632   (wide-edit-highlight-marked))
633
634 (defun wide-edit-widen (arg)
635   "widen to show all lines (see wide-edit-narrow)
636    If arg is greater than 1 (e.g. by pressing C-u as prefix to this command), then
637    all items curenntly visible will be marked."
638   (interactive "p")
639   
640   ;; Mark those visible now
641   (if (> arg 1)
642       (map-extents (lambda (ext mapargs)
643                           (if (not (extent-property ext 'invisible))
644                               (set-extent-property ext 'mark t))
645                           nil)))
646
647   (map-extents (lambda (ext mapargs)
648                 (set-extent-property ext 'invisible nil)
649                 (set-extent-property ext 'read-only nil)
650                 nil))
651
652   (wide-edit-highlight-marked))
653
654
655 (defun wide-edit-remove-marked (arg)
656   "remove lines marked, possible saving if any is changed"
657   (interactive "P")
658   (let ((do-save arg)
659         (extents '())
660         ext)
661     (map-extents 
662      (lambda (ext mapargs)
663        
664        (if (extent-property ext 'mark)
665            (progn
666              (if (wide-edit-line-changed ext)
667                  (if (yes-or-no-p "Some of the marked lines contain changes. Save before deleting? ")
668                      (setq do-save 't)))
669              (setq extents (cons ext extents))))
670        nil)
671      (current-buffer) (point-min) (point-max) nil nil 'matched-line 't)
672
673     (if do-save
674         (wide-edit-save-changes))
675       
676     (while extents
677       (setq ext (car extents))
678       (setq extents (cdr extents))
679       (delete-extent (extent-property ext 'co-extent))
680       (delete-region (extent-start-position ext) (extent-end-position ext)))))
681
682   
683   
684
685 ;----------------------------------------------------------------------
686 ;                         Movement
687 ;----------------------------------------------------------------------
688
689 (defun wide-edit-next-line ()
690   "Move point to the next visible line in a wide-edit buffer"
691   (interactive)
692   (wide-edit-move 1 'end-of-buffer))
693
694 (defun wide-edit-prev-line ()
695   "Move point to the previous visible line in a wide-edit buffer"
696   (interactive)
697   (wide-edit-move -1 'beginning-of-buffer))
698
699 (defun wide-edit-move (count errmsg)
700   "Move point count line up or down
701    Point will be moved up if count is negative.
702    show errmsg if at the boundary of the buffer"
703   (let* ((more 't)
704          (offset (current-column))
705         ext)
706     (while more
707       (setq more nil)
708       (if (not (eq (forward-line count) 0))
709           (error errmsg))
710       (setq ext (extent-at (point) (current-buffer) 'matched-line))
711       (if ext
712           (progn
713             (setq more (extent-property ext 'invisible))
714             (if (not more)
715                 (move-to-column (min (max offset (extent-property ext 'offset)) (progn (end-of-line) (current-column))))))
716         (setq more 't)))))
717
718 (defun wide-edit-beginning-of-line ()
719   "Goto the beginning of line"
720   (interactive)
721     (let ((ext (extent-at (point) (current-buffer) 'matched-line)))
722       (if ext
723           (move-to-column (extent-property ext 'offset))
724         (beginning-of-line))))
725
726 (defun wide-edit-goto-content ( &optional extent wide-edit-buffer)
727   "Jump to the line in the file specified at the current match line"
728   (interactive)
729   (let ((ext (if extent extent (extent-at (point) (current-buffer) 'matched-line))))
730     (if ext
731         (progn
732           (if wide-edit-buffer
733               (set-window-buffer (selected-window) wide-edit-buffer))
734           (setq compilation-last-buffer (current-buffer))
735           (wide-edit-goto-pos ext 't))
736       (error "not on a matched line"))))
737
738 (defun wide-edit-mouse-goto (event)
739   "Jumps to the extent under mouse"
740   (interactive "e")
741   (mouse-set-point event)
742   (setq compilation-last-buffer (current-buffer))
743   (wide-edit-goto-content))
744
745 (defun wide-edit-goto-pos (ext show)
746   (let ((filename (extent-property ext 'file))
747         buffer)
748     (if (file-exists-p filename)
749         (progn
750           (if show
751               (setq buffer (find-file-other-window filename))
752             (setq buffer (find-file-noselect filename)))
753           (set-buffer buffer)
754           (widen)
755           (goto-line (string-to-number (extent-property ext 'line))))
756       (error "file did not exist"))
757     buffer)) ;return value
758
759 (defun wide-edit-next-error (&optional argp)
760   "This is a replacement of next-error function.
761 It calls wide-edit-internal-next-error if the current compilation buffer is
762 in wide edit mode, otherwide it call the original next-error from compile.el.
763 The original next-error is avaiable from wide-edit-next-error-orig"
764   (interactive "P")
765   
766   (if (and (bufferp compilation-last-buffer)
767            (wide-edit-buffer compilation-last-buffer))
768       (wide-edit-internal-next-error)
769     (wide-edit-next-error-orig argp)))
770
771 (defun wide-edit-buffer (buffer) 
772   "return whether `buffer´ is in wide-edit-mode"
773   (save-excursion
774     (set-buffer buffer)
775     (eq major-mode 'wide-edit-mode)))
776   
777 (defun wide-edit-internal-next-error ()
778   "Goto next error"
779   (progn
780     (pop-to-buffer compilation-last-buffer)
781     (if wide-edit-init
782         (progn
783           (goto-char (point-min))
784           (setq wide-edit-init nil)))
785     (wide-edit-next-line)
786     (set-window-start (get-buffer-window (current-buffer)) 
787                       (save-excursion (beginning-of-line) (point)))
788     (wide-edit-goto-content)))
789   
790 ;; next-error do not contain proper hooks for makeing it work with wide-edit
791 ;; Therefore I need to replace the body of the function.
792 (defun wide-edit-insinuate-next-error (on)
793   "make next-error understand wide-edit buffers"
794   (require 'compile)
795   (if on
796       (if (not (eq (symbol-function 'next-error) (symbol-function 'wide-edit-next-error)))
797           (progn
798             (fset 'wide-edit-next-error-orig (symbol-function 'next-error))
799             (fset 'next-error (symbol-function 'wide-edit-next-error))))
800     (if (and (eq (symbol-function 'next-error) (symbol-function 'wide-edit-next-error))
801              (boundp 'wide-edit-next-error-orig))
802         (fset 'next-error (symbol-function 'wide-edit-next-error-orig)))))
803         
804
805 (defun wide-edit-reverse-text ()
806   "Reverse all lines of current buffer.
807 i.e. the first line will be the last, and the last will be the first"
808   (let* ( (tmp (generate-new-buffer "*tmp*"))
809           (cont 't)
810           line)
811
812     ;; Copy lines reverted into the other buffer
813     (goto-char (point-min))
814     (while cont
815       (progn
816         (setq line (buffer-substring (point) (progn (end-of-line) (point))))
817         (setq cont (and
818                     (not (eq (point) (point-max)))
819                     (not (eq (+ (point) 1) (point-max)))))
820         (if cont
821             (forward-char 1))
822         (save-excursion
823           (set-buffer tmp)
824           (goto-char (point-min))
825           (insert (concat line "\n"))
826         )))
827
828     ;; copy reverted buffer to current buffer
829     (erase-buffer)
830     (insert 
831      (save-excursion
832        (set-buffer tmp)
833        (buffer-substring (point-min) (point-max))))
834     ))
835
836 (defun wide-edit-reverse-buffer ()
837   (interactive)
838   (if (wide-edit-has-changes)
839       (progn
840         (if (not (yes-or-no-p "You need to save before reversing, otherwise changes will be lost. Save and continue reversing? "))
841             (error "reverse operation canceled"))
842         (wide-edit-save-changes)))
843   (wide-edit-init-buffer 't))
844
845 (wide-edit-insinuate-next-error 't)