1 ;;; tinyreplace.el --- Handy query-replace, area, case preserve, words
3 ;; This file is not part of Emacs
7 ;; Copyright (C) 1995-2007 Jari Aalto
10 ;; Maintainer: Jari Aalto
12 ;; To get information on this program, call M-x tinyreplace-version.
13 ;; Look at the code with folding.el.
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)
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
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.
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
37 ;; ....................................................... &t-install ...
38 ;; Put this file on your Emacs-Lisp load path, add following into your
39 ;; ~/.emacs startup file
41 ;; (require 'tinyreplace)
43 ;; Or you can use autoload (preferred) and your emacs starts up faster
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)
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.
56 ;; M-x tinyreplace-install ;; C-u to uninstall
58 ;; Check that you have colors on, otherwise the replaced region may
61 ;; (set-face-background 'highlight "blue")
63 ;; If you have any questions, contact maintainer with function
65 ;; M-x tinyreplace-submit-bug-report
70 ;; .................................................... &t-commentary ...
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.
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")
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.
96 ;; Overview of features
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
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
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
115 ;; If you know lisp, you can go and take straight advantage of the
118 ;; tinyreplace-replace-region-1
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:
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
128 ;; o `tinyreplace-latex-math-replace', replace text within latex
129 ;; math equations only.
131 ;; What commands do I have while replacing interactively?
133 ;; There are some handy commands that normal emacs replace lacks:
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)
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
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.
147 ;; Command line prompt explanation
149 ;; The command line prompt will look like this
151 ;; Replace 'xx' with 'yy' (a,bvuBFNU? [+CSX] !ynqQ)
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
160 ;; Special commands in command line
162 ;; When you edit the seach string or destination string, there are
163 ;; some keys that you can use:
165 ;; C-l Yank the text under point to current prompt
166 ;; C-o Yank previous SRING1
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.
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.
182 ;; < Moves buffer's point backward
183 ;; > Moves buffer's point forward
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
191 ;; Note about the arrow pointer
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.
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.
203 ;; Note about commands
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.
211 ;; There is no plan to convert this module to minor mode.
221 ;;; ......................................................... &require ...
226 (autoload 'vc-registered "vc"))
228 (eval-when-compile (ti::package-use-dynamic-compilation))
230 (ti::package-defgroup-tiny TinyReplace tinyreplace-: tools
231 "Overview of features
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
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
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.")
252 (defcustom tinyreplace-load-hook nil
253 "*Hook run when file has been loaded."
257 (defcustom tinyreplace-:args-keymap-hook nil
258 "*Hook which can define additional key bindings to `tinyreplace-:args-keymap'."
262 (defcustom tinyreplace-:pre-replace-hook nil
263 "*Hook to run just before replacing start in a buffer."
268 ;;{{{ setup: public, User configurable
270 (defcustom tinyreplace-:goto-region-beginning t
271 "If non-nil go to beginning of region before replace starts."
275 (defcustom tinyreplace-:exclude-line nil
276 "*When search stops to found position, this variable is consulted.
280 nil do nothing special. Proceed replace.
281 regexp line which matches `looking-at' REGEXP at the beginning
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.
290 (setq tinyreplace-:exclude-line 'my-tinyreplace-exclude)
291 (defun my-tinyreplace-exclude ()
292 ;; Exclude comment lines
294 ((eq major-mode 'c++-mode)
296 (looking-at \"^[ \t]*//\"))))"
297 :type '(string :tag "Regexp")
300 (defcustom tinyreplace-:arrow "=>"
301 "*Line marker where the replace takes effect.
302 Especially useful, when term cannot display colours to show the
304 :type '(string :tag "Arrow string")
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.
311 The only valid values are 'show and 'hide."
317 (defcustom tinyreplace-:face 'highlight
318 "*The match area overlay face."
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
326 \"Ignore ThIs match, but replace i with j\"
328 The complement makes sure the word is full word."
329 :type '(string :tag "Word complement charset")
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
340 And you want to preserve the symmetry when doing \"foo\" --> \"bar\".
341 This is what you get:
345 If the symmetry is nil, then the normal replace would have given:
351 (defcustom tinyreplace-:symmetry-rest nil
352 "*If non-nil then rest of the characters follow previous symmetry."
356 ;; Not in defcustom; advanced feature and expert knows what to to with this.
358 (defvar tinyreplace-:read-args-function 'tinyreplace-read-args
359 "*Function to ask two arguments ARG1 and ARG2 for replace.
368 Function must terminate with error if it cannot return list of
371 (defcustom tinyreplace-:user-function 'ti::buffer-outline-widen
372 "*User function fun from command prompt key 'U'."
379 (defvar tinyreplace-:replace-region-overlay nil
380 "Overlay used to show the replaced region.")
382 (defvar tinyreplace-:transient-mark-mode nil
383 "Transient mark mode state (Emacs).")
385 (defvar tinyreplace-:arrow-state nil
386 "Arrow display state.")
388 (defvar tinyreplace-:narrow-state nil
389 "Narrowed to function state.")
391 (defvar tinyreplace-:args-history nil
392 "History of replace strings.")
394 (defvar tinyreplace-:args-keymap nil
395 "Keymap for reading arguments.")
397 (defvar tinyreplace-:replace-buffer nil
398 "Buffer where to replace.")
400 (defvar tinyreplace-:tmp-buffer "*tinyreplace-temp*"
403 (defvar tinyreplace-:err-buffer "*tinyreplace-error*"
404 "Error message buffer.")
406 (defvar tinyreplace-:read-point nil
407 "This variable is used in interactive word reading.
408 It tells where the current point is.")
410 (defvar tinyreplace-:o-exclude nil
411 "Private. Temporary variable to keep state when calling another function.")
413 (defvar tinyreplace-:string1 nil
414 "Private. The asked string1. Set in `tinyreplace-read-args'.")
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.")
420 ;;; ............................................................ &menu ...
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
425 (defvar tinyreplace-:menu
427 replace: (f)wd (r)eg (w)ord (c)ompile buffer files (f)iles (lL)atex (?)help "
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.
443 Standard replace commands:
445 f calls function `tinyreplace-replace-forward'
446 % calls function `tinyreplace-replace-forward' (like M-%)
447 5 calls function `tinyreplace-replace-forward' (Like M-%)
449 w calls function `tinyreplace-word-replace'
450 r calls function `tinyreplace-replace-region'
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.
456 FILE:LINE-NUMBER:output
457 FILE:LINE-NUMBER:output
458 FILE:LINE-NUMBER:output
460 c calls function `tinyreplace-replace-over-files-compile-buffer'
461 F calls function `tinyreplace-replace-over-files'
465 l calls function `tinyreplace-latex-blk-replace'
466 L calls function `tinyreplace-latex-math-replace'")
469 ;;{{{ version and install
471 ;;; ----------------------------------------------------------------------
474 (ti::macrof-version-bug-report
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
484 tinyreplace-:word-boundary
485 tinyreplace-:symmetry
486 tinyreplace-:symmetry-rest)))
488 ;;; ----------------------------------------------------------------------
490 (defun tinyreplace-install-default-keybings (&optional uninstall)
491 "Install or UNINSTALL M-& default keybing to run `tinyreplace-menu'."
494 (def (lookup-key global-map key)))
495 (when (featurep 'compile)
496 (tinyreplace-define-keys-compile-map))
499 (when (setq def (ti::keymap-bind-control 'global-map 'get 'tinymy key))
500 (global-set-key key def)))
502 (ti::keymap-bind-control 'global-map 'set 'tinymy key)
503 (global-set-key key 'tinyreplace-menu)))))
505 ;;; ----------------------------------------------------------------------
507 (defun tinyreplace-install (&optional uninstall)
508 "Call `tinyreplace-install-default-keybings' with optional UNINSTALL."
510 (tinyreplace-install-default-keybings uninstall))
515 ;;; ----------------------------------------------------------------------
517 (defun tinyreplace-menu ()
518 "Run `tinyreplace-:menu'."
521 (message "My: Cannot start replace, buffer is read-only.")
522 (ti::menu-menu 'tinyreplace-:menu)))
524 ;;; ----------------------------------------------------------------------
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."
530 (when (and (boundp ,sym)
531 (setq map (symbol-value ,sym))
535 ;;; ----------------------------------------------------------------------
538 (defun tinyreplace-define-keys-compile-map ()
539 "Define key bindings."
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)))
548 ;;; ----------------------------------------------------------------------
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'.
555 '(BEG END ARG1-STRING ARG2-STRING)"
558 (barf-if-buffer-read-only)
559 (if (region-active-p)
560 (ti::list-merge-elements
563 (funcall tinyreplace-:read-args-function (, string)))
564 (error "TinyReplace: Region is not active. Please select one.")))))
566 ;;; ----------------------------------------------------------------------
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))
574 ;;; ----------------------------------------------------------------------
576 (defun tinyreplace-read-args (&optional prompt)
577 "Read two arguments with PROMPT. Return '(ARG1 ARG2)."
578 (let* ((opoint (point))
581 ;; Disable electric file minor mode, which defines specilal
583 (setq tinyreplace-:replace-buffer (current-buffer)
584 tinyreplace-:read-point (point)
585 tinyreplace-:string1 nil)
586 (tinyreplace-args-keymap-create)
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
594 (ti::remove-properties
595 (read-from-minibuffer
597 tinyreplace-:args-keymap nil tinyreplace-:args-history)))
598 (goto-char opoint) ;restore
601 ;;; ----------------------------------------------------------------------
602 ;;; - This is for user friendliness
605 (defun tinyreplace-symmetry-toggle (&optional arg verb)
606 "Toggle variable` tinyreplace-:symmetry' with ARG. VERB."
609 (ti::bool-toggle tinyreplace-:symmetry arg)
610 (put 'tinyreplace-replace-1 'tinyreplace-:symmetry tinyreplace-:symmetry)
612 (message "TinyReplace: Symmetry is now %s"
613 (if tinyreplace-:symmetry
617 ;;; ----------------------------------------------------------------------
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
626 (setq tinyreplace-:transient-mark-mode
627 (let* ((var 'transient-mark-mode)) ;XEmacs 19.14 byteComp silencer
628 (symbol-value var))))
630 tinyreplace-:transient-mark-mode))))
632 ;;; ----------------------------------------------------------------------
634 (defun tinyreplace-arrow-control (buffer mode &optional str)
635 "Handles showing the arrow.
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
645 `tinyreplace-:arrow-state'
653 (if (or (null overlay-arrow-position) ;doesn't exist
654 (not (equal (marker-buffer overlay-arrow-position)
661 ((null tinyreplace-:arrow-state)
664 ;; follow the mode which is active
665 (setq mode tinyreplace-:arrow-state)))))
669 (ti::buffer-arrow-control buffer mode tinyreplace-:arrow (point))
670 (setq tinyreplace-:arrow-state 'show))
672 (ti::buffer-arrow-control buffer 'hide str)
673 (setq tinyreplace-:arrow-state 'hide)))
676 ;;; ----------------------------------------------------------------------
678 (defun tinyreplace-replace-ask (buffer from-str to-str )
679 "Perform asking while in interactive replace mode.
683 BUFFER FROM-STR TO-STR
687 `tinyreplace-:o-exclude' must be set in the calling function"
688 (let* ((o-exclude tinyreplace-:o-exclude)
693 (setq from-str (ti::string-format-percent from-str)
694 to-str (ti::string-format-percent to-str))
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) "..")
702 (if (> (length from-str) 18)
703 (concat (ti::string-left to-str 16) "..")
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.
714 (tinyreplace-arrow-control buffer 'toggle)
715 (read-from-minibuffer
716 "Arrow refreshed. Press RET to update view."))
718 (tinyreplace-symmetry-toggle))
720 (ti::bool-toggle tinyreplace-:word-match-mode)
721 (put 'tinyreplace-replace-1
722 'tinyreplace-:word-match-mode
723 tinyreplace-:word-match-mode))
725 (ti::bool-toggle case-fold-search)
726 (put 'tinyreplace-replace-1 'case-fold-search case-fold-search))
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)))
735 (tinyreplace-show-function-name (point)))
737 (setq tinyreplace-:narrow-state t)
740 (funcall tinyreplace-:user-function)
742 ((ti::char-in-list-case ans '(?\177 ?\b ?n))
744 (if (ti::char-in-list-case ans '(?! ?? ?y ?n ?q ?Q ?b ?v ?u ?B ?N))
745 (setq loop nil))) ;; while loop
748 ;;; ----------------------------------------------------------------------
749 ;;; Press Ctrl-g to abort replace.
751 (defun tinyreplace-show-function-name (point)
752 "Flashes function name briefly from POINT."
753 (let* ((name (ti::buffer-defun-function-name point))
760 ;;; ----------------------------------------------------------------------
761 ;;; Press Ctrl-g to abort replace.
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))
770 ;;; ----------------------------------------------------------------------
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)
781 ;;; ----------------------------------------------------------------------
783 (defun tinyreplace-key-clear-input ()
786 (delete-region (line-beginning-position) (line-end-position)))
788 ;;; ----------------------------------------------------------------------
790 (defun tinyreplace-key-forward-word (&optional count)
791 "Forward word. COUNT is argument to `forward-word', and defaults to 1.
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"
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..."
803 (or (buffer-substring-no-properties (point) (line-end-position))
806 (select-window (get-buffer-window obuffer))))
808 ;;; ----------------------------------------------------------------------
810 (defun tinyreplace-key-backward-word ()
811 "See `tinyreplace-key-forward-word'."
813 (tinyreplace-key-forward-word -1))
815 ;;; ----------------------------------------------------------------------
817 (defun tinyreplace-key-yank-string1 ()
818 "Yank previous string (search string)."
820 (if tinyreplace-:string1
821 (insert tinyreplace-:string1)
822 (message "TinyReplace: Sorry, there is no STRING1 to yank yet ")))
824 ;;; ----------------------------------------------------------------------
826 (defun tinyreplace-key-yank-word ()
827 "Yank word from buffer. `tinyreplace-:replace-buffer' must be set."
830 (with-current-buffer tinyreplace-:replace-buffer
831 (goto-char tinyreplace-:read-point)
832 ;; This is just because of the space-word-command
835 ;; * << advance this cursor one char
836 (if (ti::char-in-list-case (preceding-char) '(?\ ?\t ?\n ))
838 (setq word (ti::remove-properties (ti::buffer-read-space-word))))
840 (setq word (ti::remove-properties word))
843 ;;; ----------------------------------------------------------------------
845 (defun tinyreplace-args-keymap-create ()
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))
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.
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.
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.
870 (defun tinyreplace-replace-region-1 (beg end re str &optional level ask func)
878 LEVEL subexpression level to match in RE. Default is 0, whole match.
882 Commands while ASK is non-nil: (simple undo backward is = 'v u' )
888 Q quit at current point.
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.
899 Search control forward/backward
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.
907 Moving point of search
909 B go to beginning of search.
910 Beginning or end point depending of search direction
912 F flash function name
914 N narrow to function.
915 You can't cancel this if you use it. The
916 'N' is indicated in the command line,
918 U Run user function `tinyreplace-:user-function'
922 `tinyreplace-:exclude-line'
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
939 MARK-MAXP ;marker for the last point of search
940 minp maxp ;logical point min, point-max
941 c-exclude ;Current exclude value
949 mb me ;match area, beg end
951 bypass) ;This is flag to skip searching in loop
952 ;; Se same defaults as previous time
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
969 ((eq func 're-search-forward)
970 (setq minp (min beg end) ;logical min and max
973 (setq minp (max beg end)
974 maxp (min beg end))))
977 (setq MARK-MAXP (point-marker)))
982 (run-hooks 'tinyreplace-:pre-replace-hook)
983 ;; Peek a little before we start
986 ((and (not (eq (point)
987 (point-min))) ;Already ti::pmin ?
989 (if (null (funcall func re nil t))
991 There is no matches forward. Go to beginning of buffer? "))))
993 ((and (region-active-p)
994 (or tinyreplace-:goto-region-beginning
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))))))
1002 (and (funcall func re nil t)
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)))
1009 ;; .. .. .. .. .. .. .. .. .. .. .. .. .. exclude line ..
1010 (setq c-exclude tinyreplace-:exclude-line)
1013 ((null c-exclude) ;; No exclude patterns
1015 ((and (symbolp c-exclude)
1016 (fboundp c-exclude))
1017 (if (funcall c-exclude)
1019 ((stringp c-exclude)
1021 (if (looking-at c-exclude)
1022 ;; Force forgetting this point
1024 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HANDLE it ^^^
1027 (tinyreplace-move-overlay mb me)
1028 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ask user ^^^
1031 (buffer-substring-no-properties mb me))
1034 (setq replace t) ;automatic
1036 (tinyreplace-arrow-control buffer 'maybe)
1037 ;;; (ti::d! tinyreplace-:arrow-state )
1041 (buffer-substring-no-properties mb me))
1044 (setq replace (tinyreplace-replace-ask
1050 (tinyreplace-:word-match-mode
1051 (setq re (get 'tinyreplace-:word-match-mode 'word))
1054 (setq re (get 'tinyreplace-:word-match-mode 're))
1056 ;; ... ... ... ... ... ... ... ... ... ... ... ... ...
1058 ;; .......................... beginning of buffer ...
1060 (if (eq func 're-search-forward)
1063 (setq minp (point)))
1065 (setq maxp (point)))
1068 ;; ... ... ... ... ... ... ... ... ... ... . help ..
1070 (ti::menu-help 'tinyreplace-replace-region-1)
1073 ;; ... ... ... ... ... ... ... ... ... ... narrow ..
1076 ((eq func 're-search-forward)
1077 ;; The other call isn't executed if first fails
1079 (and (setq fmin (ti::beginning-of-defun-point))
1080 (setq fmax (ti::beginning-of-defun-point 'end)))
1082 ((and fmin fmax (not (eq fmin fmax)))
1083 (setq minp fmin maxp fmax)
1085 (setq MARK-MAXP (point-marker))
1088 (setq tinyreplace-:narrow-state nil)
1089 (message "TinyReplace: Can't find narrow bounds.")
1092 (and (setq fmin (ti::beginning-of-defun-point))
1093 (setq fmax (ti::beginning-of-defun-point 'end)))
1095 ((and fmin fmax (not (eq fmin fmax)))
1096 (setq minp fmax maxp fmin)
1098 (setq MARK-MAXP (point-marker))
1101 (setq tinyreplace-:narrow-state nil)
1102 (message "TinyReplace: Can't find narrow bounds.")
1105 ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
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))))
1115 (message "TinyReplace: No more hits.")
1118 ;;; (setq undo-string (buffer-substring mb me))
1119 (tinyreplace-move-overlay mb me))
1122 ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
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))))
1134 (message "TinyReplace: No previous hit.")
1137 ;;; (setq undo-string (buffer-substring mb me))
1138 (tinyreplace-move-overlay mb me))
1139 (setq replace nil do-ask t))
1140 ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
1142 (tinyreplace-replace-1
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 ;; ... ... ... ... ... ... ... ... ... ... ... ... ..
1150 (setq ask nil replace t))
1156 (setq quit-point mb)
1159 (setq replace t)))))
1160 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ do it ^^^
1164 (tinyreplace-replace-1
1166 (ti::string-case-replace
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))
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)))
1184 (setq MARK nil MARK-MAXP nil) ;kill markers
1190 ;;; ----------------------------------------------------------------------
1192 (defun tinyreplace-read-compile-buffer-filename ()
1193 "Read filename on line."
1194 (let ((re "^\\(\\(.:\\)?[^\n:]+\\):") ;; allow DOS drive at front d:/file/
1199 (when (setq file (ti::remove-properties (ti::buffer-match re 1)))
1201 (if (re-search-backward "^cd[ \t]+\\([^\t\n]+\\)" nil t)
1202 (setq dir (ti::remove-properties (match-string 1)))))
1205 (not (string-match "^\\(.:\\)?/" file)))
1206 (setq file (concat dir file)))
1207 (setq file (ti::file-name-for-correct-system file 'emacs))))
1210 ;;; ----------------------------------------------------------------------
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.
1221 /DIR/DIR/FILE: matched text
1225 See function `tinyreplace-replace-1'
1226 BEG END STR1 STR2 &OPTIONAL FUNC VERB"
1229 (ti::list-merge-elements
1230 (tinyreplace-interactive-region-args "compile")
1233 ;; ................................................. interactive end ...
1234 (let ((o-frame (selected-frame))
1235 (w-frame (ti::non-dedicated-frame))
1237 (func (or func 'tinyreplace-replace-forward))
1238 (err-buffer (ti::temp-buffer tinyreplace-:err-buffer 'clear))
1243 ro-cache ;read only file list
1248 (ti::keep-lower-order beg end)
1254 (while (and (not (eobp))
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: ...
1261 ((and file (not (file-exists-p file)))
1262 (ti::read-char-safe-until
1263 (format "TinyReplace: [press] invalid filename %s" file)))
1265 ((and file (not (member file cache)))
1266 (raise-frame (select-frame w-frame))
1267 (push file cache) ;Now we have dealt with it
1269 ;; If it's under RCS and not locked, ask if we should
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
1281 (expand-file-name file))
1282 (pop-to-buffer err-buffer)))
1284 ((not (file-writable-p file))
1286 (push file ro-cache))
1289 ;; Also jumps to buffer if it's already in Emacs
1291 (setq buffer (find-file file))
1293 ;; Open outline/folding before doing anything
1294 (ti::buffer-outline-widen)
1298 ;; Automatic replace
1299 (message "TinyReplace: Processing %s" file)
1300 (replace-string str1 str2)
1301 (with-current-buffer buffer
1304 (funcall func str1 str2)
1305 ;; What to do after replace
1310 (ti::char-in-list-case
1313 (ti::read-char-safe-until
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 )))
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))
1324 ((ti::char-in-list-case ch '(?Q))
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
1335 ;; In here we want to be sure that the right buffer
1336 ;; "the replace buffer" is touched.
1339 (with-current-buffer buffer (save-buffer))
1340 (kill-buffer buffer)))))))))))
1341 (select-frame o-frame)
1343 (raise-frame (select-frame o-frame))
1346 "TinyReplace: Handled %s files, %s read-only: %s"
1347 (length cache) read-only
1348 (mapconcat 'concat ro-cache " ")))
1351 ;;; ----------------------------------------------------------------------
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))
1360 ;;; ----------------------------------------------------------------------
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
1371 (regexp-quote str1) str2 0 t))
1373 ;;; ----------------------------------------------------------------------
1374 ;;; ** Not gurranteed to work interactively.
1377 (defun tinyreplace-latex-blk-replace (str1 str2 blk &optional beg-re end-re)
1378 "Select latex block areas for replace.
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 "}")))
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))
1401 (if (null (re-search-forward end-re nil t))
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)
1411 (if (< move (point-max))
1413 (goto-char (point-max)))))))
1415 ;;; ----------------------------------------------------------------------
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
1424 (goto-char (point-min))
1425 ;; first $ .. $ blocks
1426 (tinyreplace-latex-blk-replace str1 str2 nil "[^\\][$]" "[^\\][$]")
1428 (tinyreplace-latex-blk-replace str1 str2 math-blocks))))
1432 (add-hook 'compilation-mode-hook 'tinyreplace-define-keys-compile-map)
1434 (provide 'tinyreplace)
1435 (run-hooks 'tinyreplace-load-hook)
1437 ;;; tinyreplace.el ends here