changeset 10516:488c5be866c3

(tempo-insert-template): Quoted transient-mark-mode Expansion around region now puts point at the first mark. (tempo-region-start, tempo-region-stop): New variables (tempo-insert-template, tempo-insert): Don't affect the mark. Check for Transient Mark mode (tempo-find-match-string): Removed the stupid 1+ again (tempo-use-tag-list): Set tempo-match-finder to completion-function. (tempo-match-finder): Renamed variable from tempo-default-match-finder. Change the value too. (tempo-collection, tempo-dirty-collection): New variables. (tempo-user-elements): New variable. (tempo-insert): New argument ON-REGION. New elements 'l and 'r. Use tempo-is-user-element. (tempo-is-user-element): New function. (tempo-invalidate-collection, tempo-build-collection): New functions. (tempo-find-match-string): Reinserted bugfix for re-search-backward. (tempo-complete-tag): Complete rewrite. (tempo-insert): Added the 'o tag.
author Richard M. Stallman <rms@gnu.org>
date Sun, 22 Jan 1995 06:18:46 +0000
parents d32348ef2b69
children 08e3895c1fdc
files lisp/tempo.el
diffstat 1 files changed, 205 insertions(+), 117 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/tempo.el	Sun Jan 22 02:21:32 1995 +0000
+++ b/lisp/tempo.el	Sun Jan 22 06:18:46 1995 +0000
@@ -1,9 +1,9 @@
-;;; tempo.el --- templates with hotspots
+;;; tempo.el --- Flexible template insertion
 ;; Copyright (C) 1994 Free Software Foundation, Inc.
 
 ;; Author: David K}gedal <davidk@lysator.liu.se >
 ;; Created: 16 Feb 1994
-;; Version: 1.1.1
+;; Version: 1.2
 ;; Keywords: extensions, languages, tools
 
 ;; This file is part of GNU Emacs.
@@ -71,18 +71,30 @@
 ;; be the template definition, and its function value will be an
 ;; interactive function that inserts the template at the point.
 
-;; Full documentation for tempo.el can be found on the World Wide Web
-;; at http://www.lysator.liu.se:7500/~davidk/tempo.html (not yet
-;; completed)
-
 ;; The latest tempo.el distribution can be fetched from
 ;; ftp.lysator.liu.se in the directory /pub/emacs
 
+;;; Known bugs:
+
+;; If the 'o is the first element in a template, strange things can
+;; happen when the template is inserted at the beginning of a
+;; line. This is due to strange behaviour in open-line. But it should
+;; be easily avoided.
+
+;; The 'o tag is also a problem when including the region. This will
+;; be looked into.
+
+;; Clicking mouse-2 in the completion buffer gives strange results.
+
+;; There is a bug in some emacs versions that prevents completion from
+;; working. If it doesn't work for you, send me a note indicating your
+;; emacs version and your problems.
+
 ;;; Code:
 
