;;; yasnippet.el --- Yet another snippet extension for Emacs.
+;; Copyright 2008 pluskid
+;;
;; Author: pluskid <pluskid@gmail.com>
-;; Version: 0.1
+;; Version: 0.2.1
+;; X-URL: http://code.google.com/p/yasnippet/
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;;; Commentary:
-;; Nothing.
+;; Basic steps to setup:
+;; 1. Place `yasnippet.el' in your `load-path'.
+;; 2. In your .emacs file:
+;; (require 'yasnippet)
+;; 3. Place the `snippets' directory somewhere. E.g: ~/.emacs.d/snippets
+;; 4. In your .emacs file
+;; (yas/initialize)
+;; (yas/load-directory "~/.emacs.d/snippets")
+;;
+;; For more information and detailed usage, refer to the project page:
+;; http://code.google.com/p/yasnippet/
(require 'cl)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; User customizable variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(defvar yas/key-syntax "w"
- "Syntax of a key. This is used to determine the current key being
-expanded.")
+(defvar yas/key-syntaxes (list "w" "w_" "w_." "^ ")
+ "A list of syntax of a key. This list is tried in the order
+to try to find a key. For example, if the list is '(\"w\" \"w_\").
+And in emacs-lisp-mode, where \"-\" has the syntax of \"_\":
+
+foo-bar
+
+will first try \"bar\", if not found, then \"foo-bar\" is tried.")
+
+(defvar yas/root-directory nil
+ "The root directory that stores the snippets for each major modes.")
(defvar yas/indent-line t
"Each (except the 1st) line of the snippet template is indented to
current column if this variable is non-`nil'.")
(make-variable-buffer-local 'yas/indent-line)
+(defvar yas/trigger-key (kbd "<tab>")
+ "The key to bind as a trigger of snippet.")
+(defvar yas/trigger-fallback 'yas/default-trigger-fallback
+ "The fallback command to call when there's no snippet to expand.")
+(make-variable-buffer-local 'yas/trigger-fallback)
+
(defvar yas/keymap (make-sparse-keymap)
"The keymap of snippet.")
+(define-key yas/keymap (kbd "<tab>") 'yas/next-field-group)
(define-key yas/keymap (kbd "TAB") 'yas/next-field-group)
(define-key yas/keymap (kbd "S-TAB") 'yas/prev-field-group)
(define-key yas/keymap (kbd "<S-iso-lefttab>") 'yas/prev-field-group)
(define-key yas/keymap (kbd "<S-tab>") 'yas/prev-field-group)
+(defvar yas/show-all-modes-in-menu nil
+ "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.")
+(defvar yas/use-menu t
+ "If this is set to `t', all snippet template of the current
+mode will be listed under the menu \"yasnippet\".")
+(defvar yas/trigger-symbol " =>"
+ "The text that will be used in menu to represent the trigger.")
+
+(defface yas/field-highlight-face
+ '((((class color) (background light)) (:background "DarkSeaGreen2"))
+ (t (:background "DimGrey")))
+ "The face used to highlight a field of snippet.")
+(defface yas/mirror-highlight-face
+ '((((class color) (background light)) (:background "LightYellow2"))
+ (t (:background "gray22")))
+ "The face used to highlight mirror fields of a snippet.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar yas/version "0.2.1")
+
(defvar yas/snippet-tables (make-hash-table)
"A hash table of snippet tables corresponding to each major-mode.")
-
+(defvar yas/menu-table (make-hash-table)
+ "A hash table of menus of corresponding major-mode.")
+(defvar yas/menu-keymap (make-sparse-keymap "YASnippet"))
+;; empty menu will cause problems, so we insert some items
+(define-key yas/menu-keymap [yas/about]
+ '(menu-item "About" yas/about))
+(define-key yas/menu-keymap [yas/reload]
+ '(menu-item "Reload all snippets" yas/reload-all))
+(define-key yas/menu-keymap [yas/separator]
+ '(menu-item "--"))
+
+(defvar yas/known-modes
+ '(ruby-mode rst-mode)
+ "A list of mode which is well known but not part of emacs.")
(defconst yas/escape-backslash
(concat "YASESCAPE" "BACKSLASH" "PROTECTGUARD"))
(defconst yas/escape-dollar
(concat "YASESCAPE" "BACKQUOTE" "PROTECTGUARD"))
(defconst yas/field-regexp
- (concat "$\\(?1:[0-9]+\\)" "\\|"
- "${\\(?:\\(?1:[0-9]+\\):\\)?\\(?2:[^}]*\\)}"))
+ (concat "$\\([0-9]+\\)" "\\|"
+ "${\\(?:\\([0-9]+\\):\\)?\\([^}]*\\)}"))
(defvar yas/snippet-id-seed 0
"Contains the next id for a snippet")
(defvar yas/overlay-insert-in-front-hooks
(list 'yas/overlay-insert-in-front-hook)
"The list of hooks of the overlay inserted in front event.")
-(defvar yas/overlay-insert-behind-hooks
- (list 'yas/overlay-insert-behind-hook)
- "The list of hooks of the overlay inserted behind event.")
+(defvar yas/keymap-overlay-modification-hooks
+ (list 'yas/overlay-maybe-insert-behind-hook)
+ "The list of hooks of the big keymap overlay modification event.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(next nil)
(prev nil)
snippet)
-(defstruct (yas/field (:constructor yas/make-field (overlay number value)))
+(defstruct (yas/field
+ (:constructor yas/make-field (overlay number value transform)))
"A field in a snippet."
overlay
number
+ transform
value)
+(defstruct (yas/snippet-table (:constructor yas/make-snippet-table ()))
+ "A table to store snippets for a perticular mode."
+ (hash (make-hash-table :test 'equal))
+ (parent nil))
(defun yas/snippet-add-field (snippet field)
"Add FIELD to SNIPPET."
(yas/snippet-groups snippet)
:test
'(lambda (field group)
- (= (yas/field-number field)
- (yas/group-number group))))))
+ (and (not (null (yas/field-number field)))
+ (not (null (yas/group-number group)))
+ (= (yas/field-number field)
+ (yas/group-number group)))))))
(if group
(yas/group-add-field group field)
(push (yas/make-group field snippet)
(< (overlay-start (yas/field-overlay field1))
(overlay-start (yas/field-overlay field2)))))))
+(defun yas/snippet-table-fetch (table key)
+ "Fetch a snippet binding to KEY from TABLE. If not found,
+fetch from parent if any."
+ (let ((templates (gethash key (yas/snippet-table-hash table))))
+ (when (and (null templates)
+ (not (null (yas/snippet-table-parent table))))
+ (setq templates (yas/snippet-table-fetch
+ (yas/snippet-table-parent table)
+ key)))
+ templates))
+(defun yas/snippet-table-store (table full-key key template)
+ "Store a snippet template in the table."
+ (puthash key
+ (yas/modify-alist (gethash key
+ (yas/snippet-table-hash table))
+ full-key
+ template)
+ (yas/snippet-table-hash table)))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun yas/real-mode? (mode)
+ "Try to find out if MODE is a real mode. The MODE bound to
+a function (like `c-mode') is considered real mode. Other well
+known mode like `ruby-mode' which is not part of Emacs might
+not bound to a function until it is loaded. So yasnippet keeps
+a list of modes like this to help the judgement."
+ (or (fboundp mode)
+ (find mode yas/known-modes)))
+
(defun yas/eval-string (string)
"Evaluate STRING and convert the result to string."
(condition-case err
(format "%s" (eval (read string)))
(error (format "(error in elisp evaluation: %s)"
(error-message-string err)))))
+(defun yas/calculate-field-value (field value)
+ "Calculate the value of the field. If there's a transform
+for this field, apply it. Otherwise, the value is returned
+unmodified."
+ (let ((text value)
+ (transform (yas/field-transform field)))
+ (if transform
+ (yas/eval-string transform)
+ text)))
(defsubst yas/replace-all (from to)
"Replace all occurance from FROM to TO."
(goto-char (point-min))
(while (search-forward from nil t)
(replace-match to t t)))
+
(defun yas/snippet-table (mode)
"Get the snippet table corresponding to MODE."
(let ((table (gethash mode yas/snippet-tables)))
(unless table
- (setq table (make-hash-table :test 'equal))
+ (setq table (yas/make-snippet-table))
(puthash mode table yas/snippet-tables))
table))
(defsubst yas/current-snippet-table ()
"Get the snippet table for current major-mode."
(yas/snippet-table major-mode))
-(defsubst yas/template (key snippet-table)
- "Get template for KEY in SNIPPET-TABLE."
- (gethash key snippet-table))
+(defun yas/menu-keymap-for-mode (mode)
+ "Get the menu keymap correspondong to MODE."
+ (let ((keymap (gethash mode yas/menu-table)))
+ (unless keymap
+ (setq keymap (make-sparse-keymap))
+ (puthash mode keymap yas/menu-table))
+ keymap))
(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)))
- (save-excursion
- (skip-syntax-backward yas/key-syntax)
- (setq start (point))
- (list (buffer-substring-no-properties start end)
- start
- end))))
+ (end (point))
+ (syntaxes yas/key-syntaxes)
+ syntax done)
+ (while (and (not done) syntaxes)
+ (setq syntax (car syntaxes))
+ (setq syntaxes (cdr syntaxes))
+ (save-excursion
+ (skip-syntax-backward syntax)
+ (when (yas/snippet-table-fetch
+ (yas/current-snippet-table)
+ (buffer-substring-no-properties (point) end))
+ (setq done t)
+ (setq start (point)))))
+ (list (buffer-substring-no-properties start end)
+ start
+ end)))
(defun yas/synchronize-fields (field-group)
"Update all fields' text according to the primary field."
(overlay-start field-overlay))))
(unless (eq field-overlay primary-overlay)
(goto-char (overlay-start field-overlay))
- (insert text)
+ (insert (yas/calculate-field-value field text))
(if (= (overlay-start field-overlay)
(overlay-end field-overlay))
(move-overlay field-overlay
(goto-char end)
(delete-char (- (overlay-end overlay) end)))))
(yas/synchronize-fields field-group))))
-(defun yas/overlay-insert-behind-hook (overlay after? beg end &optional length)
- "Hook for snippet overlay when text is inserted just behind a snippet field."
+(defun yas/overlay-maybe-insert-behind-hook (overlay after? beg end &optional length)
+ "Insert behind hook sometimes doesn't get called. I don't know why.
+So I add modification hook in the big overlay and try to detect `insert-behind'
+event manually."
(when (and after?
- (null (yas/current-snippet-overlay beg))) ; not inside another field
- (move-overlay overlay
- (overlay-start overlay)
- end)
- (yas/synchronize-fields (overlay-get overlay 'yas/group))))
+ (= length 0)
+ (> end beg)
+ (null (yas/current-snippet-overlay beg))
+ (not (bobp)))
+ (let ((field-overlay (yas/current-snippet-overlay (1- beg))))
+ (if field-overlay
+ (when (= beg (overlay-end field-overlay))
+ (move-overlay field-overlay
+ (overlay-start field-overlay)
+ end)
+ (yas/synchronize-fields (overlay-get field-overlay 'yas/group)))
+ (let ((snippet (yas/snippet-of-current-keymap))
+ (done nil))
+ (if snippet
+ (do* ((tabstops (yas/snippet-tabstops snippet) (cdr tabstops))
+ (tabstop (car tabstops) (car tabstops)))
+ ((or (null tabstops)
+ done))
+ (setq field-overlay (yas/field-overlay
+ (yas/group-primary-field tabstop)))
+ (when (= beg
+ (overlay-start field-overlay))
+ (move-overlay field-overlay beg end)
+ (yas/synchronize-fields tabstop)
+ (setq done t)))))))))
(defun yas/undo-expand-snippet (start end key snippet)
"Undo a snippet expansion. Delete the overlays. This undo can't be
;; Step 5: Create fields
(goto-char (point-min))
(while (re-search-forward yas/field-regexp nil t)
- (let ((number (match-string-no-properties 1)))
+ (let ((number (or (match-string-no-properties 1)
+ (match-string-no-properties 2)))
+ (transform nil)
+ (value (match-string-no-properties 3)))
+ (when (eq (elt value 0) ?\$)
+ (setq transform (substring value 1))
+ (setq value nil))
(if (and number
(string= "0" number))
(progn
(yas/make-field
(make-overlay (match-beginning 0) (match-end 0))
(and number (string-to-number number))
- (match-string-no-properties 2))))))
+ value
+ transform)))))
;; Step 6: Sort and link each field group
(setf (yas/snippet-groups snippet)
nil
nil
t)))
+ (overlay-put overlay
+ 'modification-hooks
+ yas/keymap-overlay-modification-hooks)
+ (overlay-put overlay
+ 'insert-behind-hooks
+ yas/keymap-overlay-modification-hooks)
(overlay-put overlay 'keymap yas/keymap)
(overlay-put overlay 'yas/snippet-reference snippet)
(setf (yas/snippet-overlay snippet) overlay))
(end (overlay-end overlay))
(length (- end start)))
(goto-char start)
- (insert value)
+ (insert (yas/calculate-field-value field value))
(delete-char length)))))
;; Step 9: restore all escape characters
(overlay-put overlay 'yas/modified? nil)
(overlay-put overlay 'modification-hooks yas/overlay-modification-hooks)
(overlay-put overlay 'insert-in-front-hooks yas/overlay-insert-in-front-hooks)
- (overlay-put overlay 'insert-behind-hooks yas/overlay-insert-behind-hooks)
+ (overlay-put overlay 'face 'yas/field-highlight-face)
(dolist (field (yas/group-fields group))
- (overlay-put (yas/field-overlay field)
- 'face
- 'highlight))))
+ (unless (equal overlay (yas/field-overlay field))
+ (overlay-put (yas/field-overlay field)
+ 'face
+ 'yas/mirror-highlight-face)))))
;; Step 11: move to end and make sure exit-marker exist
(goto-char (point-max))
(widen)
(delete-char length)
+ (setq buffer-undo-list original-undo-list)
+
;; Step 14: place the cursor at a proper place
(let ((groups (yas/snippet-groups snippet))
(exit-marker (yas/snippet-exit-marker snippet)))
(yas/group-primary-field
(car groups)))))
;; no need to call exit-snippet, since no overlay created.
- (goto-char exit-marker)))
-
- (setq buffer-undo-list original-undo-list)))))
+ (yas/exit-snippet snippet)))))))
(defun yas/current-snippet-overlay (&optional point)
"Get the most proper overlay which is belongs to a snippet."
(defun yas/directory-files (directory file?)
"Return directory files or subdirectories in full path."
- (filter (lambda (file)
- (and (not (string-match "/\\.\\.?$" file))
- (if file?
- (not (file-directory-p file))
- (file-directory-p file))))
- (directory-files directory t)))
+ (remove-if (lambda (file)
+ (or (string-match "^\\."
+ (file-name-nondirectory file))
+ (if file?
+ (file-directory-p file)
+ (not (file-directory-p file)))))
+ (directory-files directory t)))
+
+(defun yas/make-menu-binding (template)
+ (lexical-let ((template template))
+ (lambda ()
+ (interactive)
+ (yas/expand-snippet (point)
+ (point)
+ template))))
+
+(defun yas/modify-alist (alist key value)
+ "Modify ALIST to map KEY to VALUE. return the new alist."
+ (let ((pair (assoc key alist)))
+ (if (null pair)
+ (cons (cons key value)
+ alist)
+ (setcdr pair value)
+ alist)))
+
+(defun yas/fake-keymap-for-popup (templates)
+ "Create a fake keymap for popup menu usage."
+ (cons 'keymap
+ (mapcar (lambda (pair)
+ (let* ((template (cdr pair))
+ (name (yas/template-name template))
+ (content (yas/template-content template)))
+ (list content 'menu-item name t)))
+ templates)))
+
+(defun yas/point-to-coord (&optional point)
+ "Get the xoffset/yoffset information of POINT.
+If POINT is not given, default is to current point.
+If `posn-at-point' is not available (like in Emacs 21.3),
+t is returned simply."
+ (if (fboundp 'posn-at-point)
+ (let ((x-y (posn-x-y (posn-at-point (or point (point))))))
+ (list (list (+ (car x-y) 10)
+ (+ (cdr x-y) 20))
+ (selected-window)))
+ t))
+
+(defun yas/popup-for-template (templates)
+ "Show a popup menu listing templates to let the user select one."
+ (if window-system
+ (car (x-popup-menu (yas/point-to-coord)
+ (yas/fake-keymap-for-popup templates)))
+ ;; no window system, simply select the first one
+ (cdar templates)))
+
+(defun yas/load-directory-1 (directory &optional parent)
+ "Really do the job of loading snippets from a directory
+hierarchy."
+ (let ((mode-sym (intern (file-name-nondirectory directory)))
+ (snippets nil))
+ (with-temp-buffer
+ (dolist (file (yas/directory-files directory t))
+ (when (file-readable-p file)
+ (insert-file-contents file nil nil nil t)
+ (push (cons (file-name-nondirectory file)
+ (yas/parse-template))
+ snippets))))
+ (yas/define-snippets mode-sym
+ snippets
+ parent)
+ (dolist (subdir (yas/directory-files directory nil))
+ (yas/load-directory-1 subdir mode-sym))))
+
+(defun yas/quote-string (string)
+ "Escape and quote STRING.
+foo\"bar\\! -> \"foo\\\"bar\\\\!\""
+ (concat "\""
+ (replace-regexp-in-string "[\\\"]"
+ "\\\\\\&"
+ string
+ t)
+ "\""))
+
+(defun yas/compile-bundle (yasnippet yasnippet-bundle snippet-roots)
+ "Compile snippets in SNIPPET-ROOTS to a single bundle file.
+SNIPPET-ROOTS is a list of root directories that contains the snippets
+definition. YASNIPPET is the yasnippet.el file path. YASNIPPET-BUNDLE
+is the output file of the compile result. Here's an example:
+
+ (yas/compile-bundle \"~/.emacs.d/plugins/yasnippet/yasnippet.el\"
+ \"~/.emacs.d/plugins/yasnippet-bundle.el\"
+ '(\"~/.emacs.d/plugins/yasnippet/snippets\"))"
+ (let ((dirs (or (and (listp snippet-roots) snippet-roots)
+ (list snippet-roots)))
+ (bundle-buffer nil))
+ (with-temp-buffer
+ (setq bundle-buffer (current-buffer))
+ (insert-file-contents yasnippet)
+ (goto-char (point-max))
+ (insert ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n")
+ (insert ";;;; Auto-generated code ;;;;\n")
+ (insert ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n")
+ (insert "(yas/initialize)\n")
+ (flet ((yas/define-snippets
+ (mode snippets &optional parent)
+ (with-current-buffer bundle-buffer
+ (insert ";;; snippets for " (symbol-name mode) "\n")
+ (insert "(yas/define-snippets '" (symbol-name mode) "\n")
+ (insert "'(\n")
+ (dolist (snippet snippets)
+ (insert " ("
+ (yas/quote-string (car snippet))
+ (yas/quote-string (cadr snippet))
+ (if (caddr snippet)
+ (yas/quote-string (caddr snippet))
+ "nil")
+ ")\n"))
+ (insert " )\n")
+ (insert (if parent
+ (concat "'" (symbol-name parent))
+ "nil")
+ ")\n\n"))))
+ (dolist (dir dirs)
+ (dolist (subdir (yas/directory-files dir nil))
+ (yas/load-directory-1 subdir nil))))
+ (insert "(provide '"
+ (file-name-nondirectory
+ (file-name-sans-extension
+ yasnippet-bundle))
+ ")\n")
+ (setq buffer-file-name yasnippet-bundle)
+ (save-buffer))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; User level functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun yas/default-trigger-fallback ()
+ "Default fallback when a snippet expansion failed.
+It looks key binding for TAB. If found, execute it. If not found.
+Run `indent-for-tab-command'."
+ (interactive)
+ (let ((command (key-binding (kbd "TAB"))))
+ (if (and command
+ (not (eq command 'yas/expand))
+ (not (eq command 'yas/next-field-group)))
+ (call-interactively command)
+ (call-interactively 'indent-for-tab-command))))
+
+(defun yas/about ()
+ (interactive)
+ (message (concat "yasnippet (version "
+ yas/version
+ ") -- pluskid <pluskid@gmail.com>")))
+(defun yas/reload-all ()
+ "Reload all snippets."
+ (interactive)
+ (if yas/root-directory
+ (yas/load-directory-1 yas/root-directory)
+ (call-interactively 'yas/load-directory))
+ (message "done."))
+
+(defun yas/load-directory (directory)
+ "Load snippet definition from a directory hierarchy.
+Below the top-level directory, each directory is a mode
+name. And under each subdirectory, each file is a definition
+of a snippet. The file name is the trigger key and the
+content of the file is the template."
+ (interactive "DSelect the root directory: ")
+ (unless yas/root-directory
+ (setq yas/root-directory directory))
+ (dolist (dir (yas/directory-files directory nil))
+ (yas/load-directory-1 dir))
+ (when (interactive-p)
+ (message "done.")))
+
+(defun yas/initialize ()
+ "Do necessary initialization."
+ (global-set-key yas/trigger-key 'yas/expand)
+ (when yas/use-menu
+ (define-key-after
+ (lookup-key global-map [menu-bar])
+ [yasnippet]
+ (cons "YASnippet" yas/menu-keymap)
+ 'buffer)))
+
+(defun yas/define-snippets (mode snippets &optional parent-mode)
+ "Define snippets for MODE. SNIPPETS is a list of
+snippet definition, of the following form:
+ (KEY TEMPLATE NAME)
+or the NAME may be omitted. The optional 3rd parameter
+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 may not need to be a
+real mode."
+ (let ((snippet-table (yas/snippet-table mode))
+ (parent-table (if parent-mode
+ (yas/snippet-table parent-mode)
+ nil))
+ (keymap (if yas/use-menu
+ (yas/menu-keymap-for-mode mode)
+ nil)))
+ (when parent-table
+ (setf (yas/snippet-table-parent snippet-table)
+ parent-table)
+ (when yas/use-menu
+ (define-key keymap (vector 'parent-mode)
+ `(menu-item "parent mode"
+ ,(yas/menu-keymap-for-mode parent-mode)))))
+ (when (and yas/use-menu
+ (yas/real-mode? mode))
+ (define-key yas/menu-keymap (vector mode)
+ `(menu-item ,(symbol-name mode) ,keymap)))
+ (dolist (snippet snippets)
+ (let* ((full-key (car snippet))
+ (key (file-name-sans-extension full-key))
+ (name (caddr snippet))
+ (template (yas/make-template (cadr snippet)
+ (or name key))))
+ (yas/snippet-table-store snippet-table
+ full-key
+ key
+ template)
+ (when yas/use-menu
+ (define-key keymap (vector (make-symbol full-key))
+ `(menu-item ,(yas/template-name template)
+ ,(yas/make-menu-binding (yas/template-content template))
+ :keys ,(concat key yas/trigger-symbol))))))))
+
+(defun yas/set-mode-parent (mode parent)
+ "Set parent mode of MODE to PARENT."
+ (setf (yas/snippet-table-parent
+ (yas/snippet-table mode))
+ (yas/snippet-table 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)
"Define a snippet. Expanding KEY into TEMPLATE.
-NAME is a description to this template."
- (puthash key
- (yas/make-template template (or name key))
- (yas/snippet-table mode)))
+NAME is a description to this template. Also update
+the menu if `yas/use-menu' is `t'."
+ (yas/define-snippets mode
+ (list (list key template name))))
+
(defun yas/expand ()
- "Expand a snippet. When a snippet is expanded, t is returned,
-otherwise, nil returned."
+ "Expand a snippet."
(interactive)
(multiple-value-bind (key start end) (yas/current-key)
- (let ((template (yas/template key (yas/current-snippet-table))))
- (if template
- (progn
- (yas/expand-snippet start end (yas/template-content template))
- t)
- nil))))
+ (let ((templates (yas/snippet-table-fetch (yas/current-snippet-table)
+ key)))
+ (if templates
+ (let ((template (if (null (cdr templates)) ; only 1 template
+ (yas/template-content (cdar templates))
+ (yas/popup-for-template templates))))
+ (when template
+ (yas/expand-snippet start end template)))
+ (when yas/trigger-fallback
+ (call-interactively yas/trigger-fallback))))))
(defun yas/next-field-group ()
"Navigate to next field group. If there's none, exit the snippet."
(let ((snippet (yas/snippet-of-current-keymap))
(done nil))
(if snippet
- (do* ((tabstops (yas/snippet-tabstops snippet) (cdr tabstops))
- (tabstop (car tabstops) (car tabstops)))
- ((or (null tabstops)
- done)
- (unless done (message "Not in a snippet field.")))
- (when (= (point)
- (overlay-start
- (yas/field-overlay
- (yas/group-primary-field tabstop))))
- (setq done t)
- (yas/navigate-group tabstop t)))
- (message "Not in a snippet field."))))))
+ (do* ((tabstops (yas/snippet-tabstops snippet) (cdr tabstops))
+ (tabstop (car tabstops) (car tabstops)))
+ ((or (null tabstops)
+ done)
+ (unless done (call-interactively 'yas/expand)))
+ (when (= (point)
+ (overlay-start
+ (yas/field-overlay
+ (yas/group-primary-field tabstop))))
+ (setq done t)
+ (yas/navigate-group tabstop t)))
+ (call-interactively 'yas/expand))))))
(defun yas/prev-field-group ()
"Navigate to prev field group. If there's none, exit the snippet."
(dolist (field (yas/group-fields group))
(delete-overlay (yas/field-overlay field)))))
-(defun yas/load-directory (directory)
- "Load snippet definition from a directory hierarchy.
-Below the top-level directory, each directory is a mode
-name. And under each subdirectory, each file is a definition
-of a snippet. The file name is the trigger key and the
-content of the file is the template."
- (with-temp-buffer
- (dolist (mode (yas/directory-files directory nil))
- (let ((table (yas/snippet-table (intern (file-name-nondirectory mode)))))
- (dolist (key (yas/directory-files mode t))
- (when (file-readable-p key)
- (insert-file-contents key nil nil nil t)
- (multiple-value-bind
- (key template name)
- (cons (file-name-sans-extension
- (file-name-nondirectory key))
- (yas/parse-template))
- (puthash key
- (yas/make-template
- template
- (or name key))
- table))))))))
-
(provide 'yasnippet)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Monkey patching for other functions that's causing
+;; problems to yasnippet. For details on why I patch
+;; those functions, refer to
+;; http://code.google.com/p/yasnippet/wiki/MonkeyPatching
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defadvice c-neutralize-syntax-in-CPP
+ (around yas-mp/c-neutralize-syntax-in-CPP activate)
+ "Adviced `c-neutralize-syntax-in-CPP' to properly
+handle the end-of-buffer error fired in it by calling
+`forward-char' at the end of buffer."
+ (condition-case err
+ ad-do-it
+ (error (message (error-message-string err)))))