* 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'
;;
;; 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'."