-(provide 'tempo)
+;; (provide 'tempo)
 
-;;; Variables
+;;; User options
 
 (defvar tempo-interactive nil
   "*Prompt user for strings in templates.
@@ -93,7 +105,9 @@
   "*Automatically insert current region when there is a `r' in the template
 If this variable is NIL, `r' elements will be treated just like `p'
 elements, unless the template function is given a prefix (or a non-nil
-argument). If this variable is non-NIL, the behaviour is reversed.")
+argument). If this variable is non-NIL, the behaviour is reversed.
+
+In Transient Mark mode, this option is unused.")
 
 (defvar tempo-show-completion-buffer t
   "*If non-NIL, show a buffer with possible completions, when only
@@ -103,6 +117,8 @@
   "*If NIL, a completion buffer generated by \\[tempo-complete-tag]
 disappears at the next keypress; otherwise, it remains forever.")
 
+;;; Internal variables
+
 (defvar tempo-insert-string-functions nil
   "List of functions to run when inserting a string.
 Each function is called with a single arg, STRING."  )
@@ -112,7 +128,6 @@
 
 (defvar tempo-local-tags '((tempo-tags . nil))
   "A list of locally installed tag completion lists.
-
 It is a association list where the car of every element is a symbol
 whose varable value is a template list. The cdr part, if non-nil, is a
 function or a regexp that defines the string to match. See the
@@ -120,19 +135,56 @@
 
 `tempo-tags' is always in the last position in this list.")
 
+(defvar tempo-collection nil
+  "A collection of all the tags defined for the current buffer.")
+
+(defvar tempo-dirty-collection t
+  "Indicates if the tag collection needs to be rebuilt.")
+
 (defvar tempo-marks nil
   "A list of marks to jump to with `\\[tempo-forward-mark]' and `\\[tempo-backward-mark]'.")
 
-(defvar tempo-default-match-finder "\\b\\([^\\b]*\\)\\="
-  "The default regexp used to find the string to match against the tags.")
+(defvar tempo-match-finder "\\b\\([^\\b]+\\)\\="
+  "The regexp or function used to find the string to match against tags.
+
+If `tempo-match-finder is a string, it should contain a regular
+expression with at least one \\( \\) pair. When searching for tags,
+`tempo-complete-tag' calls `re-search-backward' with this string, and
+the string between the first \\( and \\) is used for matching against
+each string in the tag list. If one is found, the whole text between
+the first \\( and the point is replaced with the inserted template.
+
+You will probably want to include \\ \= at the end of the regexp to
+make sure that the string is matched only against text adjacent to the
+point.
+
+If `tempo-match-finder' is a symbol, it should be a function that
+returns a pair of the form (STRING . POS), where STRING is the string
+used for matching and POS is the buffer position after which text
+should be replaced with a template.")
+
+(defvar tempo-user-elements nil
+  "Element handlers for user-defined elements.
+A list of symbols which are bound to functions that take one argument.
+This function should return somthing to be sent to `tempo-insert' if
+it recognizes the argument, and NIL otherwise")
 
 (defvar tempo-named-insertions nil
   "Temporary storage for named insertions")
 
+(defvar tempo-region-start (make-marker)
+  "Region start when inserting around the region")
+
+(defvar tempo-region-stop (make-marker)
+  "Region stop when inserting around the region")
+
 ;; Make some variables local to every buffer
 
 (make-variable-buffer-local 'tempo-marks)
 (make-variable-buffer-local 'tempo-local-tags)
+(make-variable-buffer-local 'tempo-match-finder)
+(make-variable-buffer-local 'tempo-collection)
+(make-variable-buffer-local 'tempo-dirty-collection)
 
 ;;; Functions
 
@@ -163,10 +215,11 @@
    prompted in the minbuffer with PROMPT for a string to be inserted.
    If the optional parameter NAME is non-nil, the text is saved for
    later insertion with the `s' tag.
-   If `tempo-interactive is nil, it works like 'p.
+   If `tempo-interactive' is nil, it works like 'p.
  - (r PROMPT) like the previous, but if `tempo-interactive' is nil
    and `tempo-insert' is called with ON-REGION non-nil, the current
-   region is placed here.
+   region is placed here. This usually happens when you call the
+   template function with a prefix argument.
  - (s NAME) Inserts text previously read with the (p ..) construct.
    Finds the insertion saved under NAME and inserts it. Acts like 'p
    if tempo-interactive is nil.
@@ -178,7 +231,8 @@
  - '> The line is indented using `indent-according-to-mode'. Note that
    you often should place this item after the text you want on the
    line.
- - 'n> inserts a newline and indents line.
+ - 'n> Inserts a newline and indents line.
+ - 'o Like '% but leaves the point before the newline.
  - nil. It is ignored.
  - Anything else. It is evaluated and the result is parsed again."
 
@@ -204,40 +258,62 @@
 (defun tempo-insert-template (template on-region)
   "Insert a template.
 TEMPLATE is the template to be inserted.  If ON-REGION is non-nil the
-`r' elements are replaced with the current region."
+`r' elements are replaced with the current region. In Transient Mark
+mode, ON-REGION is ignored and assumed true if the region is active."
+  (if (and (boundp 'transient-mark-mode)
+	   transient-mark-mode
+	   mark-active)
+      (setq on-region t))
   (and on-region
-       (< (mark) (point))
-       (exchange-point-and-mark))
+       (set-marker tempo-region-start (min (mark) (point)))
+       (set-marker tempo-region-stop (max (mark) (point))))
+  (if on-region
+      (goto-char tempo-region-start))
   (save-excursion
     (tempo-insert-mark (point-marker))
-    (mapcar 'tempo-insert 
+    (mapcar (function (lambda (elt)
+			(tempo-insert elt on-region)))
 	    (symbol-value template))
     (tempo-insert-mark (point-marker)))
   (tempo-forward-mark)
-  (tempo-forget-insertions))
+  (tempo-forget-insertions)
+  (and (boundp 'transient-mark-mode)
+       transient-mark-mode
+       (deactivate-mark)))
 
 ;;;
 ;;; tempo-insert
 
-(defun tempo-insert (element) 
+(defun tempo-insert (element on-region)
   "Insert a template element.
-Insert one element from a template. See documentation for
-`tempo-define-template' for the kind of elements possible."
+Insert one element from a template. If ON-REGION is non-nil the `r'
+elements are replaced with the current region.
+
+See documentation for `tempo-define-template' for the kind of elements
+possible."
   (cond ((stringp element) (tempo-process-and-insert-string element))
 	((and (consp element) (eq (car element) 'p))
 	 (tempo-insert-prompt (cdr element)))
 	((and (consp element) (eq (car element) 'r))
 	 (if on-region
-	     (exchange-point-and-mark)
+	     (goto-char tempo-region-stop)
 	   (tempo-insert-prompt (cdr element))))
 	((and (consp element) (eq (car element) 's))
 	 (if tempo-interactive
 	     (tempo-insert-named (cdr element))
 	   (tempo-insert-mark (point-marker))))
+	((and (consp element) (eq (car element) 'l))
+	 (mapcar (function (lambda (elt) (tempo-insert elt on-region)))
+		 (cdr element)))
 	((eq element 'p) (tempo-insert-mark (point-marker)))
 	((eq element 'r) (if on-region
-			     (exchange-point-and-mark)
+			     (goto-char tempo-region-stop)
 			   (tempo-insert-mark (point-marker))))
+	((eq element 'r>) (if on-region
+			      (progn
+				(goto-char tempo-region-stop)
+				(indent-region (mark) (point) nil))
+			    (tempo-insert-mark (point-marker))))
 	((eq element '>) (indent-according-to-mode))
 	((eq element '&) (if (not (or (= (current-column) 0)
 				      (save-excursion
@@ -251,8 +327,19 @@
 			     (insert "\n")))
 	((eq element 'n) (insert "\n"))
 	((eq element 'n>) (insert "\n") (indent-according-to-mode))
+	;; Bug: If the 'o is the first element in a template, strange
+	;; things can happen when the template is inserted at the
+	;; beginning of a line.
+	((eq element 'o) (if (not (or on-region
+				      (eolp)
+				      (save-excursion
+					(re-search-forward
+					 "\\=\\s-*$" nil t))))
+			     (open-line 1)))
 	((null element))
-	(t (tempo-insert (eval element)))))
+	(t (tempo-insert (or (tempo-is-user-element element)
+			     (eval element))
+			 on-region))))
 
 ;;;
 ;;; tempo-insert-prompt
@@ -281,6 +368,20 @@
     (tempo-insert-mark (point-marker))))
 
 ;;;
+;;; tempo-is-user-element
+
+(defun tempo-is-user-element (element)
+  "Tries all the user-defined element handlers in
+`tempo-user-elements'"
+  ;; Sigh... I need (some list)
+  (catch 'found
+    (mapcar (function (lambda (handler)
+			(let ((result (funcall handler element)))
+			  (if result (throw 'found result)))))
+	    tempo-user-elements)
+    (throw 'found nil)))
+
+;;;
 ;;; tempo-remember-insertion
 
 (defun tempo-remember-insertion (save-name string)
@@ -384,7 +485,6 @@
 
 (defun tempo-add-tag (tag template &optional tag-list)
   "Add a template tag.
-
 Add the TAG, that should complete to TEMPLATE to the list in TAG-LIST,
 or to `tempo-tags' if TAG-LIST is nil."
 
@@ -392,52 +492,70 @@
   (if (null tag-list)
       (setq tag-list 'tempo-tags))
   (if (not (assoc tag (symbol-value tag-list)))
-      (set tag-list (cons (cons tag template) (symbol-value tag-list)))))
+      (set tag-list (cons (cons tag template) (symbol-value tag-list))))
+  (tempo-invalidate-collection))
 
 ;;;
 ;;; tempo-use-tag-list
 
 (defun tempo-use-tag-list (tag-list &optional completion-function)
   "Install TAG-LIST to be used for template completion in the current buffer.
-
 TAG-LIST is a symbol whose variable value is a tag list created with
-`tempo-add-tag' and COMPLETION-FUNCTION is an optional function or
-string that is used by `\\[tempo-complete-tag]' to find a string to
-match the tag against.
+`tempo-add-tag'.
 
-If COMPLETION-FUNCTION is a string, it should contain a regular
-expression with at least one \\( \\) pair. When searching for tags,
-`tempo-complete-tag' calls `re-search-backward' with this string, and
-the string between the first \\( and \\) is used for matching against
-each string in the tag list. If one is found, the whole text between
-the first \\( and the point is replaced with the inserted template.
-
-You will probably want to include \\ \= at the end of the regexp to make
-sure that the string is matched only against text adjacent to the
-point.
-
-If COPMLETION-FUNCTION is a symbol, it should be a function that
-returns a cons cell of the form (STRING . POS), where STRING is the
-string used for matching and POS is the buffer position after which
-text should be replaced with a template."
-
+COMPLETION-FUNCTION is an obsolete option for specifyingis an optional
+function or string that is used by `\\[tempo-complete-tag]' to find a
+string to match the tag against. It has the same definition as the
+variable `tempo-match-finder'. In this version, supplying a
+COMPLETION-FUNCTION just sets `tempo-match-finder' locally."
   (let ((old (assq tag-list tempo-local-tags)))
     (if old
 	(setcdr old completion-function)
       (setq tempo-local-tags (cons (cons tag-list completion-function)
-				   tempo-local-tags)))))
+				   tempo-local-tags))))
+  (if completion-function
+      (setq tempo-match-finder completion-function))
+  (tempo-invalidate-collection))
+
+;;;
+;;; tempo-invalidate-collection
+
+(defun tempo-invalidate-collection ()
+  "Marks the tag collection as obsolete.
+Whenever it is needed again it will be rebuilt."
+  (setq tempo-dirty-collection t))
+
+;;;
+;;; tempo-build-collection
+
+(defun tempo-build-collection ()
+  "Build a collection of all the tags and return it.
+If `tempo-dirty-collection' is NIL, the old collection is reused."
+  (setq tempo-dirty nil)
+  (or (and (not tempo-dirty-collection)
+	   tempo-collection)
+      (setq tempo-collection
+	    (apply (function append)
+		   (mapcar (function (lambda (tag-list)
+					; If the format for
+					; tempo-local-tags changes,
+					; change this
+				       (eval (car tag-list))))
+			   tempo-local-tags)))))
 
 ;;;
 ;;; tempo-find-match-string
 
 (defun tempo-find-match-string (finder)
   "Find a string to be matched against a tag list.
-
 FINDER is a function or a string. Returns (STRING . POS)."
   (cond ((stringp finder)
 	 (save-excursion
-	   (re-search-backward finder nil t))
-	 (cons (buffer-substring (match-beginning 1) (1+ (match-end 1)))
+	   (or (re-search-backward finder nil t)
+	       0))
+	 (cons (buffer-substring (match-beginning 1)
+				 (match-end 1)) ; This seems to be a
+						; bug in emacs
 	       (match-beginning 1)))
 	(t
 	 (funcall finder))))
@@ -447,76 +565,44 @@
 
 (defun tempo-complete-tag (&optional silent)
   "Look for a tag and expand it.
-
-It goes through the tag lists in `tempo-local-tags' (this includes
-`tempo-tags') and for each list it uses the corresponding match-finder
-function, or `tempo-default-match-finder' if none is given, and tries
-to match the match string against the tags in the list using
-`try-completion'. If none is found it proceeds to the next list until
-one is found. If a partial completion is found, it is replaced by the
-template if it can be completed uniquely, or completed as far as
-possible.
+All the tags in the tag lists in `tempo-local-tags' (this includes
+`tempo-tags') are searched for a match for the text before the point.
+The way the string to match for is determined can be altered with the
+variable `tempo-match-finder'
 
-When doing partial completion, only tags in the currently examined
-list are considered, so if you provide similar tags in different lists
-in `tempo-local-tags', the result may not be desirable.
+If a single match is found, the corresponding template is expanded in
+place of the matching string.
 
-If no match is found or a partial match is found, and SILENT is
-non-nil, the function will give a signal.
+If a partial completion or no match at all is found, and SILENT is
+non-NIL, the function will give a signal.
 
-If tempo-show-completion-buffer is non-NIL, a buffer containing
-possible completions is displayed when a partial completion is found."
+If a partial completion is found and `tempo-show-completion-buffer' is
+non-NIL, a buffer containing possible completions is displayed."
 
-  ;; This function is really messy. Some cleaning up is necessary.
-  (interactive)
-  (if (catch 'completed
-	(mapcar
-	 (function
-	  (lambda (tag-list-a)
-	    (let* ((tag-list (symbol-value(car tag-list-a)))
-		   (match-string-finder (or (cdr tag-list-a)
-					    tempo-default-match-finder))
-		   (match-info (tempo-find-match-string match-string-finder))
-		   (match-string (car match-info))
-		   (match-start (cdr match-info))
-		   (compl (or (cdr (assoc match-string tag-list))
-			      (try-completion match-string
-					      tag-list))))
-	
-	      (if compl			;any match
-		  (delete-region match-start (point)))
+  ;; This function may look like a hack, but this is how I want it to
+  ;; work.
+  (interactive "*")
+  (let* ((collection (tempo-build-collection))
+	 (match-info (tempo-find-match-string tempo-match-finder))
+	 (match-string (car match-info))
+	 (match-start (cdr match-info))
+	 (exact (assoc match-string collection))
+	 (compl (or (car exact)
+		    (try-completion match-string collection))))
+    (if compl (delete-region match-start (point)))
+    (cond ((null compl) (or silent (ding)))
+	  ((eq compl t) (tempo-insert-template
+			 (cdr (assoc match-string
+				     collection))
+			 nil))
+	  (t (if (setq exact (assoc compl collection))
+		 (tempo-insert-template (cdr exact) nil)
+	       (insert compl)
+	       (or silent (ding))
+	       (if tempo-show-completion-buffer
+		   (tempo-display-completions match-string
+					      collection)))))))
 
-	      (cond
-	       ((null compl)		; No match
-		nil)
-	       ((symbolp compl)		; ??
-		(tempo-insert-template compl nil)
-		(throw 'completed t))
-	       ((eq compl t)		; Exact, sole match
-		(tempo-insert-template (cdr (assoc match-string tag-list))
-				       nil)
-		(throw 'completed t))
-	       ((stringp compl)		; (partial) completion found
-		(let ((compl2 (assoc compl tag-list)))
-		  (if compl2
-		      (tempo-insert-template (cdr compl2) nil)
-		    (insert compl)
-		    (if t ;(string= match-string compl)
-			(if tempo-show-completion-buffer
-			    (tempo-display-completions match-string
-						       tag-list)
-			  (if (not silent)
-			      (ding))))))
-		(throw 'completed t))))))
-	 tempo-local-tags)
-	;; No completion found. Return nil
-	nil)
-      ;; Do nothing if a completion was found
-      t
-    ;; No completion was found
-    (if (not silent)
-	(ding))
-    nil))
 
 ;;;
 ;;; tempo-display-completions
@@ -533,4 +619,6 @@
 	 (all-completions string tag-list)))
       (sit-for 32767))))
 
+(provide 'tempo)
+
 ;;; tempo.el ends here