mirror of
https://github.com/joaotavora/yasnippet.git
synced 2025-10-13 21:13:04 +00:00
sophisticated condition system for yasnippet
This commit is contained in:
parent
25ed4cdaa6
commit
b2c59c0722
145
yasnippet.el
145
yasnippet.el
@ -117,6 +117,30 @@ The hooks will be run in an environment where some variables bound to
|
|||||||
proper values:
|
proper values:
|
||||||
* yas/snippet-beg : The beginning of the region of the snippet.
|
* yas/snippet-beg : The beginning of the region of the snippet.
|
||||||
* yas/snippet-end : Similar to beg.")
|
* yas/snippet-end : Similar to beg.")
|
||||||
|
|
||||||
|
(defvar yas/before-expand-snippet-hook
|
||||||
|
'()
|
||||||
|
"Hooks to run after a before expanding a snippet.
|
||||||
|
If you move the cursor (e.g. call `re-search-forward') in this hook,
|
||||||
|
please wrap it with `save-excursion', or else yanippet will get confused.")
|
||||||
|
|
||||||
|
(defvar yas/buffer-local-condition t
|
||||||
|
"Condition to yasnippet local to each buffer.
|
||||||
|
If this eval to nil, no snippet can be expanded.
|
||||||
|
If this eval to 'require-snippet-condition, then a snippet can be expanded
|
||||||
|
if and only if it has a condition attached and that condition eval to non-nil.
|
||||||
|
Otherwise, if a snippet has no condition or its conditin eval to non-nil, it
|
||||||
|
will be expanded.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
|
||||||
|
(add-hook 'python-mode-hook
|
||||||
|
'(lambda ()
|
||||||
|
(setq yas/buffer-local-condition
|
||||||
|
'(if (python-in-string/comment)
|
||||||
|
'require-snippet-condition
|
||||||
|
t))))")
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Internal variables
|
;; Internal variables
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@ -201,10 +225,12 @@ You can customize the key through `yas/trigger-key'."
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Internal Structs
|
;; Internal Structs
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(defstruct (yas/template (:constructor yas/make-template (content name)))
|
(defstruct (yas/template (:constructor yas/make-template
|
||||||
|
(content name condition)))
|
||||||
"A template for a snippet."
|
"A template for a snippet."
|
||||||
content
|
content
|
||||||
name)
|
name
|
||||||
|
condition)
|
||||||
(defstruct (yas/snippet (:constructor yas/make-snippet ()))
|
(defstruct (yas/snippet (:constructor yas/make-snippet ()))
|
||||||
"A snippet."
|
"A snippet."
|
||||||
(groups nil)
|
(groups nil)
|
||||||
@ -278,10 +304,37 @@ have, compare through the start point of the overlay."
|
|||||||
(< (overlay-start (yas/field-overlay field1))
|
(< (overlay-start (yas/field-overlay field1))
|
||||||
(overlay-start (yas/field-overlay field2)))))))
|
(overlay-start (yas/field-overlay field2)))))))
|
||||||
|
|
||||||
|
(defun yas/template-condition-predicate (condition)
|
||||||
|
(condition-case err
|
||||||
|
(save-excursion
|
||||||
|
(save-restriction
|
||||||
|
(save-match-data
|
||||||
|
(eval condition))))
|
||||||
|
(error (progn
|
||||||
|
(message (format "[yas]error in condition evaluation: %s"
|
||||||
|
(error-message-string err)))
|
||||||
|
nil))))
|
||||||
|
|
||||||
|
(defun yas/filter-templates-by-condition (templates)
|
||||||
|
"Filter the templates using the condition. The rules are:
|
||||||
|
|
||||||
|
* If the template has no condition, it is kept.
|
||||||
|
* If the template's condition eval to non-nil, it is kept.
|
||||||
|
* Otherwise (eval error or eval to nil) it is filtered."
|
||||||
|
(remove-if-not '(lambda (pair)
|
||||||
|
(let ((condition (yas/template-condition (cdr pair))))
|
||||||
|
(if (null condition)
|
||||||
|
(if yas/require-template-condition
|
||||||
|
nil
|
||||||
|
t)
|
||||||
|
(yas/template-condition-predicate condition))))
|
||||||
|
templates))
|
||||||
|
|
||||||
(defun yas/snippet-table-fetch (table key)
|
(defun yas/snippet-table-fetch (table key)
|
||||||
"Fetch a snippet binding to KEY from TABLE. If not found,
|
"Fetch a snippet binding to KEY from TABLE. If not found,
|
||||||
fetch from parent if any."
|
fetch from parent if any."
|
||||||
(let ((templates (gethash key (yas/snippet-table-hash table))))
|
(let ((templates (yas/filter-templates-by-condition
|
||||||
|
(gethash key (yas/snippet-table-hash table)))))
|
||||||
(when (and (null templates)
|
(when (and (null templates)
|
||||||
(not (null (yas/snippet-table-parent table))))
|
(not (null (yas/snippet-table-parent table))))
|
||||||
(setq templates (yas/snippet-table-fetch
|
(setq templates (yas/snippet-table-fetch
|
||||||
@ -364,11 +417,12 @@ the template of a snippet in the current snippet-table."
|
|||||||
(setq syntaxes (cdr syntaxes))
|
(setq syntaxes (cdr syntaxes))
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(skip-syntax-backward syntax)
|
(skip-syntax-backward syntax)
|
||||||
(when (yas/snippet-table-fetch
|
(setq start (point)))
|
||||||
(yas/current-snippet-table)
|
(if (yas/snippet-table-fetch
|
||||||
(buffer-substring-no-properties (point) end))
|
(yas/current-snippet-table)
|
||||||
|
(buffer-substring-no-properties start end))
|
||||||
(setq done t)
|
(setq done t)
|
||||||
(setq start (point)))))
|
(setq start end)))
|
||||||
(list (buffer-substring-no-properties start end)
|
(list (buffer-substring-no-properties start end)
|
||||||
start
|
start
|
||||||
end)))
|
end)))
|
||||||
@ -686,14 +740,17 @@ line through the syntax:
|
|||||||
|
|
||||||
#name : value
|
#name : value
|
||||||
|
|
||||||
Currently only the \"name\" variable is recognized. Here's
|
Here's a list of currently recognized variables:
|
||||||
an example:
|
|
||||||
|
* name
|
||||||
|
* contributor
|
||||||
|
* condition
|
||||||
|
|
||||||
#name: #include \"...\"
|
#name: #include \"...\"
|
||||||
# --
|
# --
|
||||||
#include \"$1\""
|
#include \"$1\""
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(let (template name bound)
|
(let (template name bound condition)
|
||||||
(if (re-search-forward "^# --\n" nil t)
|
(if (re-search-forward "^# --\n" nil t)
|
||||||
(progn (setq template
|
(progn (setq template
|
||||||
(buffer-substring-no-properties (point)
|
(buffer-substring-no-properties (point)
|
||||||
@ -702,10 +759,12 @@ an example:
|
|||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(while (re-search-forward "^#\\([^ ]+\\) *: *\\(.*\\)$" bound t)
|
(while (re-search-forward "^#\\([^ ]+\\) *: *\\(.*\\)$" bound t)
|
||||||
(when (string= "name" (match-string-no-properties 1))
|
(when (string= "name" (match-string-no-properties 1))
|
||||||
(setq name (match-string-no-properties 2)))))
|
(setq name (match-string-no-properties 2)))
|
||||||
|
(when (string= "condition" (match-string-no-properties 1))
|
||||||
|
(setq condition (read (match-string-no-properties 2))))))
|
||||||
(setq template
|
(setq template
|
||||||
(buffer-substring-no-properties (point-min) (point-max))))
|
(buffer-substring-no-properties (point-min) (point-max))))
|
||||||
(list template name)))
|
(list template name condition)))
|
||||||
|
|
||||||
(defun yas/directory-files (directory file?)
|
(defun yas/directory-files (directory file?)
|
||||||
"Return directory files or subdirectories in full path."
|
"Return directory files or subdirectories in full path."
|
||||||
@ -847,6 +906,9 @@ is the output file of the compile result. Here's an example:
|
|||||||
(if (caddr snippet)
|
(if (caddr snippet)
|
||||||
(yas/quote-string (caddr snippet))
|
(yas/quote-string (caddr snippet))
|
||||||
"nil")
|
"nil")
|
||||||
|
(if (nth 3 snippet)
|
||||||
|
(format "'%s" (nth 3 snippet))
|
||||||
|
"nil")
|
||||||
")\n"))
|
")\n"))
|
||||||
(insert " )\n")
|
(insert " )\n")
|
||||||
(insert (if parent
|
(insert (if parent
|
||||||
@ -911,12 +973,13 @@ content of the file is the template."
|
|||||||
(defun yas/define-snippets (mode snippets &optional parent-mode)
|
(defun yas/define-snippets (mode snippets &optional parent-mode)
|
||||||
"Define snippets for MODE. SNIPPETS is a list of
|
"Define snippets for MODE. SNIPPETS is a list of
|
||||||
snippet definition, of the following form:
|
snippet definition, of the following form:
|
||||||
(KEY TEMPLATE NAME)
|
|
||||||
or the NAME may be omitted. The optional 3rd parameter
|
(KEY TEMPLATE NAME CONDITION)
|
||||||
can be used to specify the parent mode of MODE. That is,
|
|
||||||
when looking a snippet in MODE failed, it can refer to
|
or the NAME and CONDITION may be omitted. The optional 3rd
|
||||||
its parent mode. The PARENT-MODE may not need to be a
|
parameter can be used to specify the parent mode of MODE. That
|
||||||
real mode."
|
is, when looking a snippet in MODE failed, it can refer to its
|
||||||
|
parent mode. The PARENT-MODE may not need to be a real mode."
|
||||||
(let ((snippet-table (yas/snippet-table mode))
|
(let ((snippet-table (yas/snippet-table mode))
|
||||||
(parent-table (if parent-mode
|
(parent-table (if parent-mode
|
||||||
(yas/snippet-table parent-mode)
|
(yas/snippet-table parent-mode)
|
||||||
@ -939,8 +1002,10 @@ real mode."
|
|||||||
(let* ((full-key (car snippet))
|
(let* ((full-key (car snippet))
|
||||||
(key (file-name-sans-extension full-key))
|
(key (file-name-sans-extension full-key))
|
||||||
(name (caddr snippet))
|
(name (caddr snippet))
|
||||||
|
(condition (nth 3 snippet))
|
||||||
(template (yas/make-template (cadr snippet)
|
(template (yas/make-template (cadr snippet)
|
||||||
(or name key))))
|
(or name key)
|
||||||
|
condition)))
|
||||||
(yas/snippet-table-store snippet-table
|
(yas/snippet-table-store snippet-table
|
||||||
full-key
|
full-key
|
||||||
key
|
key
|
||||||
@ -961,30 +1026,40 @@ real mode."
|
|||||||
`(menu-item "parent mode"
|
`(menu-item "parent mode"
|
||||||
,(yas/menu-keymap-for-mode parent)))))
|
,(yas/menu-keymap-for-mode parent)))))
|
||||||
|
|
||||||
(defun yas/define (mode key template &optional name)
|
(defun yas/define (mode key template &optional name condition)
|
||||||
"Define a snippet. Expanding KEY into TEMPLATE.
|
"Define a snippet. Expanding KEY into TEMPLATE.
|
||||||
NAME is a description to this template. Also update
|
NAME is a description to this template. Also update
|
||||||
the menu if `yas/use-menu' is `t'."
|
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
|
(yas/define-snippets mode
|
||||||
(list (list key template name))))
|
(list (list key template name condition))))
|
||||||
|
|
||||||
|
|
||||||
(defun yas/expand ()
|
(defun yas/expand ()
|
||||||
"Expand a snippet."
|
"Expand a snippet."
|
||||||
(interactive)
|
(interactive)
|
||||||
(multiple-value-bind (key start end) (yas/current-key)
|
(let ((local-condition (yas/template-condition-predicate
|
||||||
(let ((templates (yas/snippet-table-fetch (yas/current-snippet-table)
|
yas/buffer-local-condition)))
|
||||||
key)))
|
(if local-condition
|
||||||
(if templates
|
(let ((yas/require-template-condition (if (eq local-condition
|
||||||
(let ((template (if (null (cdr templates)) ; only 1 template
|
'require-snippet-condition)
|
||||||
(yas/template-content (cdar templates))
|
t
|
||||||
(yas/popup-for-template templates))))
|
nil)))
|
||||||
(when template
|
(multiple-value-bind (key start end) (yas/current-key)
|
||||||
(yas/expand-snippet start end template)))
|
(let ((templates (yas/snippet-table-fetch (yas/current-snippet-table)
|
||||||
(let* ((yas/minor-mode nil)
|
key)))
|
||||||
(command (key-binding yas/trigger-key)))
|
(if templates
|
||||||
(when (commandp command)
|
(let ((template (if (null (cdr templates)) ; only 1 template
|
||||||
(call-interactively command)))))))
|
(yas/template-content (cdar templates))
|
||||||
|
(yas/popup-for-template templates))))
|
||||||
|
(when template
|
||||||
|
(yas/expand-snippet start end template)))
|
||||||
|
(let* ((yas/minor-mode nil)
|
||||||
|
(command (key-binding yas/trigger-key)))
|
||||||
|
(when (commandp command)
|
||||||
|
(call-interactively command))))))))))
|
||||||
|
|
||||||
(defun yas/next-field-group ()
|
(defun yas/next-field-group ()
|
||||||
"Navigate to next field group. If there's none, exit the snippet."
|
"Navigate to next field group. If there's none, exit the snippet."
|
||||||
|
Loading…
x
Reference in New Issue
Block a user