diff --git a/yasnippet.el b/yasnippet.el index 55165bd..0e96a55 100644 --- a/yasnippet.el +++ b/yasnippet.el @@ -50,10 +50,11 @@ ;; `yas/snippet-dirs' ;; ;; The directory where user-created snippets are to be -;; stored. Can also be a list of directories that -;; `yas/reload-all' will use for bulk-reloading snippets. In -;; that case the first directory is the default for storing new -;; snippets. +;; stored. Can also be a list of directories. In that case, +;; when used for bulk (re)loading of snippets (at startup or +;; via `yas/reload-all'), directories appearing earlier in +;; the list shadow other dir's snippets. Also, the first +;; directory is taken as the default for storing the user's new snippets. ;; ;; `yas/mode-symbol' ;; @@ -113,6 +114,13 @@ ;; sets it to the appropriate major mode and inserts the ;; snippet there, so you can see what it looks like. This is ;; bound to "C-c C-t" while in `snippet-mode'. +;; +;; M-x yas/list-snippets +;; +;; Lists known snippets in a separate buffer. User is +;; prompted as to whether only the currently active tables +;; are to be displayed, or all the tables for all major +;; modes. ;; ;; The `dropdown-list.el' extension is bundled with YASnippet, you ;; can optionally use it the preferred "prompting method", puting in @@ -152,11 +160,12 @@ (defcustom yas/snippet-dirs nil "Directory or list of snippet dirs for each major mode. -If you set this from your .emacs, can also be a list of strings, -for multiple root directories. If you make this a list, the first -element is always the user-created snippets directory. Other -directories are used for bulk reloading of all snippets using -`yas/reload-all'" +The directory where user-created snippets are to be stored. Can +also be a list of directories. In that case, when used for +bulk (re)loading of snippets (at startup or via +`yas/reload-all'), directories appearing earlier in the list +shadow other dir's snippets. Also, the first directory is taken +as the default for storing the user's new snippets." :type '(choice (string :tag "Single directory (string)") (repeat :args (string) :tag "List of directories (strings)")) :group 'yasnippet @@ -867,7 +876,7 @@ Do this unless `yas/dont-activate' is t or the function ;;; Internal structs for template management (defstruct (yas/template (:constructor yas/make-template - (table key content name condition expand-env file keybinding))) + (table key content name condition expand-env file keybinding uid))) "A template for a snippet." table key @@ -876,7 +885,8 @@ Do this unless `yas/dont-activate' is t or the function condition expand-env file - keybinding) + keybinding + uid) (defstruct (yas/snippet-table (:constructor yas/make-snippet-table (name))) "A table to store snippets for a particular mode. @@ -909,33 +919,22 @@ Has the following fields: keybindings. This is kept in sync with the keyhash, i.e., all the elements of the keyhash that are vectors appear here as bindings to `yas/expand-from-keymap'. + +`yas/snippet-table-uidhash' + + A hash table mapping snippets uid's to the same `yas/template' + objects. A snippet uid defaults to the snippet's name. " name (hash (make-hash-table :test 'equal)) + (uidhash (make-hash-table :test 'equal)) (parents nil) (direct-keymap (make-sparse-keymap))) -;; Apropos storing/updating, this is works with two steps: +;; Apropos storing/updating, this works with two steps: ;; -;; 1. `yas/remove-snippet' to remove any existing mappings, with two -;; searches: -;; -;; a) Try to get the existing namehash from TABLE using key. -;; -;; b) When the user changed KEY, the previous key indexing the -;; namehash is lost, so try to get the existing namehash by -;; searching the *whole* snippet table for NAME *and* checking -;; that the key for that previous namehash is of the same type -;; as KEY. This latter detail enables independent changes in -;; the trigger key and direct keybinding for a snippet. -;; -;; Search b) is only performed if a) failed and -;; `yas/better-guess-for-replacements' is non-nil. The latter is -;; now on by default, but I vaguely recall some situation where we -;; needed it to be nil.... -;; -;; If any existing namesomething is found it is deleted, and is -;; maybe added later on: +;; 1. `yas/remove-snippet-by-uid' to remove any existing mappings by +;; snippet uid ;; ;; 2. `yas/add-snippet' to add the mappings again: ;; @@ -944,41 +943,35 @@ Has the following fields: ;; TEMPLATE, and is also created a new namehash inside that ;; entry. ;; -;; TODO: This is still not ideal. A well designed system (like -;; TextMate's) indexes the snippets by UUID or filename or something -;; that uniquely identify a snippet. I.e. this replacement strategy -;; fails if we have lost the original key and name, in that case it's -;; as if a brand new snippet has been created. -;; -;; UPDATE: `yas/visit-snippet-file' attempts to circumvent this using -;; `yas/current-template' and seems to work quite nicely. -;; -(defvar yas/better-guess-for-replacements t - "If non-nil `yas/store' guesses snippet replacements \"better\".") - -(defun yas/remove-snippet (table name key type-fn) +(defun yas/remove-snippet-by-uid (table uid) "Attempt to remove from TABLE a template with NAME and KEY. TYPE-FN indicates if KEY is a trigger key (string) or a keybinding (vector)." - (let ((key-and-namehash-alist '()) - (namehash-for-key (gethash key (yas/snippet-table-hash table)))) - (when namehash-for-key - (push (cons key namehash-for-key) key-and-namehash-alist)) - (when (and (null key-and-namehash-alist) - yas/better-guess-for-replacements) - ;; "cand" means "candidate for removal" - (maphash #'(lambda (cand namehash) - (when (and (gethash name namehash) - (funcall type-fn cand)) - (push (cons cand namehash) key-and-namehash-alist))) - (yas/snippet-table-hash table))) - (dolist (elem key-and-namehash-alist) - (remhash name (cdr elem)) - (when (= 0 (hash-table-count (cdr elem))) - (remhash (car elem) (yas/snippet-table-hash table)) - (when (vectorp (car elem)) - (define-key (yas/snippet-table-direct-keymap table) (car elem) nil)))))) + (let ((template (gethash uid (yas/snippet-table-uidhash table)))) + (when template + (let* ((name (yas/template-name template)) + (key (yas/template-key template)) + (keybinding (yas/template-keybinding template)) + (key-namehash (and key (gethash key (yas/snippet-table-hash table)))) + (keybinding-namehash (and keybinding (gethash keybinding (yas/snippet-table-hash table))))) + ;; Remove the name from each of the targeted namehashes + ;; + (dolist (namehash (remove nil (list key-namehash + keybinding-namehash))) + (remhash name namehash)) + ;; Cleanup if any of the namehashes in now empty. The + ;; keybinding namehash, if empty, leads to the actual + ;; keybinding being removed as well. + ;; + (when (and key-namehash (zerop (hash-table-count key-namehash))) + (remhash key (yas/snippet-table-hash table)) + (when (and keybinding-namehash (zerop (hash-table-count keybinding-namehash))) + (define-key (yas/snippet-table-direct-keymap table) keybinding nil) + (remhash keybinding (yas/snippet-table-hash table))) + ;; Finally, remove the uid from the uidhash + ;; + (remhash uid (yas/snippet-table-uidhash table))))))) (defun yas/add-snippet (table template) "Store in TABLE the snippet template TEMPLATE. @@ -997,16 +990,14 @@ keybinding)." (make-hash-table :test 'equal) (yas/snippet-table-hash table)))) (when (vectorp key) - (define-key (yas/snippet-table-direct-keymap table) key 'yas/expand-from-keymap))))) + (define-key (yas/snippet-table-direct-keymap table) key 'yas/expand-from-keymap))) + (when keys + (puthash (yas/template-uid template) template (yas/snippet-table-uidhash table))))) -(defun yas/update-snippet (snippet-table template name key keybinding) - "Add TEMPLATE to SNIPPET-TABLE, first removing existing according to remaining args. +(defun yas/update-snippet (snippet-table template) + "Add or update TEMPLATE in SNIPPET-TABLE" -TEMPLATE, NAME, KEY and KEYBINDING." - ;; The direct keybinding - (yas/remove-snippet snippet-table name keybinding #'vectorp) - (yas/remove-snippet snippet-table name key #'stringp) - + (yas/remove-snippet-by-uid snippet-table (yas/template-uid template)) (yas/add-snippet snippet-table template)) (defun yas/fetch (table key) @@ -1567,7 +1558,7 @@ content of the file is the template." ;; (if yas/snippet-dirs (if (listp yas/snippet-dirs) - (dolist (directory yas/snippet-dirs) + (dolist (directory (reverse yas/snippet-dirs)) (yas/load-directory directory)) (yas/load-directory yas/snippet-dirs)) (call-interactively 'yas/load-directory)) @@ -1824,6 +1815,8 @@ not need to be a real mode." (condition (fourth snippet)) (group (fifth snippet)) (keybinding (yas/read-keybinding (eighth snippet))) + (uid (or (ninth snippet) + name)) (template nil)) ;; Create the `yas/template' object and store in the @@ -1838,9 +1831,10 @@ not need to be a real mode." condition (sixth snippet) (seventh snippet) - keybinding)) + keybinding + uid)) (when name - (yas/update-snippet snippet-table template name key keybinding)) + (yas/update-snippet snippet-table template)) ;; Setup the menu groups, reorganizing from group to group if ;; necessary @@ -2333,10 +2327,7 @@ With optional prefix argument KILL quit the window and buffer." (setf (yas/template-expand-env yas/current-template) (sixth parsed)) (setf (yas/template-keybinding yas/current-template) (yas/read-keybinding (eighth parsed))) (yas/update-snippet (yas/template-table yas/current-template) - yas/current-template - old-name - old-key - old-keybinding)) + yas/current-template)) ;; Now, prompt for new file creation much like ;; `yas/new-snippet' if one of the following is true: ;; @@ -2459,6 +2450,7 @@ With optional prefix argument KILL quit the window and buffer." nil (sixth parsed) nil + nil nil)))) (cond (template (let ((buffer-name (format "*YAS TEST: %s*" (yas/template-name template)))) @@ -2475,6 +2467,52 @@ With optional prefix argument KILL quit the window and buffer." (t (message "[yas] Cannot test snippet for unknown major mode"))))) +(defun yas/list-tables (all-tables &optional by-name-hash) + "Display snippets for each table." + (interactive (list (y-or-n-p "Show also non-active tables?") + nil)) + (with-output-to-temp-buffer "*YASnippet tables*" + (let ((tables (or (and all-tables + (let ((all)) + (maphash #'(lambda (k v) + (push v all)) + yas/snippet-tables) + all)) + (yas/get-snippet-tables)))) + (cond ((not by-name-hash) + (princ "YASnippet tables by UUID: \n") + (dolist (table tables) + (princ (format "\nSnippet table `%s':\n\n" (yas/snippet-table-name table))) + (let ((templates)) + (maphash #'(lambda (k v) + (push v templates)) + (yas/snippet-table-uidhash table)) + (dolist (p templates) + (let ((name (yas/template-name p))) + (princ (format " * %s" name)) + (princ (make-string (max (- 50 (length name)) + 1) ? )) + (when (yas/template-key p) + (princ (format "key \"%s\" " (yas/template-key p)))) + (when (yas/template-keybinding p) + (princ (format "bound to %s " (key-description (yas/template-keybinding p))))) + (princ "\n")))))) + (t + (princ "\n\nYASnippet tables by NAMEHASH: \n") + (dolist (table tables) + (princ (format "\nSnippet table `%s':\n\n" (yas/snippet-table-name table))) + (let ((keys)) + (maphash #'(lambda (k v) + (push k keys)) + (yas/snippet-table-hash table)) + (dolist (key keys) + (princ (format " key %s maps snippets: %s\n" key + (let ((names)) + (maphash #'(lambda (k v) + (push k names)) + (gethash key (yas/snippet-table-hash table))) + names))))))))))) + ;;; User convenience functions, for using in snippet definitions @@ -3926,22 +3964,21 @@ object satisfying `yas/field-p' to restrict the expansion to."))) ;; (yas/snippet-table-hash (gethash 'ruby-mode yas/snippet-tables))) ;; shit))) ;; -(defun yas/debug-tables () - (interactive) - (with-output-to-temp-buffer "*YASnippet tables*" - (dolist (table (yas/get-snippet-tables)) - (princ (format "Table hash keys for %s:\n\n" (yas/snippet-table-name table)) - (let ((keys)) - (maphash #'(lambda (k v) - (push k keys)) - (yas/snippet-table-hash table)) - (dolist (key keys) - (princ (format " key %s maps snippets: %s\n" key - (let ((names)) - (maphash #'(lambda (k v) - (push k names)) - (gethash key (yas/snippet-table-hash table))) - names))))))))) +;; and here's a way to do it by namehash +;; +;; (let ((keys)) +;; (maphash #'(lambda (k v) +;; (push k keys)) +;; (yas/snippet-table-hash table)) +;; (dolist (key keys) +;; (princ (format " key %s maps snippets: %s\n" key +;; (let ((names)) +;; (maphash #'(lambda (k v) +;; (push k names)) +;; (gethash key (yas/snippet-table-hash table))) +;; names))))) +;; + (defun yas/debug-snippet-vars () "Debug snippets, fields, mirrors and the `buffer-undo-list'."