]> git.donarmstrong.com Git - lib.git/blob - emacs_el/tiny-tools/tiny/tinyreplace.el
add tiny-tools
[lib.git] / emacs_el / tiny-tools / tiny / tinyreplace.el
1 ;;; tinyreplace.el --- Handy query-replace, area, case preserve, words
2
3 ;; This file is not part of Emacs
4
5 ;;{{{ Id
6
7 ;; Copyright (C)    1995-2007 Jari Aalto
8 ;; Keywords:        tools
9 ;; Author:          Jari Aalto
10 ;; Maintainer:      Jari Aalto
11 ;;
12 ;; To get information on this program, call M-x tinyreplace-version.
13 ;; Look at the code with folding.el.
14
15 ;; COPYRIGHT NOTICE
16 ;;
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
20 ;; any later version.
21 ;;
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
25 ;; for more details.
26 ;;
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
31 ;;
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
33
34 ;;}}}
35 ;;{{{ Install
36
37 ;; ....................................................... &t-install ...
38 ;; Put this file on your Emacs-Lisp load path, add following into your
39 ;; ~/.emacs startup file
40 ;;
41 ;;      (require 'tinyreplace)
42 ;;
43 ;; Or you can use autoload (preferred) and your emacs starts up faster
44 ;;
45 ;;      (autoload 'tinyreplace-replace-forward          "tinyreplace" "" t)
46 ;;      (autoload 'tinyreplace-replace-region           "tinyreplace" "" t)
47 ;;      (autoload 'tinyreplace-replace-over-files       "tinyreplace" "" t)
48 ;;      (autoload 'tinyreplace-define-keys-compile-map  "tinyreplace" "" t)
49 ;;      (autoload 'tinyreplace-replace-over-files-compile-buffer "tinyreplace" "" t)
50 ;;      (add-hook 'compilation-mode-hook 'tinyreplace-define-keys-compile-map)
51 ;;
52 ;; For easy access to replace functions, bind function `tinyreplace-menu'
53 ;; to a free key. The default install uses M-&, which is next to
54 ;; standard M-% replace key.
55 ;;
56 ;;      M-x tinyreplace-install  ;; C-u to uninstall
57 ;;
58 ;; Check that you have colors on, otherwise the replaced region may
59 ;; not be visible.
60 ;;
61 ;;      (set-face-background 'highlight "blue")
62 ;;
63 ;; If you have any questions, contact maintainer with function
64 ;;
65 ;;      M-x tinyreplace-submit-bug-report
66
67 ;;}}}
68 ;;{{{ Documentation
69
70 ;; .................................................... &t-commentary ...
71 ;;; Commentary:
72
73 ;;  Preface 1995
74 ;;
75 ;;      There was post in gnu.emacs.help where Brian Paul asked for
76 ;;      help to replace his C variables: "Suppose I want to replace
77 ;;      all occurances of the variable i in my C program with j." The
78 ;;      normal emacs function query-replace wasn't suitable for this
79 ;;      task because it offered too many false hits. Guess how many i
80 ;;      characters are used in non-variable context.
81 ;;
82 ;;      Well later I rembered that one could have used \bi\b to search
83 ;;      words. But the nature of "word" is very different here as it
84 ;;      would have been with \b, which relies on syntax table which you
85 ;;      seldom want to change, whilst the "word definition " here can be
86 ;;      changed on the fly. (Remember that \bi\b still matches entries
87 ;;      like "i.here" where you would want to match only plain "i")
88 ;;
89 ;;      Things are not that simple always, in fact, the first
90 ;;      implementation of this package had to do with the latex math
91 ;;      equation replace, so that program would automatically skip over
92 ;;      normal text and perform replace within the blocks only.
93 ;;      I decided to pull out the v1.0 and make it a complete package,
94 ;;      so here it is folks.
95 ;;
96 ;;  Overview of features
97 ;;
98 ;;      o   Companion to emacs's query-replace. Simple interface.
99 ;;      o   Text beeing replaced is highlighted AND terminals that cannot see
100 ;;          the highlight will see "=>" string marking the line beeing
101 ;;          processed
102 ;;      o   Preserve case while replacing "FoO" --> "BaR" (symmetry)
103 ;;      o   Toggle case sensitivity and symmetry during the replace.
104 ;;      o   Word match mode on/off during replace: 'matchTHIS or THIS '
105 ;;      o   "Narrow to function", go to "beginning of file" when you start
106 ;;          replacing. You're put back to position where you were when
107 ;;          you quit.
108 ;;      o   Can replace over many files. (Reads compile buffer output)
109 ;;          Checks Out files from RCS when needed (if they are not locked)
110 ;;      o   Variable `tinyreplace-exlude-line-regexp' can be use
111 ;;          to ignore lines
112 ;;
113 ;;  How to use
114 ;;
115 ;;      If you know lisp, you can go and take straight advantage of the
116 ;;      engine function:
117 ;;
118 ;;          tinyreplace-replace-region-1
119 ;;
120 ;;      Normally functions work within area defined by you, but
121 ;;      there is 'applications' section which offers several ready to run
122 ;;      functions for various needs:
123 ;;
124 ;;      o   `tinyreplace-replace-region', like query replace but in selected area
125 ;;      o   `tinyreplace-replace-forward', start from current point.
126 ;;      o   `tinyreplace-latex-blk-replace', replace text surrounded
127 ;;          by latex BLOCKS
128 ;;      o   `tinyreplace-latex-math-replace', replace text within latex
129 ;;          math equations only.
130 ;;
131 ;;  What commands do I have while replacing interactively?
132 ;;
133 ;;      There are some handy commands that normal emacs replace lacks:
134 ;;
135 ;;      o   toggle case sensitivity during replace
136 ;;      o   toggle symmetry during replace (character by character conversion)
137 ;;      o   Go to start of buffer _now_ (return back when you exit replace)
138 ;;      o   search backward
139 ;;      o   Narrow to current function, so that you can replace local variables
140 ;;      o   Flash function name where you're (only for some programming
141 ;;          languages.)
142 ;;
143 ;;      See function `tinyreplace-replace-region-1' for command
144 ;;      explanation. To abort the search, you just press Ctrl-g or 'Q'
145 ;;      and you'll be returned to the starting point of search.
146 ;;
147 ;;  Command line prompt explanation
148 ;;
149 ;;      The command line prompt will look like this
150 ;;
151 ;;          Replace 'xx' with 'yy' (a,bvuBFNU? [+CSX] !ynqQ)
152 ;;
153 ;;      Where the flag settings active are displayed between brackets.
154 ;;      The '+' means that you have used (N)arrow command, C indicates
155 ;;      case sensitivity, S tells that symmetry is activated and X
156 ;;      means that line exclude is in effect. For full explanation of
157 ;;      the commands, please press help key (?) Which will print the
158 ;;      command summary.
159 ;;
160 ;;  Special commands in command line
161 ;;
162 ;;      When you edit the seach string or destination string, there are
163 ;;      some keys that you can use:
164 ;;
165 ;;          C-l     Yank the text under point to current prompt
166 ;;          C-o     Yank previous SRING1
167 ;;
168 ;;      The Yank command is `C-l' not `C-y', because if you edit and
169 ;;      kill inside the prompt line, you can use regular `C-y' to yank
170 ;;      text back. The `C-l' command reads a space separated text from
171 ;;      the buffer and pastes it into the prompt for editing.
172 ;;
173 ;;      The `C-o' command yanks the SEARCH string to the prompt. It
174 ;;      comes handy if you used `C-l' to yank the initial search
175 ;;      string, edited yanked text and wanted to share it in the next
176 ;;      prompt. This way you don't have to do the editing again, but
177 ;;      only modify the previous string. To pick right word (Yank
178 ;;      `C-l') from the buffer, when you don't have have mouse, you
179 ;;      can use following keys. Text to the left shows you briefly
180 ;;      where the point currenly is.
181 ;;
182 ;;          <           Moves buffer's point backward
183 ;;          >           Moves buffer's point forward
184 ;;
185 ;;      This feature propably is at its best in a compile buffer where you
186 ;;      have grep results and you draw region around the the files where
187 ;;      you want the replace to happen. Move a little with [<>] and you
188 ;;      will be soon in a line that has the grep word, then yank it to the
189 ;;      replace prompt.
190 ;;
191 ;;  Note about the arrow pointer
192 ;;
193 ;;      Terminals that do not have highlight capability to see which
194 ;;      portion of text will be replaced will appreciate the arrow at
195 ;;      the beginning of line to show where the text is located.
196 ;;
197 ;;      The option "a" that refreshes the arrow marker is *forced* to
198 ;;      ask a minibuffer question in order to change the state of
199 ;;      arrow (hide or show). There was no other way to do this and I
200 ;;      think it's a bug in 19.28 emacs, because the state is not
201 ;;      immediately shown in buffer.
202 ;;
203 ;;  Note about commands
204 ;;
205 ;;      The commands are hard wired in this module and you cannot add
206 ;;      new ones as you can in replace.el which is minor-mode based.
207 ;;      This package is meant to be companion to replace.el, and for
208 ;;      that reason the interface has been designed to be as simple as
209 ;;      possible without any additional modes.
210 ;;
211 ;;      There is no plan to convert this module to minor mode.
212
213 ;;}}}
214
215 ;;; Change Log:
216
217 ;;; Code:
218
219 ;;{{{ require
220
221 ;;; ......................................................... &require ...
222
223 (require 'tinylibm)
224
225 (eval-and-compile
226   (autoload 'vc-registered "vc"))
227
228 (eval-when-compile (ti::package-use-dynamic-compilation))
229
230 (ti::package-defgroup-tiny TinyReplace tinyreplace-: tools
231   "Overview of features
232
233         o   Companion to emacs's query-replace. Simple interface.
234         o   Text beeing replaced is highlighted AND terminals that cannot see
235             the highlight will see '=>' string marking the line beeing
236             processed
237         o   Preserve case while replacing FoO --> BaR (symmetry)
238         o   Toggle case sensitivity and symmetry during the replace.
239         o   Word match mode on/off during replace: matchTHIS or THIS
240         o   'Narrow to function', go to 'beg of file' when you start
241             replacing. You're put back to position where you were when
242             you quit.
243         o   Can replace over many files. (Reads compile buffer output)
244             ChecksOut files from RCS when needed (if they are not locked)
245         o   You can define `tinyreplace-exlude-line-regexp' that skips any line
246             matching looking-at regexp at the beginning of line.")
247
248 ;;}}}
249
250 ;;{{{ setup: hooks
251
252 (defcustom tinyreplace-load-hook nil
253   "*Hook run when file has been loaded."
254   :type 'hook
255   :group 'TinyReplace)
256
257 (defcustom tinyreplace-:args-keymap-hook  nil
258   "*Hook which can define additional key bindings to `tinyreplace-:args-keymap'."
259   :type 'hook
260   :group 'TinyReplace)
261
262 (defcustom tinyreplace-:pre-replace-hook  nil
263   "*Hook to run just before replacing start in a buffer."
264   :type 'hook
265   :group 'TinyReplace)
266
267 ;;}}}
268 ;;{{{ setup: public, User configurable
269
270 (defcustom tinyreplace-:goto-region-beginning t
271   "If non-nil go to beginning of region before replace starts."
272   :type 'boolean
273   :group 'TinyReplace)
274
275 (defcustom tinyreplace-:exclude-line  nil
276   "*When search stops to found position, this variable is consulted.
277
278 It can be:
279
280   nil       do nothing special. Proceed replace.
281   regexp    line which matches `looking-at' REGEXP at the beginning
282             of line is skipped.
283   function  if it returns t, then the line is skipped and search continues.
284             Function takes no arguments and it can move point, since
285             it is run under `save-excursion'. Point is at replace point when
286             the function is called.
287
288 Example:
289
290   (setq tinyreplace-:exclude-line 'my-tinyreplace-exclude)
291   (defun my-tinyreplace-exclude ()
292     ;;  Exclude comment lines
293     (cond
294      ((eq major-mode 'c++-mode)
295       (beginning-of-line)
296       (looking-at \"^[ \t]*//\"))))"
297   :type  '(string :tag "Regexp")
298   :group 'TinyReplace)
299
300 (defcustom tinyreplace-:arrow "=>"
301   "*Line marker where the replace takes effect.
302 Especially useful, when term cannot display colours to show the
303 replacement place."
304   :type  '(string :tag "Arrow string")
305   :group 'TinyReplace)
306
307 (defcustom tinyreplace-:arrow-initial-state 'show
308   "*When replacing interactively, this is the default arrow state.
309 If your terminal supports highlighting you may want to set this to 'hide.
310
311 The only valid values are 'show and 'hide."
312   :type '(choice
313           (const show)
314           (const hide))
315   :group 'TinyReplace)
316
317 (defcustom tinyreplace-:face 'highlight
318   "*The match area overlay face."
319   :type 'face
320   :group 'TinyReplace)
321
322 (defcustom tinyreplace-:word-boundary "[^a-zA-Z0-9_]"
323   "*This is complement set of characters forming a word.
324 For example if you want to replace 'i' with 'j' , you don't want to match
325
326      \"Ignore ThIs match, but replace i with j\"
327                                      ^^^
328 The complement makes sure the word is full word."
329   :type  '(string :tag "Word complement charset")
330   :group 'TinyReplace)
331
332 (defcustom tinyreplace-:symmetry nil
333   "*Non-nil perform replacement using same symmetry.
334 When replacing text, it may be desirable to have the same symmetry,
335 the case of the characters, to be preserved while replace takes effect.
336 Suppose you have text
337
338         FOO Foo fooUx foo
339
340 And you want to preserve the symmetry when doing \"foo\" --> \"bar\".
341 This is what you get:
342
343         BAR Bar barUx bar
344
345 If the symmetry is nil, then the normal replace would have given:
346
347         bar bar barUx bar"
348   :type  'boolean
349   :group 'TinyReplace)
350
351 (defcustom tinyreplace-:symmetry-rest nil
352   "*If non-nil then rest of the characters follow previous symmetry."
353   :type  'boolean
354   :group 'TinyReplace)
355
356 ;; Not in defcustom; advanced feature and expert knows what to to with this.
357 ;;
358 (defvar tinyreplace-:read-args-function  'tinyreplace-read-args
359   "*Function to ask two arguments ARG1 and ARG2 for replace.
360 Input:
361
362   String
363
364 Output:
365
366   '(\"arg1\" \"arg2\")
367
368 Function must terminate with error if it cannot return list of
369 two strings.")
370
371 (defcustom tinyreplace-:user-function 'ti::buffer-outline-widen
372   "*User function fun from command prompt key 'U'."
373   :type  'function
374   :group 'TinyReplace)
375
376 ;;}}}
377 ;;{{{ setup: private
378
379 (defvar tinyreplace-:replace-region-overlay nil
380   "Overlay used to show the replaced region.")
381
382 (defvar tinyreplace-:transient-mark-mode nil
383   "Transient mark mode state (Emacs).")
384
385 (defvar tinyreplace-:arrow-state nil
386   "Arrow display state.")
387
388 (defvar tinyreplace-:narrow-state nil
389   "Narrowed to function state.")
390
391 (defvar tinyreplace-:args-history  nil
392   "History of replace strings.")
393
394 (defvar tinyreplace-:args-keymap nil
395   "Keymap for reading arguments.")
396
397 (defvar tinyreplace-:replace-buffer  nil
398   "Buffer where to replace.")
399
400 (defvar tinyreplace-:tmp-buffer  "*tinyreplace-temp*"
401   "Temp buffer.")
402
403 (defvar tinyreplace-:err-buffer  "*tinyreplace-error*"
404   "Error message buffer.")
405
406 (defvar tinyreplace-:read-point  nil
407   "This variable is used in interactive word reading.
408 It tells where the current point is.")
409
410 (defvar tinyreplace-:o-exclude  nil
411   "Private. Temporary variable to keep state when calling another function.")
412
413 (defvar tinyreplace-:string1  nil
414   "Private. The asked string1. Set in `tinyreplace-read-args'.")
415
416 (defvar tinyreplace-:word-match-mode nil
417   "Not a user variable. Hold value t if user switch to exact word matching.
418 Property 're will have the original regexp.")
419
420 ;;; ............................................................ &menu ...
421
422 ;;  You propably want to copy this to your ~/.emacs and define your
423 ;;  own key combinations. See tinylibmenu.el how to use the menu variable
424
425 (defvar tinyreplace-:menu
426   '("\
427 replace: (f)wd (r)eg (w)ord (c)ompile buffer files (f)iles (lL)atex (?)help "
428     (
429      ;;  If `tinyreplace-menu' is bound to M-%, then the "5" key makes
430      ;;  sense, because "%" is shift-5.
431      (?f  . ( (call-interactively 'tinyreplace-replace-forward)))
432      (?5  . ( (call-interactively 'tinyreplace-replace-forward)))
433      (?%  . ( (call-interactively 'tinyreplace-replace-forward)))
434      (?w  . ( (call-interactively 'tinyreplace-word-replace)))
435      (?r  . ( (call-interactively 'tinyreplace-replace-region)))
436      (?c  . ( (call-interactively
437                'tinyreplace-replace-over-files-compile-buffer)))
438      (?F  . ( (call-interactively 'tinyreplace-replace-over-files)))
439      (?l  . ( (call-interactively 'tinyreplace-latex-blk-replace)))
440      (?L  . ( (call-interactively 'tinyreplace-latex-math-replace)))))
441   "Help menu for the commands. Press 'q' to return to menu.
442
443 Standard replace commands:
444
445     f  calls function `tinyreplace-replace-forward'
446     %  calls function `tinyreplace-replace-forward'  (like M-%)
447     5  calls function `tinyreplace-replace-forward'  (Like M-%)
448
449     w  calls function `tinyreplace-word-replace'
450     r  calls function `tinyreplace-replace-region'
451
452 The following keys can be used in compile-like buffers, where each line
453 contains standard grep-like output. If you mark a region, the selected
454 files are searched and matches replaced.
455
456     FILE:LINE-NUMBER:output
457     FILE:LINE-NUMBER:output
458     FILE:LINE-NUMBER:output
459
460     c  calls function `tinyreplace-replace-over-files-compile-buffer'
461     F  calls function `tinyreplace-replace-over-files'
462
463 Special commands:
464
465     l  calls function `tinyreplace-latex-blk-replace'
466     L  calls function `tinyreplace-latex-math-replace'")
467
468 ;;}}}
469 ;;{{{ version and install
470
471 ;;; ----------------------------------------------------------------------
472 ;;;
473 (eval-and-compile
474   (ti::macrof-version-bug-report
475    "tinyreplace.el"
476    "tinyreplace"
477    tinyreplace-:version-id
478    "$Id: tinyreplace.el,v 2.59 2007/05/07 10:50:13 jaalto Exp $"
479    '(tinyreplace-:version-id
480      tinyreplace-:arrow-state
481      tinyreplace-:narrow-state
482      tinyreplace-:arrow-initial-state
483      tinyreplace-:face
484      tinyreplace-:word-boundary
485      tinyreplace-:symmetry
486      tinyreplace-:symmetry-rest)))
487
488 ;;; ----------------------------------------------------------------------
489 ;;;###autoload
490 (defun tinyreplace-install-default-keybings (&optional uninstall)
491   "Install or UNINSTALL M-& default keybing to run `tinyreplace-menu'."
492   (interactive)
493   (let* ((key "\M-&")
494          (def (lookup-key global-map key)))
495     (when (featurep 'compile)
496       (tinyreplace-define-keys-compile-map))
497     (cond
498      (uninstall
499       (when (setq def (ti::keymap-bind-control 'global-map 'get 'tinymy key))
500         (global-set-key key def)))
501      (t
502       (ti::keymap-bind-control 'global-map 'set 'tinymy key)
503       (global-set-key key 'tinyreplace-menu)))))
504
505 ;;; ----------------------------------------------------------------------
506 ;;;###autoload
507 (defun tinyreplace-install (&optional uninstall)
508   "Call `tinyreplace-install-default-keybings' with optional UNINSTALL."
509   (interactive "p")
510   (tinyreplace-install-default-keybings uninstall))
511
512 ;;}}}
513 ;;{{{ misc
514
515 ;;; ----------------------------------------------------------------------
516 ;;;###autoload
517 (defun tinyreplace-menu ()
518   "Run `tinyreplace-:menu'."
519   (interactive)
520   (if buffer-read-only
521       (message "My: Cannot start replace, buffer is read-only.")
522     (ti::menu-menu 'tinyreplace-:menu)))
523
524 ;;; ----------------------------------------------------------------------
525 ;;;
526 (put 'tinyreplace-with-keymap 'lisp-indent-function 1)
527 (defmacro tinyreplace-with-keymap (sym &rest body)
528   "If keymap SYM exists, run BODY. Variable `map' is set to keymap."
529   `(let (map)
530      (when (and (boundp ,sym)
531                 (setq map (symbol-value ,sym))
532                 (keymapp map))
533        ,@body)))
534
535 ;;; ----------------------------------------------------------------------
536 ;;;
537 ;;;###autoload
538 (defun tinyreplace-define-keys-compile-map  ()
539   "Define key bindings."
540   (interactive)
541   (tinyreplace-with-keymap 'compilation-mode-map
542                            (define-key map "%" 'tinyreplace-replace-over-files-compile-buffer))
543   (tinyreplace-with-keymap 'compilation-minor-mode-map
544                            (define-key map "%" 'tinyreplace-replace-over-files-compile-buffer))
545   (tinyreplace-with-keymap 'grep-mode-map
546                            (define-key map "%" 'tinyreplace-replace-over-files-compile-buffer)))
547
548 ;;; ----------------------------------------------------------------------
549 ;;;
550 (defmacro tinyreplace-interactive-region-args  (string)
551   "Construct interactive tag for functions that need region.
552 STRING is argument to `tinyreplace-:read-args-function'.
553
554 Return:
555  '(BEG END ARG1-STRING ARG2-STRING)"
556   (`
557    (if buffer-read-only
558        (barf-if-buffer-read-only)
559      (if (region-active-p)
560          (ti::list-merge-elements
561           (region-beginning)
562           (region-end)
563           (funcall tinyreplace-:read-args-function (, string)))
564        (error "TinyReplace: Region is not active. Please select one.")))))
565
566 ;;; ----------------------------------------------------------------------
567 ;;;
568 (defun tinyreplace-make-word-regexp  (string)
569   "See `tinyreplace-:word-boundary'. Make regexp from STRING."
570   (concat tinyreplace-:word-boundary
571           "\\(" (regexp-quote string) "\\)"
572           tinyreplace-:word-boundary))
573
574 ;;; ----------------------------------------------------------------------
575 ;;;
576 (defun tinyreplace-read-args (&optional prompt)
577   "Read two arguments with PROMPT. Return '(ARG1 ARG2)."
578   (let* ((opoint   (point))
579          arg1
580          arg2)
581     ;; Disable electric file minor mode, which defines specilal
582     ;; characters.
583     (setq tinyreplace-:replace-buffer   (current-buffer)
584           tinyreplace-:read-point       (point)
585           tinyreplace-:string1          nil)
586     (tinyreplace-args-keymap-create)
587     (setq arg1
588           (ti::remove-properties
589            (read-from-minibuffer
590             (concat (or prompt "") " Search: ") nil
591             tinyreplace-:args-keymap nil tinyreplace-:args-history)))
592     (setq tinyreplace-:string1 arg1)    ;Now available
593     (setq arg2
594           (ti::remove-properties
595            (read-from-minibuffer
596             "Replace with: " nil
597             tinyreplace-:args-keymap nil tinyreplace-:args-history)))
598     (goto-char opoint)                  ;restore
599     (list arg1 arg2)))
600
601 ;;; ----------------------------------------------------------------------
602 ;;; - This is for user friendliness
603 ;;;
604 ;;;###autoload
605 (defun tinyreplace-symmetry-toggle (&optional arg verb)
606   "Toggle variable` tinyreplace-:symmetry' with ARG. VERB."
607   (interactive "P")
608   (ti::verb)
609   (ti::bool-toggle tinyreplace-:symmetry arg)
610   (put 'tinyreplace-replace-1 'tinyreplace-:symmetry tinyreplace-:symmetry)
611   (if verb
612       (message "TinyReplace: Symmetry is now %s"
613                (if tinyreplace-:symmetry
614                    "on"
615                  "off"))))
616
617 ;;; ----------------------------------------------------------------------
618 ;;;
619 (defun tinyreplace-transient-mark-mode  (mode)
620   "Record function  `transient-mark-mode' status.
621 This is done only if function exists. MODE can be 'write or 'read."
622   (when (and (ti::emacs-p)                  ;#todo: zmacs-region-stays
623              (boundp 'transient-mark-mode)) ;XEmacs doesn't have this
624     (cond
625      ((eq mode 'write)
626       (setq tinyreplace-:transient-mark-mode
627             (let* ((var 'transient-mark-mode)) ;XEmacs 19.14 byteComp silencer
628               (symbol-value var))))
629      ((eq mode 'read)
630       tinyreplace-:transient-mark-mode))))
631
632 ;;; ----------------------------------------------------------------------
633 ;;;
634 (defun tinyreplace-arrow-control (buffer mode &optional str)
635   "Handles showing the arrow.
636
637 Input:
638
639   BUFFER        buffer pointer
640   MODE          symbol 'show, 'hide, 'toggle, 'maybe or 'move.
641   STR           Used for restoring the original contents when mode is 'hide
642
643 Sets global:
644
645   `tinyreplace-:arrow-state'
646
647 Returns:
648
649   mode     current state"
650
651   (cond
652    ((eq mode 'toggle)
653     (if (or (null overlay-arrow-position) ;doesn't exist
654             (not (equal (marker-buffer overlay-arrow-position)
655                         (current-buffer))))
656         (setq mode 'show)
657       (setq mode 'hide)))
658
659    ((eq mode 'maybe)
660     (cond
661      ((null tinyreplace-:arrow-state)
662       (setq mode 'show))
663      (t
664       ;;  follow the mode which is active
665       (setq mode tinyreplace-:arrow-state)))))
666   (cond
667    ((or (eq mode 'show)
668         (eq mode 'move))
669     (ti::buffer-arrow-control buffer mode tinyreplace-:arrow  (point))
670     (setq tinyreplace-:arrow-state 'show))
671    ((eq mode 'hide)
672     (ti::buffer-arrow-control buffer 'hide str)
673     (setq tinyreplace-:arrow-state 'hide)))
674   mode)
675
676 ;;; ----------------------------------------------------------------------
677 ;;;
678 (defun tinyreplace-replace-ask (buffer from-str to-str )
679   "Perform asking while in interactive replace mode.
680
681 Input:
682
683  BUFFER FROM-STR TO-STR
684
685 Note:
686
687   `tinyreplace-:o-exclude'    must be set in the calling function"
688   (let* ((o-exclude  tinyreplace-:o-exclude)
689          (loop       t)
690          msg
691          ans)
692     (while loop
693       (setq from-str (ti::string-format-percent from-str)
694             to-str   (ti::string-format-percent to-str))
695       (setq
696        msg
697        (format "Replace '%s' with '%s' (a,bvuBFNU [%s%s%s%s] ?!ynqQ) "
698                ;; Make prompt fit nicely
699                (if (> (length from-str) 18)
700                    (concat (ti::string-left from-str 16) "..")
701                  from-str)
702                (if (> (length from-str) 18)
703                    (concat (ti::string-left to-str 16) "..")
704                  to-str)
705                (if tinyreplace-:narrow-state "N " "")
706                (if tinyreplace-:symmetry  "S" "")
707                (if case-fold-search "" "C")
708                (if tinyreplace-:word-match-mode "W" "")
709                (if tinyreplace-:exclude-line "X" "")))
710       (setq ans (ti::read-char-safe-until msg))
711       ;;  There is purposively a dummy COND case.
712       (cond
713        ((char= ?a ans)
714         (tinyreplace-arrow-control buffer 'toggle)
715         (read-from-minibuffer
716          "Arrow refreshed. Press RET to update view."))
717        ((char= ?s ans)
718         (tinyreplace-symmetry-toggle))
719        ((char= ?w ans)
720         (ti::bool-toggle tinyreplace-:word-match-mode)
721         (put 'tinyreplace-replace-1
722              'tinyreplace-:word-match-mode
723              tinyreplace-:word-match-mode))
724        ((char= ?c ans)
725         (ti::bool-toggle case-fold-search)
726         (put 'tinyreplace-replace-1 'case-fold-search case-fold-search))
727        ((char= ?\  ans)
728         (setq ans ?y))
729        ((char= ?x ans)                  ;exclude toggle
730         (if tinyreplace-:exclude-line
731             (setq tinyreplace-:exclude-line nil)
732           ;; Dynamically bound in call func
733           (setq tinyreplace-:exclude-line o-exclude)))
734        ((char= ?F ans)
735         (tinyreplace-show-function-name (point)))
736        ((char= ?N ans)
737         (setq  tinyreplace-:narrow-state t)
738         (setq ans ?N))
739        ((char= ?U ans)
740         (funcall tinyreplace-:user-function)
741         (setq ans ?U))
742        ((ti::char-in-list-case ans '(?\177 ?\b ?n))
743         (setq ans ?n)))
744       (if (ti::char-in-list-case ans '(?! ?? ?y ?n ?q ?Q ?b ?v ?u  ?B  ?N))
745           (setq loop nil))) ;; while loop
746     ans))
747
748 ;;; ----------------------------------------------------------------------
749 ;;; Press Ctrl-g to abort replace.
750 ;;;
751 (defun tinyreplace-show-function-name (point)
752   "Flashes function name briefly from POINT."
753   (let* ((name (ti::buffer-defun-function-name point))
754          (txt  (if name
755                    name
756                  "<not found>")))
757     (message txt)
758     (sit-for 1)))
759
760 ;;; ----------------------------------------------------------------------
761 ;;; Press Ctrl-g to abort replace.
762 ;;;
763 (defun tinyreplace-move-overlay (beg end)
764   "Move overlay to BEG END."
765   (ti::compat-overlay-move 'tinyreplace-:replace-region-overlay beg end  nil)
766   (ti::compat-overlay-put
767    'tinyreplace-:replace-region-overlay
768    'face tinyreplace-:face))
769
770 ;;; ----------------------------------------------------------------------
771 ;;;
772 (defun tinyreplace-replace-1 (beg end str)
773   "Replace region BEG END with STR, point with after replace."
774   (when (and (integerp beg) (integerp end))
775     (delete-region beg end) (goto-char beg)
776     (insert str)))
777
778 ;;}}}
779 ;;{{{ key
780
781 ;;; ----------------------------------------------------------------------
782 ;;;
783 (defun tinyreplace-key-clear-input  ()
784   "Clear the line."
785   (interactive)
786   (delete-region (line-beginning-position) (line-end-position)))
787
788 ;;; ----------------------------------------------------------------------
789 ;;;
790 (defun tinyreplace-key-forward-word (&optional count)
791   "Forward word. COUNT is argument to `forward-word', and defaults to 1.
792
793 This function is meant to position you to to right word which you can
794 then insert into the replace prompt with \\[tinyreplace-key-yank-word]. When"
795   (interactive)
796   (let* ((obuffer (current-buffer)))
797     (select-window (get-buffer-window tinyreplace-:replace-buffer))
798     (forward-word (or count 1))
799     (setq tinyreplace-:read-point (point))
800     (message "TinyReplace: reading point %d:%s..."
801              (point)
802              (ti::string-left
803               (or (buffer-substring-no-properties (point) (line-end-position))
804                   "")
805               40))
806     (select-window (get-buffer-window  obuffer))))
807
808 ;;; ----------------------------------------------------------------------
809 ;;;
810 (defun tinyreplace-key-backward-word ()
811   "See `tinyreplace-key-forward-word'."
812   (interactive)
813   (tinyreplace-key-forward-word -1))
814
815 ;;; ----------------------------------------------------------------------
816 ;;;
817 (defun tinyreplace-key-yank-string1  ()
818   "Yank previous string (search string)."
819   (interactive)
820   (if tinyreplace-:string1
821       (insert tinyreplace-:string1)
822     (message "TinyReplace: Sorry, there is no STRING1 to yank yet ")))
823
824 ;;; ----------------------------------------------------------------------
825 ;;;
826 (defun tinyreplace-key-yank-word ()
827   "Yank word from buffer. `tinyreplace-:replace-buffer' must be set."
828   (interactive)
829   (let* (word)
830     (with-current-buffer tinyreplace-:replace-buffer
831       (goto-char  tinyreplace-:read-point)
832       ;;  This is just because of the space-word-command
833       ;;
834       ;;     Word-here
835       ;;     *             << advance this cursor one char
836       (if (ti::char-in-list-case (preceding-char) '(?\ ?\t ?\n ))
837           (forward-char 1))
838       (setq word (ti::remove-properties (ti::buffer-read-space-word))))
839     (when word
840       (setq word (ti::remove-properties word))
841       (insert word))))
842
843 ;;; ----------------------------------------------------------------------
844 ;;;
845 (defun tinyreplace-args-keymap-create  ()
846   "Create keymap."
847   (setq tinyreplace-:args-keymap (copy-keymap minibuffer-local-map))
848   (define-key tinyreplace-:args-keymap "\C-l"  'tinyreplace-key-yank-word)
849   (define-key tinyreplace-:args-keymap "\C-o"  'tinyreplace-key-yank-string1)
850   (define-key tinyreplace-:args-keymap "\C-p"  'tinyreplace-key-clear-input)
851   (define-key tinyreplace-:args-keymap "\C-b"  'tinyreplace-key-backward-word)
852   (define-key tinyreplace-:args-keymap "\C-f"  'tinyreplace-key-forward-word)
853   (run-hooks 'tinyreplace-:args-keymap-hook))
854
855 ;;}}}
856 ;;{{{ main
857
858 ;;; ----------------------------------------------------------------------
859 ;;; - The "v" has been chosen because it's close to "b". I first
860 ;;;   used "B" for backward REPLACEMENT, but it was too much
861 ;;;   trouble to reach extra shift key.
862 ;;;
863 ;;;   Eg. If I want to replace backward (to undo some changes),
864 ;;;   you just press "v" and "u". Much more awkward would have
865 ;;;   been "B" and "u". The shift-modifier is not good in this case.
866 ;;;
867 ;;; - The "undo" feature here is hand coded, because I couldn't find
868 ;;;   any emacs command that would undo last change...one at a time.
869 ;;;
870 (defun tinyreplace-replace-region-1 (beg end re str &optional level ask func)
871   "Perform replace.
872
873 Input:
874
875   BEG END   region
876   RE        regexp to search
877   STR       replace string
878   LEVEL     subexpression level to match in RE. Default is 0, whole match.
879   ASK       interactive
880   FUNC      Call function
881
882 Commands while ASK is non-nil:     (simple undo backward is = 'v u' )
883
884  y or SPACE             replace
885  n or BACKSPACE         skip
886  !                      replace rest
887  q                      quit
888  Q                      quit at current point.
889
890 Search modes
891
892  s                      Mode: toggle symmetry.
893                         When mode is on, he written case is preserved.
894  c                      Mode: toggle case sensitivity in search.
895  w                      Mode: toggle word only search.
896  a                      Mode: toggle arrow display.
897  x                      Mode: toggle `tinyreplace-:exclude-line' variable on/off.
898
899 Search control forward/backward
900
901  b                      search backward for MATCH
902  v                      search backward for REPLACEMENT
903  u                      undo -- This is very limited undo and works
904                         only when replacing ordinary strings, not
905                         regexps. The symmetry must not be used.
906
907 Moving point of search
908
909  B                      go to beginning of search.
910                         Beginning or end point depending of search direction
911
912  F                      flash function name
913
914  N                      narrow to function.
915                         You can't cancel this if you use it. The
916                         'N' is indicated in the command line,
917
918  U                      Run user function `tinyreplace-:user-function'
919
920 References:
921
922   `tinyreplace-:exclude-line'
923
924 Return:
925
926   number  last replace area position. This is not same as the END
927           parameter, because replacing text modifies the buffer's points."
928   (let* (case-fold-search
929          (arrow-orig    overlay-arrow-string)
930          (arrow-init    tinyreplace-:arrow-initial-state)
931          (symm-rest     tinyreplace-:symmetry-rest)
932          (tinyreplace-:exclude-line tinyreplace-:exclude-line) ;Make local copy
933          (o-exclude     tinyreplace-:exclude-line) ;original value
934          (level         (or level 0))              ;default value
935          (buffer        (current-buffer))
936          (func          (or func 're-search-forward))
937          (MARK          (point-marker)) ;record user position
938          PREV-ME
939          MARK-MAXP                ;marker for the last point of search
940          minp maxp                      ;logical point min, point-max
941          c-exclude                      ;Current exclude value
942          str-to
943          do-ask
944          quit-point
945          read-string
946 ;;;      undo-string
947          fmin fmax
948          pos
949          mb me                          ;match area, beg end
950          replace
951          bypass)               ;This is flag to skip searching in loop
952     ;; Se same defaults as previous time
953     (when ask
954       (setq tinyreplace-:word-match-mode
955             (get 'tinyreplace-replace-1 'tinyreplace-:word-match-mode))
956       (setq tinyreplace-:symmetry
957             (get 'tinyreplace-replace-1 'tinyreplace-:symmetry))
958       (setq case-fold-search
959             (get 'tinyreplace-replace-1 'case-fold-search)))
960     (put 'tinyreplace-:word-match-mode 're re)
961     (put 'tinyreplace-:word-match-mode 'word (tinyreplace-make-word-regexp re))
962     (setq tinyreplace-:o-exclude o-exclude)
963     (tinyreplace-transient-mark-mode 'write)
964     (transient-mark-mode 0)             ;turn this off for now..
965     (setq tinyreplace-:arrow-state arrow-init) ;<< set global, see 'maybe
966     (tinyreplace-arrow-control buffer 'maybe)
967     (setq tinyreplace-:narrow-state nil) ;reset
968     (cond
969      ((eq func 're-search-forward)
970       (setq minp (min beg end)          ;logical min and max
971             maxp (max beg end)))
972      (t
973       (setq minp (max beg end)
974             maxp (min beg end))))
975     (save-excursion
976       (goto-char maxp)
977       (setq MARK-MAXP (point-marker)))
978     (goto-char minp)
979     (save-excursion
980       (unwind-protect
981           (catch 'cancel
982             (run-hooks 'tinyreplace-:pre-replace-hook)
983             ;;  Peek a little before we start
984             ;;
985             (cond
986              ((and (not (eq (point)
987                             (point-min))) ;Already ti::pmin ?
988                    (save-excursion
989                      (if (null (funcall func re nil t))
990                          (y-or-n-p "\
991 There is no matches forward. Go to beginning of buffer? "))))
992               (ti::pmin))
993              ((and (region-active-p)
994                    (or tinyreplace-:goto-region-beginning
995                        (y-or-n-p "\
996 Region is active. Go to beginning of region? "))
997                    (if (eq func 're-search-forward)
998                        (goto-char (region-beginning))
999                      (goto-char (region-end))))))
1000             ;;   SEARCH
1001             (while (or bypass
1002                        (and (funcall func re nil t)
1003                             (<= (point)
1004                                 (marker-position MARK-MAXP))))
1005               (or bypass ;; Not moved, do not calculate positions
1006                   (setq mb (match-beginning level)
1007                         me (match-end level)))
1008               (setq bypass nil)
1009               ;; .. .. .. .. .. .. .. .. .. .. .. .. .. exclude line ..
1010               (setq c-exclude tinyreplace-:exclude-line)
1011               (save-excursion
1012                 (cond
1013                  ((null c-exclude)   ;; No exclude patterns
1014                   t)                 ;; flag "ok"
1015                  ((and (symbolp c-exclude)
1016                        (fboundp c-exclude))
1017                   (if (funcall c-exclude)
1018                       (setq mb nil)))
1019                  ((stringp c-exclude)
1020                   (beginning-of-line)
1021                   (if (looking-at c-exclude)
1022                       ;; Force forgetting this point
1023                       (setq mb nil)))))
1024               ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HANDLE it ^^^
1025               (if (null mb)
1026                   nil                   ;submatch error
1027                 (tinyreplace-move-overlay mb me)
1028                 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ask user  ^^^
1029 ;;;               (ti::d! ask)
1030                 (setq read-string
1031                       (buffer-substring-no-properties mb me))
1032                 (setq str-to str)
1033                 (if (null ask)
1034                     (setq replace t)    ;automatic
1035
1036                   (tinyreplace-arrow-control buffer 'maybe)
1037 ;;;                 (ti::d!  tinyreplace-:arrow-state )
1038
1039                   (setq do-ask t)
1040                   (setq read-string
1041                         (buffer-substring-no-properties mb me))
1042                   (setq str-to str)
1043                   (while do-ask
1044                     (setq replace (tinyreplace-replace-ask
1045                                    buffer
1046                                    read-string
1047                                    str))
1048                     (setq do-ask nil)
1049                     (cond
1050                      (tinyreplace-:word-match-mode
1051                       (setq re (get 'tinyreplace-:word-match-mode 'word))
1052                       (setq level 1))
1053                      (t
1054                       (setq re (get 'tinyreplace-:word-match-mode 're))
1055                       (setq level 0)))
1056                     ;; ... ... ... ... ... ... ... ... ... ... ... ... ...
1057                     (cond
1058                      ;; .......................... beginning of buffer ...
1059                      ((char= ?B replace)
1060                       (if (eq func 're-search-forward)
1061                           (progn
1062                             (ti::pmin)
1063                             (setq minp  (point)))
1064                         (ti::pmax)
1065                         (setq maxp (point)))
1066                       (redraw-display)
1067                       (setq replace nil))
1068                      ;; ... ... ... ... ... ... ... ... ... ... . help  ..
1069                      ((char= ?? replace)
1070                       (ti::menu-help 'tinyreplace-replace-region-1)
1071                       (setq bypass t)
1072                       (setq replace nil))
1073                      ;; ... ... ... ... ... ... ... ... ... ... narrow  ..
1074                      ((char= ?N replace)
1075                       (cond
1076                        ((eq func 're-search-forward)
1077                         ;;  The other call isn't executed if first fails
1078                         ;;
1079                         (and (setq fmin (ti::beginning-of-defun-point))
1080                              (setq fmax (ti::beginning-of-defun-point 'end)))
1081                         (cond
1082                          ((and fmin fmax  (not (eq fmin fmax)))
1083                           (setq minp fmin maxp fmax)
1084                           (goto-char maxp)
1085                           (setq MARK-MAXP (point-marker))
1086                           (goto-char minp))
1087                          (t
1088                           (setq tinyreplace-:narrow-state nil)
1089                           (message "TinyReplace: Can't find narrow bounds.")
1090                           (sit-for 1))))
1091                        (t
1092                         (and (setq fmin (ti::beginning-of-defun-point))
1093                              (setq fmax (ti::beginning-of-defun-point 'end)))
1094                         (cond
1095                          ((and fmin fmax  (not (eq fmin fmax)))
1096                           (setq minp fmax maxp fmin)
1097                           (goto-char maxp)
1098                           (setq MARK-MAXP (point-marker))
1099                           (goto-char minp))
1100                          (t
1101                           (setq tinyreplace-:narrow-state nil)
1102                           (message "TinyReplace: Can't find narrow bounds.")
1103                           (sit-for 1)))))
1104                       (setq replace nil))
1105                      ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
1106                      ((char= ?b replace)
1107                       (save-excursion
1108                         (goto-char mb)
1109                         (setq mb nil me nil)
1110                         (when (re-search-backward re minp t)
1111                           (setq mb (match-beginning level)
1112                                 me (match-end level))))
1113                       (if (null mb)
1114                           (progn
1115                             (message "TinyReplace: No more hits.")
1116                             (setq replace nil))
1117                         (goto-char me)
1118 ;;;                     (setq undo-string (buffer-substring mb me))
1119                         (tinyreplace-move-overlay mb me))
1120                       (setq replace nil
1121                             do-ask  t))
1122                      ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
1123                      ((char= ?v replace)
1124                       (save-excursion
1125                         (if PREV-ME
1126                             (goto-char PREV-ME))
1127                         (setq mb nil me nil)
1128 ;;;                     (setq P (point)   R (regexp-quote str)   M minp)
1129                         (when (re-search-backward (regexp-quote str) minp t)
1130                           (setq mb (match-beginning level)
1131                                 me (match-end level))))
1132                       (if (null mb)
1133                           (progn
1134                             (message "TinyReplace: No previous hit.")
1135                             (setq replace nil))
1136                         (goto-char me)
1137 ;;;                     (setq undo-string (buffer-substring mb me))
1138                         (tinyreplace-move-overlay mb me))
1139                       (setq replace nil  do-ask t))
1140                      ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
1141                      ((char= ?u replace)
1142                       (tinyreplace-replace-1
1143                        mb me
1144                        (ti::string-case-replace str read-string
1145                                                 tinyreplace-:symmetry symm-rest))
1146                       (tinyreplace-move-overlay mb me)
1147                       (setq replace nil  do-ask t))
1148                      ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
1149                      ((char= ?! replace)
1150                       (setq   ask nil     replace t))
1151                      ((char= ?n replace)
1152                       (setq replace nil))
1153                      ((char= ?q replace)
1154                       (throw 'cancel t))
1155                      ((char= ?Q replace)
1156                       (setq quit-point mb)
1157                       (throw 'cancel t))
1158                      (t
1159                       (setq replace t)))))
1160                 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ do it ^^^
1161                 (when replace
1162                   (setq PREV-ME me)
1163
1164                   (tinyreplace-replace-1
1165                    mb me
1166                    (ti::string-case-replace
1167                     read-string str-to
1168                     tinyreplace-:symmetry symm-rest)))))) ;; cancel - while
1169         ;; ............................ condition
1170         ;; - Clean up if Ctrl-g pressed
1171         ;; - We're done, dehilit and restore possible transient mode
1172         (if (and tinyreplace-:replace-region-overlay
1173                  (ti::overlay-supported-p))
1174             (ti::funcall
1175              'overlay-put tinyreplace-:replace-region-overlay 'face nil))
1176         (setq pos maxp)                 ;update return value
1177         (tinyreplace-arrow-control buffer 'hide arrow-orig) ;remove it
1178         (if (tinyreplace-transient-mark-mode 'read) ;if it were on previously
1179             (transient-mark-mode 1))))
1180     (if quit-point                      ;only when interactive "Q"
1181         (goto-char quit-point)          ;handy if you made mistake...
1182       (goto-char (marker-position MARK)))
1183     (message "")
1184     (setq MARK nil  MARK-MAXP nil)      ;kill markers
1185     pos))
1186
1187 ;;}}}
1188 ;;{{{ applications
1189
1190 ;;; ----------------------------------------------------------------------
1191 ;;;
1192 (defun tinyreplace-read-compile-buffer-filename ()
1193   "Read filename on line."
1194   (let ((re "^\\(\\(.:\\)?[^\n:]+\\):") ;; allow DOS drive at front d:/file/
1195         dir
1196         file)
1197     (save-excursion
1198       (beginning-of-line)
1199       (when (setq file (ti::remove-properties (ti::buffer-match re 1)))
1200         (save-excursion
1201           (if (re-search-backward "^cd[ \t]+\\([^\t\n]+\\)" nil t)
1202               (setq dir (ti::remove-properties (match-string 1)))))
1203
1204         (when (and dir
1205                    (not (string-match "^\\(.:\\)?/" file)))
1206           (setq file (concat dir file)))
1207         (setq file (ti::file-name-for-correct-system file 'emacs))))
1208     file))
1209
1210 ;;; ----------------------------------------------------------------------
1211 ;;;
1212 ;;;###autoload
1213 (defun tinyreplace-replace-over-files-compile-buffer
1214   (beg end str1 str2 &optional func verb)
1215   "Read all files forward in buffer that is in compile buffer format.
1216 Perform replace over the found files. Checks Out files that are
1217 RCS controlled if necessary.
1218
1219 Line format:
1220
1221   /DIR/DIR/FILE: matched text
1222
1223 Input:
1224
1225   See function `tinyreplace-replace-1'
1226   BEG END STR1 STR2 &OPTIONAL FUNC VERB"
1227
1228   (interactive
1229    (ti::list-merge-elements
1230     (tinyreplace-interactive-region-args "compile")
1231     nil
1232     t))
1233   ;; ................................................. interactive end ...
1234   (let ((o-frame        (selected-frame))
1235         (w-frame        (ti::non-dedicated-frame))
1236
1237         (func           (or func 'tinyreplace-replace-forward))
1238         (err-buffer     (ti::temp-buffer tinyreplace-:err-buffer 'clear))
1239         (read-only      0)
1240         no-confirm
1241         buffer
1242         cache
1243         ro-cache                        ;read only file list
1244         file
1245         ch)
1246     (ti::verb)
1247     (save-excursion
1248       (ti::keep-lower-order beg end)
1249       (goto-char end)
1250       (end-of-line)
1251       (setq end (point))
1252       (goto-char beg)
1253       (catch 'exit
1254         (while (and (not (eobp))
1255                     (< (point) end))
1256           (setq file (tinyreplace-read-compile-buffer-filename))
1257           ;;  See that the file is loaded only once.
1258           ;;  /users/jaalto/elisp/test.el:;; @(#) ...
1259           ;;  /users/jaalto/elisp/test.el:;; $Id: ...
1260           (cond
1261            ((and file (not (file-exists-p file)))
1262             (ti::read-char-safe-until
1263              (format "TinyReplace: [press] invalid filename %s" file)))
1264
1265            ((and file (not (member file cache)))
1266             (raise-frame (select-frame w-frame))
1267             (push file cache)           ;Now we have dealt with it
1268
1269             ;;  If it's under RCS and not locked, ask if we should
1270             ;;  CheckOut it.
1271             (when (and (vc-registered file)
1272                        (eq 'RCS (vc-backend file))
1273                        (not (file-writable-p file))
1274                        (y-or-n-p (format "Co rcs file: %s" file)))
1275               (unless (call-process
1276                        "co"
1277                        nil
1278                        err-buffer
1279                        nil
1280                        "-l"
1281                        (expand-file-name file))
1282                 (pop-to-buffer err-buffer)))
1283             (cond
1284              ((not (file-writable-p file))
1285               (incf  read-only)
1286               (push file ro-cache))
1287              (t
1288               (save-excursion
1289                 ;;  Also jumps to buffer if it's already in Emacs
1290
1291                 (setq buffer (find-file file))
1292
1293                 ;;  Open outline/folding before doing anything
1294                 (ti::buffer-outline-widen)
1295                 (ti::pmin)
1296                 (cond
1297                  (no-confirm
1298                   ;; Automatic replace
1299                   (message "TinyReplace: Processing %s" file)
1300                   (replace-string str1 str2)
1301                   (with-current-buffer buffer
1302                     (save-buffer)))
1303                  (t
1304                   (funcall func str1 str2)
1305                   ;;  What to do after replace
1306                   (if (and
1307                        verb
1308                        (null no-confirm)
1309                        (buffer-modified-p)
1310                        (ti::char-in-list-case
1311                         (setq
1312                          ch
1313                          (ti::read-char-safe-until
1314                           (format
1315                            "\
1316 %s: SPC s)ave n)ext k)save and kill C)ontinue all Q)uit-exit"
1317                            (file-name-nondirectory file))))
1318                         '(?s ?S  ?n ?N ?\b  ?\ ?C ?Q )))
1319                       (cond
1320                        ((ti::char-in-list-case ch '(?\ ?s ?S))
1321                         (with-current-buffer buffer (save-buffer)))
1322                        ((ti::char-in-list-case ch '(?n ?N))
1323                         nil)
1324                        ((ti::char-in-list-case ch '(?Q))
1325                         (throw 'exit t))
1326                        ((ti::char-in-list-case ch '(?C))
1327                         (with-current-buffer buffer (save-buffer))
1328                         (setq no-confirm t))
1329                        ((ti::char-in-list-case ch '(?k ?K))
1330                         ;;  Why with-current-buffer? Well I have automatic
1331                         ;;  select-buffer programmed to my my mouse movement,
1332                         ;;  so if I point some other frame, that buffer
1333                         ;;  gets activated.
1334                         ;;
1335                         ;;  In here we want to be sure that the right buffer
1336                         ;;  "the replace buffer" is touched.
1337                         ;;
1338                         ;;
1339                         (with-current-buffer buffer (save-buffer))
1340                         (kill-buffer buffer)))))))))))
1341           (select-frame o-frame)
1342           (forward-line 1))))
1343     (raise-frame (select-frame o-frame))
1344     (when verb
1345       (message
1346        "TinyReplace: Handled %s files, %s read-only: %s"
1347        (length cache) read-only
1348        (mapconcat 'concat ro-cache " ")))
1349     nil))
1350
1351 ;;; ----------------------------------------------------------------------
1352 ;;;
1353 ;;;###autoload
1354 (defun tinyreplace-replace-region (beg end str1 str2)
1355   "In region BEG END, find STR1 and replace with STR2."
1356   (interactive (tinyreplace-interactive-region-args "region"))
1357   (tinyreplace-replace-region-1
1358    beg end (regexp-quote str1) str2 0 t))
1359
1360 ;;; ----------------------------------------------------------------------
1361 ;;;
1362 ;;;###autoload
1363 (defun tinyreplace-replace-forward (str1 str2)
1364   "Find STR1 and replace with STR2 from current point forward.
1365 See C-h f `tinyreplace-args-keymap-create' what key bindings
1366 you can use. Normally C - l yanks, and \"\\\" key deletes line."
1367   (interactive (funcall tinyreplace-:read-args-function))
1368   (tinyreplace-replace-region-1
1369    (point)
1370    (point-max)
1371    (regexp-quote str1) str2 0 t))
1372
1373 ;;; ----------------------------------------------------------------------
1374 ;;; ** Not gurranteed to work interactively.
1375 ;;;
1376 ;;;###autoload
1377 (defun tinyreplace-latex-blk-replace (str1 str2 blk &optional beg-re end-re)
1378   "Select latex block areas for replace.
1379
1380 Input:
1381
1382  STR1 STR2      Find and replace with.
1383  BLK            Block delimiter to find
1384  BEG-RE END-RE  Region bound regexps."
1385   (interactive "sLatex equation Search: \nsReplace with: \nsBlock names: ")
1386   (let* ((cp        (point))            ;current point
1387          (block-re  (concat "\\(" blk "\\)"))
1388          (beg-re    (or beg-re (concat "begin{" block-re "}")))
1389          (end-re    (or end-re (concat "end{" block-re "}")))
1390          MARK
1391          beg end
1392          area-end
1393          move)
1394     (save-excursion
1395       (goto-char (point-max))       (setq MARK (point-marker))
1396       (goto-char cp)                    ;start from current point
1397       (while (and (if (null (re-search-forward beg-re nil t))
1398                       nil
1399                     (setq beg (point)))
1400                   (save-excursion
1401                     (if (null (re-search-forward end-re nil t))
1402                         nil
1403                       (goto-char (match-beginning 0))
1404                       (setq end (point))))
1405                   (< (point) (marker-position MARK)))
1406         (setq area-end                  ;leave the block end
1407               (tinyreplace-replace-region-1
1408                beg end (regexp-quote str1) str2 0 t)
1409               move
1410               (+ 2 area-end))
1411         (if (< move (point-max))
1412             (goto-char move)
1413           (goto-char (point-max)))))))
1414
1415 ;;; ----------------------------------------------------------------------
1416 ;;;
1417 ;;;###autoload
1418 (defun tinyreplace-latex-math-replace (str1 str2)
1419   "Find STR1 and replace with STR2 inside latex math blocks."
1420   (interactive "sLatex equation Search: \nsReplace with:")
1421   (let ((math-blocks "equation"))
1422     ;;   first $ .. $ blocks
1423     (save-excursion
1424       (goto-char (point-min))
1425       ;;   first $ .. $ blocks
1426       (tinyreplace-latex-blk-replace str1 str2 nil "[^\\][$]" "[^\\][$]")
1427       ;;  then the rest
1428       (tinyreplace-latex-blk-replace str1 str2 math-blocks))))
1429
1430 ;;}}}
1431
1432 (add-hook   'compilation-mode-hook 'tinyreplace-define-keys-compile-map)
1433
1434 (provide    'tinyreplace)
1435 (run-hooks  'tinyreplace-load-hook)
1436
1437 ;;; tinyreplace.el ends here