1 ;;; tempo.el --- templates with hotspots
2 ;; Copyright (C) 1994 Free Software Foundation, Inc.
4 ;; Author: David K}gedal <davidk@lysator.liu.se >
5 ;; Created: 16 Feb 1994
7 ;; Keywords: extensions, languages, tools
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to
23 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
27 ;; This file provides a simple way to define powerful templates, or
28 ;; macros, if you wish. It is mainly intended for, but not limited to,
29 ;; other programmers to be used for creating shortcuts for editing
30 ;; certain kind of documents. It was originally written to be used by
31 ;; a HTML editing mode written by Nelson Minar <nelson@reed.edu>, and
32 ;; his html-helper-mode.el is probably the best example of how to use
35 ;; A template is defined as a list of items to be inserted in the
36 ;; current buffer at point. Some of the items can be simple strings,
37 ;; while other can control formatting or define special points of
38 ;; interest in the inserted text.
40 ;; If a template defines a "point of interest" that point is inserted
41 ;; in a buffer-local list of "points of interest" that the user can
42 ;; jump between with the commands `tempo-backward-mark' and
43 ;; `tempo-forward-mark'. If the template definer provides a prompt for
44 ;; the point, and the variable `tempo-interactive' is non-nil, the
45 ;; user will be prompted for a string to be inserted in the buffer,
46 ;; using the minibuffer.
48 ;; The template can also define one point to be replaced with the
49 ;; current region if the template command is called with a prefix (or
50 ;; a non-nil argument).
52 ;; More flexible templates can be created by including lisp symbols,
53 ;; which will be evaluated as variables, or lists, which will will be
54 ;; evaluated as lisp expressions.
56 ;; See the documentation for tempo-define-template for the different
57 ;; items that can be used to define a tempo template.
59 ;; One of the more powerful features of tempo templates are automatic
60 ;; completion. With every template can be assigned a special tag that
61 ;; should be recognized by `tempo-complete-tag' and expanded to the
62 ;; complete template. By default the tags are added to a global list
63 ;; of template tags, and are matched against the last word before
64 ;; point. But if you assign your tags to a specific list, you can also
65 ;; specify another method for matching text in the buffer against the
66 ;; tags. In the HTML mode, for instance, the tags are matched against
67 ;; the text between the last `<' and point.
69 ;; When defining a template named `foo', a symbol named
70 ;; `tempo-template-foo' will be created whose value as a variable will
71 ;; be the template definition, and its function value will be an
72 ;; interactive function that inserts the template at the point.
74 ;; Full documentation for tempo.el can be found on the World Wide Web
75 ;; at http://www.lysator.liu.se:7500/~davidk/tempo.html (not yet
78 ;; The latest tempo.el distribution can be fetched from
79 ;; ftp.lysator.liu.se in the directory /pub/emacs
87 (defvar tempo-interactive nil
88 "*Prompt user for strings in templates.
89 If this variable is non-nil, `tempo-insert' prompts the
90 user for text to insert in the templates")
92 (defvar tempo-insert-string-functions nil
93 "List of functions to run when inserting a string.
94 Each function is called with a single arg, STRING." )
96 (defvar tempo-tags nil
97 "An association list with tags and corresponding templates")
99 (defvar tempo-local-tags '((tempo-tags . nil))
100 "A list of locally installed tag completion lists.
102 It is a association list where the car of every element is a symbol
103 whose varable value is a template list. The cdr part, if non-nil, is a
104 function or a regexp that defines the string to match. See the
105 documentation for the function `tempo-complete-tag' for more info.
107 `tempo-tags' is always in the last position in this list.")
109 (defvar tempo-marks nil
110 "A list of marks to jump to with `\\[tempo-forward-mark]' and `\\[tempo-backward-mark]'.")
112 (defvar tempo-default-match-finder "\\b\\([^\\b]*\\)\\="
113 "The default regexp used to find the string to match against the tags.")
115 ;; Make some variables local to every buffer
117 (make-variable-buffer-local 'tempo-marks)
118 (make-variable-buffer-local 'tempo-local-tags)
123 ;; tempo-define-template
125 (defun tempo-define-template (name elements &optional tag documentation taglist)
127 This function creates a template variable `tempo-template-NAME' and an
128 interactive function `tempo-template-NAME' that inserts the template
129 at the point. The created function is returned.
131 NAME is a string that contains the name of the template, ELEMENTS is a
132 list of elements in the template, TAG is the tag used for completion,
133 DOCUMENTATION is the documentation string for the insertion command
134 created, and TAGLIST (a symbol) is the tag list that TAG (if provided)
135 should be added to). If TAGLIST is nil and TAG is non-nil, TAG is
136 added to `tempo-tags'
138 The elements in ELEMENTS can be of several types:
140 - A string. It is sent to the hooks in `tempo-insert-string-functions',
141 and the result is inserted.
142 - The symbol 'p. This position is saved in `tempo-marks'.
143 - The symbol 'r. If `tempo-insert' is called with ON-REGION non-nil
144 the current region is placed here. Otherwise it works like 'p.
145 - (p . PROMPT) If `tempo-interactive' is non-nil, the user is
146 prompted in the minbuffer with PROMPT for a string to be inserted.
147 If `tempo-interactive is nil, it works like 'p.
148 - (r . PROMPT) like the previou, but if `tempo-interactive' is nil
149 and `tempo-insert' is called with ON-REGION non-nil, the current
150 region is placed here.
151 - '& If there is only whitespace between the line start and point,
152 nothing happens. Otherwise a newline is inserted.
153 - '% If there is only whitespace between point and end-of-line
154 nothing happens. Otherwise a newline is inserted.
155 - 'n inserts a newline.
156 - '> The line is indented using `indent-according-to-mode'. Note that
157 you often should place this item after the text you want on the
159 - 'n> inserts a newline and indents line.
160 - nil. It is ignored.
161 - Anything else. It is evaluated and the result is parsed again."
163 (let* ((template-name (intern (concat "tempo-template-"
165 (command-name template-name))
166 (set template-name elements)
167 (fset command-name (list 'lambda (list '&optional 'arg)
169 (concat "Insert a " name "."))
170 (list 'interactive "*P")
171 (list 'tempo-insert-template (list 'quote
175 (tempo-add-tag tag template-name taglist))
179 ;;; tempo-insert-template
181 (defun tempo-insert-template (template on-region)
183 TEMPLATE is the template to be inserted. If ON-REGION is non-nil the
184 `r' elements are replaced with the current region."
187 (exchange-point-and-mark))
189 (tempo-insert-mark (point-marker))
190 (mapcar 'tempo-insert
191 (symbol-value template))
192 (tempo-insert-mark (point-marker)))
193 (tempo-forward-mark))
198 (defun tempo-insert (element)
199 "Insert a template element.
200 Insert one element from a template. See documentation for
201 `tempo-define-template' for the kind of elements possible."
202 (cond ((stringp element) (tempo-process-and-insert-string element))
203 ((and (consp element) (eq (car element) 'p))
204 (tempo-insert-prompt (cdr element)))
205 ((and (consp element) (eq (car element) 'r))
207 (exchange-point-and-mark)
208 (tempo-insert-prompt (cdr element))))
209 ((eq element 'p) (tempo-insert-mark (point-marker)))
210 ((eq element 'r) (if on-region
211 (exchange-point-and-mark)
212 (tempo-insert-mark (point-marker))))
213 ((eq element '>) (indent-according-to-mode))
214 ((eq element '&) (if (not (or (= (current-column) 0)
217 "^\\s-*\\=" nil t))))
219 ((eq element '%) (if (not (or (eolp)
222 "\\=\\s-*$" nil t))))
224 ((eq element 'n) (insert "\n"))
225 ((eq element 'n>) (insert "\n") (indent-according-to-mode))
227 (t (tempo-insert (eval element)))))
230 ;;; tempo-insert-prompt
232 (defun tempo-insert-prompt (prompt)
233 "Prompt for a text string and insert it in the current buffer.
234 If the variable `tempo-interactive' is non-nil the user is prompted
235 for a string in the minibuffer, which is then inserted in the current
236 buffer. If `tempo-interactive' is nil, the current point is placed on
237 `tempo-forward-mark-list'.
239 PROMPT is the prompt string."
240 (if tempo-interactive
241 (insert (read-string prompt))
242 (tempo-insert-mark (point-marker))))
245 ;;; tempo-process-and-insert-string
247 (defun tempo-process-and-insert-string (string)
248 "Insert a string from a template.
249 Run a string through the preprocessors in `tempo-insert-string-functions'
250 and insert the results."
252 (cond ((null tempo-insert-string-functions)
254 ((symbolp tempo-insert-string-functions)
256 (apply tempo-insert-string-functions (list string))))
257 ((listp tempo-insert-string-functions)
258 (mapcar (function (lambda (fn)
259 (setq string (apply fn string))))
260 tempo-insert-string-functions))
262 (error "Bogus value in tempo-insert-string-functions: %s"
263 tempo-insert-string-functions)))
267 ;;; tempo-insert-mark
269 (defun tempo-insert-mark (mark)
270 "Insert a mark `tempo-marks' while keeping it sorted"
271 (cond ((null tempo-marks) (setq tempo-marks (list mark)))
272 ((< mark (car tempo-marks)) (setq tempo-marks (cons mark tempo-marks)))
273 (t (let ((lp tempo-marks))
275 (<= (car (cdr lp)) mark))
277 (if (not (= mark (car lp)))
278 (setcdr lp (cons mark (cdr lp))))))))
281 ;;; tempo-forward-mark
283 (defun tempo-forward-mark ()
284 "Jump to the next mark in `tempo-forward-mark-list'."
286 (let ((next-mark (catch 'found
291 (throw 'found mark))))
293 ;; return nil if not found
296 (goto-char next-mark))))
299 ;;; tempo-backward-mark
301 (defun tempo-backward-mark ()
302 "Jump to the previous mark in `tempo-back-mark-list'."
304 (let ((prev-mark (catch 'found
309 (if (<= (point) mark)
315 (goto-char prev-mark))))
320 (defun tempo-add-tag (tag template &optional tag-list)
323 Add the TAG, that should complete to TEMPLATE to the list in TAG-LIST,
324 or to `tempo-tags' if TAG-LIST is nil."
326 (interactive "sTag: \nCTemplate: ")
328 (setq tag-list 'tempo-tags))
329 (if (not (assoc tag (symbol-value tag-list)))
330 (set tag-list (cons (cons tag template) (symbol-value tag-list)))))
333 ;;; tempo-use-tag-list
335 (defun tempo-use-tag-list (tag-list &optional completion-function)
336 "Install TAG-LIST to be used for template completion in the current buffer.
338 TAG-LIST is a symbol whose variable value is a tag list created with
339 `tempo-add-tag' and COMPLETION-FUNCTION is an optional function or
340 string that is used by `\\[tempo-complete-tag]' to find a string to
341 match the tag against.
343 If COMPLETION-FUNCTION is a string, it should contain a regular
344 expression with at least one \\( \\) pair. When searching for tags,
345 `tempo-complete-tag' calls `re-search-backward' with this string, and
346 the string between the first \\( and \\) is used for matching against
347 each string in the tag list. If one is found, the whole text between
348 the first \\( and the point is replaced with the inserted template.
350 You will probably want to include \\ \= at the end of the regexp to make
351 sure that the string is matched only against text adjacent to the
354 If COPMLETION-FUNCTION is a symbol, it should be a function that
355 returns a cons cell of the form (STRING . POS), where STRING is the
356 string used for matching and POS is the buffer position after which
357 text should be replaced with a template."
359 (let ((old (assq tag-list tempo-local-tags)))
361 (setcdr old completion-function)
362 (setq tempo-local-tags (cons (cons tag-list completion-function)
363 tempo-local-tags)))))
366 ;;; tempo-find-match-string
368 (defun tempo-find-match-string (finder)
369 "Find a string to be matched against a tag list.
371 FINDER is a function or a string. Returns (STRING . POS)."
372 (cond ((stringp finder)
374 (re-search-backward finder nil t))
375 (cons (buffer-substring (match-beginning 1) (1+ (match-end 1)))
376 (match-beginning 1)))
381 ;;; tempo-complete-tag
383 (defun tempo-complete-tag (&optional silent)
384 "Look for a tag and expand it..
386 It goes through the tag lists in `tempo-local-tags' (this includes
387 `tempo-tags') and for each list it uses the corresponding match-finder
388 function, or `tempo-default-match-finder' if none is given, and tries
389 to match the match string against the tags in the list using
390 `try-completion'. If none is found it proceeds to the next list until
391 one is found. If a partial completion is found, it is replaced by the
392 template if it can be completed uniquely, or completed as far as
395 When doing partial completion, only tags in the currently examined
396 list are considered, so if you provide similar tags in different lists
397 in `tempo-local-tags', the result may not be desirable.
399 If no match is found or a partial match is found, and SILENT is
400 non-nil, the function will give a signal."
403 (if (catch 'completed
407 (let* ((tag-list (symbol-value(car tag-list-a)))
408 (match-string-finder (or (cdr tag-list-a)
409 tempo-default-match-finder))
410 (match-info (tempo-find-match-string match-string-finder))
411 (match-string (car match-info))
412 (match-start (cdr match-info))
413 (compl (or (cdr (assoc match-string tag-list))
414 (try-completion (car match-info)
418 (delete-region match-start (point)))
424 (tempo-insert-template compl nil)
425 (throw 'completed t))
427 (tempo-insert-template (cdr (assoc match-string tag-list))
429 (throw 'completed t))
431 (let ((compl2 (assoc compl tag-list)))
433 (tempo-insert-template (cdr compl2) nil)
435 (if (string= match-string compl)
438 (throw 'completed t))))))
440 ;; No completion found. Return nil
442 ;; Do nothing if a completion was found
444 ;; No completion was found
449 ;;; tempo.el ends here