tempo.el 26.8 KB
Newer Older
1
;;; tempo.el --- Flexible template insertion -*- lexical-binding: t; -*-
Erik Naggum's avatar
Erik Naggum committed
2

Paul Eggert's avatar
Paul Eggert committed
3
;; Copyright (C) 1994-1995, 2001-2020 Free Software Foundation, Inc.
Richard M. Stallman's avatar
Richard M. Stallman committed
4

5
;; Author: David Kågedal <davidk@lysator.liu.se>
Richard M. Stallman's avatar
Richard M. Stallman committed
6
;; Created: 16 Feb 1994
7
;; Kågedal's last version number: 1.2.4
8
;; Keywords: abbrev, extensions, languages, tools
Richard M. Stallman's avatar
Richard M. Stallman committed
9 10 11

;; This file is part of GNU Emacs.

12
;; GNU Emacs is free software: you can redistribute it and/or modify
Richard M. Stallman's avatar
Richard M. Stallman committed
13
;; it under the terms of the GNU General Public License as published by
14 15
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
Richard M. Stallman's avatar
Richard M. Stallman committed
16 17 18 19 20 21 22

;; 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
23
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
Richard M. Stallman's avatar
Richard M. Stallman committed
24 25 26 27 28 29 30

;;; Commentary:

;; This file provides a simple way to define powerful templates, or
;; macros, if you wish. It is mainly intended for, but not limited to,
;; other programmers to be used for creating shortcuts for editing
;; certain kind of documents. It was originally written to be used by
31 32 33
;; a HTML editing mode written by Nelson Minar <nelson@santafe.edu>,
;; and his html-helper-mode.el is probably the best example of how to
;; use this program.
Richard M. Stallman's avatar
Richard M. Stallman committed
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

;; A template is defined as a list of items to be inserted in the
;; current buffer at point. Some of the items can be simple strings,
;; while other can control formatting or define special points of
;; interest in the inserted text.

;; If a template defines a "point of interest" that point is inserted
;; in a buffer-local list of "points of interest" that the user can
;; jump between with the commands `tempo-backward-mark' and
;; `tempo-forward-mark'. If the template definer provides a prompt for
;; the point, and the variable `tempo-interactive' is non-nil, the
;; user will be prompted for a string to be inserted in the buffer,
;; using the minibuffer.

;; The template can also define one point to be replaced with the
;; current region if the template command is called with a prefix (or
;; a non-nil argument).

;; More flexible templates can be created by including lisp symbols,
53
;; which will be evaluated as variables, or lists, which will be
Richard M. Stallman's avatar
Richard M. Stallman committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
;; evaluated as lisp expressions.

;; See the documentation for tempo-define-template for the different
;; items that can be used to define a tempo template.

;; One of the more powerful features of tempo templates are automatic
;; completion. With every template can be assigned a special tag that
;; should be recognized by `tempo-complete-tag' and expanded to the
;; complete template. By default the tags are added to a global list
;; of template tags, and are matched against the last word before
;; point. But if you assign your tags to a specific list, you can also
;; specify another method for matching text in the buffer against the
;; tags. In the HTML mode, for instance, the tags are matched against
;; the text between the last `<' and point.

;; When defining a template named `foo', a symbol named
;; `tempo-template-foo' will be created whose value as a variable will
;; be the template definition, and its function value will be an
;; interactive function that inserts the template at the point.

;; The latest tempo.el distribution can be fetched from
;; ftp.lysator.liu.se in the directory /pub/emacs

Richard M. Stallman's avatar
Richard M. Stallman committed
77 78 79
;; There is also a WWW page at
;; http://www.lysator.liu.se/~davidk/elisp/ which has some information

80 81 82 83
;;; 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
Glenn Morris's avatar
Glenn Morris committed
84
;; line. This is due to strange behavior in open-line. But it should
85 86 87 88 89 90 91 92 93 94 95
;; 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.

96 97
;;; Contributors:

98
;; These people have given me important feedback and new ideas for
99 100 101
;; tempo.el. Thanks.

;; Nelson Minar <nelson@santafe.edu>
Karl Heuer's avatar
Karl Heuer committed
102
;; Richard Stallman <rms@gnu.org>
103 104 105
;; Lars Lindberg <Lars.Lindberg@sypro.cap.se>
;; Glen Whitney <Glen.Whitney@math.lsa.umich.edu>

Richard M. Stallman's avatar
Richard M. Stallman committed
106 107
;;; Code:

108
;;; User options
Richard M. Stallman's avatar
Richard M. Stallman committed
109

Stephen Eglen's avatar
Stephen Eglen committed
110 111 112 113 114 115
(defgroup tempo nil
  "Flexible template insertion."
  :prefix "tempo-"
  :group 'tools)

(defcustom tempo-interactive nil
Lute Kamstra's avatar
Lute Kamstra committed
116
  "Prompt user for strings in templates.
Richard M. Stallman's avatar
Richard M. Stallman committed
117
If this variable is non-nil, `tempo-insert' prompts the
118
user for text to insert in the templates."
Stephen Eglen's avatar
Stephen Eglen committed
119 120
  :type 'boolean
  :group 'tempo)
