(Frename_buffer): Rename arg NAME to NEWNAME.
[bpt/emacs.git] / lisp / tempo.el
1 ;;; tempo.el --- templates with hotspots
2 ;; Copyright (C) 1994 Free Software Foundation, Inc.
3
4 ;; Author: David K}gedal <davidk@lysator.liu.se >
5 ;; Created: 16 Feb 1994
6 ;; Version: 1.1.1
7 ;; Keywords: extensions, languages, tools
8
9 ;; This file is part of GNU Emacs.
10
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)
14 ;; any later version.
15
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.
20
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.
24
25 ;;; Commentary:
26
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
33 ;; this program.
34
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.
39
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.
47
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).
51
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.
55
56 ;; See the documentation for tempo-define-template for the different
57 ;; items that can be used to define a tempo template.
58
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.
68
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.
73
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
76 ;; completed)
77
78 ;; The latest tempo.el distribution can be fetched from
79 ;; ftp.lysator.liu.se in the directory /pub/emacs
80
81 ;;; Code:
82
83 (provide 'tempo)
84
85 ;;; Variables
86
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")
91
92 (defvar tempo-insert-region nil
93 "*Automatically insert current region when there is a `r' in the template
94 If this variable is NIL, `r' elements will be treated just like `p'
95 elements, unless the template function is given a prefix (or a non-nil
96 argument). If this variable is non-NIL, the behaviour is reversed.")
97
98 (defvar tempo-show-completion-buffer t
99 "*If non-NIL, show a buffer with possible completions, when only
100 a partial completion can be found")
101
102 (defvar tempo-leave-completion-buffer nil
103 "*If NIL, a completion buffer generated by \\[tempo-complete-tag]
104 disappears at the next keypress; otherwise, it remains forever.")
105
106 (defvar tempo-insert-string-functions nil
107 "List of functions to run when inserting a string.
108 Each function is called with a single arg, STRING." )
109
110 (defvar tempo-tags nil
111 "An association list with tags and corresponding templates")
112
113 (defvar tempo-local-tags '((tempo-tags . nil))
114 "A list of locally installed tag completion lists.
115
116 It is a association list where the car of every element is a symbol
117 whose varable value is a template list. The cdr part, if non-nil, is a
118 function or a regexp that defines the string to match. See the
119 documentation for the function `tempo-complete-tag' for more info.
120
121 `tempo-tags' is always in the last position in this list.")
122
123 (defvar tempo-marks nil
124 "A list of marks to jump to with `\\[tempo-forward-mark]' and `\\[tempo-backward-mark]'.")
125
126 (defvar tempo-default-match-finder "\\b\\([^\\b]*\\)\\="
127 "The default regexp used to find the string to match against the tags.")
128
129 (defvar tempo-named-insertions nil
130 "Temporary storage for named insertions")
131
132 ;; Make some variables local to every buffer
133
134 (make-variable-buffer-local 'tempo-marks)
135 (make-variable-buffer-local 'tempo-local-tags)
136
137 ;;; Functions
138
139 ;;
140 ;; tempo-define-template
141
142 (defun tempo-define-template (name elements &optional tag documentation taglist)
143 "Define a template.
144 This function creates a template variable `tempo-template-NAME' and an
145 interactive function `tempo-template-NAME' that inserts the template
146 at the point. The created function is returned.
147
148 NAME is a string that contains the name of the template, ELEMENTS is a
149 list of elements in the template, TAG is the tag used for completion,
150 DOCUMENTATION is the documentation string for the insertion command
151 created, and TAGLIST (a symbol) is the tag list that TAG (if provided)
152 should be added to). If TAGLIST is nil and TAG is non-nil, TAG is
153 added to `tempo-tags'
154
155 The elements in ELEMENTS can be of several types:
156
157 - A string. It is sent to the hooks in `tempo-insert-string-functions',
158 and the result is inserted.
159 - The symbol 'p. This position is saved in `tempo-marks'.
160 - The symbol 'r. If `tempo-insert' is called with ON-REGION non-nil
161 the current region is placed here. Otherwise it works like 'p.
162 - (p PROMPT <NAME>) If `tempo-interactive' is non-nil, the user is
163 prompted in the minbuffer with PROMPT for a string to be inserted.
164 If the optional parameter NAME is non-nil, the text is saved for
165 later insertion with the `s' tag.
166 If `tempo-interactive is nil, it works like 'p.
167 - (r PROMPT) like the previous, but if `tempo-interactive' is nil
168 and `tempo-insert' is called with ON-REGION non-nil, the current
169 region is placed here.
170 - (s NAME) Inserts text previously read with the (p ..) construct.
171 Finds the insertion saved under NAME and inserts it. Acts like 'p
172 if tempo-interactive is nil.
173 - '& If there is only whitespace between the line start and point,
174 nothing happens. Otherwise a newline is inserted.
175 - '% If there is only whitespace between point and end-of-line
176 nothing happens. Otherwise a newline is inserted.
177 - 'n inserts a newline.
178 - '> The line is indented using `indent-according-to-mode'. Note that
179 you often should place this item after the text you want on the
180 line.
181 - 'n> inserts a newline and indents line.
182 - nil. It is ignored.
183 - Anything else. It is evaluated and the result is parsed again."
184
185 (let* ((template-name (intern (concat "tempo-template-"
186 name)))
187 (command-name template-name))
188 (set template-name elements)
189 (fset command-name (list 'lambda (list '&optional 'arg)
190 (or documentation
191 (concat "Insert a " name "."))
192 (list 'interactive "*P")
193 (list 'tempo-insert-template (list 'quote
194 template-name)
195 (list 'if 'tempo-insert-region
196 (list 'not 'arg) 'arg))))
197 (and tag
198 (tempo-add-tag tag template-name taglist))
199 command-name))
200
201 ;;;
202 ;;; tempo-insert-template
203
204 (defun tempo-insert-template (template on-region)
205 "Insert a template.
206 TEMPLATE is the template to be inserted. If ON-REGION is non-nil the
207 `r' elements are replaced with the current region."
208 (and on-region
209 (< (mark) (point))
210 (exchange-point-and-mark))
211 (save-excursion
212 (tempo-insert-mark (point-marker))
213 (mapcar 'tempo-insert
214 (symbol-value template))
215 (tempo-insert-mark (point-marker)))
216 (tempo-forward-mark)
217 (tempo-forget-insertions))
218
219 ;;;
220 ;;; tempo-insert
221
222 (defun tempo-insert (element)
223 "Insert a template element.
224 Insert one element from a template. See documentation for
225 `tempo-define-template' for the kind of elements possible."
226 (cond ((stringp element) (tempo-process-and-insert-string element))
227 ((and (consp element) (eq (car element) 'p))
228 (tempo-insert-prompt (cdr element)))
229 ((and (consp element) (eq (car element) 'r))
230 (if on-region
231 (exchange-point-and-mark)
232 (tempo-insert-prompt (cdr element))))
233 ((and (consp element) (eq (car element) 's))
234 (if tempo-interactive
235 (tempo-insert-named (cdr element))
236 (tempo-insert-mark (point-marker))))
237 ((eq element 'p) (tempo-insert-mark (point-marker)))
238 ((eq element 'r) (if on-region
239 (exchange-point-and-mark)
240 (tempo-insert-mark (point-marker))))
241 ((eq element '>) (indent-according-to-mode))
242 ((eq element '&) (if (not (or (= (current-column) 0)
243 (save-excursion
244 (re-search-backward
245 "^\\s-*\\=" nil t))))
246 (insert "\n")))
247 ((eq element '%) (if (not (or (eolp)
248 (save-excursion
249 (re-search-forward
250 "\\=\\s-*$" nil t))))
251 (insert "\n")))
252 ((eq element 'n) (insert "\n"))
253 ((eq element 'n>) (insert "\n") (indent-according-to-mode))
254 ((null element))
255 (t (tempo-insert (eval element)))))
256
257 ;;;
258 ;;; tempo-insert-prompt
259
260 (defun tempo-insert-prompt (prompt)
261 "Prompt for a text string and insert it in the current buffer.
262 If the variable `tempo-interactive' is non-nil the user is prompted
263 for a string in the minibuffer, which is then inserted in the current
264 buffer. If `tempo-interactive' is nil, the current point is placed on
265 `tempo-mark'.
266
267 PROMPT is the prompt string or a list containing the prompt string and
268 a name to save the inserted text under."
269 (if tempo-interactive
270 (let ((prompt-string (if (listp prompt)
271 (car prompt)
272 prompt))
273 (save-name (and (listp prompt) (nth 1 prompt)))
274 inserted-text)
275
276 (progn
277 (setq inserted-text (read-string prompt-string))
278 (insert inserted-text)
279 (if save-name
280 (tempo-remember-insertion save-name inserted-text))))
281 (tempo-insert-mark (point-marker))))
282
283 ;;;
284 ;;; tempo-remember-insertion
285
286 (defun tempo-remember-insertion (save-name string)
287 "Save the text in STRING under the name SAVE-NAME for later retrieval."
288 (setq tempo-named-insertions (cons (cons save-name string)
289 tempo-named-insertions)))
290
291 ;;;
292 ;;; tempo-forget-insertions
293
294 (defun tempo-forget-insertions ()
295 "Forget all the saved named insertions."
296 (setq tempo-named-insertions nil))
297
298 ;;;
299 ;;; tempo-insert-named
300
301 (defun tempo-insert-named (elt)
302 "Insert the previous insertion saved under a named specified in ELT.
303 The name is in the car of ELT."
304 (let* ((name (car elt))
305 (insertion (cdr (assq name tempo-named-insertions))))
306 (if insertion
307 (insert insertion)
308 (error "Named insertion not found"))))
309
310 ;;;
311 ;;; tempo-process-and-insert-string
312
313 (defun tempo-process-and-insert-string (string)
314 "Insert a string from a template.
315 Run a string through the preprocessors in `tempo-insert-string-functions'
316 and insert the results."
317 (cond ((null tempo-insert-string-functions)
318 nil)
319 ((symbolp tempo-insert-string-functions)
320 (setq string
321 (apply tempo-insert-string-functions (list string))))
322 ((listp tempo-insert-string-functions)
323 (mapcar (function (lambda (fn)
324 (setq string (apply fn string))))
325 tempo-insert-string-functions))
326 (t
327 (error "Bogus value in tempo-insert-string-functions: %s"
328 tempo-insert-string-functions)))
329 (insert string))
330
331 ;;;
332 ;;; tempo-insert-mark
333
334 (defun tempo-insert-mark (mark)
335 "Insert a mark `tempo-marks' while keeping it sorted"
336 (cond ((null tempo-marks) (setq tempo-marks (list mark)))
337 ((< mark (car tempo-marks)) (setq tempo-marks (cons mark tempo-marks)))
338 (t (let ((lp tempo-marks))
339 (while (and (cdr lp)
340 (<= (car (cdr lp)) mark))
341 (setq lp (cdr lp)))
342 (if (not (= mark (car lp)))
343 (setcdr lp (cons mark (cdr lp))))))))
344
345 ;;;
346 ;;; tempo-forward-mark
347
348 (defun tempo-forward-mark ()
349 "Jump to the next mark in `tempo-forward-mark-list'."
350 (interactive)
351 (let ((next-mark (catch 'found
352 (mapcar
353 (function
354 (lambda (mark)
355 (if (< (point) mark)
356 (throw 'found mark))))
357 tempo-marks)
358 ;; return nil if not found
359 nil)))
360 (if next-mark
361 (goto-char next-mark))))
362
363 ;;;
364 ;;; tempo-backward-mark
365
366 (defun tempo-backward-mark ()
367 "Jump to the previous mark in `tempo-back-mark-list'."
368 (interactive)
369 (let ((prev-mark (catch 'found
370 (let (last)
371 (mapcar
372 (function
373 (lambda (mark)
374 (if (<= (point) mark)
375 (throw 'found last))
376 (setq last mark)))
377 tempo-marks)
378 last))))
379 (if prev-mark
380 (goto-char prev-mark))))
381
382 ;;;
383 ;;; tempo-add-tag
384
385 (defun tempo-add-tag (tag template &optional tag-list)
386 "Add a template tag.
387
388 Add the TAG, that should complete to TEMPLATE to the list in TAG-LIST,
389 or to `tempo-tags' if TAG-LIST is nil."
390
391 (interactive "sTag: \nCTemplate: ")
392 (if (null tag-list)
393 (setq tag-list 'tempo-tags))
394 (if (not (assoc tag (symbol-value tag-list)))
395 (set tag-list (cons (cons tag template) (symbol-value tag-list)))))
396
397 ;;;
398 ;;; tempo-use-tag-list
399
400 (defun tempo-use-tag-list (tag-list &optional completion-function)
401 "Install TAG-LIST to be used for template completion in the current buffer.
402
403 TAG-LIST is a symbol whose variable value is a tag list created with
404 `tempo-add-tag' and COMPLETION-FUNCTION is an optional function or
405 string that is used by `\\[tempo-complete-tag]' to find a string to
406 match the tag against.
407
408 If COMPLETION-FUNCTION is a string, it should contain a regular
409 expression with at least one \\( \\) pair. When searching for tags,
410 `tempo-complete-tag' calls `re-search-backward' with this string, and
411 the string between the first \\( and \\) is used for matching against
412 each string in the tag list. If one is found, the whole text between
413 the first \\( and the point is replaced with the inserted template.
414
415 You will probably want to include \\ \= at the end of the regexp to make
416 sure that the string is matched only against text adjacent to the
417 point.
418
419 If COPMLETION-FUNCTION is a symbol, it should be a function that
420 returns a cons cell of the form (STRING . POS), where STRING is the
421 string used for matching and POS is the buffer position after which
422 text should be replaced with a template."
423
424 (let ((old (assq tag-list tempo-local-tags)))
425 (if old
426 (setcdr old completion-function)
427 (setq tempo-local-tags (cons (cons tag-list completion-function)
428 tempo-local-tags)))))
429
430 ;;;
431 ;;; tempo-find-match-string
432
433 (defun tempo-find-match-string (finder)
434 "Find a string to be matched against a tag list.
435
436 FINDER is a function or a string. Returns (STRING . POS)."
437 (cond ((stringp finder)
438 (save-excursion
439 (re-search-backward finder nil t))
440 (cons (buffer-substring (match-beginning 1) (1+ (match-end 1)))
441 (match-beginning 1)))
442 (t
443 (funcall finder))))
444
445 ;;;
446 ;;; tempo-complete-tag
447
448 (defun tempo-complete-tag (&optional silent)
449 "Look for a tag and expand it.
450
451 It goes through the tag lists in `tempo-local-tags' (this includes
452 `tempo-tags') and for each list it uses the corresponding match-finder
453 function, or `tempo-default-match-finder' if none is given, and tries
454 to match the match string against the tags in the list using
455 `try-completion'. If none is found it proceeds to the next list until
456 one is found. If a partial completion is found, it is replaced by the
457 template if it can be completed uniquely, or completed as far as
458 possible.
459
460 When doing partial completion, only tags in the currently examined
461 list are considered, so if you provide similar tags in different lists
462 in `tempo-local-tags', the result may not be desirable.
463
464 If no match is found or a partial match is found, and SILENT is
465 non-nil, the function will give a signal.
466
467 If tempo-show-completion-buffer is non-NIL, a buffer containing
468 possible completions is displayed when a partial completion is found."
469
470 ;; This function is really messy. Some cleaning up is necessary.
471 (interactive)
472 (if (catch 'completed
473 (mapcar
474 (function
475 (lambda (tag-list-a)
476 (let* ((tag-list (symbol-value(car tag-list-a)))
477 (match-string-finder (or (cdr tag-list-a)
478 tempo-default-match-finder))
479 (match-info (tempo-find-match-string match-string-finder))
480 (match-string (car match-info))
481 (match-start (cdr match-info))
482 (compl (or (cdr (assoc match-string tag-list))
483 (try-completion match-string
484 tag-list))))
485
486 (if compl ;any match
487 (delete-region match-start (point)))
488
489 (cond
490 ((null compl) ; No match
491 nil)
492 ((symbolp compl) ; ??
493 (tempo-insert-template compl nil)
494 (throw 'completed t))
495 ((eq compl t) ; Exact, sole match
496 (tempo-insert-template (cdr (assoc match-string tag-list))
497 nil)
498 (throw 'completed t))
499 ((stringp compl) ; (partial) completion found
500 (let ((compl2 (assoc compl tag-list)))
501 (if compl2
502 (tempo-insert-template (cdr compl2) nil)
503 (insert compl)
504 (if t ;(string= match-string compl)
505 (if tempo-show-completion-buffer
506 (tempo-display-completions match-string
507 tag-list)
508 (if (not silent)
509 (ding))))))
510 (throw 'completed t))))))
511 tempo-local-tags)
512 ;; No completion found. Return nil
513 nil)
514 ;; Do nothing if a completion was found
515 t
516 ;; No completion was found
517 (if (not silent)
518 (ding))
519 nil))
520
521 ;;;
522 ;;; tempo-display-completions
523
524 (defun tempo-display-completions (string tag-list)
525 "Show a buffer containing possible completions for STRING."
526 (if tempo-leave-completion-buffer
527 (with-output-to-temp-buffer "*Completions*"
528 (display-completion-list
529 (all-completions string tag-list)))
530 (save-window-excursion
531 (with-output-to-temp-buffer "*Completions*"
532 (display-completion-list
533 (all-completions string tag-list)))
534 (sit-for 32767))))
535
536 ;;; tempo.el ends here