]> code.delx.au - gnu-emacs-elpa/blob - yasnippet.el
place cursor at a proper position after snippet expanded.
[gnu-emacs-elpa] / yasnippet.el
1 ;;; yasnippet.el --- Yet another snippet extension for Emacs.
2
3 ;; Author: pluskid <pluskid@gmail.com>
4 ;; Version: 0.1
5
6 ;; This file is free software; you can redistribute it and/or modify
7 ;; it under the terms of the GNU General Public License as published by
8 ;; the Free Software Foundation; either version 2, or (at your option)
9 ;; any later version.
10
11 ;; This file is distributed in the hope that it will be useful,
12 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;; GNU General Public License for more details.
15
16 ;; You should have received a copy of the GNU General Public License
17 ;; along with GNU Emacs; see the file COPYING. If not, write to
18 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 ;; Boston, MA 02111-1307, USA.
20
21 ;;; Commentary:
22
23 ;; Nothing.
24
25 (require 'cl)
26
27 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
28 ;; User customizable variables
29 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
30 (defvar yas/key-syntax "w"
31 "Syntax of a key. This is used to determine the current key being
32 expanded.")
33
34 (defvar yas/indent-line t
35 "Each (except the 1st) line of the snippet template is indented to
36 current column if this variable is non-`nil'.")
37 (make-variable-buffer-local 'yas/indent-line)
38
39 (defvar yas/keymap (make-sparse-keymap)
40 "The keymap of snippet.")
41 (define-key yas/keymap (kbd "TAB") 'yas/next-field-group)
42 (define-key yas/keymap (kbd "S-TAB") 'yas/prev-field-group)
43 (define-key yas/keymap (kbd "<S-iso-lefttab>") 'yas/prev-field-group)
44 (define-key yas/keymap (kbd "<S-tab>") 'yas/prev-field-group)
45
46 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
47 ;; Internal variables
48 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
49 (defvar yas/snippet-tables (make-hash-table)
50 "A hash table of snippet tables corresponding to each major-mode.")
51
52 (defconst yas/escape-backslash
53 (concat "YASESCAPE" "BACKSLASH" "PROTECTGUARD"))
54 (defconst yas/escape-dollar
55 (concat "YASESCAPE" "DOLLAR" "PROTECTGUARD"))
56 (defconst yas/escape-backquote
57 (concat "YASESCAPE" "BACKQUOTE" "PROTECTGUARD"))
58
59 (defconst yas/field-regexp
60 (concat "$\\(?1:[0-9]+\\)" "\\|"
61 "${\\(?:\\(?1:[0-9]+\\):\\)?\\(?2:[^}]*\\)}"))
62
63 (defvar yas/snippet-id-seed 0
64 "Contains the next id for a snippet")
65 (defun yas/snippet-next-id ()
66 (let ((id yas/snippet-id-seed))
67 (incf yas/snippet-id-seed)
68 id))
69
70 (defun yas/snippet-new ()
71 "Create a new snippet."
72 (cons nil (cons nil (yas/snippet-next-id))))
73 (defun yas/snippet-field-groups (snippet)
74 "Get field groups of SNIPPET."
75 (car snippet))
76 (defun yas/snippet-field-groups-set (snippet groups)
77 "Set field groups of SNIPPET."
78 (setf (car snippet) groups))
79 (defun yas/snippet-exit-marker-set (snippet marker)
80 "Set exit marker of SNIPPET."
81 (setf (cadr snippet) marker))
82 (defun yas/snippet-exit-marker (snippet)
83 "Get exit marker of SNIPPET."
84 (cadr snippet))
85 (defun yas/snippet-id (snippet)
86 "Get id of the snippet."
87 (cddr snippet))
88 (defun yas/snippet-add-field (snippet field)
89 "Add FIELD to SNIPPET."
90 (let ((group (find field
91 (yas/snippet-field-groups snippet)
92 :test
93 '(lambda (field group)
94 (= (yas/snippet-field-number field)
95 (yas/snippet-field-group-number group))))))
96 (if group
97 (yas/snippet-field-group-add group field)
98 (push (yas/snippet-field-group-new field)
99 (car snippet)))))
100
101 (defun yas/snippet-field-group-new (field)
102 "Create a new field group."
103 (list field ; primary field
104 (list field) ; fields
105 nil ; next field group
106 nil)) ; prev field group
107 (defun yas/snippet-field-group-primary (group)
108 "Get the primary field of this group."
109 (car group))
110 (defun yas/snippet-field-group-fields (group)
111 "Get all fields belonging to this group."
112 (cadr group))
113 (defun yas/snippet-field-group-set-next (group next)
114 "Set next field group of GROUP."
115 (setf (nth 2 group) next))
116 (defun yas/snippet-field-group-next (group)
117 "Get next field group."
118 (nth 2 group))
119 (defun yas/snippet-field-group-set-prev (group prev)
120 "Set previous field group of GROUP."
121 (setf (nth 3 group) prev))
122 (defun yas/snippet-field-group-prev (group)
123 "Get previous field group."
124 (nth 3 group))
125 (defun yas/snippet-field-group-value (group)
126 "Get the default value of the field group."
127 (or (yas/snippet-field-value
128 (yas/snippet-field-group-primary group))
129 ""))
130 (defun yas/snippet-field-group-number (group)
131 "Get the number of the field group."
132 (yas/snippet-field-number
133 (yas/snippet-field-group-primary group)))
134 (defun yas/snippet-field-group-add (group field)
135 "Add a field to the field group. If the value of the primary
136 field is nil and that of the field is not nil, the field is set
137 as the primary field of the group."
138 (push field (nth 1 group))
139 (when (and (null (yas/snippet-field-value (car group)))
140 (yas/snippet-field-value field))
141 (setf (car group) field)))
142
143 (defun yas/snippet-field-new (overlay number value)
144 "Create a new snippet-field."
145 (cons overlay (cons number value)))
146 (defun yas/snippet-field-overlay (field)
147 "Get the overlay of the field."
148 (car field))
149 (defun yas/snippet-field-number (field)
150 "Get the number of the field."
151 (cadr field))
152 (defun yas/snippet-field-value (field)
153 "Get the value of the field."
154 (cddr field))
155 (defun yas/snippet-field-compare (field1 field2)
156 "Compare two fields. The field with a number is sorted first.
157 If they both have a number, compare through the number. If neither
158 have, compare through the start point of the overlay."
159 (let ((n1 (yas/snippet-field-number field1))
160 (n2 (yas/snippet-field-number field2)))
161 (if n1
162 (if n2
163 (< n1 n2)
164 t)
165 (if n2
166 nil
167 (< (overlay-start (yas/snippet-field-overlay field1))
168 (overlay-start (yas/snippet-field-overlay field2)))))))
169
170 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
171 ;; Internal functions
172 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
173 (defun yas/eval-string (string)
174 "Evaluate STRING and convert the result to string."
175 (condition-case err
176 (format "%s" (eval (read string)))
177 (error (format "(error in elisp evaluation: %s)"
178 (error-message-string err)))))
179 (defsubst yas/replace-all (from to)
180 "Replace all occurance from FROM to TO."
181 (goto-char (point-min))
182 (while (search-forward from nil t)
183 (replace-match to t t)))
184 (defun yas/snippet-table (mode)
185 "Get the snippet table corresponding to MODE."
186 (let ((table (gethash mode yas/snippet-tables)))
187 (unless table
188 (setq table (make-hash-table :test 'equal))
189 (puthash mode table yas/snippet-tables))
190 table))
191 (defsubst yas/current-snippet-table ()
192 "Get the snippet table for current major-mode."
193 (yas/snippet-table major-mode))
194
195 (defsubst yas/template (key snippet-table)
196 "Get template for KEY in SNIPPET-TABLE."
197 (gethash key snippet-table))
198
199 (defun yas/current-key ()
200 "Get the key under current position."
201 (let ((start (point))
202 (end (point)))
203 (save-excursion
204 (skip-syntax-backward yas/key-syntax)
205 (setq start (point))
206 (list (buffer-substring-no-properties start end)
207 start
208 end))))
209
210 (defun yas/expand-snippet (start end template)
211 "Expand snippet at current point. Text between START and END
212 will be deleted before inserting template."
213 (goto-char start)
214
215 (let ((length (- end start))
216 (column (current-column)))
217 (save-restriction
218 (narrow-to-region start start)
219
220 (insert template)
221 ;; Step 1: do necessary indent
222 (when yas/indent-line
223 (let* ((indent (if indent-tabs-mode
224 (concat (make-string (/ column tab-width) ?\t)
225 (make-string (% column tab-width) ?\ ))
226 (make-string column ?\ ))))
227 (goto-char (point-min))
228 (while (and (zerop (forward-line))
229 (= (current-column) 0))
230 (insert indent))))
231
232 ;; Step 2: protect backslash and backquote
233 (yas/replace-all "\\\\" yas/escape-backslash)
234 (yas/replace-all "\\`" yas/escape-backquote)
235
236 ;; Step 3: evaluate all backquotes
237 (goto-char (point-min))
238 (while (re-search-forward "`\\([^`]*\\)`" nil t)
239 (replace-match (yas/eval-string (match-string-no-properties 1))
240 t t))
241
242 ;; Step 4: protect all escapes, including backslash and backquot
243 ;; which may be produced in Step 3
244 (yas/replace-all "\\\\" yas/escape-backslash)
245 (yas/replace-all "\\`" yas/escape-backquote)
246 (yas/replace-all "\\$" yas/escape-dollar)
247
248 (let ((snippet (yas/snippet-new)))
249 ;; Step 5: Create fields
250 (goto-char (point-min))
251 (while (re-search-forward yas/field-regexp nil t)
252 (let ((number (match-string-no-properties 1)))
253 (if (and number
254 (string= "0" number))
255 (progn
256 (replace-match "")
257 (yas/snippet-exit-marker-set
258 snippet
259 (copy-marker (point) t)))
260 (yas/snippet-add-field
261 snippet
262 (yas/snippet-field-new
263 (make-overlay (match-beginning 0) (match-end 0))
264 (and number (string-to-number number))
265 (match-string-no-properties 2))))))
266
267 ;; Step 6: Sort and link each field group
268 (yas/snippet-field-groups-set
269 snippet
270 (sort (yas/snippet-field-groups snippet)
271 '(lambda (group1 group2)
272 (yas/snippet-field-compare
273 (yas/snippet-field-group-primary group1)
274 (yas/snippet-field-group-primary group2)))))
275 (let ((prev nil))
276 (dolist (group (yas/snippet-field-groups snippet))
277 (yas/snippet-field-group-set-prev group prev)
278 (when prev
279 (yas/snippet-field-group-set-next prev group))
280 (setq prev group)))
281
282 ;; Step 7: Set up properties of overlays, including keymaps
283 (dolist (group (yas/snippet-field-groups snippet))
284 (let ((overlay (yas/snippet-field-overlay
285 (yas/snippet-field-group-primary group))))
286 (overlay-put overlay 'keymap yas/keymap)
287 (overlay-put overlay 'yas/snippet snippet)
288 (overlay-put overlay 'yas/snippet-field-group group)
289 (dolist (field (yas/snippet-field-group-fields group))
290 (overlay-put (yas/snippet-field-overlay field)
291 'face
292 'highlight))))
293
294 ;; Step 8: Replace fields with default values
295 (dolist (group (yas/snippet-field-groups snippet))
296 (let ((value (yas/snippet-field-group-value group)))
297 (dolist (field (yas/snippet-field-group-fields group))
298 (let* ((overlay (yas/snippet-field-overlay field))
299 (start (overlay-start overlay))
300 (end (overlay-end overlay))
301 (length (- end start)))
302 (goto-char start)
303 (insert value)
304 (delete-char length)))))
305
306 ;; Step 9: restore all escape characters
307 (yas/replace-all yas/escape-dollar "$")
308 (yas/replace-all yas/escape-backquote "`")
309 (yas/replace-all yas/escape-backslash "\\")
310
311 ;; Step 10: move to end and make sure exit-marker exist
312 (goto-char (point-max))
313 (unless (yas/snippet-exit-marker snippet)
314 (yas/snippet-exit-marker-set snippet (copy-marker (point) t)))
315
316 ;; Step 11: remove the trigger key
317 (widen)
318 (delete-char length)
319
320 ;; Step 12: place the cursor at a proper place
321 (let ((groups (yas/snippet-field-groups snippet))
322 (exit-marker (yas/snippet-exit-marker snippet)))
323 (if groups
324 (goto-char (overlay-start
325 (yas/snippet-field-overlay
326 (yas/snippet-field-group-primary
327 (car groups)))))
328 ;; no need to call exit-snippet, since no overlay created.
329 (goto-char exit-marker)))))))
330
331 (defun yas/current-snippet-overlay ()
332 "Get the most proper overlay which is belongs to a snippet."
333 (let ((snippet-overlay nil))
334 (dolist (overlay (overlays-at (point)))
335 (when (overlay-get overlay 'yas/snippet)
336 (if (null snippet-overlay)
337 (setq snippet-overlay overlay)
338 (when (> (yas/snippet-id (overlay-get overlay 'yas/snippet))
339 (yas/snippet-id (overlay-get snippet-overlay 'yas/snippet)))
340 (setq snippet-overlay overlay)))))
341 snippet-overlay))
342
343 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
344 ;; User level functions
345 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
346 (defun yas/define (mode key template)
347 "Define a snippet. Expanding KEY into TEMPLATE."
348 (puthash key template (yas/snippet-table mode)))
349
350 (defun yas/expand ()
351 "Expand a snippet. When a snippet is expanded, t is returned,
352 otherwise, nil returned."
353 (interactive)
354 (multiple-value-bind (key start end) (yas/current-key)
355 (let ((template (yas/template key (yas/current-snippet-table))))
356 (if template
357 (progn
358 (yas/expand-snippet start end template)
359 t)
360 nil))))
361
362 (defun yas/next-field-group ()
363 "Navigate to next field group. If there's none, exit the snippet."
364 (interactive)
365 (let ((overlay (yas/current-snippet-overlay)))
366 (if overlay
367 (let ((next (yas/snippet-field-group-next
368 (overlay-get overlay 'yas/snippet-field-group))))
369 (if next
370 (goto-char (overlay-start
371 (yas/snippet-field-overlay
372 (yas/snippet-field-group-primary next))))
373 (yas/exit-snippet (overlay-get overlay 'yas/snippet))))
374 (message "Not in a snippet field."))))
375
376 (defun yas/prev-field-group ()
377 "Navigate to prev field group. If there's none, exit the snippet."
378 (interactive)
379 (let ((overlay (yas/current-snippet-overlay)))
380 (if overlay
381 (let ((prev (yas/snippet-field-group-prev
382 (overlay-get overlay 'yas/snippet-field-group))))
383 (if prev
384 (goto-char (overlay-start
385 (yas/snippet-field-overlay
386 (yas/snippet-field-group-primary prev))))
387 (yas/exit-snippet (overlay-get overlay 'yas/snippet))))
388 (message "Not in a snippet field."))))
389
390 (defun yas/exit-snippet (snippet)
391 "Goto exit-marker of SNIPPET and delete the snippet."
392 (interactive)
393 (goto-char (yas/snippet-exit-marker snippet))
394 (dolist (group (yas/snippet-field-groups snippet))
395 (dolist (field (yas/snippet-field-group-fields group))
396 (delete-overlay (yas/snippet-field-overlay field)))))
397
398 (provide 'yasnippet)