* Fixed a serious nconc (circular-list) bug that lead to infinite loops

* Implemented a .yas-make-groups hidden file (already had
  .yas-parents) to make groups from directory hierarchy. (as opposed
  to making parents)

* yas/ignore-filenames-as-triggers can be used to, well, do what it says

* yas/prefix is bound when a snippet is expanded from a keybinding
This commit is contained in:
capitaomorte 2009-08-12 13:50:03 +00:00
parent d153d84010
commit e6f4ca5473

View File

@ -309,6 +309,14 @@ An error string \"[yas] error\" is returned instead."
:type 'boolean :type 'boolean
:group 'yasnippet) :group 'yasnippet)
(defcustom yas/ignore-filenames-as-triggers nil
"If non-nil, don't derive tab triggers from filenames.
This means a snippet without a \"# key:'\ directive wont have a
tab trigger."
:type 'boolean
:group 'yasnippet)
(defface yas/field-highlight-face (defface yas/field-highlight-face
'((((class color) (background light)) (:background "DarkSeaGreen1")) '((((class color) (background light)) (:background "DarkSeaGreen1"))
(t (:background "DimGrey"))) (t (:background "DimGrey")))
@ -652,22 +660,50 @@ This function implements the rules described in
(defun yas/snippet-table-fetch (table key) (defun yas/snippet-table-fetch (table key)
"Fetch a snippet binding to KEY from TABLE." "Fetch a snippet binding to KEY from TABLE."
(when table (when table
(yas/filter-templates-by-condition (gethash key (yas/snippet-table-hash table))))) (yas/filter-templates-by-condition
(copy-list (gethash key (yas/snippet-table-hash table))))))
(defun yas/snippet-table-get-all-parents (table) (defun yas/snippet-table-get-all-parents (table)
(let ((parents (yas/snippet-table-parents table))) (let ((parents (yas/snippet-table-parents table)))
(when parents (when parents
(append parents (append (copy-list parents)
(mapcan #'yas/snippet-table-get-all-parents parents))))) (mapcan #'yas/snippet-table-get-all-parents parents)))))
(defun yas/snippet-table-templates (table) (defun yas/snippet-table-templates (table)
(when table (when table
(let ((acc)) (let ((acc (list)))
(maphash #'(lambda (key templates) (maphash #'(lambda (key templates)
(setq acc (append acc templates))) (setq acc (nconc acc (copy-list templates))))
(yas/snippet-table-hash table)) (yas/snippet-table-hash table))
(yas/filter-templates-by-condition acc)))) (yas/filter-templates-by-condition acc))))
(defun yas/current-key ()
"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))
(syntaxes yas/key-syntaxes)
syntax
done
templates)
(while (and (not done) syntaxes)
(setq syntax (car syntaxes))
(setq syntaxes (cdr syntaxes))
(save-excursion
(skip-syntax-backward syntax)
(setq start (point)))
(setq templates
(mapcan #'(lambda (table)
(yas/snippet-table-fetch table (buffer-substring-no-properties start end)))
(yas/get-snippet-tables)))
(if templates
(setq done t)
(setq start end)))
(list templates
start
end)))
(defun yas/snippet-table-all-keys (table) (defun yas/snippet-table-all-keys (table)
(when table (when table
(let ((acc)) (let ((acc))
@ -677,13 +713,13 @@ This function implements the rules described in
(yas/snippet-table-hash table)) (yas/snippet-table-hash table))
acc))) acc)))
(defun yas/snippet-table-store (table full-key key template) (defun yas/snippet-table-store (table name key template)
"Store a snippet template in the TABLE." "Store a snippet template in the TABLE."
;; If replacing a snippet template, remember to remove its ;; If replacing a snippet template, remember to remove its
;; keybinding first. ;; keybinding first.
;; ;;
(let ((existing (aget (gethash key (yas/snippet-table-hash table)) (let ((existing (aget (gethash key (yas/snippet-table-hash table))
full-key))) name)))
(when (and existing (when (and existing
(yas/template-keybinding existing)) (yas/template-keybinding existing))
(define-key (define-key
@ -695,7 +731,7 @@ This function implements the rules described in
(puthash key (puthash key
(yas/modify-alist (gethash key (yas/modify-alist (gethash key
(yas/snippet-table-hash table)) (yas/snippet-table-hash table))
full-key name
template) template)
(yas/snippet-table-hash table))) (yas/snippet-table-hash table)))
@ -780,30 +816,6 @@ MODE-SYMBOL or `major-mode'."
(or (gethash mode yas/menu-table) (or (gethash mode yas/menu-table)
(puthash mode (make-sparse-keymap) yas/menu-table))) (puthash mode (make-sparse-keymap) yas/menu-table)))
(defun yas/current-key ()
"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))
(syntaxes yas/key-syntaxes)
syntax done templates)
(while (and (not done) syntaxes)
(setq syntax (car syntaxes))
(setq syntaxes (cdr syntaxes))
(save-excursion
(skip-syntax-backward syntax)
(setq start (point)))
(setq templates
(mapcan #'(lambda (table)
(yas/snippet-table-fetch table (buffer-substring-no-properties start end)))
(yas/get-snippet-tables)))
(if templates
(setq done t)
(setq start end)))
(list templates
start
end)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Template-related and snippet loading functions ;;; Template-related and snippet loading functions
@ -815,7 +827,7 @@ parsed.
Return a snippet-definition, i.e. a list Return a snippet-definition, i.e. a list
(KEY TEMPLATE NAME CONDITION GROUP ENV) (KEY TEMPLATE NAME CONDITION GROUP VARS FILE KEYBINDING)
If the buffer contains a line of \"# --\" then the contents If the buffer contains a line of \"# --\" then the contents
above this line are ignored. Variables can be set above this above this line are ignored. Variables can be set above this
@ -830,18 +842,24 @@ Here's a list of currently recognized variables:
* condition * condition
* key * key
* group * group
* env * expand-env
#name: #include \"...\" #name: #include \"...\"
# -- # --
#include \"$1\"" #include \"$1\""
;;
;;
(goto-char (point-min)) (goto-char (point-min))
(let* ((name (and file (file-name-nondirectory file))) (let* ((name (and file
(key name) (file-name-nondirectory file)))
(key (unless yas/ignore-filenames-as-triggers
(and name
(file-name-sans-extension name))))
template template
bound bound
condition condition
group (group (and file
(yas/calculate-group file)))
env env
binding) binding)
(if (re-search-forward "^# --\n" nil t) (if (re-search-forward "^# --\n" nil t)
@ -867,6 +885,51 @@ Here's a list of currently recognized variables:
(buffer-substring-no-properties (point-min) (point-max)))) (buffer-substring-no-properties (point-min) (point-max))))
(list key template name condition group env file binding))) (list key template name condition group env file binding)))
(defun yas/calculate-group (file)
"Calculate the group for snippet file path FILE."
(let* ((dominating-dir (locate-dominating-file file
".yas-make-groups"))
(extra-path (and dominating-dir
(replace-regexp-in-string (concat "^"
(expand-file-name dominating-dir))
""
(expand-file-name file))))
(extra-dir (and extra-path
(file-name-directory extra-path)))
(group (and extra-dir
(replace-regexp-in-string "/"
"."
(directory-file-name extra-dir)))))
group))
;; (defun yas/glob-files (directory &optional recurse-p append)
;; "Returns files under DIRECTORY ignoring dirs and hidden files.
;; If RECURSE in non-nil, do that recursively."
;; (let (ret
;; (default-directory directory))
;; (dolist (entry (directory-files "."))
;; (cond ((or (string-match "^\\."
;; (file-name-nondirectory entry))
;; (string-match "~$"
;; (file-name-nondirectory entry)))
;; nil)
;; ((and recurse-p
;; (file-directory-p entry))
;; (setq ret (nconc ret
;; (yas/glob-files (expand-file-name entry)
;; recurse-p
;; (if append
;; (concat append "/" entry)
;; entry)))))
;; ((file-directory-p entry)
;; nil)
;; (t
;; (push (if append
;; (concat append "/" entry)
;; entry) ret))))
;; ret))
(defun yas/subdirs (directory &optional file?) (defun yas/subdirs (directory &optional file?)
"Return subdirs or files of DIRECTORY according to FILE?." "Return subdirs or files of DIRECTORY according to FILE?."
(remove-if (lambda (file) (remove-if (lambda (file)
@ -997,26 +1060,36 @@ TEMPLATES is a list of `yas/template'."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Loading snippets from files ;; Loading snippets from files
;; ;;
(defun yas/load-directory-1 (directory &optional parents no-hierarchy-parents) (defun yas/load-directory-1 (directory &optional parents no-hierarchy-parents making-groups-sym)
"Recursively load snippet templates from DIRECTORY." "Recursively load snippet templates from DIRECTORY."
(let* ((major-mode-and-parents (yas/compute-major-mode-and-parents (concat directory "/dummy") (let* ((major-mode-and-parents (unless making-groups-sym
nil (yas/compute-major-mode-and-parents (concat directory "/dummy")
no-hierarchy-parents)) nil
(mode-sym (car major-mode-and-parents)) no-hierarchy-parents)))
(parents (rest major-mode-and-parents)) (mode-sym (and major-mode-and-parents
(snippet-defs nil)) (car major-mode-and-parents)))
(parents (if making-groups-sym
parents
(rest major-mode-and-parents)))
(snippet-defs nil)
(make-groups-p (or making-groups-sym
(file-exists-p (concat directory "/" ".yas-make-groups")))))
(with-temp-buffer (with-temp-buffer
(dolist (file (yas/subdirs directory 'no-subdirs-just-files)) (dolist (file (yas/subdirs directory 'no-subdirs-just-files))
(when (file-readable-p file) (when (file-readable-p file)
(insert-file-contents file nil nil nil t) (insert-file-contents file nil nil nil t)
(push (yas/parse-template file) (push (yas/parse-template file)
snippet-defs)))) snippet-defs))))
(yas/define-snippets mode-sym (yas/define-snippets (or mode-sym
making-groups-sym)
snippet-defs snippet-defs
parents) parents)
(dolist (subdir (yas/subdirs directory)) (dolist (subdir (yas/subdirs directory))
(yas/load-directory-1 subdir (list mode-sym))))) (if make-groups-p
(yas/load-directory-1 subdir parents 't (or mode-sym
making-groups-sym))
(yas/load-directory-1 subdir (list mode-sym))))))
(defun yas/load-directory (directory) (defun yas/load-directory (directory)
"Load snippet definition from a directory hierarchy. "Load snippet definition from a directory hierarchy.
@ -1265,9 +1338,14 @@ its parent modes."
;; Iterate the recently parsed snippets definition ;; Iterate the recently parsed snippets definition
;; ;;
(dolist (snippet snippets) (dolist (snippet snippets)
(let* ((full-key (car snippet)) (let* ((file (seventh snippet))
(key (file-name-sans-extension full-key)) (key (or (car snippet)
(name (or (third snippet) (file-name-extension full-key))) (unless yas/ignore-filenames-as-triggers
(and file
(file-name-sans-extension (file-name-nondirectory file))))))
(name (or (third snippet)
(and file
(file-name-directory file))))
(condition (fourth snippet)) (condition (fourth snippet))
(group (fifth snippet)) (group (fifth snippet))
(keybinding (eighth snippet)) (keybinding (eighth snippet))
@ -1297,27 +1375,33 @@ its parent modes."
(setf keybinding nil))) (setf keybinding nil)))
;; Create the `yas/template' object and store in the ;; Create the `yas/template' object and store in the
;; appropriate snippet table ;; appropriate snippet table. This only done if we have found
;; a key and a name for the snippet, because that is what
;; indexes the snippet tables
;; ;;
(setq template (yas/make-template (second snippet) (when (and key
(or name key) name)
condition (setq template (yas/make-template (second snippet)
(sixth snippet) (or name key)
(seventh snippet) condition
keybinding)) (sixth snippet)
(yas/snippet-table-store snippet-table (seventh snippet)
full-key keybinding))
key (yas/snippet-table-store snippet-table
template) name
key
;; Now register the keybinding if it does not conflict! template))
;; If we have a keybinding, register it if it does not
;; conflict!
;; ;;
(unless (or (not (consp keybinding)) (unless (or (not (consp keybinding))
(lookup-key (car keybinding) (cdr keybinding))) (lookup-key (car keybinding) (cdr keybinding)))
(define-key (define-key
(car keybinding) (car keybinding)
(cdr keybinding) (cdr keybinding)
`(lambda () (interactive) (yas/expand-snippet ,(second snippet))))) `(lambda (&optional yas/prefix)
(interactive "P")
(yas/expand-snippet ,(second snippet)))))
;; Setup the menu groups, reorganizing from group to group if ;; Setup the menu groups, reorganizing from group to group if
;; necessary ;; necessary
@ -1343,7 +1427,7 @@ its parent modes."
`(menu-item ,(symbol-name subgroup) `(menu-item ,(symbol-name subgroup)
,subgroup-keymap))) ,subgroup-keymap)))
(setq group-keymap subgroup-keymap)))) (setq group-keymap subgroup-keymap))))
(define-key group-keymap (vector (make-symbol full-key)) (define-key group-keymap (vector (gensym))
`(menu-item ,(yas/template-name template) `(menu-item ,(yas/template-name template)
,(yas/make-menu-binding template) ,(yas/make-menu-binding template)
:keys ,(concat key yas/trigger-symbol))))))))) :keys ,(concat key yas/trigger-symbol)))))))))
@ -1687,10 +1771,12 @@ With optional prefix argument KILL quit the window and buffer."
"Test current buffers's snippet template in other buffer." "Test current buffers's snippet template in other buffer."
(interactive "P") (interactive "P")
(let* ((major-mode-and-parent (yas/compute-major-mode-and-parents buffer-file-name)) (let* ((major-mode-and-parent (yas/compute-major-mode-and-parents buffer-file-name))
(parsed (and major-mode-and-parent (parsed (yas/parse-template))
(fboundp (car major-mode-and-parent)) (test-mode (or (and (car major-mode-and-parent)
(yas/parse-template (symbol-name (car major-mode-and-parent))))) (fboundp (car major-mode-and-parent)))
(intern (read-from-minibuffer "[yas] please input a mode: "))))
(template (and parsed (template (and parsed
(fboundp test-mode)
(yas/make-template (second parsed) (yas/make-template (second parsed)
(third parsed) (third parsed)
nil nil
@ -1702,7 +1788,7 @@ With optional prefix argument KILL quit the window and buffer."
(set-buffer (switch-to-buffer buffer-name)) (set-buffer (switch-to-buffer buffer-name))
(erase-buffer) (erase-buffer)
(setq buffer-undo-list nil) (setq buffer-undo-list nil)
(funcall (car major-mode-and-parent)) (funcall test-mode)
(yas/expand-snippet (yas/template-content template) (yas/expand-snippet (yas/template-content template)
(point-min) (point-min)
(point-max) (point-max)
@ -1789,6 +1875,9 @@ Otherwise throw exception."
(defvar yas/field-protection-overlays nil (defvar yas/field-protection-overlays nil
"Two overlays protect the current active field ") "Two overlays protect the current active field ")
(defconst yas/prefix nil
"A prefix argument for expansion direct from keybindings")
(defvar yas/deleted-text nil (defvar yas/deleted-text nil
"The text deleted in the last snippet expansion.") "The text deleted in the last snippet expansion.")
@ -2673,11 +2762,12 @@ Meant to be called in a narrowed buffer, does various passes"
(let ((trouble-markers (remove-if-not #'(lambda (marker) (let ((trouble-markers (remove-if-not #'(lambda (marker)
(= marker (point))) (= marker (point)))
snippet-markers))) snippet-markers)))
(indent-according-to-mode) (goto-char (line-end-position))
(mapc #'(lambda (marker) (save-excursion
(set-marker marker (point))) (indent-according-to-mode))
trouble-markers) (mapc #'(lambda (marker)
(indent-according-to-mode))) (set-marker marker (point)))
trouble-markers)))
(set-marker end nil)))) (set-marker end nil))))
(t (t
nil)))) nil))))
@ -3069,6 +3159,90 @@ When multiple expressions are found, only the last one counts."
(unless quiet (unless quiet
(add-hook 'post-command-hook 'yas/debug-snippet-vars 't 'local))) (add-hook 'post-command-hook 'yas/debug-snippet-vars 't 'local)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; `locate-dominating-file' is added for compatibility in emacs < 23
(unless (eq emacs-major-version 23)
(defvar locate-dominating-stop-dir-regexp
"\\`\\(?:[\\/][\\/][^\\/]+[\\/]\\|/\\(?:net\\|afs\\|\\.\\.\\.\\)/\\)\\'"
"Regexp of directory names which stop the search in `locate-dominating-file'.
Any directory whose name matches this regexp will be treated like
a kind of root directory by `locate-dominating-file' which will stop its search
when it bumps into it.
The default regexp prevents fruitless and time-consuming attempts to find
special files in directories in which filenames are interpreted as hostnames,
or mount points potentially requiring authentication as a different user.")
;; (defun locate-dominating-files (file regexp)
;; "Look up the directory hierarchy from FILE for a file matching REGEXP.
;; Stop at the first parent where a matching file is found and return the list
;; of files that that match in this directory."
;; (catch 'found
;; ;; `user' is not initialized yet because `file' may not exist, so we may
;; ;; have to walk up part of the hierarchy before we find the "initial UID".
;; (let ((user nil)
;; ;; Abbreviate, so as to stop when we cross ~/.
;; (dir (abbreviate-file-name (file-name-as-directory file)))
;; files)
;; (while (and dir
;; ;; As a heuristic, we stop looking up the hierarchy of
;; ;; directories as soon as we find a directory belonging to
;; ;; another user. This should save us from looking in
;; ;; things like /net and /afs. This assumes that all the
;; ;; files inside a project belong to the same user.
;; (let ((prev-user user))
;; (setq user (nth 2 (file-attributes dir)))
;; (or (null prev-user) (equal user prev-user))))
;; (if (setq files (condition-case nil
;; (directory-files dir 'full regexp 'nosort)
;; (error nil)))
;; (throw 'found files)
;; (if (equal dir
;; (setq dir (file-name-directory
;; (directory-file-name dir))))
;; (setq dir nil))))
;; nil)))
(defun locate-dominating-file (file name)
"Look up the directory hierarchy from FILE for a file named NAME.
Stop at the first parent directory containing a file NAME,
and return the directory. Return nil if not found."
;; We used to use the above locate-dominating-files code, but the
;; directory-files call is very costly, so we're much better off doing
;; multiple calls using the code in here.
;;
;; Represent /home/luser/foo as ~/foo so that we don't try to look for
;; `name' in /home or in /.
(setq file (abbreviate-file-name file))
(let ((root nil)
(prev-file file)
;; `user' is not initialized outside the loop because
;; `file' may not exist, so we may have to walk up part of the
;; hierarchy before we find the "initial UID".
(user nil)
try)
(while (not (or root
(null file)
;; FIXME: Disabled this heuristic because it is sometimes
;; inappropriate.
;; As a heuristic, we stop looking up the hierarchy of
;; directories as soon as we find a directory belonging
;; to another user. This should save us from looking in
;; things like /net and /afs. This assumes that all the
;; files inside a project belong to the same user.
;; (let ((prev-user user))
;; (setq user (nth 2 (file-attributes file)))
;; (and prev-user (not (equal user prev-user))))
(string-match locate-dominating-stop-dir-regexp file)))
(setq try (file-exists-p (expand-file-name name file)))
(cond (try (setq root file))
((equal file (setq prev-file file
file (file-name-directory
(directory-file-name file))))
(setq file nil))))
root)))
(provide 'yasnippet) (provide 'yasnippet)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;