* Finally, a more reasonable uuid-based template-loading framework...

This commit is contained in:
capitaomorte 2010-03-16 16:06:00 +00:00
parent c937048036
commit ea025980d8

View File

@ -50,10 +50,11 @@
;; `yas/snippet-dirs' ;; `yas/snippet-dirs'
;; ;;
;; The directory where user-created snippets are to be ;; The directory where user-created snippets are to be
;; stored. Can also be a list of directories that ;; stored. Can also be a list of directories. In that case,
;; `yas/reload-all' will use for bulk-reloading snippets. In ;; when used for bulk (re)loading of snippets (at startup or
;; that case the first directory is the default for storing new ;; via `yas/reload-all'), directories appearing earlier in
;; snippets. ;; 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' ;; `yas/mode-symbol'
;; ;;
@ -114,6 +115,13 @@
;; snippet there, so you can see what it looks like. This is ;; snippet there, so you can see what it looks like. This is
;; bound to "C-c C-t" while in `snippet-mode'. ;; 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 ;; The `dropdown-list.el' extension is bundled with YASnippet, you
;; can optionally use it the preferred "prompting method", puting in ;; can optionally use it the preferred "prompting method", puting in
;; your .emacs file, for example: ;; your .emacs file, for example:
@ -152,11 +160,12 @@
(defcustom yas/snippet-dirs nil (defcustom yas/snippet-dirs nil
"Directory or list of snippet dirs for each major mode. "Directory or list of snippet dirs for each major mode.
If you set this from your .emacs, can also be a list of strings, The directory where user-created snippets are to be stored. Can
for multiple root directories. If you make this a list, the first also be a list of directories. In that case, when used for
element is always the user-created snippets directory. Other bulk (re)loading of snippets (at startup or via
directories are used for bulk reloading of all snippets using `yas/reload-all'), directories appearing earlier in the list
`yas/reload-all'" 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)") :type '(choice (string :tag "Single directory (string)")
(repeat :args (string) :tag "List of directories (strings)")) (repeat :args (string) :tag "List of directories (strings)"))
:group 'yasnippet :group 'yasnippet
@ -867,7 +876,7 @@ Do this unless `yas/dont-activate' is t or the function
;;; Internal structs for template management ;;; Internal structs for template management
(defstruct (yas/template (:constructor yas/make-template (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." "A template for a snippet."
table table
key key
@ -876,7 +885,8 @@ Do this unless `yas/dont-activate' is t or the function
condition condition
expand-env expand-env
file file
keybinding) keybinding
uid)
(defstruct (yas/snippet-table (:constructor yas/make-snippet-table (name))) (defstruct (yas/snippet-table (:constructor yas/make-snippet-table (name)))
"A table to store snippets for a particular mode. "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 keybindings. This is kept in sync with the keyhash, i.e., all
the elements of the keyhash that are vectors appear here as the elements of the keyhash that are vectors appear here as
bindings to `yas/expand-from-keymap'. 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 name
(hash (make-hash-table :test 'equal)) (hash (make-hash-table :test 'equal))
(uidhash (make-hash-table :test 'equal))
(parents nil) (parents nil)
(direct-keymap (make-sparse-keymap))) (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 ;; 1. `yas/remove-snippet-by-uid' to remove any existing mappings by
;; searches: ;; snippet uid
;;
;; 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:
;; ;;
;; 2. `yas/add-snippet' to add the mappings again: ;; 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 ;; TEMPLATE, and is also created a new namehash inside that
;; entry. ;; entry.
;; ;;
;; TODO: This is still not ideal. A well designed system (like (defun yas/remove-snippet-by-uid (table uid)
;; 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)
"Attempt to remove from TABLE a template with NAME and KEY. "Attempt to remove from TABLE a template with NAME and KEY.
TYPE-FN indicates if KEY is a trigger key (string) or a TYPE-FN indicates if KEY is a trigger key (string) or a
keybinding (vector)." keybinding (vector)."
(let ((key-and-namehash-alist '()) (let ((template (gethash uid (yas/snippet-table-uidhash table))))
(namehash-for-key (gethash key (yas/snippet-table-hash table)))) (when template
(when namehash-for-key (let* ((name (yas/template-name template))
(push (cons key namehash-for-key) key-and-namehash-alist)) (key (yas/template-key template))
(when (and (null key-and-namehash-alist) (keybinding (yas/template-keybinding template))
yas/better-guess-for-replacements) (key-namehash (and key (gethash key (yas/snippet-table-hash table))))
;; "cand" means "candidate for removal" (keybinding-namehash (and keybinding (gethash keybinding (yas/snippet-table-hash table)))))
(maphash #'(lambda (cand namehash) ;; Remove the name from each of the targeted namehashes
(when (and (gethash name namehash) ;;
(funcall type-fn cand)) (dolist (namehash (remove nil (list key-namehash
(push (cons cand namehash) key-and-namehash-alist))) keybinding-namehash)))
(yas/snippet-table-hash table))) (remhash name namehash))
(dolist (elem key-and-namehash-alist) ;; Cleanup if any of the namehashes in now empty. The
(remhash name (cdr elem)) ;; keybinding namehash, if empty, leads to the actual
(when (= 0 (hash-table-count (cdr elem))) ;; keybinding being removed as well.
(remhash (car elem) (yas/snippet-table-hash table)) ;;
(when (vectorp (car elem)) (when (and key-namehash (zerop (hash-table-count key-namehash)))
(define-key (yas/snippet-table-direct-keymap table) (car elem) nil)))))) (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) (defun yas/add-snippet (table template)
"Store in TABLE the snippet template TEMPLATE. "Store in TABLE the snippet template TEMPLATE.
@ -997,16 +990,14 @@ keybinding)."
(make-hash-table :test 'equal) (make-hash-table :test 'equal)
(yas/snippet-table-hash table)))) (yas/snippet-table-hash table))))
(when (vectorp key) (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) (defun yas/update-snippet (snippet-table template)
"Add TEMPLATE to SNIPPET-TABLE, first removing existing according to remaining args. "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)) (yas/add-snippet snippet-table template))
(defun yas/fetch (table key) (defun yas/fetch (table key)
@ -1567,7 +1558,7 @@ content of the file is the template."
;; ;;
(if yas/snippet-dirs (if yas/snippet-dirs
(if (listp 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 directory))
(yas/load-directory yas/snippet-dirs)) (yas/load-directory yas/snippet-dirs))
(call-interactively 'yas/load-directory)) (call-interactively 'yas/load-directory))
@ -1824,6 +1815,8 @@ not need to be a real mode."
(condition (fourth snippet)) (condition (fourth snippet))
(group (fifth snippet)) (group (fifth snippet))
(keybinding (yas/read-keybinding (eighth snippet))) (keybinding (yas/read-keybinding (eighth snippet)))
(uid (or (ninth snippet)
name))
(template nil)) (template nil))
;; Create the `yas/template' object and store in the ;; Create the `yas/template' object and store in the
@ -1838,9 +1831,10 @@ not need to be a real mode."
condition condition
(sixth snippet) (sixth snippet)
(seventh snippet) (seventh snippet)
keybinding)) keybinding
uid))
(when name (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 ;; Setup the menu groups, reorganizing from group to group if
;; necessary ;; 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-expand-env yas/current-template) (sixth parsed))
(setf (yas/template-keybinding yas/current-template) (yas/read-keybinding (eighth parsed))) (setf (yas/template-keybinding yas/current-template) (yas/read-keybinding (eighth parsed)))
(yas/update-snippet (yas/template-table yas/current-template) (yas/update-snippet (yas/template-table yas/current-template)
yas/current-template yas/current-template))
old-name
old-key
old-keybinding))
;; Now, prompt for new file creation much like ;; Now, prompt for new file creation much like
;; `yas/new-snippet' if one of the following is true: ;; `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 nil
(sixth parsed) (sixth parsed)
nil nil
nil
nil)))) nil))))
(cond (template (cond (template
(let ((buffer-name (format "*YAS TEST: %s*" (yas/template-name 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 (t
(message "[yas] Cannot test snippet for unknown major mode"))))) (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 ;;; 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))) ;; (yas/snippet-table-hash (gethash 'ruby-mode yas/snippet-tables)))
;; shit))) ;; shit)))
;; ;;
(defun yas/debug-tables () ;; and here's a way to do it by namehash
(interactive) ;;
(with-output-to-temp-buffer "*YASnippet tables*" ;; (let ((keys))
(dolist (table (yas/get-snippet-tables)) ;; (maphash #'(lambda (k v)
(princ (format "Table hash keys for %s:\n\n" (yas/snippet-table-name table)) ;; (push k keys))
(let ((keys)) ;; (yas/snippet-table-hash table))
(maphash #'(lambda (k v) ;; (dolist (key keys)
(push k keys)) ;; (princ (format " key %s maps snippets: %s\n" key
(yas/snippet-table-hash table)) ;; (let ((names))
(dolist (key keys) ;; (maphash #'(lambda (k v)
(princ (format " key %s maps snippets: %s\n" key ;; (push k names))
(let ((names)) ;; (gethash key (yas/snippet-table-hash table)))
(maphash #'(lambda (k v) ;; names)))))
(push k names)) ;;
(gethash key (yas/snippet-table-hash table)))
names)))))))))
(defun yas/debug-snippet-vars () (defun yas/debug-snippet-vars ()
"Debug snippets, fields, mirrors and the `buffer-undo-list'." "Debug snippets, fields, mirrors and the `buffer-undo-list'."