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

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

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
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.
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 223
should be added to.  If TAGLIST is nil and TAG is non-nil, TAG is
added to `tempo-tags'.
Richard M. Stallman's avatar
Richard M. Stallman committed
224 225 226

The elements in ELEMENTS can be of several types:

227
 - A string: It is sent to the hooks in `tempo-insert-string-functions',
Richard M. Stallman's avatar
Richard M. Stallman committed
228
   and the result is inserted.
229 230 231 232
 - 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
233
   user is prompted in the minibuffer with PROMPT for a string to be
234 235
   inserted.  If the optional parameter NAME is non-nil, the text is
   saved for later insertion with the `s' tag.  If there already is
236
   something saved under NAME that value is used instead and no
237 238 239 240 241 242
   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
243
   `tempo-interactive' is nil and `tempo-insert' is called with
244
   ON-REGION non-nil, the current region is placed here.  This usually
245
   happens when you call the template function with a prefix argument.
246 247
 - (s NAME): Inserts text previously read with the (p ..) construct.
   Finds the insertion saved under NAME and inserts it.  Acts like `p'
248
   if tempo-interactive is nil.
249 250 251 252 253 254 255 256 257
 - `&': 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.
258 259
 - (r> PROMPT <NAME> <NOINSERT>): Like (r ...), but is also indents
   the region.
260 261 262
 - `n>': Inserts a newline and indents line.
 - `o': Like `%' but leaves the point before the newline.
 - nil: It is ignored.
263 264 265 266 267 268 269 270
 - 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
271 272 273 274 275
  (let* ((template-name (intern (concat "tempo-template-"
				       name)))
	 (command-name template-name))
    (set template-name elements)
    (fset command-name (list 'lambda (list '&optional 'arg)
276
			     (or documentation
Richard M. Stallman's avatar
Richard M. Stallman committed
277 278 279
				 (concat "Insert a " name "."))
			     (list 'interactive "*P")
			     (list 'tempo-insert-template (list 'quote
280 281 282
								template-name)
				   (list 'if 'tempo-insert-region
					 (list 'not 'arg) 'arg))))
Richard M. Stallman's avatar
Richard M. Stallman committed
283 284 285 286 287 288 289 290 291 292
    (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
293
`r' elements are replaced with the current region.  In Transient Mark
294
mode, ON-REGION is ignored and assumed true if the region is active."
295 296
  (unwind-protect
      (progn
297 298
	(if (or (and transient-mark-mode
		     mark-active))
299 300 301 302 303 304 305 306
	    (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))
307 308 309
	  (mapc (function (lambda (elt)
			    (tempo-insert elt on-region)))
		(symbol-value template))
310 311 312
	  (tempo-insert-mark (point-marker)))
	(tempo-forward-mark))
    (tempo-forget-insertions)
313
    (and transient-mark-mode
314
	 (deactivate-mark))))
Richard M. Stallman's avatar
Richard M. Stallman committed
315 316 317 318

;;;
;;; tempo-insert

319
(defun tempo-insert (element on-region)
Richard M. Stallman's avatar
Richard M. Stallman committed
320
  "Insert a template element.
321 322 323 324 325
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
326
  (cond ((stringp element) (tempo-process-and-insert-string element))
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
	((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))))
344 345 346 347 348 349 350
        ((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))))
351 352 353 354 355 356 357
	((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
358 359
	((eq element 'p) (tempo-insert-mark (point-marker)))
	((eq element 'r) (if on-region
360
			     (goto-char tempo-region-stop)
Richard M. Stallman's avatar
Richard M. Stallman committed
361
			   (tempo-insert-mark (point-marker))))
362 363 364 365 366
	((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
367 368 369 370 371 372 373 374 375 376 377 378 379
	((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))
380 381 382 383 384 385 386 387 388
	;; 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
389
	((null element))
390 391 392
	(t (tempo-insert (or (tempo-is-user-element element)
			     (eval element))
			 on-region))))
Richard M. Stallman's avatar
Richard M. Stallman committed
393 394 395 396

;;;
;;; tempo-insert-prompt

397
(defun tempo-insert-prompt-compat (prompt)
398
  "Compatibility hack for `tempo-insert-prompt'.
399
PROMPT can be either a prompt string, or a list of arguments to
400
`tempo-insert-prompt', or nil."
Pavel Janík's avatar
Pavel Janík committed
401
  (if (consp prompt)			; not nil either
402 403 404 405
      (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
406 407 408
  "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
409
buffer.  If `tempo-interactive' is nil, the current point is placed on
410
`tempo-mark'.
Richard M. Stallman's avatar
Richard M. Stallman committed
411

412
PROMPT is the prompt string, SAVE-NAME is a name to save the inserted
413 414
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.
415 416 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

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
442

443 444 445 446
;;;
;;; tempo-is-user-element

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

456 457 458 459 460 461 462
;;;
;;; tempo-forget-insertions

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

463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
;;;
;;; 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)))

487 488 489
;;;
;;; tempo-insert-named

490 491
(defun tempo-insert-named (name)
  "Insert the previous insertion saved under a named specified in NAME.
492 493 494 495 496 497 498 499 500 501 502 503
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)))))

504

Richard M. Stallman's avatar
Richard M. Stallman committed
505 506 507 508 509 510 511 512 513 514 515
;;;
;;; 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
516
	       (funcall tempo-insert-string-functions string)))
Richard M. Stallman's avatar
Richard M. Stallman committed
517
	((listp tempo-insert-string-functions)
518
	 (dolist (fn tempo-insert-string-functions)
519
	   (setq string (funcall fn string))))
Richard M. Stallman's avatar
Richard M. Stallman committed
520 521 522 523 524 525 526 527 528
	(t
	 (error "Bogus value in tempo-insert-string-functions: %s"
		tempo-insert-string-functions)))
  (insert string))

;;;
;;; tempo-insert-mark

(defun tempo-insert-mark (mark)
529
  "Insert a mark `tempo-marks' while keeping it sorted."
Richard M. Stallman's avatar
Richard M. Stallman committed
530 531 532 533 534 535 536 537
  (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))))))))
