(x_set_offset): Set gravity and size hint
[bpt/emacs.git] / lisp / tempo.el
CommitLineData
813f532d
RS
1;;; tempo.el --- templates with hotspots
2;; Copyright (C) 1994 Free Software Foundation, Inc.
3
8f41fd64 4;; Author: David K}gedal <davidk@lysator.liu.se >
813f532d
RS
5;; Created: 16 Feb 1994
6;; Version: 1.0
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.
89If this variable is non-nil, `tempo-insert' prompts the
90user for text to insert in the templates")
91
92(defvar tempo-insert-string-functions nil
93 "List of functions to run when inserting a string.
94Each function is called with a single arg, STRING." )
95
96(defvar tempo-tags nil
97 "An association list with tags and corresponding templates")
98
99(defvar tempo-local-tags '((tempo-tags . nil))
100 "A list of locally installed tag completion lists.
101
102It is a association list where the car of every element is a symbol
103whose varable value is a template list. The cdr part, if non-nil, is a
104function or a regexp that defines the string to match. See the
105documentation for the function `tempo-complete-tag' for more info.
106
107`tempo-tags' is always in the last position in this list.")
108
109(defvar tempo-marks nil
110 "A list of marks to jump to with `\\[tempo-forward-mark]' and `\\[tempo-backward-mark]'.")
111
112(defvar tempo-default-match-finder "\\b\\([^\\b]*\\)\\="
113 "The default regexp used to find the string to match against the tags.")
114
115;; Make some variables local to every buffer
116
117(make-variable-buffer-local 'tempo-marks)
118(make-variable-buffer-local 'tempo-local-tags)
119
120;;; Functions
121
122;;
123;; tempo-define-template
124
125(defun tempo-define-template (name elements &optional tag documentation taglist)
126 "Define a template.
127This function creates a template variable `tempo-template-NAME' and an
128interactive function `tempo-template-NAME' that inserts the template
129at the point. The created function is returned.
130
131NAME is a string that contains the name of the template, ELEMENTS is a
132list of elements in the template, TAG is the tag used for completion,
133DOCUMENTATION is the documentation string for the insertion command
134created, and TAGLIST (a symbol) is the tag list that TAG (if provided)
135should be added to). If TAGLIST is nil and TAG is non-nil, TAG is
136added to `tempo-tags'
137
138The elements in ELEMENTS can be of several types:
139
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
158 line.
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."
162
163 (let* ((template-name (intern (concat "tempo-template-"
164 name)))
165 (command-name template-name))
166 (set template-name elements)
167 (fset command-name (list 'lambda (list '&optional 'arg)
168 (or documentation
169 (concat "Insert a " name "."))
170 (list 'interactive "*P")
171 (list 'tempo-insert-template (list 'quote
172 template-name)
173 'arg)))
174 (and tag
175 (tempo-add-tag tag template-name taglist))
176 command-name))
177
178;;;
179;;; tempo-insert-template
180
181(defun tempo-insert-template (template on-region)
182 "Insert a template.
183TEMPLATE is the template to be inserted. If ON-REGION is non-nil the
184`r' elements are replaced with the current region."
185 (and on-region
186 (< (mark) (point))
187 (exchange-point-and-mark))
188 (save-excursion
189 (tempo-insert-mark (point-marker))
190 (mapcar 'tempo-insert
191 (symbol-value template))
192 (tempo-insert-mark (point-marker)))
193 (tempo-forward-mark))
194
195;;;
196;;; tempo-insert
197
198(defun tempo-insert (element)
199 "Insert a template element.
200Insert 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))
206 (if on-region
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)
215 (save-excursion
216 (re-search-backward
217 "^\\s-*\\=" nil t))))
218 (insert "\n")))
219 ((eq element '%) (if (not (or (eolp)
220 (save-excursion
221 (re-search-forward
222 "\\=\\s-*$" nil t))))
223 (insert "\n")))
224 ((eq element 'n) (insert "\n"))
225 ((eq element 'n>) (insert "\n") (indent-according-to-mode))
226 ((null element))
227 (t (tempo-insert (eval element)))))
228
229;;;
230;;; tempo-insert-prompt
231
232(defun tempo-insert-prompt (prompt)
233 "Prompt for a text string and insert it in the current buffer.
234If the variable `tempo-interactive' is non-nil the user is prompted
235for a string in the minibuffer, which is then inserted in the current
236buffer. If `tempo-interactive' is nil, the current point is placed on
237`tempo-forward-mark-list'.
238
239PROMPT is the prompt string."
240 (if tempo-interactive
241 (insert (read-string prompt))
242 (tempo-insert-mark (point-marker))))
243
244;;;
245;;; tempo-process-and-insert-string
246
247(defun tempo-process-and-insert-string (string)
248 "Insert a string from a template.
249Run a string through the preprocessors in `tempo-insert-string-functions'
250and insert the results."
251
252 (cond ((null tempo-insert-string-functions)
253 nil)
254 ((symbolp tempo-insert-string-functions)
255 (setq string
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))
261 (t
262 (error "Bogus value in tempo-insert-string-functions: %s"
263 tempo-insert-string-functions)))
264 (insert string))
265
266;;;
267;;; tempo-insert-mark
268
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))
274 (while (and (cdr lp)
275 (<= (car (cdr lp)) mark))
276 (setq lp (cdr lp)))
277 (if (not (= mark (car lp)))
278 (setcdr lp (cons mark (cdr lp))))))))
279
280;;;
281;;; tempo-forward-mark
282
283(defun tempo-forward-mark ()
284 "Jump to the next mark in `tempo-forward-mark-list'."
285 (interactive)
286 (let ((next-mark (catch 'found
287 (mapcar
288 (function
289 (lambda (mark)
290 (if (< (point) mark)
291 (throw 'found mark))))
292 tempo-marks)
293 ;; return nil if not found
294 nil)))
295 (if next-mark
296 (goto-char next-mark))))
297
298;;;
299;;; tempo-backward-mark
300
301(defun tempo-backward-mark ()
302 "Jump to the previous mark in `tempo-back-mark-list'."
303 (interactive)
304 (let ((prev-mark (catch 'found
305 (let (last)
306 (mapcar
307 (function
308 (lambda (mark)
309 (if (<= (point) mark)
310 (throw 'found last))
311 (setq last mark)))
312 tempo-marks)
313 last))))
314 (if prev-mark
315 (goto-char prev-mark))))
316
317;;;
318;;; tempo-add-tag
319
320(defun tempo-add-tag (tag template &optional tag-list)
321 "Add a template tag.
322
323Add the TAG, that should complete to TEMPLATE to the list in TAG-LIST,
324or to `tempo-tags' if TAG-LIST is nil."
325
326 (interactive "sTag: \nCTemplate: ")
327 (if (null tag-list)
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)))))
331
332;;;
333;;; tempo-use-tag-list
334
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.
337
338TAG-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
340string that is used by `\\[tempo-complete-tag]' to find a string to
341match the tag against.
342
343If COMPLETION-FUNCTION is a string, it should contain a regular
344expression with at least one \\( \\) pair. When searching for tags,
345`tempo-complete-tag' calls `re-search-backward' with this string, and
346the string between the first \\( and \\) is used for matching against
347each string in the tag list. If one is found, the whole text between
348the first \\( and the point is replaced with the inserted template.
349
350You will probably want to include \\ \= at the end of the regexp to make
351sure that the string is matched only against text adjacent to the
352point.
353
354If COPMLETION-FUNCTION is a symbol, it should be a function that
355returns a cons cell of the form (STRING . POS), where STRING is the
356string used for matching and POS is the buffer position after which
357text should be replaced with a template."
358
359 (let ((old (assq tag-list tempo-local-tags)))
360 (if old
361 (setcdr old completion-function)
362 (setq tempo-local-tags (cons (cons tag-list completion-function)
363 tempo-local-tags)))))
364
365;;;
366;;; tempo-find-match-string
367
368(defun tempo-find-match-string (finder)
369 "Find a string to be matched against a tag list.
370
371FINDER is a function or a string. Returns (STRING . POS)."
372 (cond ((stringp finder)
373 (save-excursion
374 (re-search-backward finder nil t))
375 (cons (buffer-substring (match-beginning 1) (1+ (match-end 1)))
376 (match-beginning 1)))
377 (t
378 (funcall finder))))
379
380;;;
381;;; tempo-complete-tag
382
383(defun tempo-complete-tag (&optional silent)
384 "Look for a tag and expand it..
385
386It goes through the tag lists in `tempo-local-tags' (this includes
387`tempo-tags') and for each list it uses the corresponding match-finder
388function, or `tempo-default-match-finder' if none is given, and tries
389to 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
391one is found. If a partial completion is found, it is replaced by the
392template if it can be completed uniquely, or completed as far as
393possible.
394
395When doing partial completion, only tags in the currently examined
396list are considered, so if you provide similar tags in different lists
397in `tempo-local-tags', the result may not be desirable.
398
399If no match is found or a partial match is found, and SILENT is
400non-nil, the function will give a signal."
401
402 (interactive)
403 (if (catch 'completed
404 (mapcar
405 (function
406 (lambda (tag-list-a)
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)
415 tag-list))))
416
417 (if compl ;any match
418 (delete-region match-start (point)))
419
420 (cond
421 ((null compl)
422 nil)
423 ((symbolp compl)
424 (tempo-insert-template compl nil)
425 (throw 'completed t))
426 ((eq compl t)
427 (tempo-insert-template (cdr (assoc match-string tag-list))
428 nil)
429 (throw 'completed t))
430 ((stringp compl)
431 (let ((compl2 (assoc compl tag-list)))
432 (if compl2
433 (tempo-insert-template (cdr compl2) nil)
434 (insert compl)
435 (if (string= match-string compl)
436 (if (not silent)
437 (ding)))))
438 (throw 'completed t))))))
439 tempo-local-tags)
440 ;; No completion found. Return nil
441 nil)
442 ;; Do nothing if a completion was found
443 t
444 ;; No completion was found
445 (if (not silent)
446 (ding))
447 nil))
448
449;;; tempo.el ends here