;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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/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 'indent-according-to-mode
+ "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 "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/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.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar yas/version "0.1")
+
(defvar yas/snippet-tables (make-hash-table)
"A hash table of snippet tables corresponding to each major-mode.")
-
-(defstruct yas/snippet
- "The snippet structure of yasnippet."
- overlay fields exit-marker)
-(defstruct yas/snippet-field
- "The snippet-field structure of yasnippet."
- overlay state)
+(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/separator]
+ '(menu-item "--"))
(defconst yas/escape-backslash
(concat "YASESCAPE" "BACKSLASH" "PROTECTGUARD"))
(defconst yas/escape-backquote
(concat "YASESCAPE" "BACKQUOTE" "PROTECTGUARD"))
+(defconst yas/field-regexp
+ (concat "$\\(?1:[0-9]+\\)" "\\|"
+ "${\\(?:\\(?1:[0-9]+\\):\\)?\\(?2:[^}]*\\)}"))
+
+(defvar yas/snippet-id-seed 0
+ "Contains the next id for a snippet")
+(defun yas/snippet-next-id ()
+ (let ((id yas/snippet-id-seed))
+ (incf yas/snippet-id-seed)
+ id))
+
+(defvar yas/overlay-modification-hooks
+ (list 'yas/overlay-modification-hook)
+ "The list of hooks to the overlay modification event.")
+(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.")
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Internal Structs
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defstruct (yas/template (:constructor yas/make-template (content name)))
+ "A template for a snippet."
+ content
+ name)
+(defstruct (yas/snippet (:constructor yas/make-snippet ()))
+ "A snippet."
+ (groups nil)
+ (tabstops nil) ; tabstops are those groups whose init value is empty
+ (exit-marker nil)
+ (id (yas/snippet-next-id) :read-only t)
+ (overlay nil))
+(defstruct (yas/group (:constructor yas/make-group (primary-field snippet)))
+ "A group contains a list of field with the same number."
+ primary-field
+ (fields (list primary-field))
+ (next nil)
+ (prev nil)
+ snippet)
+(defstruct (yas/field (:constructor yas/make-field (overlay number value)))
+ "A field in a snippet."
+ overlay
+ number
+ value)
+
+(defun yas/snippet-add-field (snippet field)
+ "Add FIELD to SNIPPET."
+ (let ((group (find field
+ (yas/snippet-groups snippet)
+ :test
+ '(lambda (field group)
+ (and (not (null (yas/field-number field)))
+ (= (yas/field-number field)
+ (yas/group-number group)))))))
+ (if group
+ (yas/group-add-field group field)
+ (push (yas/make-group field snippet)
+ (yas/snippet-groups snippet)))))
+
+(defun yas/group-value (group)
+ "Get the default value of the field group."
+ (or (yas/field-value
+ (yas/group-primary-field group))
+ ""))
+(defun yas/group-number (group)
+ "Get the number of the field group."
+ (yas/field-number
+ (yas/group-primary-field group)))
+(defun yas/group-add-field (group field)
+ "Add a field to the field group. If the value of the primary
+field is nil and that of the field is not nil, the field is set
+as the primary field of the group."
+ (push field (yas/group-fields group))
+ (when (and (null (yas/field-value (yas/group-primary-field group)))
+ (yas/field-value field))
+ (setf (yas/group-primary-field group) field)))
+
+(defun yas/snippet-field-compare (field1 field2)
+ "Compare two fields. The field with a number is sorted first.
+If they both have a number, compare through the number. If neither
+have, compare through the start point of the overlay."
+ (let ((n1 (yas/field-number field1))
+ (n2 (yas/field-number field2)))
+ (if n1
+ (if n2
+ (< n1 n2)
+ t)
+ (if n2
+ nil
+ (< (overlay-start (yas/field-overlay field1))
+ (overlay-start (yas/field-overlay field2)))))))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(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)))
"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."
+ "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))))
-
-(defun yas/create-snippet (template
- indent? column tabify? tab-width)
- "Create a snippet according to TEMPLATE. Each line is indented to
-current column if `yas/indent-line' is non-`nil'."
- (with-temp-buffer
- (insert template)
-
- ;; Step 1: do necessary indent
- (when indent?
- (let* ((indent (if tabify?
- (concat (make-string (/ column tab-width) ?\t)
- (make-string (% column tab-width) ?\ ))
- (make-string column ?\ ))))
- (goto-char (point-min))
- (while (zerop (forward-line))
- (insert indent)
- (end-of-line))))
-
- ;; Step 2: protect backslash and backquote
- (yas/replace-all "\\\\" yas/escape-backslash)
- (yas/replace-all "\\`" yas/escape-backquote)
+ (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 (gethash (buffer-substring-no-properties (point) end)
+ (yas/current-snippet-table))
+ (setq done t)
+ (setq start (point)))))
+ (list (buffer-substring-no-properties start end)
+ start
+ end)))
- ;; Step 3: evaluate all backquotes
- (goto-char (point-min))
- (while (re-search-forward "`\\([^`]*\\)`" nil t)
- (replace-match (yas/eval-string (match-string-no-properties 1))
- t t))
+(defun yas/synchronize-fields (field-group)
+ "Update all fields' text according to the primary field."
+ (save-excursion
+ (let* ((inhibit-modification-hooks t)
+ (primary (yas/group-primary-field field-group))
+ (primary-overlay (yas/field-overlay primary))
+ (text (buffer-substring-no-properties (overlay-start primary-overlay)
+ (overlay-end primary-overlay))))
+ (dolist (field (yas/group-fields field-group))
+ (let* ((field-overlay (yas/field-overlay field))
+ (original-length (- (overlay-end field-overlay)
+ (overlay-start field-overlay))))
+ (unless (eq field-overlay primary-overlay)
+ (goto-char (overlay-start field-overlay))
+ (insert text)
+ (if (= (overlay-start field-overlay)
+ (overlay-end field-overlay))
+ (move-overlay field-overlay
+ (overlay-start field-overlay)
+ (point))
+ (delete-char original-length))))))))
+
+(defun yas/overlay-modification-hook (overlay after? beg end &optional length)
+ "Modification hook for snippet field overlay."
+ (when (and after? (not undo-in-progress))
+ (yas/synchronize-fields (overlay-get overlay 'yas/group))))
+(defun yas/overlay-insert-in-front-hook (overlay after? beg end &optional length)
+ "Hook for snippet overlay when text is inserted in front of a snippet field."
+ (when after?
+ (let ((field-group (overlay-get overlay 'yas/group))
+ (inhibit-modification-hooks t))
+ (when (not (overlay-get overlay 'yas/modified?))
+ (overlay-put overlay 'yas/modified? t)
+ (when (> (overlay-end overlay) end)
+ (save-excursion
+ (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."
+ (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))))
- ;; Step 4: protect all escapes, including backslash and backquot
- ;; which may be produced in Step 3
- (yas/replace-all "\\\\" yas/escape-backslash)
- (yas/replace-all "\\`" yas/escape-backquote)
- (yas/replace-all "\\$" yas/escape-dollar)
-
- ;; Step : restore all escape characters
- (yas/replace-all yas/escape-dollar "$")
- (yas/replace-all yas/escape-backquote "`")
- (yas/replace-all yas/escape-backslash "\\")
-
- (buffer-string)))
+(defun yas/undo-expand-snippet (start end key snippet)
+ "Undo a snippet expansion. Delete the overlays. This undo can't be
+redo-ed."
+ (let ((undo (car buffer-undo-list)))
+ (while (null undo)
+ (setq buffer-undo-list (cdr buffer-undo-list))
+ (setq undo (car buffer-undo-list)))
+ ;; Remove this undo operation record
+ (setq buffer-undo-list (cdr buffer-undo-list))
+ (let ((inhibit-modification-hooks t)
+ (buffer-undo-list t))
+ (yas/exit-snippet snippet)
+ (goto-char start)
+ (delete-char (- end start))
+ (insert key))))
(defun yas/expand-snippet (start end template)
"Expand snippet at current point. Text between START and END
will be deleted before inserting template."
(goto-char start)
- (insert (yas/create-snippet template
- yas/indent-line ; indent?
- (current-column) ; column
- indent-tabs-mode ; tabify?
- tab-width ; tab-width
- ))
- (delete-char (- end start)))
+ (let ((key (buffer-substring-no-properties start end))
+ (original-undo-list buffer-undo-list)
+ (inhibit-modification-hooks t)
+ (length (- end start))
+ (column (current-column)))
+ (save-restriction
+ (narrow-to-region start start)
+
+ (setq buffer-undo-list t)
+ (insert template)
+
+ ;; Step 1: do necessary indent
+ (when yas/indent-line
+ (let* ((indent (if indent-tabs-mode
+ (concat (make-string (/ column tab-width) ?\t)
+ (make-string (% column tab-width) ?\ ))
+ (make-string column ?\ ))))
+ (goto-char (point-min))
+ (while (and (zerop (forward-line))
+ (= (current-column) 0))
+ (insert indent))))
+
+ ;; Step 2: protect backslash and backquote
+ (yas/replace-all "\\\\" yas/escape-backslash)
+ (yas/replace-all "\\`" yas/escape-backquote)
+
+ ;; Step 3: evaluate all backquotes
+ (goto-char (point-min))
+ (while (re-search-forward "`\\([^`]*\\)`" nil t)
+ (replace-match (yas/eval-string (match-string-no-properties 1))
+ t t))
+
+ ;; Step 4: protect all escapes, including backslash and backquot
+ ;; which may be produced in Step 3
+ (yas/replace-all "\\\\" yas/escape-backslash)
+ (yas/replace-all "\\`" yas/escape-backquote)
+ (yas/replace-all "\\$" yas/escape-dollar)
+
+ (let ((snippet (yas/make-snippet)))
+ ;; Step 5: Create fields
+ (goto-char (point-min))
+ (while (re-search-forward yas/field-regexp nil t)
+ (let ((number (match-string-no-properties 1)))
+ (if (and number
+ (string= "0" number))
+ (progn
+ (replace-match "")
+ (setf (yas/snippet-exit-marker snippet)
+ (copy-marker (point) t)))
+ (yas/snippet-add-field
+ snippet
+ (yas/make-field
+ (make-overlay (match-beginning 0) (match-end 0))
+ (and number (string-to-number number))
+ (match-string-no-properties 2))))))
+
+ ;; Step 6: Sort and link each field group
+ (setf (yas/snippet-groups snippet)
+ (sort (yas/snippet-groups snippet)
+ '(lambda (group1 group2)
+ (yas/snippet-field-compare
+ (yas/group-primary-field group1)
+ (yas/group-primary-field group2)))))
+ (let ((prev nil))
+ (dolist (group (yas/snippet-groups snippet))
+ (setf (yas/group-prev group) prev)
+ (when prev
+ (setf (yas/group-next prev) group))
+ (setq prev group)))
+
+ ;; Step 7: Create keymap overlay for snippet
+ (let ((overlay (make-overlay (point-min)
+ (point-max)
+ nil
+ nil
+ t)))
+ (overlay-put overlay 'keymap yas/keymap)
+ (overlay-put overlay 'yas/snippet-reference snippet)
+ (setf (yas/snippet-overlay snippet) overlay))
+
+ ;; Step 8: Replace fields with default values
+ (dolist (group (yas/snippet-groups snippet))
+ (let ((value (yas/group-value group)))
+ (when (string= "" value)
+ (push group (yas/snippet-tabstops snippet)))
+ (dolist (field (yas/group-fields group))
+ (let* ((overlay (yas/field-overlay field))
+ (start (overlay-start overlay))
+ (end (overlay-end overlay))
+ (length (- end start)))
+ (goto-char start)
+ (insert value)
+ (delete-char length)))))
+
+ ;; Step 9: restore all escape characters
+ (yas/replace-all yas/escape-dollar "$")
+ (yas/replace-all yas/escape-backquote "`")
+ (yas/replace-all yas/escape-backslash "\\")
+
+ ;; Step 10: Set up properties of overlays
+ (dolist (group (yas/snippet-groups snippet))
+ (let ((overlay (yas/field-overlay
+ (yas/group-primary-field group))))
+ (overlay-put overlay 'yas/snippet snippet)
+ (overlay-put overlay 'yas/group group)
+ (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)
+ (dolist (field (yas/group-fields group))
+ (overlay-put (yas/field-overlay field)
+ 'face
+ 'highlight))))
+
+ ;; Step 11: move to end and make sure exit-marker exist
+ (goto-char (point-max))
+ (unless (yas/snippet-exit-marker snippet)
+ (setf (yas/snippet-exit-marker snippet) (copy-marker (point) t)))
+
+ ;; Step 12: Construct undo information
+ (unless (eq original-undo-list t)
+ (add-to-list 'original-undo-list
+ `(apply yas/undo-expand-snippet
+ ,(point-min)
+ ,(point-max)
+ ,key
+ ,snippet)))
+
+ ;; Step 13: remove the trigger key
+ (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)))
+ (if groups
+ (goto-char (overlay-start
+ (yas/field-overlay
+ (yas/group-primary-field
+ (car groups)))))
+ ;; no need to call exit-snippet, since no overlay created.
+ (yas/exit-snippet snippet)))))))
+
+(defun yas/current-snippet-overlay (&optional point)
+ "Get the most proper overlay which is belongs to a snippet."
+ (let ((point (or point (point)))
+ (snippet-overlay nil))
+ (dolist (overlay (overlays-at point))
+ (when (overlay-get overlay 'yas/snippet)
+ (if (null snippet-overlay)
+ (setq snippet-overlay overlay)
+ (when (> (yas/snippet-id (overlay-get overlay 'yas/snippet))
+ (yas/snippet-id (overlay-get snippet-overlay 'yas/snippet)))
+ (setq snippet-overlay overlay)))))
+ snippet-overlay))
+
+(defun yas/snippet-of-current-keymap (&optional point)
+ "Get the snippet holding the snippet keymap under POINT."
+ (let ((point (or point (point)))
+ (keymap-snippet nil)
+ (snippet nil))
+ (dolist (overlay (overlays-at point))
+ (setq snippet (overlay-get overlay 'yas/snippet-reference))
+ (when snippet
+ (if (null keymap-snippet)
+ (setq keymap-snippet snippet)
+ (when (> (yas/snippet-id snippet)
+ (yas/snippet-id keymap-snippet))
+ (setq keymap-snippet snippet)))))
+ keymap-snippet))
+
+(defun yas/current-overlay-for-navigation ()
+ "Get current overlay for navigation. Might be overlay at current or previous point."
+ (let ((overlay1 (yas/current-snippet-overlay))
+ (overlay2 (if (bobp)
+ nil
+ (yas/current-snippet-overlay (- (point) 1)))))
+ (if (null overlay1)
+ overlay2
+ (if (or (null overlay2)
+ (eq (overlay-get overlay1 'yas/snippet)
+ (overlay-get overlay2 'yas/snippet)))
+ overlay1
+ (if (> (yas/snippet-id (overlay-get overlay2 'yas/snippet))
+ (yas/snippet-id (overlay-get overlay1 'yas/snippet)))
+ overlay2
+ overlay1)))))
+
+(defun yas/navigate-group (group next?)
+ "Go to next of previous field group. Exit snippet if none."
+ (let ((target (if next?
+ (yas/group-next group)
+ (yas/group-prev group))))
+ (if target
+ (goto-char (overlay-start
+ (yas/field-overlay
+ (yas/group-primary-field target))))
+ (yas/exit-snippet (yas/group-snippet group)))))
+
+(defun yas/parse-template ()
+ "Parse the template in the current buffer.
+If the buffer contains a line of \"# --\" then the contents
+above this line are ignored. Variables can be set above this
+line through the syntax:
+
+#name : value
+
+Currently only the \"name\" variable is recognized. Here's
+an example:
+
+#name: #include \"...\"
+# --
+#include \"$1\""
+ (goto-char (point-min))
+ (let (template name bound)
+ (if (re-search-forward "^# --\n" nil t)
+ (progn (setq template
+ (buffer-substring-no-properties (point)
+ (point-max)))
+ (setq bound (point))
+ (goto-char (point-min))
+ (while (re-search-forward "^#\\([^ ]+\\) *: *\\(.*\\)$" bound t)
+ (when (string= "name" (match-string-no-properties 1))
+ (setq name (match-string-no-properties 2)))))
+ (setq template
+ (buffer-substring-no-properties (point-min) (point-max))))
+ (list template name)))
+
+(defun yas/directory-files (directory file?)
+ "Return directory files or subdirectories in full path."
+ (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."
+ (let* ((pn (posn-at-point (or point (point))))
+ (x-y (posn-x-y pn))
+ (x (car x-y))
+ (y (cdr x-y))
+ (coord (list (list (+ x 10) (+ y 20)) (selected-window))))
+ coord))
+
+(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)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; User level functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(defun yas/define (mode key template)
- "Define a snippet. Expanding KEY into TEMPLATE."
- (puthash key template (yas/snippet-table mode)))
+(defun yas/about ()
+ (interactive)
+ (message (concat "yasnippet (version "
+ yas/version
+ ") -- pluskid <pluskid@gmail.com>")))
+(defun yas/initialize ()
+ "Do necessary initialization."
+ (global-set-key yas/trigger-key 'yas/expand)
+ (when yas/use-menu
+ (define-key-after global-map
+ [menu-bar yasnippet]
+ (cons "YASnippet" yas/menu-keymap)
+ 'buffer)))
+
+(defun yas/define (mode key template &optional name)
+ "Define a snippet. Expanding KEY into TEMPLATE.
+NAME is a description to this template. Also update
+the menu if `yas/use-menu' is `t'."
+ (let* ((full-key key)
+ (key (file-name-sans-extension full-key))
+ (template (yas/make-template template (or name key)))
+ (snippet-table (yas/snippet-table mode)))
+ (puthash key
+ (yas/modify-alist (gethash key snippet-table)
+ full-key
+ template)
+ snippet-table)
+ (when yas/use-menu
+ (let ((keymap (yas/menu-keymap-for-mode mode)))
+ (define-key yas/menu-keymap (vector mode)
+ `(menu-item ,(symbol-name mode) ,keymap))
+ (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/expand ()
"Expand a snippet. When a snippet is expanded, t is returned,
otherwise, nil returned."
(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 template)
- t)
- nil))))
+ (let ((templates (gethash key (yas/current-snippet-table))))
+ (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."
+ (interactive)
+ (let ((overlay (yas/current-overlay-for-navigation)))
+ (if overlay
+ (yas/navigate-group (overlay-get overlay 'yas/group) t)
+ (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."))))))
+
+(defun yas/prev-field-group ()
+ "Navigate to prev field group. If there's none, exit the snippet."
+ (interactive)
+ (let ((overlay (yas/current-overlay-for-navigation)))
+ (if overlay
+ (yas/navigate-group (overlay-get overlay 'yas/group) nil)
+ (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 nil)))
+ (message "Not in a snippet field."))))))
+
+(defun yas/exit-snippet (snippet)
+ "Goto exit-marker of SNIPPET and delete the snippet."
+ (interactive)
+ (goto-char (yas/snippet-exit-marker snippet))
+ (delete-overlay (yas/snippet-overlay snippet))
+ (dolist (group (yas/snippet-groups 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 ((mode-sym (intern (file-name-nondirectory mode))))
+ (dolist (file (yas/directory-files mode t))
+ (when (file-readable-p file)
+ (insert-file-contents file nil nil nil t)
+ (multiple-value-bind
+ (key template name)
+ (cons (file-name-nondirectory file)
+ (yas/parse-template))
+ (yas/define mode-sym key template name))))))))
(provide 'yasnippet)