538

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

(defun tempo-forward-mark ()
  "Jump to the next mark in `tempo-forward-mark-list'."
  (interactive)
  (let ((next-mark (catch 'found
546
		     (mapc
Richard M. Stallman's avatar
Richard M. Stallman committed
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
		      (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)
565
		       (mapc
Richard M. Stallman's avatar
Richard M. Stallman committed
566 567 568 569 570 571 572 573 574
			(function
			 (lambda (mark)
			   (if (<= (point) mark)
			       (throw 'found last))
			   (setq last mark)))
			tempo-marks)
		       last))))
    (if prev-mark
	(goto-char prev-mark))))
575

Richard M. Stallman's avatar
Richard M. Stallman committed
576 577 578 579 580 581 582 583 584 585 586 587
;;;
;;; 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,
or to `tempo-tags' if TAG-LIST is nil."

  (interactive "sTag: \nCTemplate: ")
  (if (null tag-list)
      (setq tag-list 'tempo-tags))
  (if (not (assoc tag (symbol-value tag-list)))
588 589
      (set tag-list (cons (cons tag template) (symbol-value tag-list))))
  (tempo-invalidate-collection))
Richard M. Stallman's avatar
Richard M. Stallman committed
590 591 592 593 594 595 596

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

599
COMPLETION-FUNCTION is an obsolete option for specifying an optional
600
function or string that is used by `\\[tempo-complete-tag]' to find a
601 602
string to match the tag against.  It has the same definition as the
variable `tempo-match-finder'.  In this version, supplying a
603
COMPLETION-FUNCTION just sets `tempo-match-finder' locally."
604
  (setf (alist-get tag-list tempo-local-tags) completion-function)
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
  (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.
Pavel Janík's avatar
Pavel Janík committed
622
If `tempo-dirty-collection' is nil, the old collection is reused."
623 624 625 626 627 628
  (prog1
      (or (and (not tempo-dirty-collection)
	       tempo-collection)
	  (setq tempo-collection
		(apply (function append)
		       (mapcar (function (lambda (tag-list)
629 630 631
					; If the format for
					; tempo-local-tags changes,
					; change this
632 633 634
					   (eval (car tag-list))))
			       tempo-local-tags))))
    (setq tempo-dirty-collection nil)))
Richard M. Stallman's avatar
Richard M. Stallman committed
635 636 637 638 639 640

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

(defun tempo-find-match-string (finder)
  "Find a string to be matched against a tag list.
641
FINDER is a function or a string.  Returns (STRING . POS), or nil
642
if no reasonable string is found."
Richard M. Stallman's avatar
Richard M. Stallman committed
643
  (cond ((stringp finder)
644 645 646 647 648 649 650 651 652 653
	 (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
654 655 656 657 658 659 660
	(t
	 (funcall finder))))

;;;
;;; tempo-complete-tag

(defun tempo-complete-tag (&optional silent)
661
  "Look for a tag and expand it.
662
All the tags in the tag lists in `tempo-local-tags'
663
\(this includes `tempo-tags') are searched for a match for the text
664
before the point.  The way the string to match for is determined can
665
be altered with the variable `tempo-match-finder'.  If
666 667
`tempo-match-finder' returns nil, then the results are the same as
no match at all.
668 669 670 671 672

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
673
non-nil, the function will give a signal.
674 675

If a partial completion is found and `tempo-show-completion-buffer' is
Pavel Janík's avatar
Pavel Janík committed
676
non-nil, a buffer containing possible completions is displayed."
677 678 679 680 681 682 683 684 685 686

  ;; 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)
687
		    (and match-info (try-completion match-string collection)))))
688
    (if compl (delete-region match-start (point)))
689 690
    (cond ((null match-info) (or silent (ding)))
	  ((null compl) (or silent (ding)))
691 692 693 694 695 696 697 698 699 700 701
	  ((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
702 703


704 705 706 707 708 709 710 711
;;;
;;; 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
712 713
	 (completion-hilit-commonality (all-completions string tag-list)
				       (length string))))
714 715 716
    (save-window-excursion
      (with-output-to-temp-buffer "*Completions*"
	(display-completion-list
717 718
	 (completion-hilit-commonality (all-completions string tag-list)
				       (length string))))
719 720
      (sit-for 32767))))

721 722 723 724 725 726 727 728 729 730
;;;
;;; 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:

731
\(defun tempo-space ()
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
  (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)))

749 750
(provide 'tempo)

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