]> code.delx.au - gnu-emacs-elpa/blobdiff - yasnippet.el
a more proper popup position under Linux
[gnu-emacs-elpa] / yasnippet.el
index d23c3a4fb3ca16783fd9ada274df29beedca38c4..72bd3833e147412e03e12c24126f2b5b72e80c5e 100644 (file)
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; User customizable variables
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(defvar yas/key-syntax "w"
-  "Syntax of a key. This is used to determine the current key being 
-expanded.")
+(defvar yas/key-syntaxes (list "w" "w_" "w_." "^ ")
+  "A list of syntax of a key. This list is tried in the order
+to try to find a key. For example, if the list is '(\"w\" \"w_\").
+And in emacs-lisp-mode, where \"-\" has the syntax of \"_\":
+
+foo-bar
+
+will first try \"bar\", if not found, then \"foo-bar\" is tried.")
 
 (defvar yas/indent-line t
   "Each (except the 1st) line of the snippet template is indented to
 current column if this variable is non-`nil'.")
 (make-variable-buffer-local 'yas/indent-line)
 
+(defvar yas/trigger-key (kbd "TAB")
+  "The key to bind as a trigger of snippet.")
+(defvar yas/trigger-fallback 'indent-according-to-mode
+  "The fallback command to call when there's no snippet to expand.")
+(make-variable-buffer-local 'yas/trigger-fallback)
+
 (defvar yas/keymap (make-sparse-keymap)
   "The keymap of snippet.")
 (define-key yas/keymap (kbd "TAB") 'yas/next-field-group)
@@ -43,11 +54,26 @@ current column if this variable is non-`nil'.")
 (define-key yas/keymap (kbd "<S-iso-lefttab>") 'yas/prev-field-group)
 (define-key yas/keymap (kbd "<S-tab>") 'yas/prev-field-group)
 
+(defvar yas/use-menu t
+  "If this is set to `t', all snippet template of the current
+mode will be listed under the menu \"yasnippet\".")
+(defvar yas/trigger-symbol " =>"
+  "The text that will be used in menu to represent the trigger.")
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Internal variables
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar yas/version "0.1")
+
 (defvar yas/snippet-tables (make-hash-table)
   "A hash table of snippet tables corresponding to each major-mode.")
+(defvar yas/menu-table (make-hash-table)
+  "A hash table of menus of corresponding major-mode.")
+(defvar yas/menu-keymap (make-sparse-keymap "YASnippet"))
+;; empty menu will cause problems, so we insert some items
+(define-key yas/menu-keymap [yas/about]
+  '(menu-item "About" yas/about))
+(define-key yas/menu-keymap [yas/separator]
+  '(menu-item "--"))
 
 (defconst yas/escape-backslash
   (concat "YASESCAPE" "BACKSLASH" "PROTECTGUARD"))
@@ -73,106 +99,84 @@ current column if this variable is non-`nil'.")
 (defvar yas/overlay-insert-in-front-hooks
   (list 'yas/overlay-insert-in-front-hook)
   "The list of hooks of the overlay inserted in front event.")
+(defvar yas/overlay-insert-behind-hooks
+  (list 'yas/overlay-insert-behind-hook)
+  "The list of hooks of the overlay inserted behind event.")
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Internal Structs
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defstruct (yas/template (:constructor yas/make-template (content name)))
+  "A template for a snippet."
+  content
+  name)
+(defstruct (yas/snippet (:constructor yas/make-snippet ()))
+  "A snippet."
+  (groups nil)
+  (tabstops nil)    ; tabstops are those groups whose init value is empty
+  (exit-marker nil)
+  (id (yas/snippet-next-id) :read-only t)
+  (overlay nil))
+(defstruct (yas/group (:constructor yas/make-group (primary-field snippet)))
+  "A group contains a list of field with the same number."
+  primary-field
+  (fields (list primary-field))
+  (next nil)
+  (prev nil)
+  snippet)
+(defstruct (yas/field (:constructor yas/make-field (overlay number value)))
+  "A field in a snippet."
+  overlay
+  number
+  value)
 
-(defun yas/snippet-new ()
-  "Create a new snippet."
-  (cons nil (cons nil (yas/snippet-next-id))))
-(defun yas/snippet-field-groups (snippet)
-  "Get field groups of SNIPPET."
-  (car snippet))
-(defun yas/snippet-field-groups-set (snippet groups)
-  "Set field groups of SNIPPET."
-  (setf (car snippet) groups))
-(defun yas/snippet-exit-marker-set (snippet marker)
-  "Set exit marker of SNIPPET."
-  (setf (cadr snippet) marker))
-(defun yas/snippet-exit-marker (snippet)
-  "Get exit marker of SNIPPET."
-  (cadr snippet))
-(defun yas/snippet-id (snippet)
-  "Get id of the snippet."
-  (cddr snippet))
 (defun yas/snippet-add-field (snippet field)
   "Add FIELD to SNIPPET."
   (let ((group (find field
-                    (yas/snippet-field-groups snippet)
+                    (yas/snippet-groups snippet)
                     :test
                     '(lambda (field group)
-                       (= (yas/snippet-field-number field)
-                          (yas/snippet-field-group-number group))))))
+                       (and (not (null (yas/field-number field)))
+                            (= (yas/field-number field)
+                               (yas/group-number group)))))))
     (if group
-       (yas/snippet-field-group-add group field)
-      (push (yas/snippet-field-group-new field)
-           (car snippet)))))
-
-(defun yas/snippet-field-group-new (field)
-  "Create a new field group."
-  (list field             ; primary field
-       (list field)      ; fields
-       nil               ; next field group
-       nil))             ; prev field group
-(defun yas/snippet-field-group-primary (group)
-  "Get the primary field of this group."
-  (car group))
-(defun yas/snippet-field-group-fields (group)
-  "Get all fields belonging to this group."
-  (cadr group))
-(defun yas/snippet-field-group-set-next (group next)
-  "Set next field group of GROUP."
-  (setf (nth 2 group) next))
-(defun yas/snippet-field-group-next (group)
-  "Get next field group."
-  (nth 2 group))
-(defun yas/snippet-field-group-set-prev (group prev)
-  "Set previous field group of GROUP."
-  (setf (nth 3 group) prev))
-(defun yas/snippet-field-group-prev (group)
-  "Get previous field group."
-  (nth 3 group))
-(defun yas/snippet-field-group-value (group)
+       (yas/group-add-field group field)
+      (push (yas/make-group field snippet)
+           (yas/snippet-groups snippet)))))
+
+(defun yas/group-value (group)
   "Get the default value of the field group."
-  (or (yas/snippet-field-value
-       (yas/snippet-field-group-primary group))
+  (or (yas/field-value
+       (yas/group-primary-field group))
       ""))
-(defun yas/snippet-field-group-number (group)
+(defun yas/group-number (group)
   "Get the number of the field group."
-  (yas/snippet-field-number
-   (yas/snippet-field-group-primary group)))
-(defun yas/snippet-field-group-add (group field)
+  (yas/field-number
+   (yas/group-primary-field group)))
+(defun yas/group-add-field (group field)
   "Add a field to the field group. If the value of the primary 
 field is nil and that of the field is not nil, the field is set
 as the primary field of the group."
-  (push field (nth 1 group))
-  (when (and (null (yas/snippet-field-value (car group)))
-            (yas/snippet-field-value field))
-    (setf (car group) field)))
-
-(defun yas/snippet-field-new (overlay number value)
-  "Create a new snippet-field."
-  (cons overlay (cons number value)))
-(defun yas/snippet-field-overlay (field)
-  "Get the overlay of the field."
-  (car field))
-(defun yas/snippet-field-number (field)
-  "Get the number of the field."
-  (cadr field))
-(defun yas/snippet-field-value (field)
-  "Get the value of the field."
-  (cddr field))
+  (push field (yas/group-fields group))
+  (when (and (null (yas/field-value (yas/group-primary-field group)))
+            (yas/field-value field))
+    (setf (yas/group-primary-field group) field)))
+
 (defun yas/snippet-field-compare (field1 field2)
   "Compare two fields. The field with a number is sorted first.
 If they both have a number, compare through the number. If neither
 have, compare through the start point of the overlay."
-  (let ((n1 (yas/snippet-field-number field1))
-       (n2 (yas/snippet-field-number field2)))
+  (let ((n1 (yas/field-number field1))
+       (n2 (yas/field-number field2)))
     (if n1
        (if n2
            (< n1 n2)
          t)
       (if n2
          nil
-       (< (overlay-start (yas/snippet-field-overlay field1))
-          (overlay-start (yas/snippet-field-overlay field2)))))))
+       (< (overlay-start (yas/field-overlay field1))
+          (overlay-start (yas/field-overlay field2)))))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Internal functions
@@ -188,6 +192,7 @@ have, compare through the start point of the overlay."
   (goto-char (point-min))
   (while (search-forward from nil t)
     (replace-match to t t)))
+
 (defun yas/snippet-table (mode)
   "Get the snippet table corresponding to MODE."
   (let ((table (gethash mode yas/snippet-tables)))
@@ -199,66 +204,113 @@ have, compare through the start point of the overlay."
   "Get the snippet table for current major-mode."
   (yas/snippet-table major-mode))
 
-(defsubst yas/template (key snippet-table)
-  "Get template for KEY in SNIPPET-TABLE."
-  (gethash key snippet-table))
+(defun yas/menu-keymap-for-mode (mode)
+  "Get the menu keymap correspondong to MODE."
+  (let ((keymap (gethash mode yas/menu-table)))
+    (unless keymap
+      (setq keymap (make-sparse-keymap))
+      (puthash mode keymap yas/menu-table))
+    keymap))
 
 (defun yas/current-key ()
-  "Get the key under current position."
+  "Get the key under current position. A key is used to find
+the template of a snippet in the current snippet-table."
   (let ((start (point))
-       (end (point)))
-    (save-excursion
-      (skip-syntax-backward yas/key-syntax)
-      (setq start (point))
-      (list (buffer-substring-no-properties start end)
-           start
-           end))))
+       (end (point))
+       (syntaxes yas/key-syntaxes)
+       syntax done)
+    (while (and (not done) syntaxes)
+      (setq syntax (car syntaxes))
+      (setq syntaxes (cdr syntaxes))
+      (save-excursion
+       (skip-syntax-backward syntax)
+       (when (gethash (buffer-substring-no-properties (point) end)
+                      (yas/current-snippet-table))
+         (setq done t)
+         (setq start (point)))))
+    (list (buffer-substring-no-properties start end)
+         start
+         end)))
 
 (defun yas/synchronize-fields (field-group)
   "Update all fields' text according to the primary field."
   (save-excursion
     (let* ((inhibit-modification-hooks t)
-          (primary (yas/snippet-field-group-primary field-group))
-          (primary-overlay (yas/snippet-field-overlay primary))
+          (primary (yas/group-primary-field field-group))
+          (primary-overlay (yas/field-overlay primary))
           (text (buffer-substring-no-properties (overlay-start primary-overlay)
                                                 (overlay-end primary-overlay))))
-      (dolist (field (yas/snippet-field-group-fields field-group))
-       (let* ((field-overlay (yas/snippet-field-overlay field))
+      (dolist (field (yas/group-fields field-group))
+       (let* ((field-overlay (yas/field-overlay field))
               (original-length (- (overlay-end field-overlay)
                                   (overlay-start field-overlay))))
          (unless (eq field-overlay primary-overlay)
            (goto-char (overlay-start field-overlay))
            (insert text)
-           (delete-char original-length)))))))
+           (if (= (overlay-start field-overlay)
+                  (overlay-end field-overlay))
+               (move-overlay field-overlay
+                             (overlay-start field-overlay)
+                             (point))
+             (delete-char original-length))))))))
   
 (defun yas/overlay-modification-hook (overlay after? beg end &optional length)
   "Modification hook for snippet field overlay."
-  (when after?
-    (yas/synchronize-fields (overlay-get overlay 'yas/snippet-field-group))))
+  (when (and after? (not undo-in-progress))
+    (yas/synchronize-fields (overlay-get overlay 'yas/group))))
 (defun yas/overlay-insert-in-front-hook (overlay after? beg end &optional length)
-  "Hook for snippet overlay when text is inserted in front of snippet."
-  (let ((field-group (overlay-get overlay 'yas/snippet-field-group)))
-    (when after?
-      (when (and (= length 0)
-                (overlay-get overlay 'yas/snippet-field-initial-value))
-       (let ((inhibit-modification-hooks t))
-         (overlay-put overlay 'yas/snippet-field-initial-value nil)
+  "Hook for snippet overlay when text is inserted in front of a snippet field."
+  (when after?
+    (let ((field-group (overlay-get overlay 'yas/group))
+         (inhibit-modification-hooks t))
+      (when (not (overlay-get overlay 'yas/modified?))
+       (overlay-put overlay 'yas/modified? t)
+       (when (> (overlay-end overlay) end)
          (save-excursion
            (goto-char end)
            (delete-char (- (overlay-end overlay) end)))))
-      (yas/synchronize-fields field-group))))
+     (yas/synchronize-fields field-group))))
+(defun yas/overlay-insert-behind-hook (overlay after? beg end &optional length)
+  "Hook for snippet overlay when text is inserted just behind a snippet field."
+  (when (and after?
+            (null (yas/current-snippet-overlay beg))) ; not inside another field
+    (move-overlay overlay
+                 (overlay-start overlay)
+                 end)
+    (yas/synchronize-fields (overlay-get overlay 'yas/group))))
+
+(defun yas/undo-expand-snippet (start end key snippet)
+  "Undo a snippet expansion. Delete the overlays. This undo can't be
+redo-ed."
+  (let ((undo (car buffer-undo-list)))
+    (while (null undo)
+      (setq buffer-undo-list (cdr buffer-undo-list))
+      (setq undo (car buffer-undo-list)))
+    ;; Remove this undo operation record
+    (setq buffer-undo-list (cdr buffer-undo-list))
+  (let ((inhibit-modification-hooks t)
+       (buffer-undo-list t))
+    (yas/exit-snippet snippet)
+    (goto-char start)
+    (delete-char (- end start))
+    (insert key))))
 
 (defun yas/expand-snippet (start end template)
   "Expand snippet at current point. Text between START and END
 will be deleted before inserting template."
   (goto-char start)
 
-  (let ((length (- end start))
+  (let ((key (buffer-substring-no-properties start end))
+       (original-undo-list buffer-undo-list)
+       (inhibit-modification-hooks t)
+       (length (- end start))
        (column (current-column)))
     (save-restriction
       (narrow-to-region start start)
 
+      (setq buffer-undo-list t)
       (insert template)
+
       ;; Step 1: do necessary indent
       (when yas/indent-line
        (let* ((indent (if indent-tabs-mode
@@ -286,7 +338,7 @@ will be deleted before inserting template."
       (yas/replace-all "\\`" yas/escape-backquote)
       (yas/replace-all "\\$" yas/escape-dollar)
 
-      (let ((snippet (yas/snippet-new)))
+      (let ((snippet (yas/make-snippet)))
        ;; Step 5: Create fields
        (goto-char (point-min))
        (while (re-search-forward yas/field-regexp nil t)
@@ -295,36 +347,46 @@ will be deleted before inserting template."
                     (string= "0" number))
                (progn
                  (replace-match "")
-                 (yas/snippet-exit-marker-set
-                  snippet
-                  (copy-marker (point) t)))
+                 (setf (yas/snippet-exit-marker snippet)
+                       (copy-marker (point) t)))
              (yas/snippet-add-field
               snippet
-              (yas/snippet-field-new
+              (yas/make-field
                (make-overlay (match-beginning 0) (match-end 0))
                (and number (string-to-number number))
                (match-string-no-properties 2))))))
 
        ;; Step 6: Sort and link each field group
-       (yas/snippet-field-groups-set
-        snippet
-        (sort (yas/snippet-field-groups snippet)
-              '(lambda (group1 group2)
-                 (yas/snippet-field-compare
-                  (yas/snippet-field-group-primary group1)
-                  (yas/snippet-field-group-primary group2)))))
+       (setf (yas/snippet-groups snippet)
+             (sort (yas/snippet-groups snippet)
+                   '(lambda (group1 group2)
+                      (yas/snippet-field-compare
+                       (yas/group-primary-field group1)
+                       (yas/group-primary-field group2)))))
        (let ((prev nil))
-         (dolist (group (yas/snippet-field-groups snippet))
-           (yas/snippet-field-group-set-prev group prev)
+         (dolist (group (yas/snippet-groups snippet))
+           (setf (yas/group-prev group) prev)
            (when prev
-             (yas/snippet-field-group-set-next prev group))
+             (setf (yas/group-next prev) group))
            (setq prev group)))
 
-       ;; Step 7: Replace fields with default values
-       (dolist (group (yas/snippet-field-groups snippet))
-         (let ((value (yas/snippet-field-group-value group)))
-           (dolist (field (yas/snippet-field-group-fields group))
-             (let* ((overlay (yas/snippet-field-overlay field))
+       ;; Step 7: Create keymap overlay for snippet
+       (let ((overlay (make-overlay (point-min)
+                                    (point-max)
+                                    nil
+                                    nil
+                                    t)))
+         (overlay-put overlay 'keymap yas/keymap)
+         (overlay-put overlay 'yas/snippet-reference snippet)
+         (setf (yas/snippet-overlay snippet) overlay))
+       
+       ;; Step 8: Replace fields with default values
+       (dolist (group (yas/snippet-groups snippet))
+         (let ((value (yas/group-value group)))
+           (when (string= "" value)
+             (push group (yas/snippet-tabstops snippet)))
+           (dolist (field (yas/group-fields group))
+             (let* ((overlay (yas/field-overlay field))
                     (start (overlay-start overlay))
                     (end (overlay-end overlay))
                     (length (- end start)))
@@ -332,50 +394,62 @@ will be deleted before inserting template."
                (insert value)
                (delete-char length)))))
 
-       ;; Step 8: restore all escape characters
+       ;; Step 9: restore all escape characters
        (yas/replace-all yas/escape-dollar "$")
        (yas/replace-all yas/escape-backquote "`")
        (yas/replace-all yas/escape-backslash "\\")
 
-       ;; Step 9: Set up properties of overlays, including keymaps
-       (dolist (group (yas/snippet-field-groups snippet))
-         (let ((overlay (yas/snippet-field-overlay
-                         (yas/snippet-field-group-primary group))))
-           (overlay-put overlay 'keymap yas/keymap)
+       ;; Step 10: Set up properties of overlays
+       (dolist (group (yas/snippet-groups snippet))
+         (let ((overlay (yas/field-overlay
+                         (yas/group-primary-field group))))
            (overlay-put overlay 'yas/snippet snippet)
-           (overlay-put overlay 'yas/snippet-field-group group)
-           (overlay-put overlay 'yas/snippet-field-initial-value t)
+           (overlay-put overlay 'yas/group group)
+           (overlay-put overlay 'yas/modified? nil)
            (overlay-put overlay 'modification-hooks yas/overlay-modification-hooks)
            (overlay-put overlay 'insert-in-front-hooks yas/overlay-insert-in-front-hooks)
-           (dolist (field (yas/snippet-field-group-fields group))
-             (overlay-put (yas/snippet-field-overlay field)
+           (overlay-put overlay 'insert-behind-hooks yas/overlay-insert-behind-hooks)
+           (dolist (field (yas/group-fields group))
+             (overlay-put (yas/field-overlay field)
                           'face 
                           'highlight))))
 
-       ;; Step 10: move to end and make sure exit-marker exist
+       ;; Step 11: move to end and make sure exit-marker exist
        (goto-char (point-max))
        (unless (yas/snippet-exit-marker snippet)
-         (yas/snippet-exit-marker-set snippet (copy-marker (point) t)))
-
-       ;; Step 11: remove the trigger key
+         (setf (yas/snippet-exit-marker snippet) (copy-marker (point) t)))
+
+       ;; Step 12: Construct undo information
+       (unless (eq original-undo-list t)
+         (add-to-list 'original-undo-list
+                      `(apply yas/undo-expand-snippet
+                              ,(point-min)
+                              ,(point-max)
+                              ,key
+                              ,snippet)))
+
+       ;; Step 13: remove the trigger key
        (widen)
        (delete-char length)
 
-       ;; Step 12: place the cursor at a proper place
-       (let ((groups (yas/snippet-field-groups snippet))
+       (setq buffer-undo-list original-undo-list)
+
+       ;; Step 14: place the cursor at a proper place
+       (let ((groups (yas/snippet-groups snippet))
              (exit-marker (yas/snippet-exit-marker snippet)))
          (if groups
              (goto-char (overlay-start 
-                         (yas/snippet-field-overlay
-                          (yas/snippet-field-group-primary
+                         (yas/field-overlay
+                          (yas/group-primary-field
                            (car groups)))))
            ;; no need to call exit-snippet, since no overlay created.
-           (goto-char exit-marker)))))))
+           (yas/exit-snippet snippet)))))))
 
-(defun yas/current-snippet-overlay ()
+(defun yas/current-snippet-overlay (&optional point)
   "Get the most proper overlay which is belongs to a snippet."
-  (let ((snippet-overlay nil))
-    (dolist (overlay (overlays-at (point)))
+  (let ((point (or point (point)))
+       (snippet-overlay nil))
+    (dolist (overlay (overlays-at point))
       (when (overlay-get overlay 'yas/snippet)
        (if (null snippet-overlay)
            (setq snippet-overlay overlay)
@@ -384,59 +458,255 @@ will be deleted before inserting template."
            (setq snippet-overlay overlay)))))
     snippet-overlay))
 
+(defun yas/snippet-of-current-keymap (&optional point)
+  "Get the snippet holding the snippet keymap under POINT."
+  (let ((point (or point (point)))
+       (keymap-snippet nil)
+       (snippet nil))
+    (dolist (overlay (overlays-at point))
+      (setq snippet (overlay-get overlay 'yas/snippet-reference))
+      (when snippet
+       (if (null keymap-snippet)
+           (setq keymap-snippet snippet)
+         (when (> (yas/snippet-id snippet)
+                  (yas/snippet-id keymap-snippet))
+           (setq keymap-snippet snippet)))))
+    keymap-snippet))
+
+(defun yas/current-overlay-for-navigation ()
+  "Get current overlay for navigation. Might be overlay at current or previous point."
+  (let ((overlay1 (yas/current-snippet-overlay))
+       (overlay2 (if (bobp)
+                     nil
+                   (yas/current-snippet-overlay (- (point) 1)))))
+    (if (null overlay1)
+       overlay2
+      (if (or (null overlay2)
+             (eq (overlay-get overlay1 'yas/snippet) 
+                 (overlay-get overlay2 'yas/snippet)))
+         overlay1
+       (if (> (yas/snippet-id (overlay-get overlay2 'yas/snippet))
+              (yas/snippet-id (overlay-get overlay1 'yas/snippet)))
+           overlay2
+         overlay1)))))
+
+(defun yas/navigate-group (group next?)
+  "Go to next of previous field group. Exit snippet if none."
+  (let ((target (if next?
+                   (yas/group-next group)
+                 (yas/group-prev group))))
+    (if target
+       (goto-char (overlay-start
+                   (yas/field-overlay
+                    (yas/group-primary-field target))))
+      (yas/exit-snippet (yas/group-snippet group)))))
+
+(defun yas/parse-template ()
+  "Parse the template in the current buffer.
+If the buffer contains a line of \"# --\" then the contents
+above this line are ignored. Variables can be set above this
+line through the syntax:
+
+#name : value
+
+Currently only the \"name\" variable is recognized. Here's 
+an example:
+
+#name: #include \"...\"
+# --
+#include \"$1\""
+  (goto-char (point-min))
+  (let (template name bound)
+    (if (re-search-forward "^# --\n" nil t)
+       (progn (setq template 
+                    (buffer-substring-no-properties (point) 
+                                                    (point-max)))
+              (setq bound (point))
+              (goto-char (point-min))
+              (while (re-search-forward "^#\\([^ ]+\\) *: *\\(.*\\)$" bound t)
+                (when (string= "name" (match-string-no-properties 1))
+                  (setq name (match-string-no-properties 2)))))
+      (setq template
+           (buffer-substring-no-properties (point-min) (point-max))))
+    (list template name)))
+
+(defun yas/directory-files (directory file?)
+  "Return directory files or subdirectories in full path."
+  (remove-if (lambda (file)
+              (or (string-match "^\\."
+                                (file-name-nondirectory file))
+                  (if file?
+                      (file-directory-p file)
+                    (not (file-directory-p file)))))
+            (directory-files directory t)))
+
+(defun yas/make-menu-binding (template)
+  (lexical-let ((template template))
+    (lambda ()
+      (interactive)
+      (yas/expand-snippet (point) 
+                         (point)
+                         template))))
+
+(defun yas/modify-alist (alist key value)
+  "Modify ALIST to map KEY to VALUE. return the new alist."
+  (let ((pair (assoc key alist)))
+    (if (null pair)
+       (cons (cons key value)
+             alist)
+      (setcdr pair value)
+      alist)))
+
+(defun yas/fake-keymap-for-popup (templates)
+  "Create a fake keymap for popup menu usage."
+  (cons 'keymap 
+       (mapcar (lambda (pair)
+                 (let* ((template (cdr pair))
+                        (name (yas/template-name template))
+                        (content (yas/template-content template)))
+                   (list content 'menu-item name t)))
+               templates)))
+
+(defun yas/point-to-coord (&optional point)
+  "Get the xoffset/yoffset information of POINT.
+If POINT is not given, default is to current point."
+  (let* ((pn (posn-at-point (or point (point))))
+        (x-y (posn-x-y pn))
+        (x (car x-y))
+        (y (cdr x-y))
+        (coord (list (list (+ x 10) (+ y 20)) (selected-window))))
+    coord))
+
+(defun yas/popup-for-template (templates)
+  "Show a popup menu listing templates to let the user select one."
+  (if window-system
+      (car (x-popup-menu (yas/point-to-coord) (yas/fake-keymap-for-popup templates)))
+    ;; no window system, simply select the first one
+    (cdar templates)))
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; User level functions
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(defun yas/define (mode key template)
-  "Define a snippet. Expanding KEY into TEMPLATE."
-  (puthash key template (yas/snippet-table mode)))
+(defun yas/about ()
+  (interactive)
+  (message (concat "yasnippet (version "
+                  yas/version
+                  ") -- pluskid <pluskid@gmail.com>")))
+(defun yas/initialize ()
+  "Do necessary initialization."
+  (global-set-key yas/trigger-key 'yas/expand)
+  (when yas/use-menu
+    (define-key-after global-map 
+      [menu-bar yasnippet]
+      (cons "YASnippet" yas/menu-keymap)
+      'buffer)))
+
+(defun yas/define (mode key template &optional name)
+  "Define a snippet. Expanding KEY into TEMPLATE.
+NAME is a description to this template. Also update
+the menu if `yas/use-menu' is `t'."
+  (let* ((full-key key)
+        (key (file-name-sans-extension full-key))
+        (template (yas/make-template template (or name key)))
+        (snippet-table (yas/snippet-table mode)))
+    (puthash key
+            (yas/modify-alist (gethash key snippet-table)
+                              full-key
+                              template)
+            snippet-table)
+    (when yas/use-menu
+      (let ((keymap (yas/menu-keymap-for-mode mode)))
+       (define-key yas/menu-keymap (vector mode) 
+         `(menu-item ,(symbol-name mode) ,keymap))
+       (define-key keymap (vector (make-symbol full-key))
+         `(menu-item ,(yas/template-name template)
+                     ,(yas/make-menu-binding (yas/template-content template))
+                     :keys ,(concat key yas/trigger-symbol)))))))
 
 (defun yas/expand ()
   "Expand a snippet. When a snippet is expanded, t is returned,
 otherwise, nil returned."
   (interactive)
   (multiple-value-bind (key start end) (yas/current-key)
-    (let ((template (yas/template key (yas/current-snippet-table))))
-      (if template
-         (progn
-           (yas/expand-snippet start end template)
-           t)
-       nil))))
+    (let ((templates (gethash key (yas/current-snippet-table))))
+      (if templates
+         (let ((template (if (null (cdr templates)) ; only 1 template
+                             (yas/template-content (cdar templates))
+                           (yas/popup-for-template templates))))
+           (when template
+             (yas/expand-snippet start end template)))
+       (when yas/trigger-fallback
+         (call-interactively yas/trigger-fallback))))))
 
 (defun yas/next-field-group ()
   "Navigate to next field group. If there's none, exit the snippet."
   (interactive)
-  (let ((overlay (yas/current-snippet-overlay)))
+  (let ((overlay (yas/current-overlay-for-navigation)))
     (if overlay
-       (let ((next (yas/snippet-field-group-next 
-                    (overlay-get overlay 'yas/snippet-field-group))))
-         (if next
-             (goto-char (overlay-start
-                         (yas/snippet-field-overlay
-                          (yas/snippet-field-group-primary next))))
-           (yas/exit-snippet (overlay-get overlay 'yas/snippet))))
-      (message "Not in a snippet field."))))
+       (yas/navigate-group (overlay-get overlay 'yas/group) t)
+      (let ((snippet (yas/snippet-of-current-keymap))
+           (done nil))
+       (if snippet
+         (do* ((tabstops (yas/snippet-tabstops snippet) (cdr tabstops))
+               (tabstop (car tabstops) (car tabstops)))
+             ((or (null tabstops)
+                  done)
+              (unless done (message "Not in a snippet field.")))
+           (when (= (point)
+                    (overlay-start
+                     (yas/field-overlay
+                      (yas/group-primary-field tabstop))))
+             (setq done t)
+             (yas/navigate-group tabstop t)))
+         (message "Not in a snippet field."))))))
 
 (defun yas/prev-field-group ()
   "Navigate to prev field group. If there's none, exit the snippet."
   (interactive)
-  (let ((overlay (yas/current-snippet-overlay)))
+  (let ((overlay (yas/current-overlay-for-navigation)))
     (if overlay
-       (let ((prev (yas/snippet-field-group-prev
-                    (overlay-get overlay 'yas/snippet-field-group))))
-         (if prev
-             (goto-char (overlay-start
-                         (yas/snippet-field-overlay
-                          (yas/snippet-field-group-primary prev))))
-           (yas/exit-snippet (overlay-get overlay 'yas/snippet))))
-      (message "Not in a snippet field."))))
+       (yas/navigate-group (overlay-get overlay 'yas/group) nil)
+      (let ((snippet (yas/snippet-of-current-keymap))
+           (done nil))
+       (if snippet
+         (do* ((tabstops (yas/snippet-tabstops snippet) (cdr tabstops))
+               (tabstop (car tabstops) (car tabstops)))
+             ((or (null tabstops)
+                  done)
+              (unless done (message "Not in a snippet field.")))
+           (when (= (point)
+                    (overlay-start
+                     (yas/field-overlay
+                      (yas/group-primary-field tabstop))))
+             (setq done t)
+             (yas/navigate-group tabstop nil)))
+         (message "Not in a snippet field."))))))
 
 (defun yas/exit-snippet (snippet)
   "Goto exit-marker of SNIPPET and delete the snippet."
   (interactive)
   (goto-char (yas/snippet-exit-marker snippet))
-  (dolist (group (yas/snippet-field-groups snippet))
-    (dolist (field (yas/snippet-field-group-fields group))
-      (delete-overlay (yas/snippet-field-overlay field)))))
+  (delete-overlay (yas/snippet-overlay snippet))
+  (dolist (group (yas/snippet-groups snippet))
+    (dolist (field (yas/group-fields group))
+      (delete-overlay (yas/field-overlay field)))))
+
+(defun yas/load-directory (directory)
+  "Load snippet definition from a directory hierarchy.
+Below the top-level directory, each directory is a mode
+name. And under each subdirectory, each file is a definition
+of a snippet. The file name is the trigger key and the
+content of the file is the template."
+  (with-temp-buffer
+    (dolist (mode (yas/directory-files directory nil))
+      (let ((mode-sym (intern (file-name-nondirectory mode))))
+       (dolist (file (yas/directory-files mode t))
+         (when (file-readable-p file)
+           (insert-file-contents file nil nil nil t)
+           (multiple-value-bind 
+               (key template name)
+               (cons (file-name-nondirectory file)
+                     (yas/parse-template))
+             (yas/define mode-sym key template name))))))))
 
 (provide 'yasnippet)