1
;;; snippet.el -- insert snippets of text into a buffer
2
3
;; Copyright (C) 2005 Pete Kazmier
4
5
;; Version: 0.2
6
;; Author: Pete Kazmier
7
8
;; This file is not part of GNU Emacs, but it is distributed under
9
;; the same terms as 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
13
;; by the Free Software Foundation; either version 2, or (at your
14
;; option) 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 the
23
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24
;; Boston, MA 02111-1307, USA.
25
26
;;; Description:
27
28
;; A quick stab at providing a simple template facility like the one
29
;; present in TextMate (an OSX editor).  The general idea is that a
30
;; snippet of text (called a template) is inserted into a buffer
31
;; (perhaps triggered by an abbrev), and while the point is within the
32
;; snippet, a special keymap is active to permit the user to cycle the
33
;; point to any of the defined fields (placeholders) within the
34
;; template via `snippet-next-field' and `snippet-prev-field'.
35
36
;; For example, the following template might be a useful while editing
37
;; HTML:
38
39
;;   <a href="$$">$$</a>
40
41
;; This template might be useful for python developers.  In this
42
;; example, reasonable defaults have been supplied:
43
44
;;   for $${element} in $${sequence}:
45
;;       match = $${regexp}.search($${element})
46
47
;; When a template is inserted into a buffer (could be triggered by an
48
;; abbrev expansion, or simply bound to some key), point is moved to
49
;; the first field denoted by the "$$" characters (configurable via
50
;; `snippet-field-identifier').  The optional default for a field is
51
;; specified by the "{default}" (the delimiters are configurable via
52
;; `snippet-field-default-beg-char' and `snippet-field-defaul-end-char'.
53
54
;; If present, the default will be inserted and highlighted.  The user
55
;; then has the option of accepting the default by simply tabbing over
56
;; to the next field (any other key bound to `snippet-next-field' in
57
;; `snippet-map' can be used).  Alternatively, the user can start
58
;; typing their own value for the field which will cause the default
59
;; to be immediately replaced with the user's own input.  If two or
60
;; more fields have the same default value, they are linked together
61
;; (changing one will change the other dynamically as you type).
62
63
;; `snippet-next-field' (bound to <tab> by default) moves the point to
64
;; the next field.  `snippet-prev-field' (bound to <S-tab> by default)
65
;; moves the point to the previous field.  When the snippet has been
66
;; completed, the user simply tabs past the last field which causes
67
;; the snippet to revert to plain text in a buffer.  The idea is that
68
;; snippets should get out of a user's way as soon as they have been
69
;; filled and completed.
70
71
;; After tabbing past all of the fields, point is moved to the end of
72
;; the snippet, unless the user has specified a place within the
73
;; template with the `snippet-exit-identifier' ("$." by default).  For
74
;; example: 
75
76
;;   if ($${test} {
77
;;       $.
78
;;   }
79
80
;; Indentation can be controlled on a per line basis by including the
81
;; `snippet-indent' string within the template.  Most often one would
82
;; include this at the beginning of a line; however, there are times
83
;; when indentation is better performed in other parts of the line.
84
;; The following shows how to use the functionality:
85
86
;;   if ($${test}) {
87
;;   $>this line would be indented
88
;;   this line will be indented after being inserted$>
89
;;   }
90
91
;;; Usage:
92
93
;; Snippets are inserted with the `snippet-insert' function.  This
94
;; function inserts the snippet into the current buffer.  It expects a
95
;; single argument which is the template that is to be inserted.  For
96
;; example:
97
98
;;   (snippet-insert "for $${element} in $${sequence}:")
99
100
;; `snippet-insert' can be called interactively in which case the user
101
;; is prompted for the template to insert.  This is hardly useful at
102
;; all unless you are testing the functionality of this code.
103
104
;; Snippets are much more effective when they are bound to expansions
105
;; for abbreviations.  When binding a snippet to an abbreviation, it
106
;; is important that you disable the insertion of the character that
107
;; triggered the expansion (typically some form of whitespace).  For
108
;; example, this is what you should NOT do:
109
110
;;   (define-abbrev python-mode-abbrev-table  ; abbrev table
111
;;                  "for"                     ; name
112
;;                  ""                        ; expansion
113
;;                  '(lambda ()               ; expansion hook
114
;;                     (snippet-insert 
115
;;                      "for $${element} in $${sequence}:")))
116
117
;; The above example does not work as expected because after the
118
;; expansion hook is called, the snippet is inserted, and the point is
119
;; moved to the first field.  The problem occurs because when the user
120
;; typed "f o r <Spc>", the "<Spc>" character is inserted after the
121
;; snippet has been inserted.  The point happens to be located at the
122
;; first field and thus the "<Spc>" will delete any field default that
123
;; was present.
124
125
;; Fortunately, this is easy to fix.  According to the documentation
126
;; for `define-abbrev', if the hook function is a symbol whose
127
;; `no-self-insert' property is non-nil, then hook can control whether
128
;; or not the insertion of the character that triggered the abbrev
129
;; expansion is inserted.  `insert-snippet' returns non-nil and thus
130
;; the proper way of defining the abbrev is as follows:
131
132
;;   (defun python-foo-expansion ()
133
;;     (snippet-insert "for $${element} in $${sequence}:"))
134
135
;;   (put 'python-foo-expansion 'no-self-insert t)
136
137
;;   (define-abbrev python-mode-abbrev-table    ; abbrev table
138
;;                  "for"                       ; name
139
;;                  ""                          ; expansion
140
;;                  'python-foo-expansion)      ; expansion hook
141
142
;; Unfortunately, this is a lot of work to define what should be a
143
;; simple abbrev.  For convenience, this package provides a macro
144
;; `snippet-abbrev' that can be used with much less effort:
145
146
;;   (snippet-abbrev 'python-mode-abbrev-table            ; table
147
;;                   "for"                               ; name
148
;;                   "for $${element} in $${sequence}:") ; template
149
150
;; For even more convevience, when defining a lot of abbrevs in a
151
;; particular abbrev table, the package provides another macro
152
;; `snippet-with-abbrev-table':
153
154
;;   (snippet-with-abbrev-table 'python-mode-abbrev-table
155
;;     ("for" .  "for $${element} in $${sequence}:")
156
;;     ("im"  .  "import $$")
157
;;     ("if"  .  "if $${True}:")
158
;;     ("wh"  .  "while $${True}:"))
159
160
;; Be sure that the appropriate abbrev-table is loaded before using
161
;; the above otherwise you'll get an error.  I use the above in my
162
;; python-mode-hook.
163
164
;; Finally, for those running a recent version of Emacs, you can
165
;; disable snippet expansion in various parts of the buffer.  I use
166
;; this to disable the above "for" expansion while typing comments in
167
;; my python code.  Add the following line to your python-mode hook:
168
169
;;   (add-hook 'pre-abbrev-expand-hook
170
;;             (lambda ()
171
;;               (setq local-abbrev-table
172
;;                     (if (inside-comment-p)
173
;;                         text-mode-abbrev-table
174
;;                       python-mode-abbrev-table)))
175
;;             nil t)))
176
 
177
;;; Implementation Notes:
178
179
;; This is my first significant chunk of elisp code.  I have very
180
;; little experience coding with elisp; however, I have tried to
181
;; document the code for anyone trying to follow along.  Here are some
182
;; brief notes on the implementation.
183
184
;; When a snippet is inserted, the entire template of text has an
185
;; overlay applied.  This overlay is referred to as the "bound"
186
;; overlay in the code.  It is used to bold-face the snippet as well
187
;; as provide the keymap that is used while the point is located
188
;; within the snippet (so users can tab between fields).  This overlay
189
;; is actually one character longer than the template.  The reason is
190
;; to make sure that our local keymap is still in effect when a user
191
;; is typing in a field that happens to be at the end of the
192
;; template.
193
194
;; In addition, for each field (denoted by snippet-field-identifier),
195
;; an overlay is created.  These overlays are used to provide the
196
;; highlighting of the field values, the location of where the point
197
;; should move when tab is pressed (the start of the overlay is used
198
;; for this purpose), as well as the hooks to delete the default value
199
;; if a user starts to type their own value (the modification hooks of
200
;; the overlay are used for this purpose).
201
202
;; Once the user has tabbed out of the snippet, all overlays are
203
;; deleted and the snippet then becomes normal text.  Moving the
204
;; cursor back into the snippet has no affect (the snippet is not
205
;; activated again).  The idea is that the snippet concept should get
206
;; out of the users way as quickly as possible.
207
208
;;; Comparisons to Other Packages
209
210
;; tempo.el
211
;;  - Template definition is very lispy (although powerful).  In
212
;;    contrast, snippets are simple strings with minimal syntax.
213
;;  - Template parameters can be prompted via minibuffer.  In
214
;;    contrast, snippets use overlays to visually cue the user for
215
;;    parameters.
216
;;  + Templates can be wrapped around regions of text.
217
;;
218
219
;;; Known Limitations:
220
221
;; - When one uses something like `dabbrev-expand', when the text is
222
;;   inserted, it blows away a lot of the snippet.  Not sure why yet.
223
;; - Using 'indent-according-to-mode' does not seem to behave well
224
;;   with Python mode.  I have no idea why, the overlays end up
225
;;   getting shifted around incorrectly.
226
227
;;; Code:
228
229
(require 'cl)
230
231
(defgroup snippet nil 
232
  "Insert a template with fields that con contain optional defaults."
233
  :prefix "snippet-"
234
  :group 'abbrev
235
  :group 'convenience)
236
237
(defcustom snippet-bound-face 'bold
238
  "*Face used for the body of the snippet."
239
  :type 'face
240
  :group 'snippet)
241
242
(defcustom snippet-field-face 'highlight
243
  "*Face used for the fields' default values."
244
  :type 'face
245
  :group 'snippet)
246
247
(defcustom snippet-field-identifier "$$"
248
  "*String used to identify field placeholders."
249
  :type 'string
250
  :group 'snippet)
251
252
(defcustom snippet-exit-identifier "$."
253
  "*String used to identify the exit point of the snippet."
254
  :type 'string
255
  :group 'snippet)
256
257
(defcustom snippet-field-default-beg-char ?{
258
  "*Character used to identify the start of a field's default value."
259
  :type 'character
260
  :group 'snippet)
261
262
(defcustom snippet-field-default-end-char ?}
263
  "*Character used to identify the stop of a field's default value."
264
  :type 'character
265
  :group 'snippet)
266
267
(defcustom snippet-indent "$>"
268
  "*String used to indicate that a line is to be indented."
269
  :type 'character
270
  :group 'snippet)
271
272
(defcustom snippet-line-terminator "\n"
273
  "*String used to indicate the end of line in a snippet template."
274
  :type 'string
275
  :group 'snippet)
276
277
(defvar snippet-map (make-sparse-keymap)
278
  "Keymap used while the point is located within a snippet.")
279
280
;; Default key bindings
281
(define-key snippet-map (kbd "TAB")             'snippet-next-field)
282
(define-key snippet-map (kbd "<S-tab>")         'snippet-prev-field)
283
(define-key snippet-map (kbd "<S-iso-lefttab>") 'snippet-prev-field)
284
285
(defstruct snippet 
286
  "Structure containing the overlays used to display a snippet.
287
288
The BOUND slot contains an overlay to bound the entire text of the
289
template.  This overlay is used to provide a different face
290
configurable via `snippet-bound-face' as well as the keymap that
291
enables tabbing between fields.
292
293
The FIELDS slot contains a list of overlays used to indicate the
294
position of each field.  In addition, if a field has a default, the
295
field overlay is used to provide a different face configurable via
296
`snippet-field-face'.
297
298
The EXIT-MARKER slot contains a marker where point should be placed
299
after the user has cycled through all available fields."
300
  bound fields exit-marker)
301
302
(defvar snippet nil
303
  "Snippet in the current buffer.
304
There is no more than one snippet per buffer.  This variable is buffer
305
local.")
306
307
(make-variable-buffer-local 'snippet)
308
309
(defun snippet-make-bound-overlay ()
310
  "Create an overlay to bound a snippet.
311
Add the appropriate properties for the overlay to provide: a face used
312
to display the snippet, the keymap to use while within the snippet,
313
and the modification hooks to clean up the overlay in the event it is
314
deleted."
315
  (let ((bound (make-overlay (point) (point) (current-buffer) nil nil)))
316
    (overlay-put bound 'keymap snippet-map)
317
    (overlay-put bound 'face snippet-bound-face)
318
    (overlay-put bound 'modification-hooks '(snippet-bound-modified))
319
    bound))
320
321
(defun snippet-make-field-overlay (&optional name)
322
  "Create an overlay for a field in a snippet.  
323
Add the appropriate properties for the overlay to provide: a face used
324
to display a field's default value, and modification hooks to remove
325
the default text if the user starts typing."
326
  (let ((field (make-overlay (point) (point) (current-buffer) nil t)))
327
    (overlay-put field 'face snippet-field-face)
328
    (overlay-put field 'insert-in-front-hooks '(snippet-field-insert
329
                                                snippet-field-update))
330
    (overlay-put field 'insert-behind-hooks '(snippet-field-modified
331
                                              snippet-field-update))
332
    (overlay-put field 'modification-hooks '(snippet-field-modified
333
                                             snippet-field-update))
334
    (overlay-put field 'name (when name (intern name)))
335
    field))
336
337
(defun snippet-fields-with-name (name)
338
  "Return a list of fields whose name property is equal to NAME."
339
  (loop for field in (snippet-fields snippet) 
340
        when (eq name (overlay-get field 'name))
341
        collect field))
342
343
(defun snippet-bound-modified (bound after beg end &optional change)
344
  "Ensure the overlay that bounds a snippet is cleaned up.
345
This modification hook is triggered when the overlay that bounds the
346
snippet is modified.  It runs after the change has been made and
347
ensures that if the snippet has been deleted by the user, the
348
appropriate cleanup occurs."
349
  (when (and after (> 2 (- (overlay-end bound) (overlay-start bound))))
350
    (snippet-cleanup)))
351
352
(defun snippet-field-insert (field after beg end &optional change)
353
  "Delete the default field value.
354
This insertion hook is triggered when a user starts to type when the
355
point is positioned at the beginning of a field (this occurs when the
356
user chooses to replace the field default).  In this case, the hook
357
deletes the field default."
358
  (let ((inhibit-modification-hooks t))
359
    (when (not after)
360
      (delete-region (overlay-start field) (overlay-end field)))))
361
362
(defun snippet-field-modified (field after beg end &optional change)
363
  "Shrink the field overlay.
364
This modification hook is triggered when a user starts to type when
365
the point is positioned in the middle or at the end of a field (this
366
occurs when the user chooses to edit the field default).  It is used
367
to ensure that the bound overlay always covers the entirety of all
368
field overlays, if not, its extends the bound overlay appropriately."
369
  (let ((bound (snippet-bound snippet)))
370
    (when (and after bound (> (overlay-end field) (overlay-end bound)))
371
      (move-overlay bound (overlay-start bound) (overlay-end field)))))
372
373
(defun snippet-field-update (field after beg end &optional change)
374
  "Update all fields that have the same name.
375
This modificition hook is triggered when a user edits any field and is
376
responsible for updating all other fields that share a common name."
377
  (let ((name (overlay-get field 'name))
378
        (value (buffer-substring (overlay-start field) (overlay-end field)))
379
        (inhibit-modification-hooks t))
380
    (when (and name after)
381
      (save-excursion
382
        (dolist (like-field (set-difference (snippet-fields-with-name name) 
383
                                            (list field)))
384
          (goto-char (overlay-start like-field))
385
          (delete-region (overlay-start like-field)
386
                         (overlay-end like-field))
387
          (insert value))))))
388
389
(defun snippet-exit-snippet ()
390
  "Move point to `snippet-exit-identifier' or end of bound.
391
If the snippet has defined `snippet-exit-identifier' in the template,
392
move the point to that location.  Otherwise, move it to the end of the
393
snippet."
394
  (goto-char (snippet-exit-marker snippet))
395
  (snippet-cleanup))
396
397
(defun snippet-next-field ()
398
  "Move point forward to the next field in the `snippet'.
399
If there are no more fields in the snippet, point is moved to the end
400
of the snippet or the location specified by `snippet-exit-identifier',
401
and the snippet reverts to normal text."
402
  (interactive)
403
  (let* ((bound (snippet-bound snippet))
404
         (fields (snippet-fields snippet))
405
         (exit (snippet-exit-marker snippet))
406
         (next-pos (loop for field in fields
407
                         for start = (overlay-start field)
408
                         when (< (point) start) return start)))
409
    (if (not (null next-pos))
410
        (goto-char next-pos)
411
      (goto-char exit)
412
      (snippet-cleanup))))
413
414
(defun snippet-prev-field ()
415
  "Move point backward to the previous field in the `snippet'.
416
If there are no more fields in the snippet, point is moved to the end
417
of the snippet or the location specified by `snippet-exit-identifier',
418
and the snippet reverts to normal text."
419
  (interactive)
420
  (let* ((bound (snippet-bound snippet))
421
         (fields (snippet-fields snippet))
422
         (exit (snippet-exit-marker snippet))         
423
         (prev-pos (loop for field in (reverse fields)
424
                         for start = (overlay-start field)
425
                         when (> (point) start) return start)))
426
    (if (not (null prev-pos))
427
        (goto-char prev-pos)
428
      (goto-char exit)
429
      (snippet-cleanup))))
430
431
(defun snippet-cleanup ()
432
  "Delete all overlays associated with `snippet'.
433
This effectively reverts the snippet to normal text in the buffer."
434
  (when snippet
435
    (when (snippet-bound snippet)
436
      (delete-overlay (snippet-bound snippet)))
437
    (dolist (field (snippet-fields snippet))
438
      (delete-overlay field))
439
    (setq snippet nil)))
440
441
(defun snippet-field-regexp ()
442
  "Return a regexp that is used to search for fields within a template."
443
  (let ((beg (char-to-string snippet-field-default-beg-char))
444
        (end (char-to-string snippet-field-default-end-char)))
445
    (concat (regexp-quote snippet-field-identifier)
446
            "\\("
447
            (regexp-quote beg)
448
            "\\([^"
449
            (regexp-quote end)
450
            "]+\\)"
451
            (regexp-quote end)
452
            "\\)?")))
453
454
(defun snippet-split-string (string &optional separators include-separators-p)
455
  "Split STRING into substrings and separators at SEPARATORS.
456
Return a list of substrings and optional include the separators in the
457
list if INCLUDE-SEPARATORS-P is non-nil."
458
  (let ((start 0) (list '()))
459
    (while (string-match (or separators snippet-line-terminator) string start)
460
      (when (< start (match-beginning 0))
461
        (push (substring string start (match-beginning 0)) list))
462
      (when include-separators-p
463
        (push (substring string (match-beginning 0) (match-end 0)) list))
464
      (setq start (match-end 0)))
465
    (when (< start (length string))
466
      (push (substring string start) list))
467
    (nreverse list)))
468
469
(defun snippet-split-regexp ()
470
  "Return a regexp to split the template into component parts."
471
  (concat (regexp-quote snippet-line-terminator)
472
          "\\|"
473
          (regexp-quote snippet-indent)))
474
475
(defun snippet-insert (template)
476
  "Insert a snippet into the current buffer at point.  
477
TEMPLATE is a string that may optionally contain fields which are
478
specified by `snippet-field-identifier'.  Fields may optionally also
479
include default values delimited by `snippet-field-default-beg-char'
480
and `snippet-field-default-end-char'.
481
482
For example, the following template specifies two fields which have
483
the default values of \"element\" and \"sequence\":
484
485
  \"for $${element} in $${sequence}:\"
486
487
In the next example, only one field is specified and no default has
488
been provided:
489
490
  \"import $$\"
491
492
This function may be called interactively, in which case, the TEMPLATE
493
is prompted for.  However, users do not typically invoke this function
494
interactively, rather it is most often called as part of an abbrev
495
expansion.  See `snippet-abbrev' and `snippet-with-abbrev-table' for
496
more information."
497
  (interactive "sSnippet template: ")
498
499
  ;; Step 1: Ensure only one snippet exists at a time
500
  (snippet-cleanup)
501
502
  ;; Step 2: Create a new snippet and add the overlay to bound the
503
  ;; template body.  It should be noted that the bounded overlay is
504
  ;; sized to be one character larger than the template body text.
505
  ;; This enables our keymap to be active when a field happens to be
506
  ;; the last item in a template.  We disable abbrev mode to prevent
507
  ;; our template from triggering another abbrev expansion (I do not
508
  ;; know if the use of `insert' will actually trigger abbrevs).
509
  (let ((abbrev-mode nil))
510
    (setq snippet (make-snippet :bound (snippet-make-bound-overlay)))
511
    (let ((start (point))
512
          (count 0))
513
      (dolist (line (snippet-split-string template (snippet-split-regexp) t))
514
        (cond ((string-equal snippet-line-terminator line)
515
               (insert "\n"))
516
              ((string-equal snippet-indent line)
517
               (indent-according-to-mode))
518
              (t
519
               (insert line))))
520
      (move-overlay (snippet-bound snippet) start (1+ (point))))
521
522
523
    ;; Step 3: Insert the exit marker so we know where to move point
524
    ;; to when user is done with snippet.  If they did not specify
525
    ;; where point should land, set the exit marker to the end of the
526
    ;; snippet. 
527
    (goto-char (overlay-start (snippet-bound snippet)))
528
    (while (re-search-forward (regexp-quote snippet-exit-identifier)
529
                              (overlay-end (snippet-bound snippet)) 
530
                              t)
531
      (replace-match "")
532
      (setf (snippet-exit-marker snippet) (point-marker)))
533
    
534
    (unless (snippet-exit-marker snippet)
535
      (let ((end (overlay-end (snippet-bound snippet))))
536
        (goto-char (if (= end (point-max)) end (1- end))))
537
      (setf (snippet-exit-marker snippet) (point-marker)))
538
  
539
    (set-marker-insertion-type (snippet-exit-marker snippet) t)
540
541
    ;; Step 4: Create field overlays for each field and insert any
542
    ;; default values for the field.
543
    (goto-char (overlay-start (snippet-bound snippet)))
544
    (while (re-search-forward (snippet-field-regexp)
545
                              (overlay-end (snippet-bound snippet)) 
546
                              t)
547
      (let ((field (snippet-make-field-overlay (match-string 2)))
548
            (start (match-beginning 0)))
549
        (push field (snippet-fields snippet))
550
        (replace-match (if (match-beginning 2) "\\2" ""))
551
        (move-overlay field start (point))))
552
    
553
    ;; These are reversed so they are in order of how they appeared in
554
    ;; the template as we index into this list when cycling field to
555
    ;; field. 
556
    (setf (snippet-fields snippet) (reverse (snippet-fields snippet))))
557
558
  ;; Step 5: Position the point at the first field or the end of the
559
  ;; template body if no fields are present.  We need to take into
560
  ;; consideration the special case where the first field is at the
561
  ;; start of the snippet (otherwise the call to snippet-next-field
562
  ;; will go past it).
563
  (let ((bound (snippet-bound snippet))
564
        (first (car (snippet-fields snippet))))
565
    (if (and first (= (overlay-start bound) (overlay-start first)))
566
        (goto-char (overlay-start first))
567
      (goto-char (overlay-start (snippet-bound snippet)))
568
      (snippet-next-field))))
569
570
(defun snippet-strip-abbrev-table-suffix (str)
571
  "Strip a suffix of \"-abbrev-table\" if one is present."
572
  (if (string-match "^\\(.*\\)-abbrev-table$" str)
573
      (match-string 1 str)
574
      str))
575
576
(defun snippet-make-abbrev-expansion-hook (abbrev-table abbrev-name template)
577
  "Define a function with the `no-self-insert' property set non-nil.
578
The function name is composed of \"snippet-abbrev-\", the abbrev table
579
name, and the name of the abbrev.  If the abbrev table name ends in
580
\"-abbrev-table\", it is stripped."
581
  (let ((abbrev-expansion (intern
582
                           (concat "snippet-abbrev-" 
583
                                   (snippet-strip-abbrev-table-suffix
584
                                    (symbol-name abbrev-table))
585
                                   "-"
586
                                   abbrev-name))))
587
    (fset abbrev-expansion 
588
          `(lambda ()
589
             ,(format (concat "Abbrev expansion hook for \"%s\".\n"
590
                              "Expands to the following snippet:\n\n%s")
591
                      abbrev-name
592
                      template)
593
             (snippet-insert ,template)))
594
    (put abbrev-expansion 'no-self-insert t)
595
    abbrev-expansion))
596
597
(defmacro snippet-abbrev (abbrev-table abbrev-name template)
598
  "Establish an abbrev for a snippet template.
599
Set up an abbreviation called ABBREV-NAME in the ABBREV-TABLE (note
600
that ABBREV-TABLE must be quoted) that expands into a snippet using
601
the specified TEMPLATE string.
602
603
This macro facilitates the creation of a function for the expansion
604
hook to be used in `define-abbrev'.  In addition, it also sets the
605
`no-self-insert' property on the function to prevent `abbrev-mode'
606
from inserting the character that triggered the expansion (typically
607
whitespace) which would otherwise interfere with the first field of a
608
snippet."
609
  (let ((name (gensym))
610
        (table (gensym)))
611
    `(let ((,name ,abbrev-name)
612
           (,table ,abbrev-table))
613
       (define-abbrev (symbol-value ,table) ,name ""
614
         (snippet-make-abbrev-expansion-hook ,table ,name ,template)))))
615
616
(defmacro snippet-with-abbrev-table (abbrev-table &rest snippet-alist)
617
  "Establish a set of abbrevs for snippet templates.
618
Set up a series of snippet abbreviations in the ABBREV-TABLE (note
619
that ABBREV-TABLE must be quoted.  The abbrevs are specified in
620
SNIPPET-ALIST.  For example:
621
622
  (snippet-with-abbrev-table 'python-mode-abbrev-table
623
    (\"for\" . \"for $${element} in $${sequence}:\")
624
    (\"im\"  . \"import $$\"))
625
626
See also `snippet-abbrev."
627
  (let ((table (gensym)))
628
    `(let ((,table ,abbrev-table))
629
       (progn
630
         ,@(loop for (name . template) in snippet-alist
631
              collect (list 'snippet-abbrev table name template))))))
632
633
(provide 'snippet)