(mail-extract-address-components): Quote the comma in ,-pos.
[bpt/emacs.git] / lisp / skeleton.el
CommitLineData
f3611c70 1;;; skeleton.el --- Lisp language extension for writing statement skeletons
b578f267 2
017d787a 3;; Copyright (C) 1993, 1994, 1995, 1996 by Free Software Foundation, Inc.
ac59aed8 4
f3611c70 5;; Author: Daniel.Pfeiffer@Informatik.START.dbp.de, fax (+49 69) 7588-2389
ac59aed8 6;; Maintainer: FSF
f3611c70 7;; Keywords: extensions, abbrev, languages, tools
ac59aed8
RS
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
b578f267
EN
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
ac59aed8
RS
25
26;;; Commentary:
27
f3611c70 28;; A very concise language extension for writing structured statement
ac59aed8
RS
29;; skeleton insertion commands for programming language modes. This
30;; originated in shell-script mode and was applied to ada-mode's
31;; commands which shrunk to one third. And these commands are now
32;; user configurable.
33
34;;; Code:
35
f3611c70 36;; page 1: statement skeleton language definition & interpreter
ac59aed8
RS
37;; page 2: paired insertion
38;; page 3: mirror-mode, an example for setting up paired insertion
39
40
41(defvar skeleton-transformation nil
f3611c70 42 "*If non-nil, function applied to literal strings before they are inserted.
ac59aed8
RS
43It should take strings and characters and return them transformed, or nil
44which means no transformation.
45Typical examples might be `upcase' or `capitalize'.")
46
47; this should be a fourth argument to defvar
48(put 'skeleton-transformation 'variable-interactive
49 "aTransformation function: ")
50
51
017d787a
RS
52(defvar skeleton-autowrap t
53 "Controls wrapping behaviour of functions created with `define-skeleton'.
54When the region is visible (due to `transient-mark-mode' or marking a region
55with the mouse) and this is non-`nil' and the function was called without an
56explicit ARG, then the ARG defaults to -1, i.e. wrapping around the visible
57region.
58
59We will probably delete this variable in a future Emacs version
60unless we get a substantial number of complaints about the auto-wrap
61feature.")
ac59aed8 62
da6a884f
KH
63(defvar skeleton-end-hook
64 (lambda ()
65 (or (eolp) (newline-and-indent)))
66 "Hook called at end of skeleton but before going to point of interest.
67By default this moves out anything following to next line.
68The variables `v1' and `v2' are still set when calling this.")
69
70
f3611c70
KH
71;;;###autoload
72(defvar skeleton-filter 'identity
017d787a 73 "Function for transforming a skeleton proxy's aliases' variable value.")
f3611c70 74
f3611c70
KH
75(defvar skeleton-untabify t
76 "When non-`nil' untabifies when deleting backwards with element -ARG.")
77
4bfd70e9
KH
78(defvar skeleton-newline-indent-rigidly nil
79 "When non-`nil', indent rigidly under current line for element `\\n'.
80Else use mode's `indent-line-function'.")
f3611c70
KH
81
82(defvar skeleton-further-elements ()
83 "A buffer-local varlist (see `let') of mode specific skeleton elements.
84These variables are bound while interpreting a skeleton. Their value may
85in turn be any valid skeleton element if they are themselves to be used as
86skeleton elements.")
87(make-variable-buffer-local 'skeleton-further-elements)
88
89
ac59aed8
RS
90(defvar skeleton-subprompt
91 (substitute-command-keys
92 "RET, \\<minibuffer-local-map>\\[abort-recursive-edit] or \\[help-command]")
f3611c70
KH
93 "*Replacement for %s in prompts of recursive subskeletons.")
94
ac59aed8 95
017d787a
RS
96(defvar skeleton-abbrev-cleanup nil
97 "Variable used to delete the character that led to abbrev expansion.")
ac59aed8
RS
98
99
100(defvar skeleton-debug nil
101 "*If non-nil `define-skeleton' will override previous definition.")
102
4bfd70e9
KH
103;; reduce the number of compiler warnings
104(defvar skeleton)
105(defvar skeleton-modified)
106(defvar skeleton-point)
107(defvar skeleton-regions)
ac59aed8 108
ac59aed8 109;;;###autoload
f3611c70 110(defmacro define-skeleton (command documentation &rest skeleton)
ac59aed8
RS
111 "Define a user-configurable COMMAND that enters a statement skeleton.
112DOCUMENTATION is that of the command, while the variable of the same name,
f3611c70
KH
113which contains the skeleton, has a documentation to that effect.
114INTERACTOR and ELEMENT ... are as defined under `skeleton-insert'."
ac59aed8 115 (if skeleton-debug
f3611c70 116 (set command skeleton))
773500ab
KH
117 `(progn
118 (defvar ,command ',skeleton ,documentation)
119 (defalias ',command 'skeleton-proxy)))
f3611c70
KH
120
121
ac59aed8 122
f3611c70
KH
123;; This command isn't meant to be called, only it's aliases with meaningful
124;; names are.
125;;;###autoload
4bfd70e9
KH
126(defun skeleton-proxy (&optional str arg)
127 "Insert skeleton defined by variable of same name (see `skeleton-insert').
f3611c70 128Prefix ARG allows wrapping around words or regions (see `skeleton-insert').
017d787a
RS
129If no ARG was given, and the region is visible, it defaults to -1 depending
130on `skeleton-autowrap'. An ARG of M-0 will prevent this just for once.
f3611c70 131This command can also be an abbrev expansion (3rd and 4th columns in
4bfd70e9
KH
132\\[edit-abbrevs] buffer: \"\" command-name).
133
134When called as a function, optional first argument STR may also be a string
135which will be the value of `str' whereas the skeleton's interactor is then
136ignored."
137 (interactive "*P\nP")
f3611c70 138 (let ((function (nth 1 (backtrace-frame 1))))
017d787a 139 (if (eq function 'nth) ; uncompiled Lisp function
f3611c70
KH
140 (setq function (nth 1 (backtrace-frame 5)))
141 (if (eq function 'byte-code) ; tracing byte-compiled function
142 (setq function (nth 1 (backtrace-frame 2)))))
143 (if (not (setq function (funcall skeleton-filter (symbol-value function))))
4bfd70e9
KH
144 (if (memq this-command '(self-insert-command
145 skeleton-pair-insert-maybe
146 expand-abbrev))
147 (setq buffer-undo-list (primitive-undo 1 buffer-undo-list)))
f3611c70 148 (skeleton-insert function
f3611c70
KH
149 (if (setq skeleton-abbrev-cleanup
150 (or (eq this-command 'self-insert-command)
da6a884f
KH
151 (eq this-command
152 'skeleton-pair-insert-maybe)))
f3611c70 153 ()
4bfd70e9 154 ;; Pretend C-x a e passed its prefix arg to us
f3611c70
KH
155 (if (or arg current-prefix-arg)
156 (prefix-numeric-value (or arg
017d787a
RS
157 current-prefix-arg))
158 (and skeleton-autowrap
159 (or (eq last-command 'mouse-drag-region)
160 (and transient-mark-mode mark-active))
161 -1)))
4bfd70e9
KH
162 (if (stringp str)
163 str))
017d787a
RS
164 (and skeleton-abbrev-cleanup
165 (setq skeleton-abbrev-cleanup (point))
166 (add-hook 'post-command-hook 'skeleton-abbrev-cleanup nil t)))))
f3611c70
KH
167
168
169(defun skeleton-abbrev-cleanup (&rest list)
170 "Value for `post-command-hook' to remove char that expanded abbrev."
171 (if (integerp skeleton-abbrev-cleanup)
172 (progn
173 (delete-region skeleton-abbrev-cleanup (point))
017d787a
RS
174 (setq skeleton-abbrev-cleanup)
175 (remove-hook 'post-command-hook 'skeleton-abbrev-cleanup t))))
ac59aed8
RS
176
177
178;;;###autoload
da6a884f 179(defun skeleton-insert (skeleton &optional skeleton-regions str)
f3611c70 180 "Insert the complex statement skeleton SKELETON describes very concisely.
ac59aed8 181
f3611c70
KH
182With optional third REGIONS wrap first interesting point (`_') in skeleton
183around next REGIONS words, if REGIONS is positive. If REGIONS is negative,
184wrap REGIONS preceding interregions into first REGIONS interesting positions
185\(successive `_'s) in skeleton. An interregion is the stretch of text between
186two contiguous marked points. If you marked A B C [] (where [] is the cursor)
187in alphabetical order, the 3 interregions are simply the last 3 regions. But
188if you marked B A [] C, the interregions are B-A, A-[], []-C.
189
4bfd70e9
KH
190Optional fourth STR is the value for the variable `str' within the skeleton.
191When this is non-`nil' the interactor gets ignored, and this should be a valid
192skeleton element.
193
f3611c70
KH
194SKELETON is made up as (INTERACTOR ELEMENT ...). INTERACTOR may be nil if
195not needed, a prompt-string or an expression for complex read functions.
ac59aed8
RS
196
197If ELEMENT is a string or a character it gets inserted (see also
198`skeleton-transformation'). Other possibilities are:
199
4bfd70e9 200 \\n go to next line and indent according to mode
f3611c70
KH
201 _ interesting point, interregion here, point after termination
202 > indent line (or interregion if > _) according to major mode
203 & do next ELEMENT if previous moved point
204 | do next ELEMENT if previous didn't move point
205 -num delete num preceding characters (see `skeleton-untabify')
ac59aed8
RS
206 resume: skipped, continue here if quit is signaled
207 nil skipped
208
f3611c70
KH
209Further elements can be defined via `skeleton-further-elements'. ELEMENT may
210itself be a SKELETON with an INTERACTOR. The user is prompted repeatedly for
211different inputs. The SKELETON is processed as often as the user enters a
212non-empty string. \\[keyboard-quit] terminates skeleton insertion, but
213continues after `resume:' and positions at `_' if any. If INTERACTOR in such
214a subskeleton is a prompt-string which contains a \".. %s ..\" it is
4bfd70e9
KH
215formatted with `skeleton-subprompt'. Such an INTERACTOR may also a list of
216strings with the subskeleton being repeated once for each string.
ac59aed8 217
017d787a
RS
218Quoted Lisp expressions are evaluated evaluated for their side-effect.
219Other Lisp expressions are evaluated and the value treated as above.
49168e73 220Note that expressions may not return `t' since this implies an
f3611c70
KH
221endless loop. Modes can define other symbols by locally setting them
222to any valid skeleton element. The following local variables are
223available:
ac59aed8 224
f3611c70 225 str first time: read a string according to INTERACTOR
ac59aed8 226 then: insert previously read string once more
f3611c70 227 help help-form during interaction with the user or `nil'
773500ab 228 input initial input (string or cons with index) while reading str
017d787a 229 v1, v2 local variables for memorizing anything you want
4bfd70e9
KH
230
231When done with skeleton, but before going back to `_'-point call
232`skeleton-end-hook' if that is non-`nil'."
233 (and skeleton-regions
234 (setq skeleton-regions
235 (if (> skeleton-regions 0)
f3611c70 236 (list (point-marker)
4bfd70e9
KH
237 (save-excursion (forward-word skeleton-regions)
238 (point-marker)))
239 (setq skeleton-regions (- skeleton-regions))
240 ;; copy skeleton-regions - 1 elements from `mark-ring'
f3611c70
KH
241 (let ((l1 (cons (mark-marker) mark-ring))
242 (l2 (list (point-marker))))
4bfd70e9 243 (while (and l1 (> skeleton-regions 0))
f3611c70 244 (setq l2 (cons (car l1) l2)
4bfd70e9 245 skeleton-regions (1- skeleton-regions)
f3611c70
KH
246 l1 (cdr l1)))
247 (sort l2 '<))))
4bfd70e9
KH
248 (goto-char (car skeleton-regions))
249 (setq skeleton-regions (cdr skeleton-regions)))
773500ab 250 (let ((beg (point))
4bfd70e9 251 skeleton-modified skeleton-point resume: help input v1 v2)
f3611c70 252 (unwind-protect
773500ab 253 (eval `(let ,skeleton-further-elements
4bfd70e9
KH
254 (skeleton-internal-list skeleton str)))
255 (run-hooks 'skeleton-end-hook)
773500ab
KH
256 (sit-for 0)
257 (or (pos-visible-in-window-p beg)
258 (progn
259 (goto-char beg)
260 (recenter 0)))
4bfd70e9
KH
261 (if skeleton-point
262 (goto-char skeleton-point)))))
f3611c70 263
f3611c70 264(defun skeleton-read (str &optional initial-input recursive)
773500ab 265 "Function for reading a string from the minibuffer within skeletons.
f3611c70 266PROMPT may contain a `%s' which will be replaced by `skeleton-subprompt'.
773500ab
KH
267If non-`nil' second arg INITIAL-INPUT or variable `input' is a string or
268cons with index to insert before reading. If third arg RECURSIVE is non-`nil'
269i.e. we are handling the iterator of a subskeleton, returns empty string if
270user didn't modify input.
271While reading, the value of `minibuffer-help-form' is variable `help' if that
272is non-`nil' or a default string."
da6a884f
KH
273 (let ((minibuffer-help-form (or (if (boundp 'help) (symbol-value 'help))
274 (if recursive "\
ac59aed8
RS
275As long as you provide input you will insert another subskeleton.
276
277If you enter the empty string, the loop inserting subskeletons is
278left, and the current one is removed as far as it has been entered.
279
280If you quit, the current subskeleton is removed as far as it has been
281entered. No more of the skeleton will be inserted, except maybe for a
f3611c70 282syntactically necessary termination."
773500ab 283 "\
f3611c70 284You are inserting a skeleton. Standard text gets inserted into the buffer
da6a884f
KH
285automatically, and you are prompted to fill in the variable parts.")))
286 (eolp (eolp)))
287 ;; since Emacs doesn't show main window's cursor, do something noticeable
288 (or eolp
289 (open-line 1))
290 (unwind-protect
291 (setq str (if (stringp str)
292 (read-string (format str skeleton-subprompt)
293 (setq initial-input
294 (or initial-input
295 (symbol-value 'input))))
296 (eval str)))
297 (or eolp
298 (delete-char 1))))
773500ab
KH
299 (if (and recursive
300 (or (null str)
301 (string= str "")
302 (equal str initial-input)
303 (equal str (car-safe initial-input))))
ac59aed8
RS
304 (signal 'quit t)
305 str))
306
4bfd70e9 307(defun skeleton-internal-list (skeleton &optional str recursive)
f3611c70
KH
308 (let* ((start (save-excursion (beginning-of-line) (point)))
309 (column (current-column))
310 (line (buffer-substring start
311 (save-excursion (end-of-line) (point))))
312 opoint)
4bfd70e9
KH
313 (or str
314 (setq str `(setq str (skeleton-read ',(car skeleton) nil ,recursive))))
315 (while (setq skeleton-modified (eq opoint (point))
773500ab
KH
316 opoint (point)
317 skeleton (cdr skeleton))
318 (condition-case quit
319 (skeleton-internal-1 (car skeleton))
320 (quit
321 (if (eq (cdr quit) 'recursive)
4bfd70e9
KH
322 (setq recursive 'quit
323 skeleton (memq 'resume: skeleton))
773500ab
KH
324 ;; remove the subskeleton as far as it has been shown
325 ;; the subskeleton shouldn't have deleted outside current line
da6a884f 326 (end-of-line)
773500ab
KH
327 (delete-region start (point))
328 (insert line)
329 (move-to-column column)
330 (if (cdr quit)
331 (setq skeleton ()
332 recursive nil)
333 (signal 'quit 'recursive)))))))
334 ;; maybe continue loop or go on to next outer resume: section
335 (if (eq recursive 'quit)
336 (signal 'quit 'recursive)
337 recursive))
f3611c70
KH
338
339
340(defun skeleton-internal-1 (element &optional literal)
4bfd70e9
KH
341 (cond ((char-or-string-p element)
342 (if (and (integerp element) ; -num
343 (< element 0))
344 (if skeleton-untabify
345 (backward-delete-char-untabify (- element))
346 (delete-backward-char (- element)))
347 (insert-before-markers (if (and skeleton-transformation
348 (not literal))
349 (funcall skeleton-transformation element)
350 element))))
c93d212a 351 ((eq element '\n) ; actually (eq '\n 'n)
4bfd70e9
KH
352 (if (and skeleton-regions
353 (eq (nth 1 skeleton) '_))
354 (progn
355 (or (eolp)
356 (newline))
357 (indent-region (point) (car skeleton-regions) nil))
358 (if skeleton-newline-indent-rigidly
359 (indent-to (prog1 (current-indentation)
360 (newline)))
361 (newline)
362 (indent-according-to-mode))))
c93d212a 363 ((eq element '>)
4bfd70e9 364 (if (and skeleton-regions
f3611c70 365 (eq (nth 1 skeleton) '_))
4bfd70e9
KH
366 (indent-region (point) (car skeleton-regions) nil)
367 (indent-according-to-mode)))
c93d212a 368 ((eq element '_)
4bfd70e9 369 (if skeleton-regions
f3611c70 370 (progn
4bfd70e9 371 (goto-char (car skeleton-regions))
da6a884f
KH
372 (setq skeleton-regions (cdr skeleton-regions))
373 (and (<= (current-column) (current-indentation))
374 (eq (nth 1 skeleton) '\n)
375 (end-of-line 0)))
4bfd70e9
KH
376 (or skeleton-point
377 (setq skeleton-point (point)))))
c93d212a 378 ((eq element '&)
4bfd70e9 379 (if skeleton-modified
f3611c70 380 (setq skeleton (cdr skeleton))))
c93d212a 381 ((eq element '|)
4bfd70e9 382 (or skeleton-modified
f3611c70 383 (setq skeleton (cdr skeleton))))
4bfd70e9 384 ((eq 'quote (car-safe element))
f3611c70 385 (eval (nth 1 element)))
4bfd70e9
KH
386 ((or (stringp (car-safe element))
387 (consp (car-safe element)))
388 (if (symbolp (car-safe (car element)))
389 (while (skeleton-internal-list element nil t))
390 (setq literal (car element))
391 (while literal
392 (skeleton-internal-list element (car literal))
393 (setq literal (cdr literal)))))
f3611c70
KH
394 ((null element))
395 ((skeleton-internal-1 (eval element) t))))
ac59aed8 396
4bfd70e9 397
f3611c70
KH
398;; Maybe belongs into simple.el or elsewhere
399
a013d266
RS
400;;;(define-skeleton local-variables-section
401;;; "Insert a local variables section. Use current comment syntax if any."
a013d266
RS
402;;; (completing-read "Mode: " obarray
403;;; (lambda (symbol)
404;;; (if (commandp symbol)
405;;; (string-match "-mode$" (symbol-name symbol))))
406;;; t)
017d787a
RS
407;;; '(save-excursion
408;;; (if (re-search-forward page-delimiter nil t)
409;;; (error "Not on last page.")))
410;;; comment-start "Local Variables:" comment-end \n
411;;; comment-start "mode: " str
a013d266
RS
412;;; & -5 | '(kill-line 0) & -1 | comment-end \n
413;;; ( (completing-read (format "Variable, %s: " skeleton-subprompt)
414;;; obarray
415;;; (lambda (symbol)
416;;; (or (eq symbol 'eval)
417;;; (user-variable-p symbol)))
418;;; t)
419;;; comment-start str ": "
420;;; (read-from-minibuffer "Expression: " nil read-expression-map nil
421;;; 'read-expression-history) | _
422;;; comment-end \n)
423;;; resume:
424;;; comment-start "End:" comment-end)
ac59aed8 425\f
bc35d5b3 426;; Variables and command for automatically inserting pairs like () or "".
ac59aed8 427
bc35d5b3 428(defvar skeleton-pair nil
ac59aed8 429 "*If this is nil pairing is turned off, no matter what else is set.
bc35d5b3
RS
430Otherwise modes with `skeleton-pair-insert-maybe' on some keys
431will attempt to insert pairs of matching characters.")
ac59aed8
RS
432
433
bc35d5b3
RS
434(defvar skeleton-pair-on-word nil
435 "*If this is nil, paired insertion is inhibited before or inside a word.")
ac59aed8
RS
436
437
bc35d5b3
RS
438(defvar skeleton-pair-filter (lambda ())
439 "Attempt paired insertion if this function returns nil, before inserting.
ac59aed8
RS
440This allows for context-sensitive checking whether pairing is appropriate.")
441
442
bc35d5b3
RS
443(defvar skeleton-pair-alist ()
444 "An override alist of pairing partners matched against `last-command-char'.
445Each alist element, which looks like (ELEMENT ...), is passed to
446`skeleton-insert' with no interactor. Variable `str' does nothing.
ac59aed8 447
f3611c70 448Elements might be (?` ?` _ \"''\"), (?\\( ? _ \" )\") or (?{ \\n > _ \\n ?} >).")
ac59aed8
RS
449
450
ac59aed8 451;;;###autoload
bc35d5b3 452(defun skeleton-pair-insert-maybe (arg)
ac59aed8
RS
453 "Insert the character you type ARG times.
454
bc35d5b3
RS
455With no ARG, if `skeleton-pair' is non-nil, and if
456`skeleton-pair-on-word' is non-nil or we are not before or inside a
457word, and if `skeleton-pair-filter' returns nil, pairing is performed.
ac59aed8 458
bc35d5b3 459If a match is found in `skeleton-pair-alist', that is inserted, else
ac59aed8
RS
460the defaults are used. These are (), [], {}, <> and `' for the
461symmetrical ones, and the same character twice for the others."
462 (interactive "*P")
463 (if (or arg
773500ab 464 overwrite-mode
bc35d5b3
RS
465 (not skeleton-pair)
466 (if (not skeleton-pair-on-word) (looking-at "\\w"))
467 (funcall skeleton-pair-filter))
ac59aed8 468 (self-insert-command (prefix-numeric-value arg))
f3611c70
KH
469 (self-insert-command 1)
470 (if skeleton-abbrev-cleanup
471 ()
472 ;; (preceding-char) is stripped of any Meta-stuff in last-command-char
bc35d5b3 473 (if (setq arg (assq (preceding-char) skeleton-pair-alist))
da6a884f
KH
474 ;; typed char is inserted (car is no real interactor)
475 (let (skeleton-end-hook)
476 (skeleton-insert arg))
f3611c70
KH
477 (save-excursion
478 (insert (or (cdr (assq (preceding-char)
479 '((?( . ?))
480 (?[ . ?])
481 (?{ . ?})
482 (?< . ?>)
483 (?` . ?'))))
484 last-command-char)))))))
ac59aed8
RS
485
486\f
017d787a 487;;; A more serious example can be found in sh-script.el
bc35d5b3 488;;;(defun mirror-mode ()
017d787a
RS
489;; "This major mode is an amusing little example of paired insertion.
490;;All printable characters do a paired self insert, while the other commands
491;;work normally."
492;; (interactive)
493;; (kill-all-local-variables)
494;; (make-local-variable 'pair)
495;; (make-local-variable 'pair-on-word)
496;; (make-local-variable 'pair-filter)
497;; (make-local-variable 'pair-alist)
498;; (setq major-mode 'mirror-mode
499;; mode-name "Mirror"
500;; pair-on-word t
501;; ;; in the middle column insert one or none if odd window-width
502;; pair-filter (lambda ()
503;; (if (>= (current-column)
504;; (/ (window-width) 2))
505;; ;; insert both on next line
506;; (next-line 1)
507;; ;; insert one or both?
508;; (= (* 2 (1+ (current-column)))
509;; (window-width))))
510;; ;; mirror these the other way round as well
511;; pair-alist '((?) _ ?()
512;; (?] _ ?[)
513;; (?} _ ?{)
514;; (?> _ ?<)
515;; (?/ _ ?\\)
516;; (?\\ _ ?/)
517;; (?` ?` _ "''")
518;; (?' ?' _ "``"))
519;; ;; in this mode we exceptionally ignore the user, else it's no fun
520;; pair t)
521;; (let ((map (make-keymap))
522;; (i ? ))
523;; (use-local-map map)
524;; (setq map (car (cdr map)))
525;; (while (< i ?\^?)
526;; (aset map i 'skeleton-pair-insert-maybe)
527;; (setq i (1+ i))))
528;; (run-hooks 'mirror-mode-hook))
ac59aed8 529
2aea64fb
RS
530(provide 'skeleton)
531
ac59aed8 532;; skeleton.el ends here