unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* A prototype of intelligent replace for Emacs
@ 2007-04-29  8:32 Herbert Euler
  2007-04-29 18:39 ` Drew Adams
  2007-04-30 22:09 ` Richard Stallman
  0 siblings, 2 replies; 8+ messages in thread
From: Herbert Euler @ 2007-04-29  8:32 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2774 bytes --]

I saw the following entry in etc/TODO some days ago:

** Implement intelligent search/replace, going beyond query-replace
  (see http://graphics.csail.mit.edu/~rcm/chi04.pdf).

I thought I could work on it when reading that.  After a few days'
working, I get a prototype.  I should have discussed it in this list,
but I found difficulties talking about my thought in English, so the
only way to describe it clearly for me is to write such a prototype.
I apologize for this.



The prototype is attached in this message.  It is merely a prototype,
so many features, such as documents, mouse operation supports, more
precise classifications and so on, are not present in it.  However, it
works well (at least in my opinion) with keyboard.

To start with it, first load the attached file.  To replace text
intelligently, execute M-x ireplace.  It will ask for a piece of
text.  Another window will be opened to show all classified matches of
text in the buffer that M-x ireplace is executed in, and the cursor
will be moved into this window automatically.  I call this buffer the
"ireplace" buffer.

The ireplace buffer contains many classes, classified with some
method.  Each of the classes itself contains many "blocks".  Classes
and blocks are showed in alternate color, respectively, to help the
user distinguish them.  The user can mark several classes and/or
blocks, and then replace text of marked classes and/or blocks with
another text.  Then, the user may mark and replace again.

In the ireplace buffer, the user can:

    - Type n and p to move through blocks;

    - Type M-n and M-p to move through classes;

    - Type RET to follow the block, or open/fold a class;

    - Type m to mark or de-mark blocks or classes;

    - Type r to mark blocks reversely;

    - Type M-r to mark classes reversely;

    - Type x to perform the replace;

    - Type q to close the ireplace buffer.

Please try out the prototype.



As I said, there are many features missed in the prototype:

    - User interface re-design;

    - Mouse operations;

    - Console support;

    - More commands;

    - More precise classifying algorithm, for many mainstream major
      modes;

    - Documents and comments in source code;

    - Coordination in details;

    - Code improvement;

    - Emacs convension commits,

and perhaps other things.  These are because that I am not sure about
them.  Please give me suggestions on those things, I will improve the
prototype.  When it becomes stable/mature, I would like to contribute
it into Emacs.

Thanks.

Regards,
Guanpeng Xu

_________________________________________________________________
Express yourself instantly with MSN Messenger! Download today it's FREE! 
http://messenger.msn.click-url.com/go/onm00200471ave/direct/01/

[-- Attachment #2: ireplace.el --]
[-- Type: application/octet-stream, Size: 30078 bytes --]

;;; ireplace.el --- intelligent replace for Emacs

;; Copyright (C) 2007 Guanpeng Xu.

;; Maintainer: Guanpeng Xu <herberteuler@hotmail.com>
;; Keywords: intelligent replace

;; This file is not part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;;; Code:

;; Variable definitions.

(defvar ireplace-feature-specs nil
  "Specification for computing, comparing and displaying features.")
(make-variable-buffer-local 'ireplace-feature-specs)

(defvar ireplace-classes nil
  "All of the classes in buffer `ireplace-original-buffer'.")
(make-variable-buffer-local 'ireplace-classes)

(defvar ireplace-old-window-configuration nil
  "The old window configuration.")

(defvar ireplace-original-buffer nil
  "The original buffer in which the \\[ireplace] command was invoked.")
(make-variable-buffer-local 'ireplace-original-buffer)

(defvar ireplace-original-buffer-tick nil
  "The tick for the original buffer.
See also `buffer-chars-modified-tick'.")
(make-variable-buffer-local 'ireplace-original-buffer-tick)

(defvar ireplace-searched-text nil
  "The text searched previously.")
(make-variable-buffer-local 'ireplace-searched-text)
\f
;; The core classifying algorithm.

(defun ireplace-classify-matches (feature-specs
				  string bound noerror use-regexp)
  (let ((matches (ireplace-find-all-matches string bound
					    noerror use-regexp))
	blocks
	semi-classes
	new-semi-classes
	classes)
    ;; Compute features for each found match to create blocks for the
    ;; matches.
    (dolist (match matches)
      (let (features)
	(dolist (spec feature-specs)
	  (setq features
		(cons (save-match-data
			(funcall (ireplace-spec-feature-computing-function spec)
				 match))
		      features)))
	;; Perhaps we can avoid making a block here in some cases.
	(setq blocks (cons (ireplace-make-block match
						(nreverse features)
						nil)
			   blocks))))
    ;; Classify the blocks.
    (setq semi-classes (list blocks))
    (dotimes (i (length feature-specs))
      (let* ((spec (nth i feature-specs))
	     (less-than (ireplace-spec-feature-comparing-function spec)))
	(dolist (semi-class semi-classes)
	  (let (this-semi-class
		last-feature)
	    (setq semi-class
		  (sort semi-class
			(lambda (b1 b2)
			  (funcall less-than
				   (nth i (ireplace-block-features b1))
				   (nth i (ireplace-block-features b2))))))
	    (dolist (block semi-class)
	      (let ((feature (nth i (ireplace-block-features block))))
		(cond ((null last-feature)
		       (setq this-semi-class (list block)
			     last-feature feature))
		      ((not (or (funcall less-than last-feature feature)
				(funcall less-than feature last-feature)))
		       (setq this-semi-class (cons block this-semi-class)))
		      (t
		       (setq new-semi-classes (cons this-semi-class
						    new-semi-classes)
			     this-semi-class (list block)
			     last-feature feature)))))
	    (if this-semi-class
		(setq new-semi-classes (cons this-semi-class
					     new-semi-classes)))))
	(setq semi-classes new-semi-classes)))
    (dolist (semi-class semi-classes)
      (setq classes (cons (ireplace-make-class semi-class nil)
			  classes)))
    classes))

(defun ireplace-find-all-matches (string bound noerror use-regexp)
  (let ((search-func (if use-regexp
			 're-search-forward
		       'search-forward))
	matches)
    (save-match-data
      (save-excursion
	(goto-char (point-min))
	(while (funcall search-func string bound noerror)
	  (setq matches (cons (match-data) matches)))))
    matches))
\f
;; Features: computation, comparison and display.

(defun ireplace-compute-text-mode-feature (match)
  (let (start end)
    (set-match-data match)
    (save-excursion
      (goto-char (match-beginning 0))
      (forward-word 1)
      (backward-word 1)
      (setq start (point))
      (goto-char (match-end 0))
      (backward-word 1)
      (forward-word 1)
      (setq end (point)))
    (buffer-substring-no-properties start end)))

(defun ireplace-compare-text-mode-features (s1 s2)
  (string< s1 s2))

(defun ireplace-display-text-mode-feature (feature)
  feature)

(defvar ireplace-text-mode-spec
  (list 'ireplace-compute-text-mode-feature
	'ireplace-compare-text-mode-features
	'ireplace-display-text-mode-feature))

(defun ireplace-spec-feature-computing-function (spec)
  (nth 0 spec))

(defun ireplace-spec-feature-comparing-function (spec)
  (nth 1 spec))

(defun ireplace-spec-feature-displaying-function (spec)
  (nth 2 spec))
\f
;; Block related functions.

(defun ireplace-make-block (match features marked)
  (list match features marked))

(defun ireplace-block-match (block)
  (nth 0 block))

(defun ireplace-block-features (block)
  (nth 1 block))

(defun ireplace-block-marked (block)
  (nth 2 block))

(defun ireplace-set-block-marked (block marked)
  (setcar (nthcdr 2 block) marked))

(defun ireplace-internal-toggle-block-marked (class block)
  (let (marked
	marked-removed)
    ;; Do we need to check whether BLOCK is in CLASS?
    (ireplace-set-block-marked block
			       (not (ireplace-block-marked block)))
    (setq marked (mapcar 'ireplace-block-marked
			  (ireplace-class-blocks class))
	  marked-removed (remove nil marked))
    (cond ((equal marked marked-removed)
	   (ireplace-set-class-marked class t))
	  ((null marked-removed)
	   (ireplace-set-class-marked class nil))
	  (t
	   (ireplace-set-class-marked class 'partial)))))
\f
;; Class related functions.

(defun ireplace-make-class (blocks marked)
  (list blocks marked))

(defun ireplace-class-blocks (class)
  (nth 0 class))

(defun ireplace-class-marked (class)
  (nth 1 class))

(defun ireplace-set-class-marked (class marked)
  (setcar (nthcdr 1 class) marked))

(defun ireplace-internal-toggle-class-marked (class)
  (let ((marked (ireplace-class-marked class)))
    (setq marked (if (eq marked 'partial)
		     t
		   (not marked)))
    (dolist (block (ireplace-class-blocks class))
      (ireplace-set-block-marked block marked))
    (ireplace-set-class-marked class marked)))
\f
;; Display related stuff.

(defface ireplace-match
  '((((class color))
     (:background "yellow")))
  "Face for highlighting matches in the original buffer."
  :group 'ireplace)

(defface ireplace-match-marked
  '((((class color))
     (:background "red")))
  "Face for highlighting marked matches in the original buffer."
  :group 'ireplace)

(defface ireplace-class-header-1
  '((((class color))
     (:background "purple1" :foreground "white")))
  "Face for non-marked class header line, type I."
  :group 'ireplace)

(defface ireplace-class-header-1-partially-marked
  '((((class color))
     (:background "purple3" :foreground "white")))
  "Face for partially marked class header line, type I."
  :group 'ireplace)

(defface ireplace-class-header-1-marked
  '((((class color))
     (:background "purple4" :foreground "white")))
  "Face for marked class header line, type I."
  :group 'ireplace)

(defface ireplace-class-header-2
  '((((class color))
     (:background "RoyalBlue1" :foreground "white")))
  "Face for non-marked class header line, type II."
  :group 'ireplace)

(defface ireplace-class-header-2-partially-marked
  '((((class color))
     (:background "RoyalBlue3" :foreground "white")))
  "Face for partially marked class header line, type II."
  :group 'ireplace)

(defface ireplace-class-header-2-marked
  '((((class color))
     (:background "RoyalBlue4" :foreground "white")))
  "Face for marked class header line, type II."
  :group 'ireplace)

(defface ireplace-block-1
  '((((class color))
     (:background "white")))
  "Face for non-marked block, type I."
  :group 'ireplace)

(defface ireplace-block-1-marked
  '((((class color))
     (:background "gray66")))
  "Face for marked block, type I."
  :group 'ireplace)

(defface ireplace-block-2
  '((((class color))
     (:background "gray87")))
  "Face for non-marked block, type II."
  :group 'ireplace)

(defface ireplace-block-2-marked
  '((((class color))
     (:background "gray50")))
  "Face for marked block, type II."
  :group 'ireplace)

(defun ireplace-get-buffer ()
  (get-buffer-create (concat "ireplace-" (buffer-name))))

(defun ireplace-text-info (block)
  (let* ((match (ireplace-block-match block))
	 (buffer (marker-buffer (car match)))
	 (around-lines-amount 2))
    (if (null buffer)
	(signal 'ireplace-original-buffer-killed (current-buffer)))
    (with-current-buffer buffer
      (save-match-data
	(set-match-data match)
	(let* (continue-moving
	       (start (save-excursion
			(goto-char (match-beginning 0))
			(setq continue-moving t)
			(dotimes (i around-lines-amount)
			  (if continue-moving
			      (forward-line -1))
			  (unless (< (line-beginning-position)
				     (line-end-position))
			    (forward-line 1)
			    (setq continue-moving nil)))
			(line-beginning-position)))
	       (end (save-excursion
		      (goto-char (match-end 0))
		      (setq continue-moving t)
		      (dotimes (i around-lines-amount)
			(if continue-moving
			    (forward-line 1))
			(unless (< (line-beginning-position)
				   (line-end-position))
			  (forward-line -1)
			  (setq continue-moving nil)))
		      (line-end-position)))
	       text
	       overlay-start
	       overlay-end)
	  (font-lock-fontify-region start end)
	  (set-match-data match)
	  (setq text (buffer-substring start end)
		overlay-start (- (match-beginning 0) start)
		overlay-end (+ overlay-start
			       (- (match-end 0) (match-beginning 0))))
	  (list text overlay-start overlay-end))))))

(defun ireplace-make-circular-list (a b)
  (let ((l1 (list a))
	(l2 (list b)))
    (setcdr l1 l2)
    (setcdr l2 l1)
    l1))

(defun ireplace-setup-ireplace-buffer (original-buffer
				       buffer
				       feature-specs
				       text
				       classes
				       &optional window-configuration)
  (let ((tick (buffer-chars-modified-tick))
	class-start
	(class-faces (ireplace-make-circular-list 'ireplace-class-header-1
						  'ireplace-class-header-2))
	(block-faces (ireplace-make-circular-list 'ireplace-block-1
						 'ireplace-block-2))
	(current 0)
	(total (length classes)))
    (if window-configuration
	(setq ireplace-old-window-configuration window-configuration))
    (with-current-buffer buffer
      (ireplace-mode)
      (setq buffer-read-only nil)
      ;; Setup buffer local variables.
      (setq ireplace-feature-specs feature-specs
	    ireplace-classes classes
	    ireplace-original-buffer original-buffer
	    ireplace-original-buffer-tick tick
	    ireplace-searched-text text)
      ;; Insert ireplace information.
      (erase-buffer)
      (dolist (class ireplace-classes)
	(setq current (1+ current)
	      class-start (point))
	;; Insert class header line.
	(insert (format "[Class %d of %d] " current total))
	(dotimes (i (length ireplace-feature-specs))
	  (let* ((spec (nth i ireplace-feature-specs))
		 (display-func (ireplace-spec-feature-displaying-function spec))
		 (block (car (ireplace-class-blocks class)))
		 (feature (nth i (ireplace-block-features block))))
	    (if (> i 0)
		(insert ", "))
	    (insert (funcall display-func feature))))
	(add-text-properties class-start (point)
			     `(face ,(car class-faces) ireplace-show-body on
				    keymap ,ireplace-header-line-map))
	(setq class-faces (cdr class-faces))
	;; Insert class body.
	(dolist (block (ireplace-class-blocks class))
	  (let* ((text-info (ireplace-text-info block))
		 (text (nth 0 text-info))
		 block-start
		 match-overlay
		 (match-overlay-start (nth 1 text-info))
		 (match-overlay-end (nth 2 text-info))
		 block-overlay)
	    (insert ?\n)
	    (setq block-start (point))
	    (insert text)
	    (add-text-properties block-start (point)
				 `(ireplace-block ,block))
	    (setq block-overlay (make-overlay block-start (point)))
	    (overlay-put block-overlay 'ireplace-overlay 'block)
	    (overlay-put block-overlay 'priority 25)
	    (overlay-put block-overlay 'face (car block-faces))
	    (setq match-overlay (make-overlay (+ block-start match-overlay-start)
					      (+ block-start match-overlay-end)))
	    (overlay-put match-overlay 'ireplace-overlay 'match)
	    (overlay-put match-overlay 'priority 50)
	    (overlay-put match-overlay 'face 'ireplace-match)
	    (setq block-faces (cdr block-faces))))
	(add-text-properties class-start (point) `(ireplace-class ,class))
	(insert ?\n))
      (setq buffer-read-only t)
      (goto-char (point-min)))))

(defun ireplace-text-property-for-current-line (prop
						error-symbol
						&optional ignore-error)
  (let ((val (get-char-property (point) prop)))
    (if val
	val
      (if (bobp)
	  (unless ignore-error
	    (signal error-symbol (current-buffer)))
	(get-char-property (1- (point)) prop)))))

(defun ireplace-text-property-class (&optional ignore-error)
  (ireplace-text-property-for-current-line 'ireplace-class
					   'ireplace-no-classes
					   ignore-error))

(defun ireplace-text-property-block (&optional ignore-error)
  (ireplace-text-property-for-current-line 'ireplace-block
					   'ireplace-no-blocks
					   ignore-error))

(defun ireplace-text-property-show-body (&optional ignore-error)
  (ireplace-text-property-for-current-line 'ireplace-show-body
					   'ireplace-no-classes
					   ignore-error))

(defun ireplace-text-property-face (&optional ignore-error)
  (ireplace-text-property-for-current-line 'face
					   'ireplace-no-classes
					   ignore-error))

(defun ireplace-get-overlay (type)
  (let ((overlays (overlays-at (point)))
	desired)
    (dolist (overlay overlays)
      (if (eq (overlay-get overlay 'ireplace-overlay) type)
	  (setq desired overlay)))
    desired))

(defun ireplace-remove-all-overlays (type)
  (remove-overlays (point-min) (point-max) 'ireplace-overlay type))

(defun ireplace-setup-overlays-in-original-buffer ()
  (when (buffer-live-p ireplace-original-buffer)
    (save-match-data
      (let ((classes ireplace-classes))
	(with-current-buffer ireplace-original-buffer
	  (ireplace-remove-all-overlays 'match)
	  (dolist (class classes)
	    (dolist (block (ireplace-class-blocks class))
	      (set-match-data (ireplace-block-match block))
	      (let* ((start (match-beginning 0))
		     (end (match-end 0))
		     (state (ireplace-block-marked block))
		     (overlay (make-overlay start end)))
		(overlay-put overlay 'ireplace-overlay 'match)
		(overlay-put overlay
			     'face
			     (ireplace-compute-face 'ireplace-match
						    state))))))))))

(defun ireplace-header-line-end-position ()
  (save-excursion
    (ireplace-beginning-of-class)
    (if (null (ireplace-text-property-show-body))
	(signal 'ireplace-not-on-header-line (point))
      (save-excursion
	(ireplace-beginning-of-class)
	(next-single-property-change (point) 'ireplace-show-body)))))

(defun ireplace-class-beginning-position ()
  (save-excursion
    (ireplace-end-of-class)
    (previous-single-char-property-change (point) 'ireplace-class)))

(defun ireplace-class-end-position ()
  (let ((class (ireplace-text-property-class)))
    (save-excursion
      (if (null class)
	  ;; This can be only at the end of buffer, since
	  ;; `ireplace-text-property-for-current-line', which is
	  ;; called by `ireplace-text-property-class', will signal an
	  ;; error in other cases.
	  (progn (backward-char)
		 (point))
	(if (get-char-property (point) 'ireplace-class)
	    (next-single-char-property-change (point) 'ireplace-class)
	  (point))))))

(defun ireplace-block-beginning-position ()
  (save-excursion
    (ireplace-end-of-block)
    (previous-single-char-property-change (point) 'ireplace-block)))

(defun ireplace-block-end-position ()
  (let ((class (ireplace-text-property-class))
	(block (ireplace-text-property-block 'ignore-error)))
    (save-excursion
      (if (null class)
	  ;; This can be only at the end of buffer, since
	  ;; `ireplace-text-property-for-current-line', which is
	  ;; called by `ireplace-text-property-block' and
	  ;; `ireplace-text-property-class', will signal an error in
	  ;; other cases.
	  (progn (backward-char)
		 (point))
	(if (null block)
	    (signal 'ireplace-not-on-block (point)))
	(if (get-char-property (point) 'ireplace-block)
	    (next-single-char-property-change (point) 'ireplace-block)
	  (point))))))

(defun ireplace-compute-face (face state)
  (let ((name (replace-regexp-in-string "\\(-partially\\)?-marked$"
					""
					(symbol-name face))))
    (cond ((eq state 'partial)
	   (intern (concat name "-partially-marked")))
	  (state
	   (intern (concat name "-marked")))
	  (t
	   (intern name)))))

(defun ireplace-update-class-display (class &optional show-body)
  ;; Update display for CLASS in the ireplace buffer.
  (save-excursion
    (ireplace-beginning-of-class)
    (let* ((class-state (ireplace-class-marked class))
	   (header-line-face (ireplace-text-property-face))
	   (body-invisible (ireplace-text-property-show-body))
	   (show-body (if show-body
			  show-body
			(ireplace-text-property-show-body)))
	   buffer-read-only)
      (add-text-properties (ireplace-class-beginning-position)
			   (ireplace-header-line-end-position)
			   `(face
			     ,(ireplace-compute-face header-line-face
						     class-state)))
      (dotimes (i (length (ireplace-class-blocks class)))
	(ireplace-next-block)
	(let* ((block (ireplace-text-property-block))
	       (block-state (ireplace-block-marked block))
	       (overlay (ireplace-get-overlay 'block))
	       (block-face (overlay-get overlay 'face)))
	  (overlay-put overlay 'face (ireplace-compute-face block-face
							    block-state))))
      (cond ((eq show-body 'on)
	     (setq body-invisible nil))
	    ((eq show-body 'off)
	     (setq body-invisible t)))
      (add-text-properties (ireplace-header-line-end-position)
			   (ireplace-class-end-position)
			   `(invisible ,body-invisible))
      (add-text-properties (ireplace-class-beginning-position)
			   (ireplace-header-line-end-position)
			   `(ireplace-show-body ,show-body))))
  ;; Update display for CLASS in the original buffer.
  (when (buffer-live-p ireplace-original-buffer)
    (with-current-buffer ireplace-original-buffer
      (save-match-data
	(save-excursion
	  (dolist (block (ireplace-class-blocks class))
	    (set-match-data (ireplace-block-match block))
	    (let ((start (match-beginning 0))
		  (state (ireplace-block-marked block))
		  overlay)
	      (goto-char start)
	      (setq overlay (ireplace-get-overlay 'match))
	      (overlay-put overlay
			   'face
			   (ireplace-compute-face 'ireplace-match state)))))))))

(defun ireplace-refresh ()
  (save-excursion
    (goto-char (point-min))
    (ireplace-beginning-of-class)
    (dolist (class ireplace-classes)
      (ireplace-update-class-display class)
      (ireplace-next-class))))
\f
;; Ireplace errors.

(let ((ireplace-errors '((ireplace-no-blocks
			  . "No blocks in current buffer")
			 (ireplace-not-on-block
			  . "Point is not on a block")
			 (ireplace-not-on-header-line
			  . "Point is not on header line of a class")
			 (ireplace-no-classes
			  . "No classes in current buffer")
			 (ireplace-original-buffer-killed
			  . "The original buffer has been killed"))))
  (dolist (spec ireplace-errors)
    (let ((symbol (car spec))
	  (message (cdr spec)))
      (put symbol 'error-conditions `(error ireplace-errors ,symbol))
      (put symbol 'error-message message))))
\f
;; Ireplace major mode and user operations.

(defvar ireplace-mode-map
  (let ((map (make-sparse-keymap)))
    (suppress-keymap map t)
    (define-key map [?\C-m] 'ireplace-follow-block)
    (define-key map [?m] 'ireplace-toggle-block-marked)
    (define-key map [?n] 'ireplace-next-block)
    (define-key map [?p] 'ireplace-previous-block)
    (define-key map [?q] 'ireplace-quit)
    (define-key map [?r] 'ireplace-mark-blocks-reversely)
    (define-key map [?x] 'ireplace-perform-replace)
    (define-key map [?\M-n] 'ireplace-next-class)
    (define-key map [?\M-p] 'ireplace-previous-class)
    (define-key map [?\M-r] 'ireplace-mark-classes-reversely)
    map)
  "Keymap for `ireplace-mode'.")

(defvar ireplace-header-line-map
  (let ((map (make-sparse-keymap)))
    (suppress-keymap map t)
    (define-key map [?m] 'ireplace-toggle-class-marked)
    (define-key map [?\M-m] 'ireplace-toggle-class-marked)
    (define-key map [?\C-m] 'ireplace-toggle-class-body)
    (define-key map [?\M-\C-m] 'ireplace-toggle-class-body)
    map)
  "Keymap for `ireplace-mode', on class header line.")

(define-derived-mode ireplace-mode nil "Intelligent-Replace"
  "Major mode for intelligent replace.
\\{ireplace-mode-map}"
  :syntax-table nil
  :abbrev-table nil
  :group 'ireplace
  (make-local-variable 'pre-command-hook)
  (add-hook 'pre-command-hook 'ireplace-synchronize-with-original-buffer)
  (make-local-variable 'post-command-hook)
  (add-hook 'post-command-hook 'ireplace-update-original-buffer-position))

(defun ireplace-synchronize-with-original-buffer ()
  (let ((buffer (current-buffer))
	tick)
    (if (buffer-live-p ireplace-original-buffer)
	(progn
	  (with-current-buffer ireplace-original-buffer
	    (setq tick (buffer-chars-modified-tick)))
	  (if (not (equal tick ireplace-original-buffer-tick))
	      (progn
		(message "The original buffer has been changed, updating matches...")
		(let ((text ireplace-searched-text))
		  (with-current-buffer ireplace-original-buffer
		    (let* ((feature-specs (list ireplace-text-mode-spec))
			   (classes (ireplace-classify-matches feature-specs
							       text nil t nil)))
		      (if (null classes)
			  (message
			   (format "Text `%s' does not exist in buffer `%s' any more"
				   text (buffer-name)))
			(ireplace-setup-ireplace-buffer (current-buffer)
							buffer
							feature-specs
							text
							classes)))))
		(ireplace-setup-overlays-in-original-buffer)
		(message "The original buffer has been changed, updating matches... done"))
	    (ireplace-setup-overlays-in-original-buffer)))
      (message "The original buffer has been killed"))))

(defun ireplace-update-original-buffer-position ()
  (let ((block (ireplace-text-property-block 'ignore-error)))
    (when block
      (let* ((marker (car (ireplace-block-match block)))
	     (buffer (marker-buffer marker))
	     (pos (marker-position marker)))
	(when buffer
	  (let ((ireplace-buffer (current-buffer)))
	    (pop-to-buffer buffer)
	    (goto-char pos)
	    (pop-to-buffer ireplace-buffer)))))))

(defun ireplace-toggle-class-status (switch)
  (let ((class (ireplace-text-property-class))
	(show-body (ireplace-text-property-show-body)))
    (if (or (null class)
	    (null show-body))
	(signal 'ireplace-not-on-header-line (point)))
    (cond ((eq switch 'show-body)
	   (setq show-body (if (eq show-body 'on)
			       'off
			     'on)))
	  ((eq switch 'marked)
	   (ireplace-internal-toggle-class-marked class)))
    (ireplace-update-class-display class show-body)))

(defun ireplace-toggle-class-body ()
  (interactive)
  (ireplace-toggle-class-status 'show-body))

(defun ireplace-toggle-class-marked ()
  (interactive)
  (ireplace-toggle-class-status 'marked))

(defun ireplace-toggle-block-marked ()
  (interactive)
  (let ((class (ireplace-text-property-class))
	(block (ireplace-text-property-block 'ignore-error)))
    (if (or (null class)
	    (null block))
	(signal 'ireplace-not-on-block (point)))
    (ireplace-internal-toggle-block-marked class block)
    (ireplace-update-class-display class)))

(defun ireplace-mark-classes-reversely ()
  (interactive)
  (dolist (class ireplace-classes)
    (unless (eq (ireplace-class-marked class) 'partial)
      (ireplace-internal-toggle-class-marked class)))
  (ireplace-refresh))

(defun ireplace-mark-blocks-reversely ()
  (interactive)
  (let ((class (ireplace-text-property-class)))
    (dolist (block (ireplace-class-blocks class))
      (ireplace-internal-toggle-block-marked class block))
    (ireplace-update-class-display class)))

(defun ireplace-follow-block ()
  (interactive)
  (let ((block (ireplace-text-property-block)))
    (if (null block)
	(signal 'ireplace-not-on-block (point)))
    (let* ((marker (car (ireplace-block-match block)))
	   (buffer (marker-buffer marker))
	   (pos (marker-position marker)))
      (if (null buffer)
	  (signal 'ireplace-original-buffer-killed (current-buffer)))
      (pop-to-buffer buffer)
      (goto-char pos))))

(defun ireplace-perform-replace (newtext
				 &optional fixedcase literal string subexp)
  (interactive
   (progn
     (if (not (buffer-live-p ireplace-original-buffer))
	 (signal 'ireplace-original-buffer-killed (current-buffer)))
     (list (read-string "Replace text of marked classes/blocks with: "))))
  (let ((classes ireplace-classes)
	new-classes)
    (with-current-buffer ireplace-original-buffer
      (save-match-data
	(save-excursion
	  (dolist (class classes)
	    (let ((blocks (ireplace-class-blocks class))
		  new-blocks)
	      (dolist (block blocks)
		(if (ireplace-block-marked block)
		    (let ((match (ireplace-block-match block))
			  overlay)
		      (set-match-data match)
		      (goto-char (match-beginning 0))
		      (setq overlay (ireplace-get-overlay 'match))
		      (delete-overlay overlay)
		      (replace-match newtext fixedcase literal string subexp))
		  (setq new-blocks (cons block new-blocks))))
	      (if new-blocks
		  (setq new-classes (cons (ireplace-make-class new-blocks nil)
					  new-classes))))))))
    (ireplace-setup-ireplace-buffer ireplace-original-buffer
				    (current-buffer)
				    ireplace-feature-specs
				    ireplace-searched-text
				    new-classes)))

(defun ireplace (text)
  (interactive (list (read-string "Text for replacing: ")))
  (let* ((feature-specs (list ireplace-text-mode-spec))
	 (classes (ireplace-classify-matches feature-specs text nil t nil)))
    (if (null classes)
	(message (format "Text `%s' does not exist in current buffer" text))
      (let ((buffer (ireplace-get-buffer)))
	(ireplace-setup-ireplace-buffer (current-buffer)
					buffer
					feature-specs
					text
					classes
					(current-window-configuration))
	(delete-other-windows)
	(split-window-vertically)
	(other-window 1)
	(switch-to-buffer buffer)
	(ireplace-setup-overlays-in-original-buffer)
	(message (format "Found %d classes totally"
			 (length ireplace-classes)))))))

(defun ireplace-quit ()
  (interactive)
  (if (buffer-live-p ireplace-original-buffer)
      (with-current-buffer ireplace-original-buffer
	(ireplace-remove-all-overlays 'match)))
  (set-window-configuration ireplace-old-window-configuration)
  (message ""))

(defun ireplace-beginning-of-class ()
  (interactive)
  (goto-char (ireplace-class-beginning-position)))

(defun ireplace-end-of-class ()
  (interactive)
  (goto-char (ireplace-class-end-position)))

(defun ireplace-beginning-of-block ()
  (interactive)
  (goto-char (ireplace-block-beginning-position)))

(defun ireplace-end-of-block ()
  (interactive)
  (goto-char (ireplace-block-end-position)))

(defun ireplace-previous-class ()
  (interactive)
  (let ((on-header-line (ireplace-text-property-show-body)))
    (ireplace-beginning-of-class)
    (when (and on-header-line
	       (not (bobp)))
      (backward-char)
      (ireplace-beginning-of-class))))

(defun ireplace-next-class ()
  (interactive)
  (ireplace-end-of-class)
  (when (not (eobp))
    (forward-char)
    (ireplace-end-of-class))
  (ireplace-beginning-of-class))

(defun ireplace-previous-block-1 ()
  (let (do-not-move-again)
    (condition-case nil
	(ireplace-beginning-of-block)
      (ireplace-not-on-block
       (ireplace-beginning-of-class)
       (if (bobp)
	   ;; Point was on header line of the first class in current
	   ;; buffer.
	   (goto-char (1+ (ireplace-header-line-end-position)))
	 ;; Point was on header line of some class but the first one
	 ;; in current buffer.
	 (ireplace-previous-class)
	 (ireplace-end-of-class)
	 (ireplace-beginning-of-block))
       (setq do-not-move-again t))
      (error
       (signal 'ireplace-no-blocks (current-buffer))))
    do-not-move-again))

(defun ireplace-previous-block ()
  (interactive)
  (let ((do-not-move-again (ireplace-previous-block-1)))
    (unless do-not-move-again
      (backward-char)
      (ireplace-previous-block-1))))

(defun ireplace-next-block ()
  (interactive)
  (condition-case nil
      (progn
	(ireplace-end-of-block)
	(forward-char)
	(ireplace-end-of-block)
	(ireplace-beginning-of-block))
    (ireplace-not-on-block
     ;; Point was previously on class header line.
     (goto-char (1+ (ireplace-header-line-end-position))))
    (error
     (signal 'ireplace-no-blocks (current-buffer)))))
\f
(provide 'ireplace)

;;; ireplace.el ends here

[-- Attachment #3: Type: text/plain, Size: 142 bytes --]

_______________________________________________
Emacs-devel mailing list
Emacs-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/emacs-devel

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2007-05-06 22:26 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-04-29  8:32 A prototype of intelligent replace for Emacs Herbert Euler
2007-04-29 18:39 ` Drew Adams
2007-05-05  2:29   ` Herbert Euler
2007-04-30 22:09 ` Richard Stallman
2007-05-05  2:30   ` Herbert Euler
2007-05-05 23:18     ` Richard Stallman
2007-05-06 15:01       ` Herbert Euler
2007-05-06 22:26         ` Richard Stallman

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).