* Redesigned directory-guessing mechanism, but kept backward

compatibility

* A mode can now have multiple parent modes using a hidden
  ".yas-parents" file.

* Fixed issue 70.

* Removed a useless condition-case.

* More fixes
This commit is contained in:
capitaomorte 2009-07-29 12:54:54 +00:00
parent ad3c307bbc
commit d42e5a3189

View File

@ -44,6 +44,23 @@
;; Steps 4. and 5. are optional, you don't have to use the minor
;; mode to use YASnippet.
;;
;; Interesting variables are:
;;
;; `yas/root-directory'
;;
;; 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 the default for storing new
;; snippets.
;;
;; `yas/mode-symbol'
;;
;; A local variable that you can set in a hook to override
;; snippet-lookup based on major mode. It is a a symbol (or
;; list of symbols) that correspond to subdirectories of
;; `yas/root-directory' and is used for deciding which
;; snippets to consider for the active buffer.
;;
;; Major commands are:
;;
@ -65,8 +82,9 @@
;;
;; M-x yas/find-snippets
;;
;; Lets you find the snippet file in the directory the
;; snippet was loaded from (if it exists) like
;; Lets you find the snippet files in the correct
;; subdirectory of `yas/root-directory', according to the
;; active major mode (if it exists) like
;; `find-file-other-window'.
;;
;; M-x yas/visit-snippet-file
@ -76,6 +94,12 @@
;; you directly to the snippet definition's file, if it
;; exists.
;;
;; M-x yas/new-snippet
;;
;; Lets you create a new snippet file in the correct
;; subdirectory of `yas/root-directory', according to the
;; active major mode
;;
;; M-x yas/load-snippet-buffer
;;
;; When editing a snippet, this loads the snippet. This is
@ -121,7 +145,11 @@
(defcustom yas/root-directory nil
"Root directory that stores the snippets for each major mode.
Can also be a list of strings, for multiple root directories."
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'"
:type '(string)
:group 'yasnippet)
@ -220,10 +248,21 @@ to expand.
:group 'yasnippet)
(defcustom yas/choose-keys-first t
"If non-nil, `yas/insert-snippet' prompts for key, then for template.
"If non-nil, prompts for key first, then for template if more than one.
Otherwise `yas/insert-snippet' prompts for all possible
templates and inserts the selected one."
Otherwise prompts for all possible templates
This affects `yas/insert-snippet' and `yas/visit-snippet-file'."
:type 'boolean
:group 'yasnippet)
(defcustom yas/choose-tables-first nil
"If non-nil, and multiple eligible snippet tables, prompts user for tables first.
Otherwise, user chooses between the merging together of all
eligible tables.
This affects `yas/insert-snippet', `yas/visit-snippet-file'"
:type 'boolean
:group 'yasnippet)
@ -240,21 +279,6 @@ mode will be listed under the menu \"yasnippet\"."
:type 'string
:group 'yasnippet)
(defcustom yas/show-all-modes-in-menu nil
"Display \"artificial\" major modes in menu bar as well.
Currently, YASnippet only all \"real modes\" to menubar. For
example, you define snippets for \"cc-mode\" and make it the
parent of `c-mode', `c++-mode' and `java-mode'. There's really
no such mode like \"cc-mode\". So we don't show it in the yasnippet
menu to avoid the menu becoming too big with strange modes. The
snippets defined for \"cc-mode\" can still be accessed from
menu-bar->c-mode->parent (or c++-mode, java-mode, all are ok).
However, if you really like to show all modes in the menu, set
this variable to t."
:type 'boolean
:group 'yasnippet)
(defcustom yas/wrap-around-region nil
"If non-nil, snippet expansion wraps around selected region.
@ -378,12 +402,6 @@ Here's an example:
t))))")
(make-variable-buffer-local 'yas/buffer-local-condition)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utility functions for transformations
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal variables
;;
@ -543,11 +561,11 @@ Key bindings:
env
file)
(defstruct (yas/snippet-table (:constructor yas/make-snippet-table ()))
(defstruct (yas/snippet-table (:constructor yas/make-snippet-table (name)))
"A table to store snippets for a perticular mode."
name
(hash (make-hash-table :test 'equal))
(default-directory nil)
(parent nil))
(parents nil))
(defun yas/template-condition-predicate (condition)
(condition-case err
@ -583,26 +601,23 @@ This function implements the rules described in
templates))))
(defun yas/snippet-table-fetch (table key)
"Fetch a snippet binding to KEY from TABLE. If not found,
fetch from parent if any."
"Fetch a snippet binding to KEY from TABLE."
(when table
(let* ((unfiltered (gethash key (yas/snippet-table-hash table)))
(templates (yas/filter-templates-by-condition unfiltered)))
(when (and (null templates)
(not (null (yas/snippet-table-parent table))))
(setq templates (yas/snippet-table-fetch
(yas/snippet-table-parent table)
key)))
templates)))
(yas/filter-templates-by-condition (gethash key (yas/snippet-table-hash table)))))
(defun yas/snippet-table-all-templates (table)
(defun yas/snippet-table-get-all-parents (table)
(let ((parents (yas/snippet-table-parents table)))
(when parents
(append parents
(mapcan #'yas/snippet-table-get-all-parents parents)))))
(defun yas/snippet-table-templates (table)
(when table
(let ((acc))
(maphash #'(lambda (key templates)
(setq acc (append acc templates)))
(yas/snippet-table-hash table))
(append (yas/filter-templates-by-condition acc)
(yas/snippet-table-all-templates (yas/snippet-table-parent table))))))
(yas/filter-templates-by-condition acc))))
(defun yas/snippet-table-all-keys (table)
(when table
@ -611,8 +626,7 @@ fetch from parent if any."
(when (yas/filter-templates-by-condition templates)
(push key acc)))
(yas/snippet-table-hash table))
(append acc
(yas/snippet-table-all-keys (yas/snippet-table-parent table))))))
acc)))
(defun yas/snippet-table-store (table full-key key template)
"Store a snippet template in the TABLE."
@ -625,17 +639,7 @@ fetch from parent if any."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal functions
;;
(defun yas/ensure-minor-mode-priority ()
"Ensure that the key binding of yas/minor-mode takes priority."
(unless (eq 'yas/minor-mode
(caar minor-mode-map-alist))
(setq minor-mode-map-alist
(cons
(cons 'yas/minor-mode yas/minor-mode-map)
(assq-delete-all 'yas/minor-mode
minor-mode-map-alist)))))
;;
(defun yas/real-mode? (mode)
"Try to find out if MODE is a real mode. The MODE bound to
@ -668,7 +672,11 @@ a list of modes like this to help the judgement."
(error (cdr retval)))
retval))
(defun yas/snippet-table-get-create (mode &optional directory)
(defvar yas/mode-symbol nil
"If non-nil, lookup snippets using this instead of `major-mode'.")
(make-variable-buffer-local 'yas/mode-symbol)
(defun yas/snippet-table-get-create (mode)
"Get the snippet table corresponding to MODE.
Optional DIRECTORY gets recorded as the default directory to
@ -677,24 +685,35 @@ already have such a property."
(let ((table (gethash mode
yas/snippet-tables)))
(unless table
(setq table (yas/make-snippet-table))
(setq table (yas/make-snippet-table (symbol-name mode)))
(puthash mode table yas/snippet-tables))
(unless (or (not directory) (yas/snippet-table-default-directory table))
(setf (yas/snippet-table-default-directory table)
directory))
table))
(defun yas/current-snippet-table (&optional mode-symbol dont-search-parents)
"Get the snippet table for current major-mode."
(let ((mode (or mode-symbol
major-mode)))
(or (gethash mode
yas/snippet-tables)
(and (not dont-search-parents)
(get mode 'derived-mode-parent)
(yas/current-snippet-table (get mode 'derived-mode-parent))))))
(defun yas/get-snippet-tables (&optional mode-symbol dont-search-parents)
"Get snippet tables for current buffer.
(defun yas/menu-keymap-for-mode (mode)
Return tables in this order: optional MODE-SYMBOL, then
`yas/mode-symbol', then `major-mode' then, unless
DONT-SEARCH-PARENTS is non-nil, the guessed parent mode of either
MODE-SYMBOL or `major-mode'."
(let ((mode-tables
(mapcar #'(lambda (mode)
(gethash mode yas/snippet-tables))
(append (list mode-symbol)
(if (listp yas/mode-symbol)
yas/mode-symbol
(list yas/mode-symbol))
(list major-mode
(and (not dont-search-parents)
(get (or mode-symbol major-mode)
'derived-mode-parent))))))
(all-tables))
(dolist (table (remove nil mode-tables))
(push table all-tables)
(nconc all-tables (yas/snippet-table-get-all-parents table)))
(remove-duplicates all-tables)))
(defun yas/menu-keymap-get-create (mode)
"Get the menu keymap correspondong to MODE."
(let ((keymap (gethash mode yas/menu-table)))
(unless keymap
@ -717,9 +736,9 @@ the template of a snippet in the current snippet-table."
(skip-syntax-backward syntax)
(setq start (point)))
(setq templates
(yas/snippet-table-fetch
(yas/current-snippet-table)
(buffer-substring-no-properties start end)))
(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)))
@ -826,19 +845,27 @@ Here's a list of currently recognized variables:
"Interactively choose a template from the list TEMPLATES.
TEMPLATES is a list of `yas/template'."
(let ((template (some #'(lambda (fn)
(funcall fn (or prompt "Choose a snippet: ")
templates #'(lambda (template)
(yas/template-name template))))
yas/prompt-functions)))
template))
(when templates
(some #'(lambda (fn)
(funcall fn (or prompt "Choose a snippet: ")
templates
#'yas/template-name))
yas/prompt-functions)))
(defun yas/prompt-for-keys (keys &optional prompt)
"Interactively choose a template key from the list KEYS."
(if keys
(some #'(lambda (fn)
(funcall fn (or prompt "Choose a snippet key: ") keys))
yas/prompt-functions)))
(when keys
(some #'(lambda (fn)
(funcall fn (or prompt "Choose a snippet key: ") keys))
yas/prompt-functions)))
(defun yas/prompt-for-table (tables &optional prompt)
(when tables
(some #'(lambda (fn)
(funcall fn (or prompt "Choose a snippet table: ")
tables
#'yas/snippet-table-name))
yas/prompt-functions)))
(defun yas/x-prompt (prompt choices &optional display-fn)
(when (and window-system choices)
@ -909,24 +936,32 @@ TEMPLATES is a list of `yas/template'."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Loading snippets from files
;;
(defun yas/load-directory-1 (directory &optional parent)
(defun yas/load-directory-1 (directory &optional parents)
"Recursively load snippet templates from DIRECTORY."
(let ((mode-sym (intern (file-name-nondirectory directory)))
(snippet-defs nil))
(snippet-defs nil)
(parent-file-name (concat directory "/.yas-parents"))
more-parents)
(with-temp-buffer
(dolist (file (yas/subdirs directory 'no-subdirs-just-files))
(when (file-readable-p file)
(insert-file-contents file nil nil nil t)
(push (yas/parse-template file)
snippet-defs))))
(when (file-readable-p parent-file-name)
(setq more-parents
(mapcar #'intern
(split-string
(with-temp-buffer
(insert-file parent-file-name)
(buffer-substring-no-properties (point-min)
(point-max)))))))
(yas/define-snippets mode-sym
snippet-defs
parent
directory)
(append parents more-parents))
(dolist (subdir (yas/subdirs directory))
(yas/load-directory-1 subdir mode-sym))))
(yas/load-directory-1 subdir (list mode-sym)))))
(defun yas/load-directory (directory)
"Load snippet definition from a directory hierarchy.
@ -990,7 +1025,7 @@ foo\"bar\\! -> \"foo\\\"bar\\\\!\""
t)
"\""))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Yasnipept Bundle
;;; Yasnippet Bundle
(defun yas/initialize ()
"For backward compatibility, enable `yas/minor-mode' globally"
@ -1097,7 +1132,7 @@ Here's the default value for all the parameters:
(save-buffer))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; User level functions
;;; Some user level functions
;;;
(defun yas/about ()
@ -1106,37 +1141,35 @@ Here's the default value for all the parameters:
yas/version
") -- pluskid <pluskid@gmail.com>/joaotavora <joaotavora@gmail.com>")))
(defun yas/define-snippets (mode snippets &optional parent-mode directory)
(defun yas/define-snippets (mode snippets &optional parent-mode)
"Define snippets for MODE. SNIPPETS is a list of
snippet definitions, each taking the following form:
(KEY TEMPLATE NAME CONDITION GROUP)
NAME, CONDITION or GROUP may be omitted. Optional PARENT-MODE
can be used to specify the parent mode of MODE. That is, when
looking a snippet in MODE failed, it can refer to its parent
mode. The PARENT-MODE does not need to be a real mode.
NAME, CONDITION or GROUP may be omitted.
Optional DIRECTORY is recorded in the `yas/snippet-table' if it
is created for the first time. Then, it becomes the default
directory to find snippet files.
Optional PARENT-MODE can be used to specify the parent modes of
MODE. It can be a mode symbol of a list of mode symbols. It does
not need to be a real mode.
"
(let ((snippet-table (yas/snippet-table-get-create mode directory))
(parent-table (if parent-mode
(yas/snippet-table-get-create parent-mode)
nil))
That is, when looking a snippet in MODE failed, it can refer to
its parent modes."
(let ((snippet-table (yas/snippet-table-get-create mode))
(parent-tables (mapcar #'yas/snippet-table-get-create
(if (listp parent-mode)
parent-mode
(list parent-mode))))
(keymap (if yas/use-menu
(yas/menu-keymap-for-mode mode)
(yas/menu-keymap-get-create mode)
nil)))
(when parent-table
(setf (yas/snippet-table-parent snippet-table)
parent-table)
(when parent-tables
(setf (yas/snippet-table-parents snippet-table)
parent-tables)
(when yas/use-menu
(define-key keymap (vector 'parent-mode)
`(menu-item "parent mode"
,(yas/menu-keymap-for-mode parent-mode)))))
,(yas/menu-keymap-get-create parent-mode)))))
(when (and yas/use-menu
(yas/real-mode? mode))
(define-key yas/minor-mode-menu (vector mode)
@ -1213,23 +1246,13 @@ Skip any submenus named \"parent mode\""
(setf (nthcdr pos-in-keymap keymap)
(nthcdr (+ 1 pos-in-keymap) keymap))))))
(defun yas/set-mode-parent (mode parent)
"Set parent mode of MODE to PARENT."
(setf (yas/snippet-table-parent
(yas/snippet-table-get-create mode))
(yas/snippet-table-get-create parent))
(when yas/use-menu
(define-key (yas/menu-keymap-for-mode mode) (vector 'parent-mode)
`(menu-item "parent mode"
,(yas/menu-keymap-for-mode parent)))))
(defun yas/define (mode key template &optional name condition group)
"Define a snippet. Expanding KEY into TEMPLATE.
NAME is a description to this template. Also update
the menu if `yas/use-menu' is `t'. CONDITION is the
condition attached to this snippet. If you attach a
condition to a snippet, then it will only be expanded
when the condition evaluated to non-nil."
NAME is a description to this template. Also update the menu if
`yas/use-menu' is `t'. CONDITION is the condition attached to
this snippet. If you attach a condition to a snippet, then it
will only be expanded when the condition evaluated to non-nil."
(yas/define-snippets mode
(list (list key template name condition group))))
@ -1280,6 +1303,23 @@ conditions to filter out potential expansions."
(setq this-command command)
(call-interactively command)))))))
(defun yas/all-templates (tables)
"Return all snippet tables applicable for the current buffer.
Honours `yas/choose-tables-first', `yas/choose-keys-first' and
`yas/buffer-local-condition'"
(when yas/choose-tables-first
(setq tables (list (yas/prompt-for-table tables))))
(mapcar #'cdr
(if yas/choose-keys-first
(let ((key (yas/prompt-for-keys
(mapcan #'yas/snippet-table-all-keys tables))))
(when key
(mapcan #'(lambda (table)
(yas/snippet-table-fetch table key))
tables)))
(mapcan #'yas/snippet-table-templates tables))))
(defun yas/insert-snippet (&optional no-condition)
"Choose a snippet to expand, pop-up a list of choices according
to `yas/prompt-function'.
@ -1290,12 +1330,7 @@ by condition."
(let* ((yas/buffer-local-condition (or (and no-condition
'always)
yas/buffer-local-condition))
(templates (mapcar #'cdr
(if yas/choose-keys-first
(let ((key (yas/prompt-for-keys (yas/snippet-table-all-keys (yas/current-snippet-table)))))
(when key
(yas/snippet-table-fetch (yas/current-snippet-table) key)))
(yas/snippet-table-all-templates (yas/current-snippet-table)))))
(templates (yas/all-templates (yas/get-snippet-tables)))
(template (and templates
(or (and (rest templates) ;; more than one template for same key
(yas/prompt-for-template templates))
@ -1317,13 +1352,7 @@ Only success if selected snippet was loaded from a file. Put the
visited file in `snippet-mode'."
(interactive)
(let* ((yas/buffer-local-condition 'always)
(templates (mapcar #'cdr
(if yas/choose-keys-first
(let ((key (yas/prompt-for-keys (yas/snippet-table-all-keys (yas/current-snippet-table))
"Choose a snippet key to edit: ")))
(when key
(yas/snippet-table-fetch (yas/current-snippet-table) key)))
(yas/snippet-table-all-templates (yas/current-snippet-table)))))
(templates (yas/all-templates (yas/get-snippet-tables)))
(template (and templates
(or (and (rest templates) ;; more than one template for same key
(yas/prompt-for-template templates
@ -1341,55 +1370,70 @@ visited file in `snippet-mode'."
(message "This snippet was not loaded from a file!")))))))
(defun yas/guess-snippet-directory ()
"Try to guess the suitable yassnippet based on `major-mode'"
(let ((loaded-root (or (and (listp yas/root-directory)
(first yas/root-directory))
yas/root-directory))
"Try to guess suitable directories based on `major-mode' and
also the current active tables."
(let ((main-dir (or (and (listp yas/root-directory)
(first yas/root-directory))
yas/root-directory
"~/.emacs.d/snippets"))
(mode major-mode)
(path))
(when loaded-root
(while mode
(setq path (format "%s/%s"
mode
(or path
"")))
(setq mode (get mode 'derived-mode-parent)))
(concat loaded-root
(unless (string-match "/$" loaded-root) "/")
path))))
(options))
;; Lookup main mode and add that to the options
;;
(push (format "%s/%s" main-dir mode) options)
;; Next lookup the main active table
;;
(let ((active-tables (first (yas/get-snippet-tables)))
(other-path-alternative main-dir))
(when active-tables
(setq active-tables (cons active-tables
(yas/snippet-table-get-all-parents active-tables))))
(dolist (table (reverse active-tables))
(setq other-path-alternative
(concat other-path-alternative "/" (yas/snippet-table-name table))))
(push other-path-alternative options))
;; Finally add to the options the guessed parent of major-mode
;; (this is almost never works out)
(when (get mode 'derived-mode-parent)
(push (format "%s/%s" main-dir (get mode 'derived-mode-parent)) options))
(reverse options)))
(defun yas/find-snippets (&optional same-window file)
"Look for user snippets in guessed current mode's directory.
(defun yas/find-snippets (&optional same-window)
"Looks for snippets file in the current mode's directory.
Calls `find-file' interactively in the guessed directory.
This can be used to create new snippets for the currently active
major mode."
With prefix arg SAME-WINDOW opens the buffer in the same window.
With optional FILE, finds the file directly, i.e. `find-file' is
called non-interactively.
Because snippets can be loaded from many different locations,
this has to guess the correct directory using
`yas/guess-directory', 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-directory'."
(interactive "P")
(let* ((current-table (yas/current-snippet-table major-mode 'dont-search-parents))
(parents-table (yas/current-snippet-table major-mode))
(parents-directory (and parents-table
(yas/snippet-table-default-directory parents-table)))
(guessed-directory (or (and current-table
(yas/snippet-table-default-directory current-table))
(yas/guess-snippet-directory)
default-directory))
(let* ((guessed-directories (yas/guess-snippet-directory))
(target-directory (first (remove-if-not #'file-exists-p guessed-directories)))
(buffer))
(unless (file-exists-p guessed-directory)
(if (y-or-n-p (format "Guessed directory (%s) does not exist! Create? " guessed-directory))
(make-directory guessed-directory 'also-make-parents)
(if parents-directory
(setq guessed-directory parents-directory)
(setq guessed-directory default-directory))))
(let ((default-directory guessed-directory))
(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)))))))
(unless target-directory
(when (y-or-n-p (format "Guessed directory (%s) does not exist! Create? " (first guessed-directories)))
(setq target-directory (first guessed-directories))
(make-directory target-directory 'also-make-parents)))
(when target-directory
(let ((default-directory target-directory))
(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))))))))
(defun yas/compute-major-mode-and-parent (file &optional prompt-if-failed)
(let* ((file-dir (and file
@ -1515,6 +1559,11 @@ Otherwise throw exception."
(when field
(yas/field-text-for-display field))))
(defun yas/oni (text oni-regexp)
"Runs ruby to parse TEXT with Oniguruma regexp ONI-REGEXP."
(shell-command-to-string (format "ruby -e 'print \"%s\".gsub(\"a\",\"b\")'" "aha")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Snippet expansion and field management
@ -2248,7 +2297,8 @@ has to be called before the $-constructs are deleted."
(setq soup
(sort soup
#'yas/compare-fom-begs))
(reduce #'yas/link-foms soup))))
(when soup
(reduce #'yas/link-foms soup)))))
(defun yas/advance-end-maybe (fom newend)
"Maybe advance FOM's end to NEWEND if it needs it.
@ -2263,8 +2313,8 @@ If it does, also:
(set-marker (yas/fom-end fom) newend)
(yas/advance-start-maybe (yas/fom-next fom) newend)
(if (and (yas/field-p fom)
(yas/field-parent-field field))
(yas/advance-end-maybe (yas/field-parent-field field) newend))))
(yas/field-parent-field fom))
(yas/advance-end-maybe (yas/field-parent-field fom) newend))))
(defun yas/advance-start-maybe (fom newstart)
"Maybe advance FOM's start to NEWSTART if it needs it.