]> code.delx.au - gnu-emacs-elpa/blobdiff - yasnippet.el
docfix: YASnippet -> yasnippet
[gnu-emacs-elpa] / yasnippet.el
index 26db5625d36689ba5934468ccd936ba2fff4e72d..30b6c0cac6c8fd480692b76e4b0bf047a8adcdef 100644 (file)
@@ -1,9 +1,9 @@
-;;; Yasnippet.el --- Yet another snippet extension for Emacs.
+;;; yasnippet.el --- Yet another snippet extension for Emacs.
 
 ;; Copyright 2008 pluskid
-;;           2009 pluskid, joaotavora
-
-;; Authors: pluskid <pluskid@gmail.com>, joaotavora <joaotavora@gmail.com>
+;;           2009 pluskid, João Távora
+;;           2010,2011,2012 João Távora
+;; Authors: pluskid <pluskid@gmail.com>,  João Távora <joaotavora@gmail.com>
 ;; Version: 0.7.0
 ;; Package-version: 0.7.0
 ;; X-URL: http://github.com/capitaomorte/yasnippet
 ;; Boston, MA 02111-1307, USA.
 
 ;;; Commentary:
-
-;; Basic steps to setup:
+;;
+;;   Basic steps to setup:
 ;;
 ;;    (add-to-list 'load-path
-;;                 "~/.emacs.d/plugins/yasnippet")
+;;                 "~/path-to-yasnippet")
 ;;    (require 'yasnippet)
 ;;    (yas/global-mode 1)
 ;;
 ;;           expansion conditions.  With prefix argument, ignore these
 ;;           conditions.
 ;;
-;;       M-x yas/find-snippets
-;;
-;;           Lets you find the snippet files in the correct
-;;           subdirectory of `yas/snippet-dirs', according to the
-;;           active major mode (if it exists) like
-;;           `find-file-other-window'.
-;;
 ;;       M-x yas/visit-snippet-file
 ;;
 ;;           Prompts you for possible snippet expansions like
 ;;; Code:
 
 (require 'cl)
-(require 'assoc)
 (require 'easymenu)
 (require 'help-mode)
 
@@ -535,7 +527,7 @@ snippet itself contains a condition that returns the symbol
 \f
 ;;; Internal variables
 
-(defvar yas/version "0.7.0")
+(defvar yas/version "0.7.0 (beta)")
 
 (defvar yas/menu-table (make-hash-table)
   "A hash table of MAJOR-MODE symbols to menu keymaps.")
@@ -600,8 +592,6 @@ snippet itself contains a condition that returns the symbol
          :help "Create a new snippet in an appropriate directory"]
         ["Visit snippet file..." yas/visit-snippet-file
          :help "Prompt for an expandable snippet and find its file"]
-        ["Find snippets..." yas/find-snippets
-         :help "Invoke `find-file' in the appropriate snippet directory"]
         "----"
         ("Snippet menu behaviour"
          ["Visit snippets" (setq yas/visit-from-menu t)
@@ -701,7 +691,6 @@ snippet itself contains a condition that returns the symbol
     (define-key map "\C-c&\C-s" 'yas/insert-snippet)
     (define-key map "\C-c&\C-n" 'yas/new-snippet)
     (define-key map "\C-c&\C-v" 'yas/visit-snippet-file)
-    (define-key map "\C-c&\C-f" 'yas/find-snippets)
     map))
 
 (defvar yas/minor-mode-map (yas/init-minor-keymap)
@@ -823,9 +812,14 @@ Key bindings:
          (remove-hook 'emulation-mode-map-alists 'yas/direct-keymaps))))
 
 
-(defvar yas/dont-activate nil
+(defvar yas/dont-activate '(minibufferp)
   "If non-nil don't let `yas/minor-mode-on' active yas for this buffer.
 
+If a function, then its result is used.
+
+If a list of functions, then all functions must return nil to
+activate yas for this buffer.
+
 `yas/minor-mode-on' is usually called by `yas/global-mode' so
 this effectively lets you define exceptions to the \"global\"
 behaviour. Can also be a function of zero arguments.")
@@ -836,10 +830,11 @@ behaviour. Can also be a function of zero arguments.")
 
 Do this unless `yas/dont-activate' is truish "
   (interactive)
-  (unless (or (minibufferp)
-              (if (functionp yas/dont-activate)
-                  (funcall yas/dont-activate)
-                yas/dont-activate))
+  (unless (cond ((functionp yas/dont-activate)
+                 (funcall yas/dont-activate))
+                ((consp yas/dont-activate)
+                 (some #'funcall yas/dont-activate))
+                (yas/dont-activate))
     ;; Load all snippets definitions unless we still don't have a
     ;; root-directory or some snippets have already been loaded.
     ;;
@@ -855,10 +850,10 @@ Do this unless `yas/dont-activate' is truish "
               (numberp arg)
               (> arg 1))
          ;; explicitly enabling
-         (yas/reload-all 'with-jit))
+         (yas/reload-all))
         ((not yas/global-mode)
          ;; toggling
-         (yas/reload-all 'with-jit))))
+         (yas/reload-all))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Major mode stuff
@@ -981,17 +976,58 @@ Has the following fields:
     (when table
       (gethash uuid (yas/table-uuidhash table)))))
 
-;; Apropos storing/updating, this works with two steps:
+;; Apropos storing/updating in TABLE, this works in two steps:
+;;
+;; 1. `yas/remove-template-by-uuid' removes any
+;;    keyhash-namehash-template mappings from TABLE, grabing the
+;;    snippet by its uuid. Also removes mappings from TABLE's
+;;    `yas/table-direct-keymap' (FIXME: and should probably take care
+;;    of potentially stale menu bindings right?.)
+;;
+;; 2. `yas/add-template' adds this all over again.
+;;
+;;    Create a new or add to an existing keyhash-namehash mapping.
+;;
+;;  For reference on understanding this, consider three snippet
+;;  definitions:
+;;
+;;  A:   # name: The Foo
+;;       # key: foo
+;;       # binding: C-c M-l
 ;;
-;; 1. `yas/remove-template-by-uuid' to remove any existing mappings by
-;;    snippet uuid
+;;  B:   # name: Mrs Foo
+;;       # key: foo
 ;;
-;; 2. `yas/add-template' to add the mappings again:
+;;  C:   # name: The Bar
+;;       # binding: C-c M-l
 ;;
-;;    Create or index the entry in TABLES's `yas/table-hash'
-;;    linking KEY to a namehash. That namehash links NAME to
-;;    TEMPLATE, and is also created a new namehash inside that
-;;    entry.
+;;  D:   # name: Baz
+;;       # key: baz
+;;
+;;  keyhash       namehashes(3)      yas/template structs(4)
+;;  -----------------------------------------------------
+;;                                            __________
+;;                                           /          \
+;;  "foo"      --->  "The Foo" --->  [yas/template A]   |
+;;                   "Mrs Foo" --->  [yas/template B]   |
+;;                                                      |
+;;  [C-c M-l]  --->  "The Foo" -------------------------/
+;;                   "The Bar" --->  [yas/template C]
+;;
+;;  "baz"      --->  "Baz"     --->  [yas/template D]
+;;
+;; Additionally, since uuid defaults to the name, we have a
+;; `yas/table-uuidhash' for TABLE
+;;
+;; uuidhash       yas/template structs
+;; -------------------------------
+;; "The Foo" ---> [yas/template A]
+;; "Mrs Foo" ---> [yas/template B]
+;; "The Bar" ---> [yas/template C]
+;; "Baz"     ---> [yas/template D]
+;;
+;; FIXME: the more I look at this data-structure the more I think I'm
+;; stupid. There has to be an easier way (but beware lots of code depends on this).
 ;;
 (defun yas/remove-template-by-uuid (table uuid)
   "Remove from TABLE a template identified by UUID."
@@ -1009,16 +1045,17 @@ Has the following fields:
                          (when (zerop (hash-table-count v))
                            (push k empty-keys)))))
                  (yas/table-hash table))
-        ;; Remove the namehashed themselves if they've become empty
+        ;; Remove the namehash themselves if they've become empty
         ;;
         (dolist (key empty-keys)
+          (when (vectorp key)
+            (define-key (yas/table-direct-keymap table) key nil))
           (remhash key (yas/table-hash table)))
 
         ;; Finally, remove the uuid from the uuidhash
         ;;
         (remhash uuid (yas/table-uuidhash table))))))
 
-
 (defun yas/add-template (table template)
   "Store in TABLE the snippet template TEMPLATE.
 
@@ -1027,7 +1064,7 @@ keybinding)."
   (let ((name (yas/template-name template))
         (key (yas/template-key template))
         (keybinding (yas/template-keybinding template))
-        (menu-binding (car (yas/template-menu-binding-pair template))))
+        (menu-binding-pair (yas/snippet-menu-binding-pair-get-create template)))
     (dolist (k (remove nil (list key keybinding)))
       (puthash name
                template
@@ -1039,28 +1076,28 @@ keybinding)."
       (when (vectorp k)
         (define-key (yas/table-direct-keymap table) k 'yas/expand-from-keymap)))
 
-    (when menu-binding
-      (setf (getf (cdr menu-binding) :keys)
+    ;; Update trigger & keybinding in the menu-binding pair
+    ;;
+    (unless (eq (cdr menu-binding-pair) :none)
+      (setf (getf (cdr (car menu-binding-pair)) :keys)
             (or (and keybinding (key-description keybinding))
-                (and key (concat key yas/trigger-symbol))))
-      (setcar (cdr menu-binding)
-              name))
+                (and key (concat key yas/trigger-symbol)))))
 
     (puthash (yas/template-uuid template) template (yas/table-uuidhash table))))
 
-(defun yas/update-template (snippet-table template)
-  "Add or update TEMPLATE in SNIPPET-TABLE.
+(defun yas/update-template (table template)
+  "Add or update TEMPLATE in TABLE.
 
-Also takes care of adding and updaring to the associated menu."
+Also takes care of adding and updating to the associated menu."
   ;; Remove from table by uuid
   ;;
-  (yas/remove-template-by-uuid snippet-table (yas/template-uuid template))
+  (yas/remove-template-by-uuid table (yas/template-uuid template))
   ;; Add to table again
   ;;
-  (yas/add-template snippet-table template)
+  (yas/add-template table template)
   ;; Take care of the menu
   ;;
-  (let ((keymap (yas/menu-keymap-get-create snippet-table))
+  (let ((keymap (yas/menu-keymap-get-create table))
         (group (yas/template-group template)))
     (when (and yas/use-menu
                keymap
@@ -1291,8 +1328,9 @@ ensure your use `make-local-variable' when you set it.")
     (unless table
       (setq table (yas/make-snippet-table (symbol-name mode)))
       (puthash mode table yas/tables)
-      (aput 'yas/direct-keymaps (intern (format "yas//direct-%s" mode))
-            (yas/table-direct-keymap table)))
+      (push (cons (intern (format "yas//direct-%s" mode))
+                  (yas/table-direct-keymap table))
+            yas/direct-keymaps))
     table))
 
 (defun yas/get-snippet-tables ()
@@ -1522,22 +1560,25 @@ TEMPLATES is a list of `yas/template'."
 
 (defun yas/x-pretty-prompt-templates (prompt templates)
   "Display TEMPLATES, grouping neatly by table name."
-  (let ((pretty-alist (list))
+  (let ((organized (make-hash-table :test #'equal))
         menu
         more-than-one-table
         prefix)
     (dolist (tl templates)
-      (aput 'pretty-alist (yas/template-table tl) (cons tl (aget pretty-alist (yas/template-table tl)))))
-    (setq more-than-one-table (> (length pretty-alist) 1))
+      (puthash (yas/template-table tl)
+               (cons tl
+                     (gethash (yas/template-table tl) organized))
+               organized))
+    (setq more-than-one-table (> (hash-table-count organized) 1))
     (setq prefix (if more-than-one-table
                      "   " ""))
-    (dolist (table-and-templates pretty-alist)
-      (when (cdr table-and-templates)
-        (if more-than-one-table
-            (push (yas/table-name (car table-and-templates)) menu))
-        (dolist (template (cdr table-and-templates))
-          (push (cons (concat prefix (yas/template-name template))
-                      template) menu))))
+    (if more-than-one-table
+        (maphash #'(lambda (table templates)
+                     (push (yas/table-name table) menu)
+                     (dolist (tl templates)
+                       (push (cons (concat prefix (yas/template-name tl)) tl) menu))) organized)
+      (setq menu (mapcar #'(lambda (tl) (cons (concat prefix (yas/template-name tl)) tl)) templates)))
+
     (setq menu (nreverse menu))
     (or (x-popup-menu (if (fboundp 'posn-at-point)
                           (let ((x-y (posn-x-y (posn-at-point (point)))))
@@ -1603,13 +1644,13 @@ TEMPLATES is a list of `yas/template'."
 (defun yas/load-yas-setup-file (file)
   (load file 'noerror))
 
-(defun yas/load-directory (top-level-dir &optional nojit)
-  "Load snippet definition from directory hierarchy under TOP-LEVEL-DIR.
+(defun yas/load-directory (top-level-dir &optional use-jit)
+  "Load snippets in directory hierarchy TOP-LEVEL-DIR.
+
+Below TOP-LEVEL-DIR each directory should be a mode name.
 
-Below TOP-LEVEL-DIR each directory is a mode name."
+Optional USE-JIT use jit-loading of snippets."
   (interactive "DSelect the root directory: ")
-  (unless (file-directory-p top-level-dir)
-    (error "%s is not a directory" top-level-dir))
   (unless yas/snippet-dirs
     (setq yas/snippet-dirs top-level-dir))
   (dolist (dir (yas/subdirs top-level-dir))
@@ -1621,10 +1662,9 @@ Below TOP-LEVEL-DIR each directory is a mode name."
       (let ((form `(yas/load-directory-1 ,dir
                                          ',mode-sym
                                          ',parents)))
-        (if (or (called-interactively-p)
-                nojit)
-            (eval form)
-          (yas/schedule-jit mode-sym form)))))
+        (if use-jit
+            (yas/schedule-jit mode-sym form)
+            (eval form)))))
   (when (interactive-p)
     (yas/message 3 "Loaded snippets from %s." top-level-dir)))
 
@@ -1634,9 +1674,9 @@ Below TOP-LEVEL-DIR each directory is a mode name."
     (if (and (not no-compiled-snippets)
              (load (expand-file-name ".yas-compiled-snippets" directory) 'noerror (<= yas/verbosity 2)))
         (yas/message 2 "Loading much faster .yas-compiled-snippets from %s" directory)
-      (yas/load-directory-2 directory mode-sym parents))))
+      (yas/load-directory-2 directory mode-sym))))
 
-(defun yas/load-directory-2 (directory mode-sym parents)
+(defun yas/load-directory-2 (directory mode-sym)
   ;; Load .yas-setup.el files wherever we find them
   ;;
   (yas/load-yas-setup-file (expand-file-name ".yas-setup" directory))
@@ -1657,8 +1697,7 @@ Below TOP-LEVEL-DIR each directory is a mode name."
     ;;
     (dolist (subdir (yas/subdirs directory))
       (yas/load-directory-2 subdir
-                            mode-sym
-                            nil))))
+                            mode-sym))))
 
 (defun yas/load-snippet-dirs (&optional nojit)
   "Reload the directories listed in `yas/snippet-dirs' or
@@ -1666,49 +1705,67 @@ Below TOP-LEVEL-DIR each directory is a mode name."
   (let (errors)
     (if yas/snippet-dirs
         (dolist (directory (reverse (yas/snippet-dirs)))
-          (condition-case oops
-              (progn
-                (yas/load-directory directory nojit)
-                (yas/message 3 "Loaded %s" directory))
-            (error (push oops errors)
-                   (yas/message 3 "Check your `yas/snippet-dirs': %s" (second oops)))))
+          (cond ((file-directory-p directory)
+                 (yas/load-directory directory (not nojit))
+                 (yas/message 3 "Loaded %s" directory))
+                (t
+                 (push (yas/message 0 "Check your `yas/snippet-dirs': %s is not a directory" directory) errors))))
       (call-interactively 'yas/load-directory))
     errors))
 
-(defun yas/reload-all (&optional nojit)
-  "Reload all snippets and rebuild the YASnippet menu. "
-  (interactive "p")
-  (let ((errors))
-    ;; Empty all snippet tables, parenting info and all menu tables
-    ;;
-    (setq yas/tables (make-hash-table))
-    (setq yas/parents (make-hash-table))
-    (setq yas/menu-table (make-hash-table))
-    
-    ;; Cancel all pending 'yas/scheduled-jit-loads'
-    ;;
-    (setq yas/scheduled-jit-loads (make-hash-table))
+(defun yas/reload-all (&optional interactive)
+  "Reload all snippets and rebuild the YASnippet menu."
+  (interactive "P")
+  (catch 'abort
+    (let ((errors)
+          (snippet-editing-buffers
+           (remove-if-not #'(lambda (buffer)
+                              (with-current-buffer buffer yas/editing-template))
+                          (buffer-list))))
+      ;; Warn if there are buffers visiting snippets, since reloading will break
+      ;; any on-line editing of those buffers.
+      ;;
+      (when snippet-editing-buffers
+          (if interactive
+              (if (y-or-n-p "Some buffers editing live snippets, close them and proceed with reload?")
+                  (mapcar #'kill-buffer snippet-editing-buffers)
+                (yas/message 1 "Aborted reload...")
+                (throw 'abort nil))
+            ;; in a non-interactive use, at least set
+            ;; `yas/editing-template' to nil, make it guess it next time around
+            (mapc #'(lambda (buffer) (setq yas/editing-template nil)) (buffer-list))))
+
+      ;; Empty all snippet tables, parenting info and all menu tables
+      ;;
+      (setq yas/tables (make-hash-table))
+      (setq yas/parents (make-hash-table))
+      (setq yas/menu-table (make-hash-table))
 
-    ;; Init the `yas/minor-mode-map', taking care not to break the
-    ;; menu....
-    ;;
-    (setf (cdr yas/minor-mode-map)
-          (cdr (yas/init-minor-keymap)))
+      ;; Cancel all pending 'yas/scheduled-jit-loads'
+      ;;
+      (setq yas/scheduled-jit-loads (make-hash-table))
 
-    ;; Reload the directories listed in `yas/snippet-dirs' or prompt
-    ;; the user to select one.
-    ;;
-    (setq errors (yas/load-snippet-dirs nojit))
-    ;; Reload the direct keybindings
-    ;;
-    (yas/direct-keymaps-reload)
-    ;; Reload the trigger-key (shoudn't be needed, but see issue #237)
-    ;; 
-    (yas/trigger-key-reload)
-    (yas/message 3 "Reloaded everything...%s." (if errors " (some errors, check *Messages*)" ""))))
+      ;; Init the `yas/minor-mode-map', taking care not to break the
+      ;; menu....
+      ;;
+      (setf (cdr yas/minor-mode-map)
+            (cdr (yas/init-minor-keymap)))
+
+      ;; Reload the directories listed in `yas/snippet-dirs' or prompt
+      ;; the user to select one.
+      ;;
+      (setq errors (yas/load-snippet-dirs interactive))
+      ;; Reload the direct keybindings
+      ;;
+      (yas/direct-keymaps-reload)
+      ;; Reload the trigger-key (shoudn't be needed, but see issue #237)
+      ;;
+      (yas/trigger-key-reload)
+
+      (yas/message 3 "Reloaded everything...%s." (if errors " (some errors, check *Messages*)" "")))))
 
 (defun yas/load-pending-jits ()
-  (when yas/minor-mode 
+  (when yas/minor-mode
     (dolist (mode (yas/modes-to-activate))
       (let ((forms (gethash mode yas/scheduled-jit-loads)))
         (dolist (form forms)
@@ -1781,9 +1838,9 @@ This works by stubbing a few functions, then calling
           (let ((output-file (concat (file-name-as-directory dir) ".yas-compiled-snippets.el")))
             (with-temp-file output-file
               (insert (format ";;; Compiled snippets and support files for `%s'\n" mode))
-              (yas/load-directory-2 dir mode parents)
+              (yas/load-directory-2 dir mode)
               (insert (format ";;; Do not edit! File generated at %s\n" (current-time-string)))))))
-    (yas/load-directory top-level-dir 'im-compiling-so-no-jit-ok?)))
+    (yas/load-directory top-level-dir nil)))
 
 (defun yas/recompile-all ()
   "Compile every dir in `yas/snippet-dirs'."
@@ -1896,7 +1953,10 @@ the current buffers contents."
     template))
 
 (defun yas/snippet-menu-binding-pair-get-create (template &optional type)
-  "Get TEMPLATE's menu binding or assign it a new one."
+  "Get TEMPLATE's menu binding or assign it a new one.
+
+TYPE may be `:stay', signalling this menu binding should be
+static in the menu."
   (or (yas/template-menu-binding-pair template)
       (let ((key (yas/template-key template))
             (keybinding (yas/template-keybinding template)))
@@ -1950,7 +2010,7 @@ MENU is a list, its elements can be:
 
 - (yas/item UUID) : Creates an entry the snippet identified with
   UUID. The menu entry for a snippet thus identified is
-  permanent, i.e. it will never move in the menu.
+  permanent, i.e. it will never move (be reordered) in the menu.
 
 - (yas/separator) : Creates a separator
 
@@ -2016,11 +2076,12 @@ will only be expanded when the condition evaluated to non-nil."
 (defun yas/hippie-try-expand (first-time?)
   "Integrate with hippie expand.  Just put this function in
 `hippie-expand-try-functions-list'."
-  (if (not first-time?)
-      (let ((yas/fallback-behavior 'return-nil))
-        (yas/expand))
-    (undo 1)
-    nil))
+  (when yas/minor-mode
+    (if (not first-time?)
+        (let ((yas/fallback-behavior 'return-nil))
+          (yas/expand))
+      (undo 1)
+      nil)))
 
 
 ;;; Apropos condition-cache:
@@ -2323,55 +2384,13 @@ NO-TEMPLATE is non-nil."
     (unless no-template (yas/expand-snippet "\
 # -*- mode: snippet -*-
 # name: $1
-# key: $2${3:
+# key: ${2:${1:$(replace-regexp-in-string \"\\\\\\\\(\\\\\\\\w+\\\\\\\\).*\" \"\\\\\\\\1\" yas/text)}}${3:
 # binding: ${4:direct-keybinding}}${5:
 # expand-env: ((${6:some-var} ${7:some-value}))}${8:
 # type: command}
 # --
 $0"))))
 
-(defun yas/find-snippets (&optional same-window )
-  "Find snippet file in guessed current mode's directory.
-
-Calls `find-file' interactively in the guessed directory.
-
-With prefix arg SAME-WINDOW opens the buffer in the same window.
-
-Because snippets can be loaded from many different locations,
-this has to guess the correct directory using
-`yas/guess-snippet-directories', which returns a list of
-options.
-
-If any one of these exists, it is taken and `find-file' is called
-there, otherwise, proposes to create the first option returned by
-`yas/guess-snippet-directories'."
-  (interactive "P")
-  (let* ((guessed-directories (yas/guess-snippet-directories))
-         (chosen)
-         (buffer))
-    (setq chosen (yas/make-directory-maybe (first guessed-directories) " main"))
-    (unless chosen
-      (if (y-or-n-p (format "Continue guessing for other active tables %s? "
-                            (mapcar #'(lambda (table-and-dirs)
-                                        (yas/table-name (car table-and-dirs)))
-                                    (rest guessed-directories))))
-          (setq chosen (some #'yas/make-directory-maybe
-                             (rest guessed-directories)))))
-    (unless chosen
-      (when (y-or-n-p "Having trouble... go to snippet root dir? ")
-        (setq chosen (first (yas/snippet-dirs)))))
-    (if chosen
-        (let ((default-directory chosen))
-          (setq buffer (call-interactively (if same-window
-                                               'find-file
-                                             'find-file-other-window)))
-          (when buffer
-            (save-excursion
-              (set-buffer buffer)
-              (when (eq major-mode 'fundamental-mode)
-                (snippet-mode)))))
-      (message "Could not guess snippet dir!"))))
-
 (defun yas/compute-major-mode-and-parents (file)
   "Given FILE, find the nearest snippet directory for a given
 mode, then return a list (MODE-SYM PARENTS), the mode's symbol and a list
@@ -2585,47 +2604,46 @@ With optional prefix argument KILL quit the window and buffer."
     (insert "\n"))
   (insert (make-string 100 ?-) "\n")
   (insert "group                   state name                                    key             binding\n")
-  (let ((groups-alist (list))
-        group)
+  (let ((groups-hash (make-hash-table :test #'equal)))
     (maphash #'(lambda (k v)
-                 (setq group (or (yas/template-fine-group v)
-                                 "(top level)"))
-                 (when (yas/template-name v)
-
-                   (aput 'groups-alist group (cons v (aget groups-alist group)))))
+                 (let ((group (or (yas/template-fine-group v)
+                                  "(top level)")))
+                   (when (yas/template-name v)
+                     (puthash group
+                              (cons v (gethash group groups-hash))
+                              groups-hash))))
              (yas/table-uuidhash table))
-    (dolist (group-and-templates groups-alist)
-      (when (rest group-and-templates)
-        (setq group (truncate-string-to-width (car group-and-templates) 25 0 ?  "..."))
-        (insert (make-string 100 ?-) "\n")
-        (dolist (p (cdr group-and-templates))
-          (let ((name (truncate-string-to-width (propertize (format "\\\\snippet `%s'" (yas/template-name p))
-                                                            'yasnippet p)
-                                                50 0 ? "..."))
-                (group (prog1 group
-                         (setq group (make-string (length group) ? ))))
-                (condition-string (let ((condition (yas/template-condition p)))
-                                    (if (and condition
-                                             original-buffer)
-                                        (with-current-buffer original-buffer
-                                          (if (yas/eval-condition condition)
-                                              "(y)"
-                                            "(s)"))
-                                      "(a)"))))
-            (insert group " ")
-            (insert condition-string " ")
-            (insert name
-                    (if (string-match "\\.\\.\\.$" name)
-                        "'"
-                      " ")
-                    " ")
-            (insert (truncate-string-to-width (or (yas/template-key p) "")
-                                              15 0 ?  "...") " ")
-            (insert (truncate-string-to-width (key-description (yas/template-keybinding p))
-                                              15 0 ?  "...") " ")
-            (insert "\n")))))))
-
-
+    (maphash
+     #'(lambda (group templates)
+         (setq group (truncate-string-to-width group 25 0 ?  "..."))
+         (insert (make-string 100 ?-) "\n")
+         (dolist (p templates)
+           (let ((name (truncate-string-to-width (propertize (format "\\\\snippet `%s'" (yas/template-name p))
+                                                             'yasnippet p)
+                                                 50 0 ? "..."))
+                 (group (prog1 group
+                          (setq group (make-string (length group) ? ))))
+                 (condition-string (let ((condition (yas/template-condition p)))
+                                     (if (and condition
+                                              original-buffer)
+                                         (with-current-buffer original-buffer
+                                           (if (yas/eval-condition condition)
+                                               "(y)"
+                                             "(s)"))
+                                       "(a)"))))
+             (insert group " ")
+             (insert condition-string " ")
+             (insert name
+                     (if (string-match "\\.\\.\\.$" name)
+                         "'"
+                       " ")
+                     " ")
+             (insert (truncate-string-to-width (or (yas/template-key p) "")
+                                               15 0 ?  "...") " ")
+             (insert (truncate-string-to-width (key-description (yas/template-keybinding p))
+                                               15 0 ?  "...") " ")
+             (insert "\n"))))
+     groups-hash)))
 
 
 \f
@@ -2843,13 +2861,24 @@ have, compare through the field's start point"
 
 (defun yas/field-probably-deleted-p (snippet field)
   "Guess if SNIPPET's FIELD should be skipped."
-  (and (zerop (- (yas/field-start field) (yas/field-end field)))
-       (or (yas/field-parent-field field)
-           (and (eq field (car (last (yas/snippet-fields snippet))))
-                (= (yas/field-start field) (overlay-end (yas/snippet-control-overlay snippet)))))
-       ;; the field numbered 0, just before the exit marker, should
-       ;; never be skipped
-       (not (zerop (yas/field-number field)))))
+  (and
+   ;; field must be zero lentgh
+   ;;
+   (zerop (- (yas/field-start field) (yas/field-end field)))
+   ;; skip if:
+   (or
+    ;;  1) is a nested field and it's been modified
+    ;;
+    (and (yas/field-parent-field field)
+         (yas/field-modified-p field))
+    ;;  2) ends just before the snippet end
+    ;;
+    (and (eq field (car (last (yas/snippet-fields snippet))))
+         (= (yas/field-start field) (overlay-end (yas/snippet-control-overlay snippet)))))
+   ;; the field numbered 0, just before the exit marker, should
+   ;; never be skipped
+   ;;
+   (not (zerop (yas/field-number field)))))
 
 (defun yas/snippets-at-point (&optional all-snippets)
   "Return a sorted list of snippets at point, most recently