Richard M. Stallman's avatar
Richard M. Stallman committed
121

Stephen Eglen's avatar
Stephen Eglen committed
122
(defcustom tempo-insert-region nil
Lute Kamstra's avatar
Lute Kamstra committed
123
  "Automatically insert current region when there is a `r' in the template
Pavel Janík's avatar
Pavel Janík committed
124
If this variable is nil, `r' elements will be treated just like `p'
125
elements, unless the template function is given a prefix (or a non-nil
126
argument).  If this variable is non-nil, the behavior is reversed.
127

Stephen Eglen's avatar
Stephen Eglen committed
128 129 130
In Transient Mark mode, this option is unused."
  :type 'boolean
  :group 'tempo)
131

Stephen Eglen's avatar
Stephen Eglen committed
132
(defcustom tempo-show-completion-buffer t
Lute Kamstra's avatar
Lute Kamstra committed
133
  "If non-nil, show a buffer with possible completions, when only
134
a partial completion can be found."
Stephen Eglen's avatar
Stephen Eglen committed
135 136
  :type 'boolean
  :group 'tempo)
137

Stephen Eglen's avatar
Stephen Eglen committed
138
(defcustom tempo-leave-completion-buffer nil
Lute Kamstra's avatar
Lute Kamstra committed
139
  "If nil, a completion buffer generated by \\[tempo-complete-tag]
Stephen Eglen's avatar
Stephen Eglen committed
140 141 142
disappears at the next keypress; otherwise, it remains forever."
  :type 'boolean
  :group 'tempo)
143

144 145
;;; Internal variables

Richard M. Stallman's avatar
Richard M. Stallman committed
146 147
(defvar tempo-insert-string-functions nil
  "List of functions to run when inserting a string.
Richard M. Stallman's avatar
Richard M. Stallman committed
148
Each function is called with a single arg, STRING and should return
149
another string.  This could be used for making all strings upcase by
150
setting it to (upcase), for example.")
Richard M. Stallman's avatar
Richard M. Stallman committed
151 152

(defvar tempo-tags nil
153
  "An association list with tags and corresponding templates.")
Richard M. Stallman's avatar
Richard M. Stallman committed
154

155
(defvar-local tempo-local-tags '((tempo-tags . nil))
Richard M. Stallman's avatar
Richard M. Stallman committed
156
  "A list of locally installed tag completion lists.
157
It is an association list where the car of every element is a symbol
158 159
whose variable 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
Richard M. Stallman's avatar
Richard M. Stallman committed
160 161 162 163
documentation for the function `tempo-complete-tag' for more info.

`tempo-tags' is always in the last position in this list.")

164
(defvar-local tempo-collection nil
165 166
  "A collection of all the tags defined for the current buffer.")

167
(defvar-local tempo-dirty-collection t
168 169
  "Indicates if the tag collection needs to be rebuilt.")

170
(defvar-local tempo-marks nil
Richard M. Stallman's avatar
Richard M. Stallman committed
171 172
  "A list of marks to jump to with `\\[tempo-forward-mark]' and `\\[tempo-backward-mark]'.")

173
(defvar-local tempo-match-finder "\\b\\([[:word:]]+\\)\\="
174 175
  "The regexp or function used to find the string to match against tags.

176 177
If `tempo-match-finder' is a string, it should contain a regular
expression with at least one \\( \\) pair.  When searching for tags,
178 179 180 181 182
`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.

183
You will probably want to include \\=\\= at the end of the regexp to
184 185 186 187 188 189 190 191 192 193 194
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.
195
This function should return something to be sent to `tempo-insert' if
Pavel Janík's avatar
Pavel Janík committed
196
it recognizes the argument, and nil otherwise.")
Richard M. Stallman's avatar
Richard M. Stallman committed
197

198
(defvar-local tempo-named-insertions nil
Pavel Janík's avatar
Pavel Janík committed
199
  "Temporary storage for named insertions.")
200

201
(defvar-local tempo-region-start (make-marker)
Pavel Janík's avatar
Pavel Janík committed
202
  "Region start when inserting around the region.")
203

204
(defvar-local tempo-region-stop (make-marker)
Pavel Janík's avatar
Pavel Janík committed
205
  "Region stop when inserting around the region.")
206

Richard M. Stallman's avatar
Richard M. Stallman committed
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
;;; Functions

;;
;; tempo-define-template

(defun tempo-define-template (name elements &optional tag documentation taglist)
  "Define a template.
This function creates a template variable `tempo-template-NAME' and an
interactive function `tempo-template-NAME' that inserts the template
at the point.  The created function is returned.

NAME is a string that contains the name of the template, ELEMENTS is a
list of elements in the template, TAG is the tag used for completion,
DOCUMENTATION is the documentation string for the insertion command
created, and TAGLIST (a symbol) is the tag list that TAG (if provided)
222
should be added to.  If TAGLIST is nil and TAG is non-nil, TAG is
223 224 225
added to `tempo-tags'.  If TAG already corresponds to a template in
the tag list, modify the list so that TAG now corresponds to the newly
defined template.
Richard M. Stallman's avatar
Richard M. Stallman committed
226 227 228

The elements in ELEMENTS can be of several types:

229
 - A string: It is sent to the hooks in `tempo-insert-string-functions',
Richard M. Stallman's avatar
Richard M. Stallman committed
230
   and the result is inserted.
231 232 233 234
 - The symbol `p': This position is saved in `tempo-marks'.
 - The symbol `r': If `tempo-insert' is called with ON-REGION non-nil
   the current region is placed here.  Otherwise it works like `p'.
 - (p PROMPT <NAME> <NOINSERT>): If `tempo-interactive' is non-nil, the
235
   user is prompted in the minibuffer with PROMPT for a string to be
236 237
   inserted.  If the optional parameter NAME is non-nil, the text is
   saved for later insertion with the `s' tag.  If there already is
Richard M. Stallman's avatar
Richard M. Stallman committed
238
   something saved under NAME that value is used instead and no
239 240 241 242 243 244
   prompting is made.  If NOINSERT is provided and non-nil, nothing is
   inserted, but text is still saved when a NAME is provided.  For
   clarity, the symbol `noinsert' should be used as argument.
 - (P PROMPT <NAME> <NOINSERT>): Works just like the previous tag, but
   forces `tempo-interactive' to be true.
 - (r PROMPT <NAME> <NOINSERT>): Like the previous tag, but if
Richard M. Stallman's avatar
Richard M. Stallman committed
245
   `tempo-interactive' is nil and `tempo-insert' is called with
246
   ON-REGION non-nil, the current region is placed here.  This usually
Richard M. Stallman's avatar
Richard M. Stallman committed
247
   happens when you call the template function with a prefix argument.
248 249
 - (s NAME): Inserts text previously read with the (p ..) construct.
   Finds the insertion saved under NAME and inserts it.  Acts like `p'
250
   if tempo-interactive is nil.
251 252 253 254 255 256 257 258 259
 - `&': If there is only whitespace between the line start and point,
   nothing happens.  Otherwise a newline is inserted.
 - `%': If there is only whitespace between point and end of line,
   nothing happens.  Otherwise a newline is inserted.
 - `n': Inserts a newline.
 - `>': 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.
 - `r>': Like `r', but it also indents the region.
260 261
 - (r> PROMPT <NAME> <NOINSERT>): Like (r ...), but is also indents
   the region.
262 263 264
 - `n>': Inserts a newline and indents line.
 - `o': Like `%' but leaves the point before the newline.
 - nil: It is ignored.
265 266 267 268 269 270 271 272
 - Anything else: Each function in `tempo-user-elements' is called
   with it as argument until one of them returns non-nil, and the
   result is inserted.  If all of them return nil, it is evaluated and
   the result is treated as an element to be inserted.  One additional
   tag is useful for these cases.  If an expression returns a list (l
   foo bar), the elements after `l' will be inserted according to the
   usual rules.  This makes it possible to return several elements
   from one expression."
Richard M. Stallman's avatar
Richard M. Stallman committed
273 274 275 276 277
  (let* ((template-name (intern (concat "tempo-template-"
				       name)))
	 (command-name template-name))
    (set template-name elements)
    (fset command-name (list 'lambda (list '&optional 'arg)
278
			     (or documentation
Richard M. Stallman's avatar
Richard M. Stallman committed
279 280 281
				 (concat "Insert a " name "."))
			     (list 'interactive "*P")
			     (list 'tempo-insert-template (list 'quote
282 283 284
								template-name)
				   (list 'if 'tempo-insert-region
					 (list 'not 'arg) 'arg))))
Richard M. Stallman's avatar
Richard M. Stallman committed
285 286 287 288 289 290 291 292 293 294
    (and tag
	 (tempo-add-tag tag template-name taglist))
    command-name))

;;;
;;; tempo-insert-template

(defun tempo-insert-template (template on-region)
  "Insert a template.
TEMPLATE is the template to be inserted.  If ON-REGION is non-nil the
295
`r' elements are replaced with the current region.  In Transient Mark
296
mode, ON-REGION is ignored and assumed true if the region is active."
Richard M. Stallman's avatar
Richard M. Stallman committed
297 298
  (unwind-protect
      (progn
299 300
	(if (or (and transient-mark-mode
		     mark-active))
Richard M. Stallman's avatar
Richard M. Stallman committed
301 302 303 304 305 306 307 308
	    (setq on-region t))
	(and on-region
	     (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))
309 310 311
	  (mapc (function (lambda (elt)
			    (tempo-insert elt on-region)))
		(symbol-value template))
Richard M. Stallman's avatar
Richard M. Stallman committed
312 313 314
	  (tempo-insert-mark (point-marker)))
	(tempo-forward-mark))
    (tempo-forget-insertions)
315
    (and transient-mark-mode
Richard M. Stallman's avatar
Richard M. Stallman committed
316
	 (deactivate-mark))))
Richard M. Stallman's avatar
Richard M. Stallman committed
317 318 319 320

;;;
;;; tempo-insert

321
(defun tempo-insert (element on-region)
Richard M. Stallman's avatar
Richard M. Stallman committed
322
  "Insert a template element.
323 324 325 326 327
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."
Richard M. Stallman's avatar
Richard M. Stallman committed
328
  (cond ((stringp element) (tempo-process-and-insert-string element))
Richard M. Stallman's avatar
Richard M. Stallman committed
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
	((and (consp element)
	      (eq (car element) 'p)) (tempo-insert-prompt-compat
				      (cdr element)))
	((and (consp element)
	      (eq (car element) 'P)) (let ((tempo-interactive t))
				       (tempo-insert-prompt-compat
					(cdr element))))
;;;	((and (consp element)
;;;	      (eq (car element) 'v)) (tempo-save-named
;;;				      (nth 1 element)
;;;				      nil
;;;				      (nth 2 element)))
	((and (consp element)
	      (eq (car element) 'r)) (if on-region
					 (goto-char tempo-region-stop)
				       (tempo-insert-prompt-compat
					(cdr element))))
346 347 348 349 350 351 352
        ((and (consp element)
              (eq (car element) 'r>)) (if on-region
                                          (progn
                                            (goto-char tempo-region-stop)
                                            (indent-region (mark) (point) nil))
                                        (tempo-insert-prompt-compat
                                         (cdr element))))
Richard M. Stallman's avatar
Richard M. Stallman committed
353 354 355 356 357 358 359
	((and (consp element)
	      (eq (car element) 's)) (tempo-insert-named (car (cdr element))))
	((and (consp element)
	      (eq (car element) 'l)) (mapcar (function
					      (lambda (elt)
						(tempo-insert elt on-region)))
					     (cdr element)))
Richard M. Stallman's avatar
Richard M. Stallman committed
360 361
	((eq element 'p) (tempo-insert-mark (point-marker)))
	((eq element 'r) (if on-region
362
			     (goto-char tempo-region-stop)
Richard M. Stallman's avatar
Richard M. Stallman committed
363
			   (tempo-insert-mark (point-marker))))
364 365 366 367 368
	((eq element 'r>) (if on-region
			      (progn
				(goto-char tempo-region-stop)
				(indent-region (mark) (point) nil))
			    (tempo-insert-mark (point-marker))))
Richard M. Stallman's avatar
Richard M. Stallman committed
369 370 371 372 373 374 375 376 377 378 379 380 381
	((eq element '>) (indent-according-to-mode))
	((eq element '&) (if (not (or (= (current-column) 0)
				      (save-excursion
					(re-search-backward
					 "^\\s-*\\=" nil t))))
			     (insert "\n")))
	((eq element '%) (if (not (or (eolp)
				      (save-excursion
					(re-search-forward
					 "\\=\\s-*$" nil t))))
			     (insert "\n")))
	((eq element 'n) (insert "\n"))
	((eq element 'n>) (insert "\n") (indent-according-to-mode))
382 383 384 385 386 387 388 389 390
	;; 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)))
Richard M. Stallman's avatar
Richard M. Stallman committed
391
	((null element))
392 393 394
	(t (tempo-insert (or (tempo-is-user-element element)
			     (eval element))
			 on-region))))
Richard M. Stallman's avatar
Richard M. Stallman committed
395 396 397 398

;;;
;;; tempo-insert-prompt

Richard M. Stallman's avatar
Richard M. Stallman committed
399
(defun tempo-insert-prompt-compat (prompt)
400
  "Compatibility hack for `tempo-insert-prompt'.
Richard M. Stallman's avatar
Richard M. Stallman committed
401
PROMPT can be either a prompt string, or a list of arguments to
402
`tempo-insert-prompt', or nil."
Pavel Janík's avatar
Pavel Janík committed
403
  (if (consp prompt)			; not nil either
Richard M. Stallman's avatar
Richard M. Stallman committed
404 405 406 407
      (apply 'tempo-insert-prompt prompt)
    (tempo-insert-prompt prompt)))

(defun tempo-insert-prompt (prompt &optional save-name no-insert)
Richard M. Stallman's avatar
Richard M. Stallman committed
408 409 410
  "Prompt for a text string and insert it in the current buffer.
If the variable `tempo-interactive' is non-nil the user is prompted
for a string in the minibuffer, which is then inserted in the current
411
buffer.  If `tempo-interactive' is nil, the current point is placed on
412
`tempo-mark'.
Richard M. Stallman's avatar
Richard M. Stallman committed
413

Richard M. Stallman's avatar
Richard M. Stallman committed
414
PROMPT is the prompt string, SAVE-NAME is a name to save the inserted
415 416
text under.  If the optional argument NO-INSERT is non-nil, no text is
inserted.  This can be useful when there is a SAVE-NAME.
Richard M. Stallman's avatar
Richard M. Stallman committed
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443

If there already is a value for SAVE-NAME, it is used and the user is
never prompted."
  (let (insertion
	(previous (and save-name
		       (tempo-lookup-named save-name))))
    (cond
     ;; Insert  previous value, unless no-insert is non-nil
     ((and previous
	   (not no-insert))
      (tempo-insert-named save-name)) ; A double lookup here, but who
				      ; cares
     ;; If no-insert is non-nil, don't insert the previous value. Just
     ;; keep it
     (previous
      nil)
     ;; No previous value. Prompt or insert mark
     (tempo-interactive
      (if (not (stringp prompt))
	  (error "tempo: The prompt (%s) is not a string" prompt))
      (setq insertion (read-string prompt))
      (or no-insert
	  (insert insertion))
      (if save-name
	  (tempo-save-named save-name insertion)))
     (t
      (tempo-insert-mark (point-marker))))))
Richard M. Stallman's avatar
Richard M. Stallman committed
444

445 446 447 448
;;;
;;; tempo-is-user-element

(defun tempo-is-user-element (element)
449
  "Tries all the user-defined element handlers in `tempo-user-elements'."
450 451
  ;; Sigh... I need (some list)
  (catch 'found
452 453 454 455
    (mapc (function (lambda (handler)
		      (let ((result (funcall handler element)))
			(if result (throw 'found result)))))
	  tempo-user-elements)
456 457
    (throw 'found nil)))

458 459 460 461 462 463 464
;;;
;;; tempo-forget-insertions

(defun tempo-forget-insertions ()
  "Forget all the saved named insertions."
  (setq tempo-named-insertions nil))

Richard M. Stallman's avatar
Richard M. Stallman committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
;;;
;;; tempo-save-named

(defun tempo-save-named (name data)	; Had an optional prompt for 'v
  "Save some data for later insertion
The contents of DATA is saved under the name NAME.

The data can later be retrieved with `tempo-lookup-named'.

This function returns nil, so it can be used in a template without
inserting anything."
  (setq tempo-named-insertions
	(cons (cons name data)
	      tempo-named-insertions))
  nil)

;;;
;;; tempo-lookup-named

(defun tempo-lookup-named (name)
  "Lookup some saved data under the name NAME.
Returns the data if NAME was found, and nil otherwise."
  (cdr (assq name tempo-named-insertions)))

489 490 491
;;;
;;; tempo-insert-named

492 493
(defun tempo-insert-named (name)
  "Insert the previous insertion saved under a named specified in NAME.
Richard M. Stallman's avatar
Richard M. Stallman committed
494 495 496 497 498 499 500 501 502 503 504 505
If there is no such name saved, a tempo mark is inserted.

Note that if the data is a string, it will not be run through the string
processor."
  (let* ((insertion (tempo-lookup-named name)))
    (cond ((null insertion)
	   (tempo-insert-mark (point-marker)))
	  ((stringp insertion)
	   (insert insertion))
	  (t
	   (tempo-insert insertion nil)))))

506

Richard M. Stallman's avatar
Richard M. Stallman committed
507 508 509 510 511 512 513 514 515 516 517
;;;
;;; tempo-process-and-insert-string

(defun tempo-process-and-insert-string (string)
  "Insert a string from a template.
Run a string through the preprocessors in `tempo-insert-string-functions'
and insert the results."
  (cond ((null tempo-insert-string-functions)
	 nil)
	((symbolp tempo-insert-string-functions)
	 (setq string
518
	       (funcall tempo-insert-string-functions string)))
Richard M. Stallman's avatar
Richard M. Stallman committed
519
	((listp tempo-insert-string-functions)
520
	 (dolist (fn tempo-insert-string-functions)
521
	   (setq string (funcall fn string))))
Richard M. Stallman's avatar
Richard M. Stallman committed
522 523 524 525 526 527 528 529 530
	(t
	 (error "Bogus value in tempo-insert-string-functions: %s"
		tempo-insert-string-functions)))
  (insert string))

;;;
;;; tempo-insert-mark

(defun tempo-insert-mark (mark)
531
  "Insert a mark `tempo-marks' while keeping it sorted."
Richard M. Stallman's avatar
Richard M. Stallman committed
532 533 534 535 536 537 538 539
  (cond ((null tempo-marks) (setq tempo-marks (list mark)))
	((< mark (car tempo-marks)) (setq tempo-marks (cons mark tempo-marks)))
	(t (let ((lp tempo-marks))
	     (while (and (cdr lp)
			 (<= (car (cdr lp)) mark))
	       (setq lp (cdr lp)))
	     (if (not (= mark (car lp)))
		 (setcdr lp (cons mark (cdr lp))))))))
540

Richard M. Stallman's avatar
Richard M. Stallman committed
541 542 543 544 545 546 547
;;;
;;; tempo-forward-mark

(defun tempo-forward-mark ()
  "Jump to the next mark in `tempo-forward-mark-list'."
  (interactive)
  (let ((next-mark (catch 'found
548
		     (mapc
Richard M. Stallman's avatar
Richard M. Stallman committed
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
		      (function
		       (lambda (mark)
			 (if (< (point) mark)
			     (throw 'found mark))))
		      tempo-marks)
		     ;; return nil if not found
		     nil)))
    (if next-mark
	(goto-char next-mark))))

;;;
;;; tempo-backward-mark

(defun tempo-backward-mark ()
  "Jump to the previous mark in `tempo-back-mark-list'."
  (interactive)
  (let ((prev-mark (catch 'found
		     (let (last)
567
		       (mapc
Richard M. Stallman's avatar
Richard M. Stallman committed
568 569 570 571 572 573 574 575 576
			(function
			 (lambda (mark)
			   (if (<= (point) mark)
			       (throw 'found last))
			   (setq last mark)))
			tempo-marks)
		       last))))
    (if prev-mark
	(goto-char prev-mark))))
577

Richard M. Stallman's avatar
Richard M. Stallman committed
578 579 580 581 582 583
;;;
;;; tempo-add-tag

(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,
584 585
or to `tempo-tags' if TAG-LIST is nil.  If TAG was already in the list,
replace its template with TEMPLATE."
Richard M. Stallman's avatar
Richard M. Stallman committed
586 587 588 589

  (interactive "sTag: \nCTemplate: ")
  (if (null tag-list)
      (setq tag-list 'tempo-tags))
590 591
  (let ((entry (assoc tag (symbol-value tag-list))))
    (if entry
592
        ;; Tag is already in the list, assign a new template to it.
593
        (setcdr entry template)
594
      ;; Tag is not present in the list, add it with its template.
595
      (set tag-list (cons (cons tag template) (symbol-value tag-list)))))
596
  ;; Invalidate globally if we're modifying 'tempo-tags'.
597
  (tempo-invalidate-collection (eq tag-list 'tempo-tags)))
Richard M. Stallman's avatar
Richard M. Stallman committed
598 599 600 601 602 603 604

;;;
;;; 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
605
`tempo-add-tag'.
Richard M. Stallman's avatar
Richard M. Stallman committed
606

607
COMPLETION-FUNCTION is an obsolete option for specifying an optional
608
function or string that is used by `\\[tempo-complete-tag]' to find a
609 610
string to match the tag against.  It has the same definition as the
variable `tempo-match-finder'.  In this version, supplying a
611
COMPLETION-FUNCTION just sets `tempo-match-finder' locally."
612
  (setf (alist-get tag-list tempo-local-tags) completion-function)
613 614 615 616 617 618 619
  (if completion-function
      (setq tempo-match-finder completion-function))
  (tempo-invalidate-collection))

;;;
;;; tempo-invalidate-collection

620
(defun tempo-invalidate-collection (&optional global)
621
  "Marks the tag collection as obsolete.
622 623 624 625 626 627 628 629 630
Whenever it is needed again it will be rebuilt.  If GLOBAL is non-nil,
mark the tag collection of all buffers as obsolete, not just the
current one."
  (if global
      (dolist (buffer (buffer-list))
        (with-current-buffer buffer
          (when (assq 'tempo-dirty-collection (buffer-local-variables))
            (setq tempo-dirty-collection t))))
    (setq tempo-dirty-collection t)))
631 632 633 634 635 636

;;;
;;; tempo-build-collection

(defun tempo-build-collection ()
  "Build a collection of all the tags and return it.
Pavel Janík's avatar
Pavel Janík committed
637
If `tempo-dirty-collection' is nil, the old collection is reused."
638 639 640 641 642 643
  (prog1
      (or (and (not tempo-dirty-collection)
	       tempo-collection)
	  (setq tempo-collection
		(apply (function append)
		       (mapcar (function (lambda (tag-list)
644 645 646
					; If the format for
					; tempo-local-tags changes,
					; change this
647 648 649
					   (eval (car tag-list))))
			       tempo-local-tags))))
    (setq tempo-dirty-collection nil)))
Richard M. Stallman's avatar
Richard M. Stallman committed
650 651 652 653 654 655

;;;
;;; tempo-find-match-string

(defun tempo-find-match-string (finder)
  "Find a string to be matched against a tag list.
656
FINDER is a function or a string.  Returns (STRING . POS), or nil
657
if no reasonable string is found."
Richard M. Stallman's avatar
Richard M. Stallman committed
658
  (cond ((stringp finder)
659 660 661 662 663 664 665 666 667 668
	 (let (successful)
	   (save-excursion
	     (or (setq successful (re-search-backward finder nil t))
		 0))
	   (if successful
	       (cons (buffer-substring (match-beginning 1)
				       (match-end 1)) ; This seems to be a
					; bug in emacs
		     (match-beginning 1))
	     nil)))
Richard M. Stallman's avatar
Richard M. Stallman committed
669 670 671 672 673 674 675
	(t
	 (funcall finder))))

;;;
;;; tempo-complete-tag

(defun tempo-complete-tag (&optional silent)
676
  "Look for a tag and expand it.
677
All the tags in the tag lists in `tempo-local-tags'
678
\(this includes `tempo-tags') are searched for a match for the text
679
before the point.  The way the string to match for is determined can
680
be altered with the variable `tempo-match-finder'.  If
681 682
`tempo-match-finder' returns nil, then the results are the same as
no match at all.
683 684 685 686 687

If a single match is found, the corresponding template is expanded in
place of the matching string.

If a partial completion or no match at all is found, and SILENT is
Pavel Janík's avatar
Pavel Janík committed
688
non-nil, the function will give a signal.
689 690

If a partial completion is found and `tempo-show-completion-buffer' is
Pavel Janík's avatar
Pavel Janík committed
691
non-nil, a buffer containing possible completions is displayed."
692 693 694 695 696 697 698 699 700 701

  ;; 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)
702
		    (and match-info (try-completion match-string collection)))))
703
    (if compl (delete-region match-start (point)))
704 705
    (cond ((null match-info) (or silent (ding)))
	  ((null compl) (or silent (ding)))
706 707 708 709 710 711 712 713 714 715 716
	  ((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)))))))
Richard M. Stallman's avatar
Richard M. Stallman committed
717 718


719 720 721 722 723 724 725 726
;;;
;;; tempo-display-completions

(defun tempo-display-completions (string tag-list)
  "Show a buffer containing possible completions for STRING."
  (if tempo-leave-completion-buffer
      (with-output-to-temp-buffer "*Completions*"
	(display-completion-list
727 728
	 (completion-hilit-commonality (all-completions string tag-list)
				       (length string))))
729 730 731
    (save-window-excursion
      (with-output-to-temp-buffer "*Completions*"
	(display-completion-list
732 733
	 (completion-hilit-commonality (all-completions string tag-list)
				       (length string))))
734 735
      (sit-for 32767))))

736 737 738 739 740 741 742 743 744 745
;;;
;;; tempo-expand-if-complete

(defun tempo-expand-if-complete ()
  "Expand the tag before point if it is complete.
Returns non-nil if an expansion was made and nil otherwise.

This could as an example be used in a command that is bound to the
space bar, and looks something like this:

746
\(defun tempo-space ()
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
  (interactive \"*\")
  (or (tempo-expand-if-complete)
      (insert \" \")))"

  (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)))
    (if exact
	(progn
	  (delete-region match-start (point))
	  (tempo-insert-template (cdr exact) nil)
	  t)
      nil)))

764 765
(provide 'tempo)

Richard M. Stallman's avatar
Richard M. Stallman committed
766
;;; tempo.el ends here