X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/f6920eeb4d23ec582c6d1bb7707cb120d3640883..a5433b904ffc3b1cb51cc108e24a9d6bd6ca6bc6:/packages/yasnippet/yasnippet.el diff --git a/packages/yasnippet/yasnippet.el b/packages/yasnippet/yasnippet.el index d932f5b6b..b279f1206 100644 --- a/packages/yasnippet/yasnippet.el +++ b/packages/yasnippet/yasnippet.el @@ -37,10 +37,10 @@ ;; `yas-snippet-dirs' ;; ;; The directory where user-created snippets are to be -;; stored. Can also be a list of directories. In that case, +;; stored. Can also be a list of directories. In that case, ;; when used for bulk (re)loading of snippets (at startup or ;; via `yas-reload-all'), directories appearing earlier in -;; the list shadow other dir's snippets. Also, the first +;; the list shadow other dir's snippets. Also, the first ;; directory is taken as the default for storing the user's ;; new snippets. ;; @@ -50,7 +50,7 @@ ;; `yas-extra-modes' ;; ;; A local variable that you can set in a hook to override -;; snippet-lookup based on major mode. It is a a symbol (or +;; snippet-lookup based on major mode. It is a symbol (or ;; list of symbols) that correspond to subdirectories of ;; `yas-snippet-dirs' and is used for deciding which ;; snippets to consider for the active buffer. @@ -63,7 +63,8 @@ ;; M-x yas-expand ;; ;; Try to expand snippets before point. In `yas-minor-mode', -;; this is bound to `yas-trigger-key' which you can customize. +;; this is normally bound to TAB, but you can customize it in +;; `yas-minor-mode-map'. ;; ;; M-x yas-load-directory ;; @@ -104,14 +105,14 @@ ;; ;; M-x yas-describe-tables ;; -;; Lists known snippets in a separate buffer. User is +;; Lists known snippets in a separate buffer. User is ;; prompted as to whether only the currently active tables ;; are to be displayed, or all the tables for all major ;; modes. ;; -;; The `dropdown-list.el' extension is bundled with YASnippet, you -;; can optionally use it the preferred "prompting method", puting in -;; your .emacs file, for example: +;; If you have `dropdown-list' installed, you can optionally use it +;; as the preferred "prompting method", putting in your .emacs file, +;; for example: ;; ;; (require 'dropdown-list) ;; (setq yas-prompt-functions '(yas-dropdown-prompt @@ -136,6 +137,16 @@ (require 'easymenu) (require 'help-mode) +(eval-when-compile + (defvar yas--editing-template) + (defvar yas--guessed-modes) + (defvar yas--indent-original-column) + (defvar yas--scheduled-jit-loads) + (defvar yas-keymap) + (defvar yas-selected-text) + (defvar yas-verbosity)) + + ;;; User customizable variables @@ -152,11 +163,11 @@ (concat (file-name-directory yas--load-file-name) "snippets")))) "Directory or list of snippet dirs for each major mode. -The directory where user-created snippets are to be stored. Can -also be a list of directories. In that case, when used for +The directory where user-created snippets are to be stored. Can +also be a list of directories. In that case, when used for bulk (re)loading of snippets (at startup or via `yas-reload-all'), directories appearing earlier in the list -shadow other dir's snippets. Also, the first directory is taken +shadow other dir's snippets. Also, the first directory is taken as the default for storing the user's new snippets." :type '(choice (string :tag "Single directory (string)") (repeat :args (string) :tag "List of directories (strings)")) @@ -176,6 +187,20 @@ as the default for storing the user's new snippets." (defvaralias 'yas/root-directory 'yas-snippet-dirs) +(defcustom yas-new-snippet-default "\ +# -*- mode: snippet -*- +# name: $1 +# key: ${2:${1:$(yas--key-from-desc yas-text)}}${3: +# binding: ${4:direct-keybinding}}${5: +# expand-env: ((${6:some-var} ${7:some-value}))}${8: +# type: command} +# -- +$0" + "Default snippet to use when creating a new snippet. If nil, +don't use any snippet." + :type 'string + :group 'yasnippet) + (defcustom yas-prompt-functions '(yas-x-prompt yas-dropdown-prompt yas-completing-prompt @@ -216,7 +241,7 @@ The following values are possible: - `auto' Indent each line of the snippet with `indent-according-to-mode' -Every other value means don't apply any snippet-side indendation +Every other value means don't apply any snippet-side indentation after expansion (the manual per-line \"$>\" indentation still applies)." :type '(choice (const :tag "Nothing" nothing) @@ -236,86 +261,25 @@ Naturally this is only valid when `yas-indent-line' is `auto'" :type 'boolean :group 'yasnippet) -(defcustom yas-trigger-key "TAB" - "The key bound to `yas-expand' when function `yas-minor-mode' is active. - -Value is a string that is converted to the internal Emacs key -representation using `read-kbd-macro'." - :type 'string - :group 'yasnippet - :set #'(lambda (symbol key) - (let ((old (and (boundp symbol) - (symbol-value symbol)))) - (set-default symbol key) - ;; On very first loading of this defcustom, - ;; `yas-trigger-key' is *not* loaded. - (if (fboundp 'yas--trigger-key-reload) - (yas--trigger-key-reload old))))) - -(defcustom yas-next-field-key '("TAB" "") - "The key to navigate to next field when a snippet is active. - -Value is a string that is converted to the internal Emacs key -representation using `read-kbd-macro'. - -Can also be a list of strings." - :type '(choice (string :tag "String") - (repeat :args (string) :tag "List of strings")) - :group 'yasnippet - :set #'(lambda (symbol val) - (set-default symbol val) - (if (fboundp 'yas--init-yas-in-snippet-keymap) - (yas--init-yas-in-snippet-keymap)))) - - -(defcustom yas-prev-field-key '("" "") - "The key to navigate to previous field when a snippet is active. - -Value is a string that is converted to the internal Emacs key -representation using `read-kbd-macro'. - -Can also be a list of strings." - :type '(choice (string :tag "String") - (repeat :args (string) :tag "List of strings")) - :group 'yasnippet - :set #'(lambda (symbol val) - (set-default symbol val) - (if (fboundp 'yas--init-yas-in-snippet-keymap) - (yas--init-yas-in-snippet-keymap)))) - -(defcustom yas-skip-and-clear-key '("C-d" "" "") - "The key to clear the currently active field. - -Value is a string that is converted to the internal Emacs key -representation using `read-kbd-macro'. - -Can also be a list of strings." - :type '(choice (string :tag "String") - (repeat :args (string) :tag "List of strings")) - :group 'yasnippet - :set #'(lambda (symbol val) - (set-default symbol val) - (if (fboundp 'yas--init-yas-in-snippet-keymap) - (yas--init-yas-in-snippet-keymap)))) - (defcustom yas-triggers-in-field nil - "If non-nil, `yas-next-field-key' can trigger stacked expansions. + "If non-nil, allow stacked expansions (snippets inside snippets). -Otherwise, `yas-next-field-key' just tries to move on to the next -field" +Otherwise `yas-next-field-or-maybe-expand' just moves on to the +next field" :type 'boolean :group 'yasnippet) (defcustom yas-fallback-behavior 'call-other-command - "How to act when `yas-trigger-key' does *not* expand a snippet. + "How to act when `yas-expand' does *not* expand a snippet. - `call-other-command' means try to temporarily disable YASnippet - and call the next command bound to `yas-trigger-key'. + and call the next command bound to whatever key was used to + invoke `yas-expand'. - nil or the symbol `return-nil' mean do nothing. (and `yas-expand' returns nil) -- A lisp form (apply COMMAND . ARGS) means interactively call +- A Lisp form (apply COMMAND . ARGS) means interactively call COMMAND, if ARGS is non-nil, call COMMAND non-interactively with ARGS as arguments." :type '(choice (const :tag "Call previous command" call-other-command) @@ -361,8 +325,9 @@ Any other non-nil value, every submenu is listed." (const :tag "No menu" nil)) :group 'yasnippet) -(defcustom yas-trigger-symbol (if (eq window-system 'mac) - (char-to-string ?\x21E5) ;; little ->| sign +(defcustom yas-trigger-symbol (or (and (eq window-system 'mac) + (ignore-errors + (char-to-string ?\x21E5))) ;; little ->| sign " =>") "The text that will be used in menu to represent the trigger." :type 'string @@ -372,7 +337,7 @@ Any other non-nil value, every submenu is listed." "If non-nil, snippet expansion wraps around selected region. The wrapping occurs just before the snippet's exit marker. This -can be overriden on a per-snippet basis." +can be overridden on a per-snippet basis." :type 'boolean :group 'yasnippet) @@ -386,7 +351,7 @@ An error string \"[yas] error\" is returned instead." (defcustom yas-visit-from-menu nil "If non-nil visit snippets's files from menu, instead of expanding them. -This cafn only work when snippets are loaded from files." +This can only work when snippets are loaded from files." :type 'boolean :group 'yasnippet) @@ -397,7 +362,7 @@ Leave this set at nil (the default) to be able to trigger an expansion simply by placing the cursor after a valid tab trigger, using whichever commands. -Optionallly, set this to something like '(self-insert-command) if +Optionally, set this to something like '(self-insert-command) if you to wish restrict expansion to only happen when the last letter of the snippet tab trigger was typed immediately before the trigger key itself." @@ -417,38 +382,30 @@ the trigger key itself." :group 'yasnippet) -;;; User can also customize the next defvars - -(defun yas--define-some-keys (keys keymap definition) - "Bind KEYS to DEFINITION in KEYMAP, read with `read-kbd-macro'." - (let ((keys (or (and (listp keys) keys) - (list keys)))) - (dolist (key keys) - (define-key keymap (read-kbd-macro key) definition)))) - -(defvar yas-keymap - (let ((map (make-sparse-keymap))) - (mapc #'(lambda (binding) - (yas--define-some-keys (car binding) map (cdr binding))) - `((,yas-next-field-key . yas-next-field-or-maybe-expand) - (,yas-prev-field-key . yas-prev-field) - ("C-g" . yas-abort-snippet) - (,yas-skip-and-clear-key . yas-skip-and-clear-or-delete-char))) - map) - "The keymap active while a snippet expansion is in progress.") +;;; User-visible variables + +(defvar yas-keymap (let ((map (make-sparse-keymap))) + (define-key map [(tab)] 'yas-next-field-or-maybe-expand) + (define-key map (kbd "TAB") 'yas-next-field-or-maybe-expand) + (define-key map [(shift tab)] 'yas-prev-field) + (define-key map [backtab] 'yas-prev-field) + (define-key map (kbd "C-g") 'yas-abort-snippet) + (define-key map (kbd "C-d") 'yas-skip-and-clear-or-delete-char) + map) + "The active keymap while a snippet expansion is in progress.") (defvar yas-key-syntaxes (list "w" "w_" "w_." "w_.()" "^ ") "List of character syntaxes used to find a trigger key before point. The list is tried in the order while scanning characters -backwards from point. For example, if the list is '(\"w\" \"w_\") +backwards from point. For example, if the list is '(\"w\" \"w_\") first look for trigger keys which are composed exclusively of \"word\"-syntax characters, and then, if that fails, look for keys which are either of \"word\" or \"symbol\" -syntax. Triggering after +syntax. Triggering after foo-bar -will, according to the \"w\" element first try \"bar\". If that +will, according to the \"w\" element first try \"bar\". If that isn't a trigger key, \"foo-bar\" is tried, respecting a second \"w_\" element.") @@ -463,7 +420,7 @@ proper values: `yas-snippet-end' : Similar to beg. -Attention: These hooks are not run when exiting nested/stackd snippet expansion!") +Attention: These hooks are not run when exiting nested/stacked snippet expansion!") (defvar yas-before-expand-snippet-hook '() @@ -472,13 +429,14 @@ Attention: These hooks are not run when exiting nested/stackd snippet expansion! (defvar yas-buffer-local-condition '(if (and (or (fourth (syntax-ppss)) (fifth (syntax-ppss))) - (eq (symbol-function this-command) 'yas-expand-from-trigger-key)) + this-command + (eq this-command 'yas-expand-from-trigger-key)) '(require-snippet-condition . force-in-comment) t) "Snippet expanding condition. -This variable is a lisp form which is evaluated everytime a -snippet expansion is attemped: +This variable is a Lisp form which is evaluated every time a +snippet expansion is attempted: * If it evaluates to nil, no snippets can be expanded. @@ -489,7 +447,7 @@ snippet expansion is attemped: considered * Snippets bearing conditions that evaluate to nil (or - produce an error) won't be onsidered. + produce an error) won't be considered. * If the snippet has a condition that evaluates to non-nil RESULT: @@ -531,14 +489,14 @@ snippet itself contains a condition that returns the symbol ;;; Internal variables -(defvar yas--version "0.8.0 (beta)") +(defvar yas--version "0.8.0beta") (defvar yas--menu-table (make-hash-table) "A hash table of MAJOR-MODE symbols to menu keymaps.") (defvar yas--known-modes '(ruby-mode rst-mode markdown-mode) - "A list of mode which is well known but not part of emacs.") + "A list of mode which is well known but not part of Emacs.") (defvar yas--escaped-characters '(?\\ ?` ?\" ?' ?$ ?} ?{ ?\( ?\)) @@ -579,9 +537,10 @@ snippet itself contains a condition that returns the symbol (defvar last-buffer-undo-list nil) (defvar yas--minor-mode-menu nil - "Holds the YASnippet menu") + "Holds the YASnippet menu.") (defun yas--init-minor-keymap () + "Set up the `yas-minor-mode' keymap." (let ((map (make-sparse-keymap))) (when yas-use-menu (easy-menu-define yas--minor-mode-menu @@ -678,7 +637,7 @@ snippet itself contains a condition that returns the symbol ["Good grace " (setq yas-good-grace (not yas-good-grace)) - :help "If non-nil don't raise errors in bad embedded eslip in snippets" + :help "If non-nil don't raise errors in bad embedded elisp in snippets" :style toggle :selected yas-good-grace] ) "----" @@ -687,10 +646,12 @@ snippet itself contains a condition that returns the symbol ["Reload everything" yas-reload-all :help "Cleanup stuff, reload snippets, rebuild menus"] ["About" yas-about - :help "Display some information about YASsnippet"]))) + :help "Display some information about YASnippet"]))) ;; Now for the stuff that has direct keybindings ;; + (define-key map [(tab)] 'yas-expand) + (define-key map (kbd "TAB") 'yas-expand) (define-key map "\C-c&\C-s" 'yas-insert-snippet) (define-key map "\C-c&\C-n" 'yas-new-snippet) (define-key map "\C-c&\C-v" 'yas-visit-snippet-file) @@ -699,20 +660,6 @@ snippet itself contains a condition that returns the symbol (defvar yas-minor-mode-map (yas--init-minor-keymap) "The keymap used when `yas-minor-mode' is active.") -(defun yas--trigger-key-reload (&optional unbind-key) - "Rebind `yas-expand' to the new value of `yas-trigger-key'. - -With optional UNBIND-KEY, try to unbind that key from -`yas-minor-mode-map'." - (when (and unbind-key - (stringp unbind-key) - (not (string= unbind-key ""))) - (define-key yas-minor-mode-map (read-kbd-macro unbind-key) nil)) - (when (and yas-trigger-key - (stringp yas-trigger-key) - (not (string= yas-trigger-key ""))) - (define-key yas-minor-mode-map (read-kbd-macro yas-trigger-key) 'yas-expand))) - (defvar yas--tables (make-hash-table) "A hash table of mode symbols to `yas--table' objects.") @@ -723,19 +670,24 @@ This list is populated when reading the \".yas-parents\" files found when traversing snippet directories with `yas-load-directory'. -There might be additionalal parenting information stored in the +There might be additional parenting information stored in the `derived-mode-parent' property of some mode symbols, but that is not recorded here.") +(defvar yas--ancestors (make-hash-table) + "A hash table of mode symbols do lists of all parent mode symbols. + +A cache managed by `yas--all-parents'") + (defvar yas--direct-keymaps (list) "Keymap alist supporting direct snippet keybindings. -This variable is is placed in `emulation-mode-map-alists'. +This variable is placed in `emulation-mode-map-alists'. -Its elements looks like (TABLE-NAME . KEYMAP). They're +Its elements looks like (TABLE-NAME . KEYMAP). They're instantiated on `yas-reload-all' but KEYMAP is added to only when -loading snippets. `yas--direct-TABLE-NAME' is then a variable set -buffer-locally when entering `yas-minor-mode'. KEYMAP binds all +loading snippets. `yas--direct-TABLE-NAME' is then a variable set +buffer-locally when entering `yas-minor-mode'. KEYMAP binds all defined direct keybindings to the command `yas-expand-from-keymap' which then which snippet to expand.") @@ -765,21 +717,20 @@ and friends." modes-to-activate))))) (defvar yas-minor-mode-hook nil - "Hook run when yas-minor-mode is turned on") + "Hook run when `yas-minor-mode' is turned on.") ;;;###autoload (define-minor-mode yas-minor-mode "Toggle YASnippet mode. -When YASnippet mode is enabled, the `yas-trigger-key' key expands -snippets of code depending on the major mode. +When YASnippet mode is enabled, `yas-expand', normally bound to +the TAB key, expands snippets of code depending on the major +mode. With no argument, this command toggles the mode. positive prefix argument turns on the mode. Negative prefix argument turns off the mode. -You can customize the key through `yas-trigger-key'. - Key bindings: \\{yas-minor-mode-map}" nil @@ -787,9 +738,6 @@ Key bindings: " yas" :group 'yasnippet (cond (yas-minor-mode - ;; Reload the trigger key - ;; - (yas--trigger-key-reload) ;; Install the direct keymaps in `emulation-mode-map-alists' ;; (we use `add-hook' even though it's not technically a hook, ;; but it works). Then define variables named after modes to @@ -814,32 +762,39 @@ Key bindings: (remove-hook 'post-command-hook 'yas--post-command-handler t) (remove-hook 'emulation-mode-map-alists 'yas--direct-keymaps)))) -(defvar yas--dont-activate '(minibufferp) - "If non-nil don't let `yas-minor-mode-on' active yas for this buffer. +(defvar yas-dont-activate '(minibufferp) + "If non-nil don't let `yas-global-mode' affect some buffers. -If a function, then its result is used. +If a function of zero arguments, then its result is used. If a list of functions, then all functions must return nil to activate yas for this buffer. -`yas-minor-mode-on' is usually called by `yas-global-mode' so -this effectively lets you define exceptions to the \"global\" -behaviour. Can also be a function of zero arguments.") -(make-variable-buffer-local 'yas--dont-activate) +In Emacsen <= 23, this variable is buffer-local. Because +`yas-minor-mode-on' is called by `yas-global-mode' after +executing the buffer's major mode hook, setting this variable +there is an effective way to define exceptions to the \"global\" +activation behaviour. + +In Emacsen > 23, only the global value is used. To define +per-mode exceptions to the \"global\" activation behaviour, call +`yas-minor-mode' with a negative argument directily in the major +mode's hook.") +(unless (> emacs-major-version 23) + (make-variable-buffer-local 'yas-dont-activate)) + (defun yas-minor-mode-on () "Turn on YASnippet minor mode. -Do this unless `yas--dont-activate' is truish " +Honour `yas-dont-activate', which see." (interactive) - (unless (cond ((functionp yas--dont-activate) - (funcall yas--dont-activate)) - ((consp yas--dont-activate) - (some #'funcall yas--dont-activate)) - (yas--dont-activate)) - ;; Load all snippets definitions unless we still don't have a - ;; root-directory or some snippets have already been loaded. - ;; + ;; Check `yas-dont-activate' + (unless (cond ((functionp yas-dont-activate) + (funcall yas-dont-activate)) + ((consp yas-dont-activate) + (some #'funcall yas-dont-activate)) + (yas-dont-activate)) (yas-minor-mode 1))) ;;;###autoload @@ -847,19 +802,15 @@ Do this unless `yas--dont-activate' is truish " :group 'yasnippet :require 'yasnippet) -(defadvice yas-global-mode (before yas--reload-with-jit (arg) activate) - (cond ((and arg - (numberp arg) - (> arg 1)) - ;; explicitly enabling - (yas-reload-all)) - ((not yas-global-mode) - ;; toggling - (yas-reload-all)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Major mode stuff -;; +(defun yas--global-mode-reload-with-jit-maybe () + "Run `yas-reload-all' when `yas-global-mode' is on." + (when yas-global-mode (yas-reload-all))) + +(add-hook 'yas-global-mode-hook 'yas--global-mode-reload-with-jit-maybe) + + +;;; Major mode stuff + (defvar yas--font-lock-keywords (append '(("^#.*$" . font-lock-comment-face)) lisp-font-lock-keywords @@ -878,6 +829,7 @@ Do this unless `yas--dont-activate' is truish " (0 font-lock-keyword-face))))) (defun yas--init-major-keymap () + "Setup YASnippet major-mode keymap." (let ((map (make-sparse-keymap))) (easy-menu-define nil map @@ -894,7 +846,7 @@ Do this unless `yas--dont-activate' is truish " (defvar snippet-mode-map (yas--init-major-keymap) - "The keymap used when `snippet-mode' is active") + "The keymap used when `snippet-mode' is active.") (define-derived-mode snippet-mode text-mode "Snippet" @@ -925,7 +877,7 @@ Do this unless `yas--dont-activate' is truish " ) (defun yas--populate-template (template &rest args) - "Helper function to populate a template with properties" + "Helper function to populate TEMPLATE with properties." (let (p v) (while args (aset template @@ -981,7 +933,7 @@ Has the following fields: ;; Apropos storing/updating in TABLE, this works in two steps: ;; ;; 1. `yas--remove-template-by-uuid' removes any -;; keyhash-namehash-template mappings from TABLE, grabing the +;; keyhash-namehash-template mappings from TABLE, grabbing the ;; snippet by its uuid. Also removes mappings from TABLE's ;; `yas--table-direct-keymap' (FIXME: and should probably take care ;; of potentially stale menu bindings right?.) @@ -1100,7 +1052,7 @@ Also takes care of adding and updating to the associated menu." (yas--update-template-menu table template))) (defun yas--update-template-menu (table template) - "Update every menu-related for TEMPLATE" + "Update every menu-related for TEMPLATE." (let ((menu-binding-pair (yas--template-menu-binding-pair-get-create template)) (key (yas--template-key template)) (keybinding (yas--template-keybinding template))) @@ -1144,6 +1096,7 @@ Also takes care of adding and updating to the associated menu." (car (yas--template-menu-binding-pair template)))))) (defun yas--namehash-templates-alist (namehash) + "Return NAMEHASH as an alist." (let (alist) (maphash #'(lambda (k v) (push (cons k v) alist)) @@ -1207,7 +1160,7 @@ conditions to filter out potential expansions." (cdr local-condition))))))) (defun yas--template-can-expand-p (condition requirement) - "Evaluates CONDITION and REQUIREMENT and returns a boolean" + "Evaluates CONDITION and REQUIREMENT and returns a boolean." (let* ((result (or (null condition) (yas--eval-condition condition)))) (cond ((eq requirement t) @@ -1216,10 +1169,22 @@ conditions to filter out potential expansions." (eq requirement result))))) (defun yas--all-parents (mode) - "Returns a list of all parent modes of MODE" - (let ((parents (gethash mode yas--parents))) - (append parents - (mapcan #'yas--all-parents parents)))) + "Returns a list of all parent modes of MODE." + (or (gethash mode yas--ancestors) + (let ((seen '())) + (labels ((yas--all-parents-1 + (m) + (cond ((memq m seen) + (yas--message 1 + "Cyclic parenthood: mode %s has already seen as a parent of mode %s" + m mode) + nil) + (t + (let* ((parents (gethash m yas--parents))) + (setq seen (append seen parents)) + (append parents (mapcan #'yas--all-parents-1 parents))))))) + (puthash mode (yas--all-parents-1 mode) + yas--ancestors))))) (defun yas--table-templates (table) (when table @@ -1259,26 +1224,27 @@ the template of a snippet in the current snippet-table." (defun yas--table-all-keys (table) - (when table - (let ((acc)) - (maphash #'(lambda (key namehash) - (when (yas--filter-templates-by-condition (yas--namehash-templates-alist namehash)) - (push key acc))) - (yas--table-hash table)) - acc))) + "Get trigger keys of all active snippets in TABLE." + (let ((acc)) + (maphash #'(lambda (key namehash) + (when (yas--filter-templates-by-condition (yas--namehash-templates-alist namehash)) + (push key acc))) + (yas--table-hash table)) + acc)) (defun yas--table-mode (table) (intern (yas--table-name table))) -;;; Internal functions: +;;; Internal functions and macros: (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." + "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 judgment." (or (fboundp mode) (find mode yas--known-modes))) @@ -1360,7 +1326,7 @@ ensure your use `make-local-variable' when you set it.") (defun yas--get-snippet-tables () "Get snippet tables for current buffer. -Return a list of `yas--table' objects. The list of modes to +Return a list of `yas--table' objects. The list of modes to consider is returned by `yas--modes-to-activate'" (remove nil (mapcar #'(lambda (mode-name) @@ -1380,7 +1346,54 @@ them all in `yas--menu-table'" :visible (yas--show-menu-p ',mode))) menu-keymap)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro yas--called-interactively-p (&optional kind) + "A backward-compatible version of `called-interactively-p'. + +Optional KIND is as documented at `called-interactively-p' +in GNU Emacs 24.1 or higher." + (if (string< emacs-version "24.1") + '(called-interactively-p) + `(called-interactively-p ,kind))) + + +(defun yas--call-with-temporary-redefinitions (function + &rest function-names-and-overriding-functions) + (let* ((overrides (remove-if-not #'(lambda (fdef) + (fboundp (first fdef))) + function-names-and-overriding-functions)) + (definition-names (mapcar #'first overrides)) + (overriding-functions (mapcar #'second overrides)) + (saved-functions (mapcar #'symbol-function definition-names))) + ;; saving all definitions before overriding anything ensures FDEFINITION + ;; errors don't cause accidental permanent redefinitions. + ;; + (labels ((set-fdefinitions (names functions) + (loop for name in names + for fn in functions + do (fset name fn)))) + (set-fdefinitions definition-names overriding-functions) + (unwind-protect (funcall function) + (set-fdefinitions definition-names saved-functions))))) + + +(defmacro yas--with-temporary-redefinitions (fdefinitions &rest body) + ;; "Temporarily (but globally) redefine each function in FDEFINITIONS. + ;; E.g.: (yas--with-temporary-redefinitions ((foo (x) ...) + ;; (bar (x) ...)) + ;; ;; code that eventually calls foo, bar of (setf foo) + ;; ...)" + `(yas--call-with-temporary-redefinitions + (lambda () ,@body) + ,@(mapcar #'(lambda (thingy) + `(list ',(first thingy) + (lambda ,@(rest thingy)))) + fdefinitions))) + +(put 'yas--with-temporary-redefinitions 'lisp-indent-function 1) +(put 'yas--with-temporary-redefinitions 'edebug-form-spec '((&rest (defun*)) cl-declarations body)) + + ;;; Template-related and snippet loading functions (defun yas--parse-template (&optional file) @@ -1511,9 +1524,13 @@ Here's a list of currently recognized directives: (cdr where) (yas--template-expand-env yas--current-template))))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Popping up for keys and templates -;; +(defun yas--key-from-desc (text) + "Return a yasnippet key from a description string TEXT." + (replace-regexp-in-string "\\(\\w+\\).*" "\\1" text)) + + +;;; Popping up for keys and templates + (defvar yas--x-pretty-prompt-templates nil "If non-nil, attempt to prompt for templates like TextMate.") @@ -1521,7 +1538,9 @@ Here's a list of currently recognized directives: (defun yas--prompt-for-template (templates &optional prompt) "Interactively choose a template from the list TEMPLATES. -TEMPLATES is a list of `yas--template'." +TEMPLATES is a list of `yas--template'. + +Optional PROMPT sets the prompt to use." (when templates (setq templates (sort templates #'(lambda (t1 t2) @@ -1536,13 +1555,18 @@ TEMPLATES is a list of `yas--template'." yas-prompt-functions)))) (defun yas--prompt-for-keys (keys &optional prompt) - "Interactively choose a template key from the list KEYS." + "Interactively choose a template key from the list KEYS. + +Optional PROMPT sets the prompt to use." (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) + "Interactively choose a table from the list TABLES. + +Optional PROMPT sets the prompt to use." (when tables (some #'(lambda (fn) (funcall fn (or prompt "Choose a snippet table: ") @@ -1556,7 +1580,7 @@ TEMPLATES is a list of `yas--template'." ;; actually a `yas--template', defer to `yas--x-prompt-pretty-templates' ;; ;; This would be better implemented by passing CHOICES as a - ;; strucutred tree rather than a list. Modifications would go as far + ;; structured tree rather than a list. Modifications would go as far ;; up as `yas--all-templates' I think. ;; (when (and window-system choices) @@ -1617,7 +1641,10 @@ TEMPLATES is a list of `yas--template'." ido-mode)) (yas-completing-prompt prompt choices display-fn #'ido-completing-read))) -(eval-when-compile (require 'dropdown-list nil t)) +(eval-when-compile + (if (fboundp 'declare-function) + (declare-function dropdown-list "dropdown-list"))) + (defun yas-dropdown-prompt (prompt choices &optional display-fn) (when (featurep 'dropdown-list) (let (formatted-choices @@ -1664,12 +1691,99 @@ TEMPLATES is a list of `yas--template'." (defun yas-no-prompt (prompt choices &optional display-fn) (first choices)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Loading snippets from files + +;;; Defining snippets +;; This consists of creating and registering `yas--template' objects in the +;; correct tables. ;; + +(defun yas--define-snippets-1 (snippet snippet-table) + "Helper for `yas-define-snippets'." + ;; X) Calculate some more defaults on the values returned by + ;; `yas--parse-template'. + ;; + (let* ((file (seventh snippet)) + (key (car snippet)) + (name (or (third snippet) + (and file + (file-name-directory file)))) + (condition (fourth snippet)) + (group (fifth snippet)) + (keybinding (yas--read-keybinding (eighth snippet))) + (uuid (or (ninth snippet) + name)) + (template (or (gethash uuid (yas--table-uuidhash snippet-table)) + (yas--make-blank-template)))) + ;; X) populate the template object + ;; + (yas--populate-template template + :table snippet-table + :key key + :content (second snippet) + :name (or name key) + :group group + :condition condition + :expand-env (sixth snippet) + :file (seventh snippet) + :keybinding keybinding + :uuid uuid) + ;; X) Update this template in the appropriate table. This step + ;; also will take care of adding the key indicators in the + ;; templates menu entry, if any + ;; + (yas--update-template snippet-table template) + ;; X) Return the template + ;; + ;; + template)) + +(defun yas-define-snippets (mode snippets) + "Define SNIPPETS for MODE. + +SNIPPETS is a list of snippet definitions, each taking the +following form + + (KEY TEMPLATE NAME CONDITION GROUP EXPAND-ENV FILE KEYBINDING UUID) + +Within these, only KEY and TEMPLATE are actually mandatory. + +TEMPLATE might be a lisp form or a string, depending on whether +this is a snippet or a snippet-command. + +CONDITION, EXPAND-ENV and KEYBINDING are lisp forms, they have +been `yas--read-lisp'-ed and will eventually be +`yas--eval-lisp'-ed. + +The remaining elements are strings. + +FILE is probably of very little use if you're programatically +defining snippets. + +UUID is the snippets \"unique-id\". Loading a second snippet file +with the same uuid replaced the previous snippet. + +You can use `yas--parse-template' to return such lists based on +the current buffers contents." + (let ((snippet-table (yas--table-get-create mode)) + (template nil)) + (dolist (snippet snippets) + (setq template (yas--define-snippets-1 snippet + snippet-table))) + template)) + + +;;; Loading snippets from files + (defun yas--load-yas-setup-file (file) (load file 'noerror)) +(defun yas--define-parents (mode parents) + "Add PARENTS to the list of MODE's parents." + (puthash mode (remove-duplicates + (append parents + (gethash mode yas--parents))) + yas--parents)) + (defun yas-load-directory (top-level-dir &optional use-jit) "Load snippets in directory hierarchy TOP-LEVEL-DIR. @@ -1700,10 +1814,16 @@ Optional USE-JIT use jit-loading of snippets." (let ((form `(yas--load-directory-1 ,dir ',mode-sym ',parents))) - (if use-jit + (if (and use-jit + (not (some #'(lambda (buffer) + (with-current-buffer buffer + (when (eq major-mode mode-sym) + (yas--message 3 "Discovered there was already %s in %s" buffer mode-sym) + t))) + (buffer-list)))) (yas--schedule-jit mode-sym form) (eval form))))) - (when (interactive-p) + (when (yas--called-interactively-p 'interactive) (yas--message 3 "Loaded snippets from %s." top-level-dir))) (defun yas--load-directory-1 (directory mode-sym parents &optional no-compiled-snippets) @@ -1740,7 +1860,7 @@ Optional USE-JIT use jit-loading of snippets." (defun yas--load-snippet-dirs (&optional nojit) "Reload the directories listed in `yas-snippet-dirs' or - prompt the user to select one." +prompt the user to select one." (let (errors) (if yas-snippet-dirs (dolist (directory (reverse (yas-snippet-dirs))) @@ -1772,7 +1892,7 @@ loading." ;; (when snippet-editing-buffers (if interactive - (if (y-or-n-p "Some buffers editing live snippets, close them and proceed with reload?") + (if (y-or-n-p "Some buffers editing live snippets, close them and proceed with reload? ") (mapc #'kill-buffer snippet-editing-buffers) (yas--message 1 "Aborted reload...") (throw 'abort nil)) @@ -1780,21 +1900,26 @@ loading." ;; `yas--editing-template' to nil, make it guess it next time around (mapc #'(lambda (buffer) (setq yas--editing-template nil)) (buffer-list)))) - ;; Empty all snippet tables, parenting info and all menu tables + ;; Empty all snippet tables and parenting info ;; (setq yas--tables (make-hash-table)) (setq yas--parents (make-hash-table)) + (setq yas--ancestors (make-hash-table)) + + ;; Before killing `yas--menu-table' use its keys to cleanup the + ;; mode menu parts of `yas--minor-mode-menu' (thus also cleaning + ;; up `yas-minor-mode-map', which points to it) + ;; + (maphash #'(lambda (menu-symbol keymap) + (define-key yas--minor-mode-menu (vector menu-symbol) nil)) + yas--menu-table) + ;; Now empty `yas--menu-table' as well (setq yas--menu-table (make-hash-table)) ;; Cancel all pending 'yas--scheduled-jit-loads' ;; (setq yas--scheduled-jit-loads (make-hash-table)) - ;; Init the `yas-minor-mode-map', taking care not to break the - ;; menu.... - ;; - (setcdr yas-minor-mode-map (cdr (yas--init-minor-keymap))) - ;; Reload the directories listed in `yas-snippet-dirs' or prompt ;; the user to select one. ;; @@ -1802,22 +1927,19 @@ loading." ;; Reload the direct keybindings ;; (yas-direct-keymaps-reload) - ;; Reload the trigger-key (shoudn't be needed, but see issue #237) - ;; - (yas--trigger-key-reload) (yas--message 3 "Reloaded everything%s...%s." (if interactive "" " (snippets will load just-in-time)") (if errors " (some errors, check *Messages*)" ""))))) (defun yas--load-pending-jits () - (when yas-minor-mode - (dolist (mode (yas--modes-to-activate)) - (let ((forms (gethash mode yas--scheduled-jit-loads))) - (dolist (form forms) - (yas--message 3 "Loading for `%s', just-in-time: %s!" mode form) - (eval form)) - (remhash mode yas--scheduled-jit-loads))))) + (dolist (mode (yas--modes-to-activate)) + (let ((forms (reverse (gethash mode yas--scheduled-jit-loads)))) + ;; must reverse to maintain coherence with `yas-snippet-dirs' + (dolist (form forms) + (yas--message 3 "Loading for `%s', just-in-time: %s!" mode form) + (eval form)) + (remhash mode yas--scheduled-jit-loads)))) ;; (when (<= emacs-major-version 22) ;; (add-hook 'after-change-major-mode-hook 'yas--load-pending-jits)) @@ -1831,11 +1953,11 @@ foo\"bar\\! -> \"foo\\\"bar\\\\!\"" string t) "\"")) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;;; Snippet compilation function (defun yas--initialize () - "For backward compatibility, enable `yas-minor-mode' globally" + "For backward compatibility, enable `yas-minor-mode' globally." (yas-global-mode 1)) (defun yas-compile-directory (top-level-dir) @@ -1844,49 +1966,50 @@ foo\"bar\\! -> \"foo\\\"bar\\\\!\"" This works by stubbing a few functions, then calling `yas-load-directory'." (interactive "DTop level snippet directory?") - (flet ((yas--load-yas-setup-file - (file) - (let ((elfile (concat file ".el"))) - (when (file-exists-p elfile) - (insert ";;; .yas-setup.el support file if any:\n;;;\n") - (insert-file-contents elfile) - (end-of-buffer) - ))) - (yas-define-snippets - (mode snippets) - (insert ";;; Snippet definitions:\n;;;\n") - (let ((literal-snippets (list)) - (print-length nil)) - (dolist (snippet snippets) - (let ((key (first snippet)) - (template-content (second snippet)) - (name (third snippet)) - (condition (fourth snippet)) - (group (fifth snippet)) - (expand-env (sixth snippet)) - (file nil) ;; (seventh snippet)) ;; omit on purpose - (binding (eighth snippet)) - (uuid (ninth snippet))) - (push `(,key - ,template-content - ,name - ,condition - ,group - ,expand-env - ,file - ,binding - ,uuid) - literal-snippets))) - (insert (pp-to-string `(yas-define-snippets ',mode ',literal-snippets))) - (insert "\n\n"))) - (yas--load-directory-1 - (dir mode parents &rest ignore) - (let ((output-file (concat (file-name-as-directory dir) ".yas-compiled-snippets.el"))) - (with-temp-file output-file - (insert (format ";;; Compiled snippets and support files for `%s'\n" mode)) - (yas--load-directory-2 dir mode) - (insert (format ";;; Do not edit! File generated at %s\n" (current-time-string))))))) - (yas-load-directory top-level-dir nil))) + (yas--with-temporary-redefinitions + ((yas--load-yas-setup-file + (file) + (let ((elfile (concat file ".el"))) + (when (file-exists-p elfile) + (insert ";;; .yas-setup.el support file if any:\n;;;\n") + (insert-file-contents elfile) + (goto-char (point-max)) + ))) + (yas-define-snippets + (mode snippets) + (insert ";;; Snippet definitions:\n;;;\n") + (let ((literal-snippets (list)) + (print-length nil)) + (dolist (snippet snippets) + (let ((key (first snippet)) + (template-content (second snippet)) + (name (third snippet)) + (condition (fourth snippet)) + (group (fifth snippet)) + (expand-env (sixth snippet)) + (file nil) ;; (seventh snippet)) ;; omit on purpose + (binding (eighth snippet)) + (uuid (ninth snippet))) + (push `(,key + ,template-content + ,name + ,condition + ,group + ,expand-env + ,file + ,binding + ,uuid) + literal-snippets))) + (insert (pp-to-string `(yas-define-snippets ',mode ',literal-snippets))) + (insert "\n\n"))) + (yas--load-directory-1 + (dir mode parents &rest ignore) + (let ((output-file (concat (file-name-as-directory dir) ".yas-compiled-snippets.el"))) + (with-temp-file output-file + (insert (format ";;; Compiled snippets and support files for `%s'\n" mode)) + (yas--load-directory-2 dir mode) + (insert (format ";;; Do not edit! File generated at %s\n" (current-time-string))))))) + (yas-load-directory top-level-dir nil))) (defun yas-recompile-all () "Compile every dir in `yas-snippet-dirs'." @@ -1907,9 +2030,8 @@ This works by stubbing a few functions, then calling yas--scheduled-jit-loads)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;;; Some user level functions -;;; (defun yas-about () (interactive) @@ -1917,87 +2039,6 @@ This works by stubbing a few functions, then calling yas--version ") -- pluskid /joaotavora "))) -(defun yas--define-parents (mode parents) - "Add PARENTS to the list of MODE's parents" - (puthash mode (remove-duplicates - (append parents - (gethash mode yas--parents))) - yas--parents)) - -(defun yas-define-snippets (mode snippets) - "Define SNIPPETS for MODE. - -SNIPPETS is a list of snippet definitions, each taking the -following form - - (KEY TEMPLATE NAME CONDITION GROUP EXPAND-ENV FILE KEYBINDING UUID) - -Within these, only KEY and TEMPLATE are actually mandatory. - -TEMPLATE might be a lisp form or a string, depending on whether -this is a snippet or a snippet-command. - -CONDITION, EXPAND-ENV and KEYBINDING are lisp forms, they have -been `yas--read-lisp'-ed and will eventually be -`yas--eval-lisp'-ed. - -The remaining elements are strings. - -FILE is probably of very little use if you're programatically -defining snippets. - -UUID is the snippets \"unique-id\". Loading a second snippet file -with the same uuid replaced the previous snippet. - -You can use `yas--parse-template' to return such lists based on -the current buffers contents." - (let ((snippet-table (yas--table-get-create mode)) - (template nil)) - (dolist (snippet snippets) - (setq template (yas-define-snippets-1 snippet - snippet-table))) - template)) - -(defun yas-define-snippets-1 (snippet snippet-table) - "Helper for `yas-define-snippets'." - ;; X) Calculate some more defaults on the values returned by - ;; `yas--parse-template'. - ;; - (let* ((file (seventh snippet)) - (key (car snippet)) - (name (or (third snippet) - (and file - (file-name-directory file)))) - (condition (fourth snippet)) - (group (fifth snippet)) - (keybinding (yas--read-keybinding (eighth snippet))) - (uuid (or (ninth snippet) - name)) - (template (or (gethash uuid (yas--table-uuidhash snippet-table)) - (yas--make-blank-template)))) - ;; X) populate the template object - ;; - (yas--populate-template template - :table snippet-table - :key key - :content (second snippet) - :name (or name key) - :group group - :condition condition - :expand-env (sixth snippet) - :file (seventh snippet) - :keybinding keybinding - :uuid uuid) - ;; X) Update this template in the appropriate table. This step - ;; also will take care of adding the key indicators in the - ;; templates menu entry, if any - ;; - (yas--update-template snippet-table template) - ;; X) Return the template - ;; - ;; - template)) - ;;; Apropos snippet menu: ;; @@ -2033,7 +2074,7 @@ the current buffers contents." (defun yas--template-menu-binding-pair-get-create (template &optional type) "Get TEMPLATE's menu binding or assign it a new one. -TYPE may be `:stay', signalling this menu binding should be +TYPE may be `:stay', signaling this menu binding should be static in the menu." (or (yas--template-menu-binding-pair template) (let ((key (yas--template-key template)) @@ -2086,25 +2127,24 @@ static in the menu." (rest keymap)))) (defun yas-define-menu (mode menu &optional omit-items) - "Define a snippet menu for MODE according to MENU, ommitting OMIT-ITEMS. + "Define a snippet menu for MODE according to MENU, omitting OMIT-ITEMS. MENU is a list, its elements can be: - (yas-item UUID) : Creates an entry the snippet identified with - UUID. The menu entry for a snippet thus identified is + UUID. The menu entry for a snippet thus identified is permanent, i.e. it will never move (be reordered) in the menu. - (yas-separator) : Creates a separator - (yas-submenu NAME SUBMENU) : Creates a submenu with NAME, - SUBMENU has the same form as MENU. NAME is also added to the + SUBMENU has the same form as MENU. NAME is also added to the list of groups of the snippets defined thereafter. OMIT-ITEMS is a list of snippet uuid's that will always be -ommited from MODE's menu, even if they're manually loaded. +omitted from MODE's menu, even if they're manually loaded. -This function does nothing if `yas-use-menu' is nil. -" +This function does nothing if `yas-use-menu' is nil." (when yas-use-menu (let* ((table (yas--table-get-create mode)) (hash (yas--table-uuidhash table))) @@ -2122,6 +2162,7 @@ This function does nothing if `yas-use-menu' is nil. (setf (yas--template-menu-binding-pair template) (cons nil :none))))))) (defun yas--define-menu-1 (table menu-keymap menu uuidhash &optional group-list) + "Helper for `yas-define-menu'." (dolist (e (reverse menu)) (cond ((eq (first e) 'yas-item) (let ((template (or (gethash (second e) uuidhash) @@ -2159,8 +2200,9 @@ will only be expanded when the condition evaluated to non-nil." (list (list key template name condition group)))) (defun yas-hippie-try-expand (first-time?) - "Integrate with hippie expand. Just put this function in -`hippie-expand-try-functions-list'." + "Integrate with hippie expand. + +Just put this function in `hippie-expand-try-functions-list'." (when yas-minor-mode (if (not first-time?) (let ((yas-fallback-behavior 'return-nil)) @@ -2247,7 +2289,7 @@ If expansion fails, execute the previous binding for this key" "Expand one of TEMPLATES from START to END. Prompt the user if TEMPLATES has more than one element, else -expand immediately. Common gateway for +expand immediately. Common gateway for `yas-expand-from-trigger-key' and `yas-expand-from-keymap'." (let ((yas--current-template (or (and (rest templates) ;; more than one (yas--prompt-for-template (mapcar #'cdr templates))) @@ -2258,6 +2300,13 @@ expand immediately. Common gateway for end (yas--template-expand-env yas--current-template))))) +;; Apropos the trigger key and the fallback binding: +;; +;; When `yas-minor-mode-map' binds , that correctly overrides +;; org-mode's , for example and searching for fallbacks correctly +;; returns `org-cycle'. However, most other modes bind "TAB". TODO, +;; improve this explanation. +;; (defun yas--fallback (&optional from-trigger-key-p) "Fallback after expansion has failed. @@ -2267,26 +2316,11 @@ Common gateway for `yas-expand-from-trigger-key' and ;; return nil nil) ((eq yas-fallback-behavior 'call-other-command) - (let* ((yas-minor-mode nil) - (yas--direct-keymaps nil) - (keys-1 (this-command-keys-vector)) - (keys-2 (and yas-trigger-key - from-trigger-key-p - (stringp yas-trigger-key) - (read-kbd-macro yas-trigger-key))) - (command-1 (and keys-1 (key-binding keys-1))) - (command-2 (and keys-2 (key-binding keys-2))) - ;; An (ugly) safety: prevents infinite recursion of - ;; yas-expand* calls. - (command (or (and (symbolp command-1) - (not (string-match "yas-expand" (symbol-name command-1))) - command-1) - (and (symbolp command-2) - command-2)))) - (when (and (commandp command) - (not (string-match "yas-expand" (symbol-name command)))) - (setq this-command command) - (call-interactively command)))) + (let* ((beyond-yasnippet (yas--keybinding-beyond-yasnippet))) + (yas--message 4 "Falling back to %s" beyond-yasnippet) + (assert (or (null beyond-yasnippet) (commandp beyond-yasnippet))) + (setq this-original-command beyond-yasnippet) + (call-interactively beyond-yasnippet))) ((and (listp yas-fallback-behavior) (cdr yas-fallback-behavior) (eq 'apply (car yas-fallback-behavior))) @@ -2300,12 +2334,43 @@ Common gateway for `yas-expand-from-trigger-key' and ;; also return nil if all the other fallbacks have failed nil))) +(defun yas--keybinding-beyond-yasnippet () + "Returns the " + (let* ((yas-minor-mode nil) + (yas--direct-keymaps nil) + (keys (this-single-command-keys))) + (or (key-binding keys t) + (key-binding (yas--fallback-translate-input keys) t)))) + +(defun yas--fallback-translate-input (keys) + "Emulate `read-key-sequence', at least what I think it does. + +Keys should be an untranslated key vector. Returns a translated +vector of keys. FIXME not thoroughly tested" + (let ((retval []) + (i 0)) + (while (< i (length keys)) + (let ((j i) + (translated local-function-key-map)) + (while (and (< j (length keys)) + translated + (keymapp translated)) + (setq translated (cdr (assoc (aref keys j) (remove 'keymap translated))) + j (1+ j))) + (setq retval (vconcat retval (cond ((symbolp translated) + `[,translated]) + ((vectorp translated) + translated) + (t + (substring keys i j))))) + (setq i j))) + retval)) ;;; Utils for snippet development: (defun yas--all-templates (tables) - "Return all snippet tables applicable for the current buffer. + "Get `yas--template' objects in TABLES, applicable for buffer and point. Honours `yas-choose-tables-first', `yas-choose-keys-first' and `yas-buffer-local-condition'" @@ -2367,6 +2432,7 @@ visited file in `snippet-mode'." (message "No snippets tables active!")))) (defun yas--visit-snippet-file-1 (template) + "Helper for `yas-visit-snippet-file'." (let ((file (yas--template-file template))) (cond ((and file (file-readable-p file)) (find-file-other-window file) @@ -2407,7 +2473,7 @@ visited file in `snippet-mode'." "Try to guess suitable directories based on the current active tables (or optional TABLE). -Returns a list of elemts (TABLE . DIRS) where TABLE is a +Returns a list of elements (TABLE . DIRS) where TABLE is a `yas--table' object and DIRS is a list of all possible directories where snippets of table might exist." (let ((main-dir (replace-regexp-in-string @@ -2431,7 +2497,7 @@ where snippets of table might exist." tables))) (defun yas--make-directory-maybe (table-and-dirs &optional main-table-string) - "Returns a dir inside TABLE-AND-DIRS, prompts for creation if none exists." + "Returns a dir inside TABLE-AND-DIRS, prompts for creation if none exists." (or (some #'(lambda (dir) (when (file-directory-p dir) dir)) (cdr table-and-dirs)) (let ((candidate (first (cdr table-and-dirs)))) (unless (file-writable-p (file-name-directory candidate)) @@ -2466,19 +2532,13 @@ NO-TEMPLATE is non-nil." (set (make-local-variable 'yas--guessed-modes) (mapcar #'(lambda (d) (yas--table-mode (car d))) guessed-directories)) - (unless no-template (yas-expand-snippet "\ -# -*- mode: snippet -*- -# name: $1 -# key: ${2:${1:$(replace-regexp-in-string \"\\\\\\\\(\\\\\\\\w+\\\\\\\\).*\" \"\\\\\\\\1\" yas-text)}}${3: -# binding: ${4:direct-keybinding}}${5: -# expand-env: ((${6:some-var} ${7:some-value}))}${8: -# type: command} -# -- -$0")))) + (if (and (not no-template) yas-new-snippet-default) + (yas-expand-snippet yas-new-snippet-default)))) (defun yas--compute-major-mode-and-parents (file) - "Given FILE, find the nearest snippet directory for a given -mode, then return a list (MODE-SYM PARENTS), the mode's symbol and a list + "Given FILE, find the nearest snippet directory for a given mode. + +Returns a list (MODE-SYM PARENTS), the mode's symbol and a list representing one or more of the mode's parents. Note that MODE-SYM need not be the symbol of a real major mode, @@ -2503,10 +2563,10 @@ neither do the elements of PARENTS." (buffer-substring-no-properties (point-min) (point-max)))))))) (when major-mode-sym - (cons major-mode-sym parents)))) + (cons major-mode-sym (remove major-mode-sym parents))))) (defvar yas--editing-template nil - "Supporting variable for `yas-load-snippet-buffer' and `yas--visit-snippet'") + "Supporting variable for `yas-load-snippet-buffer' and `yas--visit-snippet'.") (defvar yas--current-template nil "Holds the current template being expanded into a snippet.") @@ -2514,18 +2574,41 @@ neither do the elements of PARENTS." (defvar yas--guessed-modes nil "List of guessed modes supporting `yas-load-snippet-buffer'.") -(defun yas-load-snippet-buffer (&optional kill) - "Parse and load current buffer's snippet definition. - -With optional prefix argument KILL quit the window and buffer." - (interactive "P") +(defun yas--read-table () + "Ask user for a snippet table, help with some guessing." + (let ((prompt (if (and (featurep 'ido) + ido-mode) + 'ido-completing-read 'completing-read))) + (unless yas--guessed-modes + (set (make-local-variable 'yas--guessed-modes) + (or (yas--compute-major-mode-and-parents buffer-file-name)))) + (intern + (funcall prompt (format "Choose or enter a table (yas guesses %s): " + (if yas--guessed-modes + (first yas--guessed-modes) + "nothing")) + (mapcar #'symbol-name yas--guessed-modes) + nil + nil + nil + nil + (if (first yas--guessed-modes) + (symbol-name (first yas--guessed-modes))))))) + +(defun yas-load-snippet-buffer (table &optional interactive) + "Parse and load current buffer's snippet definition into TABLE. + +TABLE is a symbol naming a passed to `yas--table-get-create'. + +When called interactively, prompt for the table name and +whether (and where) to save the snippet, then quit the window." + (interactive (list (yas--read-table) t)) (cond - ;; We have `yas--editing-template', this buffer's - ;; content comes from a template which is already loaded and - ;; neatly positioned,... + ;; We have `yas--editing-template', this buffer's content comes from a + ;; template which is already loaded and neatly positioned,... ;; (yas--editing-template - (yas-define-snippets-1 (yas--parse-template (yas--template-file yas--editing-template)) + (yas--define-snippets-1 (yas--parse-template (yas--template-file yas--editing-template)) (yas--template-table yas--editing-template))) ;; Try to use `yas--guessed-modes'. If we don't have that use the ;; value from `yas--compute-major-mode-and-parents' @@ -2533,61 +2616,43 @@ With optional prefix argument KILL quit the window and buffer." (t (unless yas--guessed-modes (set (make-local-variable 'yas--guessed-modes) (or (yas--compute-major-mode-and-parents buffer-file-name)))) - (let* ((prompt (if (and (featurep 'ido) - ido-mode) - 'ido-completing-read 'completing-read)) - (table (yas--table-get-create - (intern - (funcall prompt (format "Choose or enter a table (yas guesses %s): " - (if yas--guessed-modes - (first yas--guessed-modes) - "nothing")) - (mapcar #'symbol-name yas--guessed-modes) - nil - nil - nil - nil - (if (first yas--guessed-modes) - (symbol-name (first yas--guessed-modes)))))))) + (let* ((table (yas--table-get-create table))) (set (make-local-variable 'yas--editing-template) - (yas-define-snippets-1 (yas--parse-template buffer-file-name) + (yas--define-snippets-1 (yas--parse-template buffer-file-name) table))))) - ;; Now, offer to save this iff: - ;; - ;; 1) `yas-snippet-dirs' is a list and its first element does not - ;; match this template's file (i.e. this is a library snippet, not - ;; a user snippet) OR - ;; - ;; 2) yas--editing-template comes from a file that we cannot write to... - ;; - (when (or (not (yas--template-file yas--editing-template)) - (not (file-writable-p (yas--template-file yas--editing-template))) - (and (listp yas-snippet-dirs) - (second yas-snippet-dirs) - (not (string-match (expand-file-name (first yas-snippet-dirs)) - (yas--template-file yas--editing-template))))) - - (when (y-or-n-p (yas--format "Looks like a library or new snippet. Save to new file? ")) - (let* ((option (first (yas--guess-snippet-directories (yas--template-table yas--editing-template)))) - (chosen (and option - (yas--make-directory-maybe option)))) - (when chosen - (let ((default-file-name (or (and (yas--template-file yas--editing-template) - (file-name-nondirectory (yas--template-file yas--editing-template))) - (yas--template-name yas--editing-template)))) - (write-file (concat chosen "/" - (read-from-minibuffer (format "File name to create in %s? " chosen) - default-file-name))) - (setf (yas--template-file yas--editing-template) buffer-file-name)))))) - (when kill - (quit-window kill)) - (yas--message 3 "Snippet \"%s\" loaded for %s." - (yas--template-name yas--editing-template) - (yas--table-name (yas--template-table yas--editing-template)))) + (when (and interactive + (or + ;; Only offer to save this if it looks like a library or new + ;; snippet (loaded from elisp, from a dir in `yas-snippet-dirs' + ;; which is not the first, or from an unwritable file) + ;; + (not (yas--template-file yas--editing-template)) + (not (file-writable-p (yas--template-file yas--editing-template))) + (and (listp yas-snippet-dirs) + (second yas-snippet-dirs) + (not (string-match (expand-file-name (first yas-snippet-dirs)) + (yas--template-file yas--editing-template))))) + (y-or-n-p (yas--format "Looks like a library or new snippet. Save to new file? "))) + (let* ((option (first (yas--guess-snippet-directories (yas--template-table yas--editing-template)))) + (chosen (and option + (yas--make-directory-maybe option)))) + (when chosen + (let ((default-file-name (or (and (yas--template-file yas--editing-template) + (file-name-nondirectory (yas--template-file yas--editing-template))) + (yas--template-name yas--editing-template)))) + (write-file (concat chosen "/" + (read-from-minibuffer (format "File name to create in %s? " chosen) + default-file-name))) + (setf (yas--template-file yas--editing-template) buffer-file-name))))) + (when interactive + (yas--message 3 "Snippet \"%s\" loaded for %s." + (yas--template-name yas--editing-template) + (yas--table-name (yas--template-table yas--editing-template))) + (quit-window interactive))) (defun yas-tryout-snippet (&optional debug) - "Test current buffers's snippet template in other buffer." + "Test current buffer's snippet template in other buffer." (interactive "P") (let* ((major-mode-and-parent (yas--compute-major-mode-and-parents buffer-file-name)) (parsed (yas--parse-template)) @@ -2611,6 +2676,7 @@ With optional prefix argument KILL quit the window and buffer." (switch-to-buffer (get-buffer-create buffer-name)) (setq buffer-undo-list nil) (condition-case nil (funcall test-mode) (error nil)) + (yas-minor-mode 1) (setq buffer-read-only nil) (yas-expand-snippet (yas--template-content yas--current-template) (point-min) @@ -2622,6 +2688,12 @@ With optional prefix argument KILL quit the window and buffer." (t (yas--message 3 "Cannot test snippet for unknown major mode"))))) +(defun yas-active-keys () + "Return all active trigger keys for current buffer and point." + (remove-duplicates + (remove-if-not #'stringp (mapcan #'yas--table-all-keys (yas--get-snippet-tables))) + :test #'string=)) + (defun yas--template-fine-group (template) (car (last (or (yas--template-group template) (yas--template-perm-group template))))) @@ -2754,16 +2826,21 @@ If found, the content of subexp group SUBEXP (default 0) is (match-string-no-properties grp str) str)))) -(defun yas-choose-value (possibilities) - "Prompt for a string in the list POSSIBILITIES and return it." +(defun yas-choose-value (&rest possibilities) + "Prompt for a string in POSSIBILITIES and return it. + +The last element of POSSIBILITIES may be a list of strings." (unless (or yas-moving-away-p yas-modified-p) + (setq possibilities (nreverse possibilities)) + (setq possibilities (if (listp (car possibilities)) + (append (reverse (car possibilities)) (rest possibilities)) + possibilities)) (some #'(lambda (fn) (funcall fn "Choose: " possibilities)) yas-prompt-functions))) (defun yas-key-to-value (alist) - "Prompt for a string in the list POSSIBILITIES and return it." (unless (or yas-moving-away-p yas-modified-p) (let ((key (read-key-sequence ""))) @@ -2776,7 +2853,7 @@ If found, the content of subexp group SUBEXP (default 0) is (throw 'yas--exception (cons 'yas--exception text))) (defun yas-verify-value (possibilities) - "Verify that the current field value is in POSSIBILITIES + "Verify that the current field value is in POSSIBILITIES. Otherwise throw exception." (when (and yas-moving-away-p (notany #'(lambda (pos) (string= pos yas-text)) possibilities)) @@ -2798,11 +2875,11 @@ Use this in primary and mirror transformations to tget." (not (string= "" yas-text))) yas-text)) -;; (defun yas-selected-text () -;; "Return `yas-selected-text' if that exists and is non-empty, else nil." -;; (if (and yas-selected-text -;; (not (string= "" yas-selected-text))) -;; yas-selected-text)) +(defun yas-selected-text () + "Return `yas-selected-text' if that exists and is non-empty, else nil." + (if (and yas-selected-text + (not (string= "" yas-selected-text))) + yas-selected-text)) (defun yas--get-field-once (number &optional transform-fn) (unless yas-modified-p @@ -2815,6 +2892,7 @@ Use this in primary and mirror transformations to tget." (yas-field-value number))) (defun yas-inside-string () + "Return non-nil if the point is inside a string according to font-lock." (equal 'font-lock-string-face (get-char-property (1- (point)) 'face))) (defun yas-unimplemented (&optional missing-feature) @@ -2832,10 +2910,10 @@ Use this in primary and mirror transformations to tget." "Overlays the currently active field.") (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") + "A prefix argument for expansion direct from keybindings.") (defvar yas-selected-text nil "The selected region deleted on the last snippet expansion.") @@ -2863,7 +2941,16 @@ Use this in primary and mirror transformations to tget." force-exit) (defstruct (yas--field (:constructor yas--make-field (number start end parent-field))) - "A field." + "A field. + +NUMBER is the field number. +START and END are mostly buffer markers, but see \"apropos markers-to-points\". +PARENT-FIELD is a `yas--field' this field is nested under, or nil. +MIRRORS is a list of `yas--mirror's +TRANSFORM is a lisp form. +MODIFIED-P is a boolean set to true once user inputs text. +NEXT is another `yas--field' or `yas--mirror' or `yas--exit'. +" number start end parent-field @@ -2872,12 +2959,20 @@ Use this in primary and mirror transformations to tget." (modified-p nil) next) + (defstruct (yas--mirror (:constructor yas--make-mirror (start end transform))) - "A mirror." + "A mirror. + +START and END are mostly buffer markers, but see \"apropos markers-to-points\". +TRANSFORM is a lisp form. +PARENT-FIELD is a `yas--field' this mirror is nested under, or nil. +NEXT is another `yas--field' or `yas--mirror' or `yas--exit' +DEPTH is a count of how many nested mirrors can affect this mirror" start end (transform nil) parent-field - next) + next + depth) (defstruct (yas--exit (:constructor yas--make-exit (marker))) marker @@ -2908,7 +3003,7 @@ string iff EMPTY-ON-NIL-P is true." transformed)) (defsubst yas--replace-all (from to &optional text) - "Replace all occurance from FROM to TO. + "Replace all occurrences from FROM to TO. With optional string TEXT do it in that string." (if text @@ -2929,9 +3024,11 @@ With optional string TEXT do it in that string." #'yas--snippet-field-compare))) (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 field's start point" + "Compare FIELD1 and FIELD2. + +The field with a number is sorted first. If they both have a +number, compare through the number. If neither have, compare +through the field's start point" (let ((n1 (yas--field-number field1)) (n2 (yas--field-number field2))) (if n1 @@ -2947,7 +3044,7 @@ have, compare through the field's start point" (defun yas--field-probably-deleted-p (snippet field) "Guess if SNIPPET's FIELD should be skipped." (and - ;; field must be zero lentgh + ;; field must be zero length ;; (zerop (- (yas--field-start field) (yas--field-end field))) ;; skip if: @@ -2966,8 +3063,9 @@ have, compare through the field's start point" (not (zerop (yas--field-number field))))) (defun yas--snippets-at-point (&optional all-snippets) - "Return a sorted list of snippets at point, most recently -inserted first." + "Return a sorted list of snippets at point. + +The most recently-inserted snippets are returned first." (sort (remove nil (remove-duplicates (mapcar #'(lambda (ov) (overlay-get ov 'yas--snippet)) @@ -2978,8 +3076,9 @@ inserted first." (<= (yas--snippet-id s2) (yas--snippet-id s1))))) (defun yas-next-field-or-maybe-expand () - "Try to expand a snippet at a key before point, otherwise -delegate to `yas-next-field'." + "Try to expand a snippet at a key before point. + +Otherwise delegate to `yas-next-field'." (interactive) (if yas-triggers-in-field (let ((yas-fallback-behavior 'return-nil) @@ -2990,7 +3089,9 @@ delegate to `yas-next-field'." (yas-next-field))) (defun yas-next-field (&optional arg) - "Navigate to next field. If there's none, exit the snippet." + "Navigate to the ARGth next field. + +If there's none, exit the snippet." (interactive) (let* ((arg (or arg 1)) @@ -3022,7 +3123,7 @@ delegate to `yas-next-field'." nil)))) (defun yas--place-overlays (snippet field) - "Correctly place overlays for SNIPPET's FIELD" + "Correctly place overlays for SNIPPET's FIELD." (yas--make-move-field-protection-overlays snippet field) (yas--make-move-active-field-overlay snippet field)) @@ -3087,12 +3188,13 @@ Also create some protection overlays" `(let ((yas--inhibit-overlay-hooks t)) (progn ,@body))) -(defvar yas-snippet-beg nil "Beginning position of the last snippet commited.") -(defvar yas-snippet-end nil "End position of the last snippet commited.") +(defvar yas-snippet-beg nil "Beginning position of the last snippet committed.") +(defvar yas-snippet-end nil "End position of the last snippet committed.") (defun yas--commit-snippet (snippet) - "Commit SNIPPET, but leave point as it is. This renders the -snippet as ordinary text." + "Commit SNIPPET, but leave point as it is. + +This renders the snippet as ordinary text." (let ((control-overlay (yas--snippet-control-overlay snippet))) ;; @@ -3142,8 +3244,9 @@ snippet as ordinary text." (defun yas--check-commit-snippet () - "Checks if point exited the currently active field of the -snippet, if so cleans up the whole snippet up." + "Checks if point exited the currently active field of the snippet. + +If so cleans up the whole snippet up." (let* ((snippets (yas--snippets-at-point 'all-snippets)) (snippets-left snippets) (snippet-exit-transform)) @@ -3210,8 +3313,9 @@ the original marker object with the position set to nil." (setf (yas--exit-marker snippet-exit) (cons exit (yas--exit-marker snippet-exit))))))) (defun yas--points-to-markers (snippet) - "Convert all cons (POINT . MARKER) in SNIPPET to markers. This -is done by setting MARKER to POINT with `set-marker'." + "Convert all cons (POINT . MARKER) in SNIPPET to markers. + +This is done by setting MARKER to POINT with `set-marker'." (dolist (field (yas--snippet-fields snippet)) (setf (yas--field-start field) (set-marker (cdr (yas--field-start field)) (car (yas--field-start field)))) @@ -3234,11 +3338,11 @@ is done by setting MARKER to POINT with `set-marker'." (<= point (yas--field-end field))))) (defun yas--field-text-for-display (field) - "Return the propertized display text for field FIELD. " + "Return the propertized display text for field FIELD." (buffer-substring (yas--field-start field) (yas--field-end field))) (defun yas--undo-in-progress () - "True if some kind of undo is in progress" + "True if some kind of undo is in progress." (or undo-in-progress (eq this-command 'undo) (eq this-command 'redo))) @@ -3274,9 +3378,9 @@ Otherwise deletes a character normally by calling `delete-char'." (call-interactively 'delete-char))))) (defun yas--skip-and-clear (field) - "Deletes the region of FIELD and sets it modified state to t" + "Deletes the region of FIELD and sets it's modified state to t." ;; Just before skipping-and-clearing the field, mark its children - ;; fields as modified, too. If the childen have mirrors-in-fields + ;; fields as modified, too. If the children have mirrors-in-fields ;; this prevents them from updating erroneously (we're skipping and ;; deleting!). ;; @@ -3316,7 +3420,7 @@ Move the overlay, or create it if it does not exit." '(yas--on-field-overlay-modification)))) (defvar yas--inhibit-overlay-hooks nil - "Bind this temporarity to non-nil to prevent running `yas--on-*-modification'.") + "Bind this temporarily to non-nil to prevent running `yas--on-*-modification'.") (defun yas--on-field-overlay-modification (overlay after? beg end &optional length) "Clears the field and updates mirrors, conditionally. @@ -3356,7 +3460,7 @@ progress." ;; ;; Alternatively, I've experimented with an implementation that ;; commits the snippet before actually calling `this-command' -;; interactively, and then signals an eror, which is ignored. but +;; interactively, and then signals an error, which is ignored. but ;; blocks all other million modification hooks. This presented some ;; problems with stacked expansion. ;; @@ -3393,11 +3497,11 @@ Move the overlays, or create them if they do not exit." (overlay-put ov 'modification-hooks '(yas--on-protection-overlay-modification))))))) (defvar yas--protection-violation nil - "When non-nil, signals attempts to erronesly exit or modify the snippet. + "When non-nil, signals attempts to erroneously exit or modify the snippet. Functions in the `post-command-hook', for example `yas--post-command-handler' can check it and reset its value to -nil. The variables value is the point where the violation +nil. The variables value is the point where the violation originated") (defun yas--on-protection-overlay-modification (overlay after? beg end &optional length) @@ -3413,7 +3517,7 @@ The error should be ignored in `debug-ignored-errors'" (add-to-list 'debug-ignored-errors "^Exit the snippet first!$") -;; Snippet expansion and "stacked" expansion: +;;; Snippet expansion and "stacked" expansion: ;; ;; Stacked expansion is when you try to expand a snippet when already ;; inside a snippet expansion. @@ -3430,7 +3534,7 @@ The error should be ignored in `debug-ignored-errors'" ;; `yas--commit-snippet'. I've tried to mark them with "stacked ;; expansion:". ;; -;; This was thought to be safer in in an undo/redo perpective, but +;; This was thought to be safer in an undo/redo perspective, but ;; maybe the correct implementation is to make the globals ;; `yas--active-field-overlay' and `yas--field-protection-overlays' be ;; snippet-local and be active even while the child snippet is @@ -3442,26 +3546,28 @@ The error should be ignored in `debug-ignored-errors'" "Expand snippet CONTENT at current point. Text between START and END will be deleted before inserting -template. EXPAND-ENV is are let-style variable to value bindings +template. EXPAND-ENV is are let-style variable to value bindings considered when expanding the snippet." (run-hooks 'yas-before-expand-snippet-hook) - ;; If a region is active, set `yas-selected-text' - (setq yas-selected-text - (when (region-active-p) - (prog1 (buffer-substring-no-properties (region-beginning) - (region-end)) - (unless start (setq start (region-beginning)) - (unless end (setq end (region-end))))))) - - (when start - (goto-char start)) - ;; - (let ((to-delete (and start end (buffer-substring-no-properties start end))) - (start (or start (point))) - (end (or end (point))) - snippet) + (let* ((yas-selected-text (or yas-selected-text + (and (region-active-p) + (buffer-substring-no-properties (region-beginning) + (region-end))))) + (start (or start + (and (region-active-p) + (region-beginning)) + (point))) + (end (or end + (and (region-active-p) + (region-end)) + (point))) + (to-delete (and start + end + (buffer-substring-no-properties start end))) + snippet) + (goto-char start) (setq yas--indent-original-column (current-column)) ;; Delete the region to delete, this *does* get undo-recorded. ;; @@ -3501,7 +3607,7 @@ considered when expanding the snippet." (yas--snippet-create (point-min) (point-max))))))) ;; stacked-expansion: This checks for stacked expansion, save the - ;; `yas--previous-active-field' and advance its boudary. + ;; `yas--previous-active-field' and advance its boundary. ;; (let ((existing-field (and yas--active-field-overlay (overlay-buffer yas--active-field-overlay) @@ -3542,8 +3648,7 @@ considered when expanding the snippet." t)))) (defun yas--take-care-of-redo (beg end snippet) - "Commits SNIPPET, which in turn pushes an undo action for -reviving it. + "Commits SNIPPET, which in turn pushes an undo action for reviving it. Meant to exit in the `buffer-undo-list'." ;; slightly optimize: this action is only needed for snippets with @@ -3552,10 +3657,9 @@ Meant to exit in the `buffer-undo-list'." (yas--commit-snippet snippet))) (defun yas--snippet-revive (beg end snippet) - "Revives the SNIPPET and creates a control overlay from BEG to -END. + "Revives SNIPPET and creates a control overlay from BEG to END. -BEG and END are, we hope, the original snippets boudaries. All +BEG and END are, we hope, the original snippets boundaries. All the markers/points exiting existing inside SNIPPET should point to their correct locations *at the time the snippet is revived*. @@ -3654,19 +3758,19 @@ Returns the newly created snippet." This is according to their relative positions in the buffer, and has to be called before the $-constructs are deleted." - (flet ((yas--fom-set-next-fom (fom nextfom) - (cond ((yas--field-p fom) - (setf (yas--field-next fom) nextfom)) - ((yas--mirror-p fom) - (setf (yas--mirror-next fom) nextfom)) - (t - (setf (yas--exit-next fom) nextfom)))) - (yas--compare-fom-begs (fom1 fom2) - (if (= (yas--fom-start fom2) (yas--fom-start fom1)) - (yas--mirror-p fom2) - (>= (yas--fom-start fom2) (yas--fom-start fom1)))) - (yas--link-foms (fom1 fom2) - (yas--fom-set-next-fom fom1 fom2))) + (labels ((yas--fom-set-next-fom (fom nextfom) + (cond ((yas--field-p fom) + (setf (yas--field-next fom) nextfom)) + ((yas--mirror-p fom) + (setf (yas--mirror-next fom) nextfom)) + (t + (setf (yas--exit-next fom) nextfom)))) + (yas--compare-fom-begs (fom1 fom2) + (if (= (yas--fom-start fom2) (yas--fom-start fom1)) + (yas--mirror-p fom2) + (>= (yas--fom-start fom2) (yas--fom-start fom1)))) + (yas--link-foms (fom1 fom2) + (yas--fom-set-next-fom fom1 fom2))) ;; make some yas--field, yas--mirror and yas--exit soup (let ((soup)) (when (yas--snippet-exit snippet) @@ -3684,8 +3788,8 @@ has to be called before the $-constructs are deleted." (defun yas--calculate-mirrors-in-fields (snippet mirror) "Attempt to assign a parent field of SNIPPET to the mirror MIRROR. -Use the tighest containing field if more than one field contains -the mirror. Intended to be called *before* the dollar-regions are +Use the tightest containing field if more than one field contains +the mirror. Intended to be called *before* the dollar-regions are deleted." (let ((min (point-min)) (max (point-max))) @@ -3709,10 +3813,8 @@ If it does, also: field Also, if FOM is an exit-marker, always call -`yas--advance-start-maybe' on its next fom. This is beacuse -exit-marker have identical start and end markers. - -" +`yas--advance-start-maybe' on its next fom. This is because +exit-marker have identical start and end markers." (cond ((and fom (< (yas--fom-end fom) newend)) (set-marker (yas--fom-end fom) newend) (yas--advance-start-maybe (yas--fom-next fom) newend) @@ -3740,7 +3842,12 @@ next FOM. Works its way up recursively for parents of parents." (defvar yas--dollar-regions nil "When expanding the snippet the \"parse-create\" functions add - cons cells to this var") +cons cells to this var.") + +(defvar yas--backquote-markers-and-strings nil + "List of (MARKER . STRING) marking where the values from +backquoted lisp expressions should be inserted at the end of +expansion.") (defun yas--snippet-parse-create (snippet) "Parse a recently inserted snippet template, creating all @@ -3751,17 +3858,15 @@ Meant to be called in a narrowed buffer, does various passes" ;; Reset the yas--dollar-regions ;; (setq yas--dollar-regions nil) - ;; protect escaped quote, backquotes and backslashes + ;; protect just the backquotes ;; - (yas--protect-escapes nil `(?\\ ?` ?')) + (yas--protect-escapes nil '(?`)) ;; replace all backquoted expressions ;; (goto-char parse-start) - (yas--replace-backquotes) - ;; protect escapes again since previous steps might have generated - ;; more characters needing escaping + (yas--save-backquotes) + ;; protect escaped characters ;; - (goto-char parse-start) (yas--protect-escapes) ;; parse fields with {} ;; @@ -3781,6 +3886,9 @@ Meant to be called in a narrowed buffer, does various passes" ;; Delete $-constructs ;; (yas--delete-regions yas--dollar-regions) + ;; restore backquoted expression values + ;; + (yas--restore-backquotes) ;; restore escapes ;; (goto-char parse-start) @@ -3794,8 +3902,7 @@ Meant to be called in a narrowed buffer, does various passes" (yas--indent snippet))) (defun yas--indent-according-to-mode (snippet-markers) - "Indent current line according to mode, preserving -SNIPPET-MARKERS." + "Indent current line according to mode, preserving SNIPPET-MARKERS." ;;; Apropos indenting problems.... ;; ;; `indent-according-to-mode' uses whatever `indent-line-function' @@ -3912,15 +4019,33 @@ With optional string TEXT do it in string instead of the buffer." (or escaped yas--escaped-characters)) changed-text)) -(defun yas--replace-backquotes () - "Replace all the \"`(lisp-expression)`\"-style expression - with their evaluated value" +(defun yas--save-backquotes () + "Save all the \"`(lisp-expression)`\"-style expressions +with their evaluated value into `yas--backquote-markers-and-strings'." (while (re-search-forward yas--backquote-lisp-expression-regexp nil t) - (let ((current-string (match-string 1)) transformed) + (let ((current-string (match-string-no-properties 1)) transformed) (delete-region (match-beginning 0) (match-end 0)) - (setq transformed (yas--eval-lisp (yas--read-lisp (yas--restore-escapes current-string)))) + (setq transformed (yas--eval-lisp (yas--read-lisp (yas--restore-escapes current-string '(?`))))) (goto-char (match-beginning 0)) - (when transformed (insert transformed))))) + (when transformed + (let ((marker (make-marker))) + (insert "Y") ;; quite horrendous, I love it :) + (set-marker marker (point)) + (insert "Y") + (push (cons marker transformed) yas--backquote-markers-and-strings)))))) + +(defun yas--restore-backquotes () + "Replace markers in `yas--backquote-markers-and-strings' with their values." + (while yas--backquote-markers-and-strings + (let* ((marker-and-string (pop yas--backquote-markers-and-strings)) + (marker (car marker-and-string)) + (string (cdr marker-and-string))) + (save-excursion + (goto-char marker) + (delete-char -1) + (insert string) + (delete-char 1) + (set-marker marker nil))))) (defun yas--scan-sexps (from count) (condition-case err @@ -3930,13 +4055,13 @@ With optional string TEXT do it in string instead of the buffer." nil))) (defun yas--make-marker (pos) - "Create a marker at POS with `nil' `marker-insertion-type'" + "Create a marker at POS with nil `marker-insertion-type'." (let ((marker (set-marker (make-marker) pos))) (set-marker-insertion-type marker nil) marker)) (defun yas--field-parse-create (snippet &optional parent-field) - "Parse most field expressions, except for the simple one \"$n\". + "Parse most field expressions in SNIPPET, except for the simple one \"$n\". The following count as a field: @@ -3981,7 +4106,7 @@ When multiple expressions are found, only the last one counts." (goto-char (point-min)) (yas--field-parse-create snippet brand-new-field))))))) ;; if we entered from a parent field, now search for the - ;; `yas--multi-dollar-lisp-expression-regexp'. THis is used for + ;; `yas--multi-dollar-lisp-expression-regexp'. This is used for ;; primary field transformations ;; (when parent-field @@ -4011,7 +4136,7 @@ When multiple expressions are found, only the last one counts." yas--dollar-regions))))))) (defun yas--transform-mirror-parse-create (snippet) - "Parse the \"${n:$(lisp-expression)}\" mirror transformations." + "Parse the \"${n:$(lisp-expression)}\" mirror transformations in SNIPPET." (while (re-search-forward yas--transform-mirror-regexp nil t) (let* ((real-match-end-0 (yas--scan-sexps (1+ (match-beginning 0)) 1)) (number (string-to-number (match-string-no-properties 1))) @@ -4034,7 +4159,7 @@ When multiple expressions are found, only the last one counts." (push (cons (match-beginning 0) real-match-end-0) yas--dollar-regions))))) (defun yas--simple-mirror-parse-create (snippet) - "Parse the simple \"$n\" fields/mirrors/exitmarkers." + "Parse the simple \"$n\" fields/mirrors/exitmarkers in SNIPPET." (while (re-search-forward yas--simple-mirror-regexp nil t) (let ((number (string-to-number (match-string-no-properties 1)))) (cond ((zerop number) @@ -4079,30 +4204,63 @@ When multiple expressions are found, only the last one counts." #'(lambda (r1 r2) (>= (car r1) (car r2)))))) +(defun yas--calculate-mirror-depth (mirror &optional traversed) + (let* ((parent (yas--mirror-parent-field mirror)) + (parents-mirrors (and parent + (yas--field-mirrors parent)))) + (or (yas--mirror-depth mirror) + (setf (yas--mirror-depth mirror) + (cond ((memq mirror traversed) + 0) + ((and parent parents-mirrors) + (1+ (reduce #'max + (mapcar #'(lambda (m) + (yas--calculate-mirror-depth m + (cons mirror + traversed))) + parents-mirrors)))) + (parent + 1) + (t + 0)))))) + (defun yas--update-mirrors (snippet) "Updates all the mirrors of SNIPPET." (save-excursion - (let* ((fields (copy-list (yas--snippet-fields snippet))) - (field (car fields))) - (while field - (dolist (mirror (yas--field-mirrors field)) - (let ((mirror-parent-field (yas--mirror-parent-field mirror))) - ;; updatte this mirror - ;; - (yas--mirror-update-display mirror field) - ;; for mirrors-in-fields: schedule a possible - ;; parent field for reupdting later on - ;; - (when mirror-parent-field - (add-to-list 'fields mirror-parent-field 'append #'eq)) - ;; `yas--place-overlays' is needed if the active field and - ;; protected overlays have been changed because of insertions - ;; in `yas--mirror-update-display' - ;; - (when (eq field (yas--snippet-active-field snippet)) - (yas--place-overlays snippet field)))) - (setq fields (cdr fields)) - (setq field (car fields)))))) + (dolist (field-and-mirror (sort + ;; make a list of ((F1 . M1) (F1 . M2) (F2 . M3) (F2 . M4) ...) + ;; where F is the field that M is mirroring + ;; + (mapcan #'(lambda (field) + (mapcar #'(lambda (mirror) + (cons field mirror)) + (yas--field-mirrors field))) + (yas--snippet-fields snippet)) + ;; then sort this list so that entries with mirrors with parent + ;; fields appear before. This was important for fixing #290, and + ;; luckily also handles the case where a mirror in a field causes + ;; another mirror to need reupdating + ;; + #'(lambda (field-and-mirror1 field-and-mirror2) + (> (yas--calculate-mirror-depth (cdr field-and-mirror1)) + (yas--calculate-mirror-depth (cdr field-and-mirror2)))))) + (let* ((field (car field-and-mirror)) + (mirror (cdr field-and-mirror)) + (parent-field (yas--mirror-parent-field mirror))) + ;; before updating a mirror with a parent-field, maybe advance + ;; its start (#290) + ;; + (when parent-field + (yas--advance-start-maybe mirror (yas--fom-start parent-field))) + ;; update this mirror + ;; + (yas--mirror-update-display mirror field) + ;; `yas--place-overlays' is needed if the active field and + ;; protected overlays have been changed because of insertions + ;; in `yas--mirror-update-display' + ;; + (when (eq field (yas--snippet-active-field snippet)) + (yas--place-overlays snippet field)))))) (defun yas--mirror-update-display (mirror field) "Update MIRROR according to FIELD (and mirror transform)." @@ -4126,7 +4284,7 @@ When multiple expressions are found, only the last one counts." (yas--advance-end-of-parents-maybe mirror-parent-field (point)))))) (defun yas--field-update-display (field snippet) - "Much like `yas--mirror-update-display', but for fields" + "Much like `yas--mirror-update-display', but for fields." (when (yas--field-transform field) (let ((transformed (and (not (eq (yas--field-number field) 0)) (yas--apply-transform field field))) @@ -4146,7 +4304,7 @@ When multiple expressions are found, only the last one counts." ;;; Post-command hook: - +;; (defun yas--post-command-handler () "Handles various yasnippet conditions after each command." (cond (yas--protection-violation @@ -4179,18 +4337,17 @@ When multiple expressions are found, only the last one counts." (put 'yas-expand 'function-documentation '(yas--expand-from-trigger-key-doc)) (defun yas--expand-from-trigger-key-doc () - "A doc synthethizer for `yas--expand-from-trigger-key-doc'." + "A doc synthesizer for `yas--expand-from-trigger-key-doc'." (let ((fallback-description (cond ((eq yas-fallback-behavior 'call-other-command) - (let* ((yas-minor-mode nil) - (fallback (key-binding (read-kbd-macro yas-trigger-key)))) + (let* ((fallback (yas--keybinding-beyond-yasnippet))) (or (and fallback (format " call command `%s'." (pp-to-string fallback))) - " do nothing."))) + " do nothing (`yas-expand' doesn't shadow\nanything)"))) ((eq yas-fallback-behavior 'return-nil) ", do nothing.") (t - ", defer to `yas--fallback-behaviour' :-)")))) + ", defer to `yas-fallback-behaviour' (which see)")))) (concat "Expand a snippet before point. If no snippet expansion is possible," fallback-description @@ -4199,7 +4356,7 @@ object satisfying `yas--field-p' to restrict the expansion to."))) (put 'yas-expand-from-keymap 'function-documentation '(yas--expand-from-keymap-doc)) (defun yas--expand-from-keymap-doc () - "A doc synthethizer for `yas--expand-from-keymap-doc'." + "A doc synthesizer for `yas--expand-from-keymap-doc'." (add-hook 'temp-buffer-show-hook 'yas--snippet-description-finish-runonce) (concat "Expand/run snippets from keymaps, possibly falling back to original binding.\n" (when (eq this-command 'describe-key) @@ -4264,8 +4421,9 @@ Remaining args as in `yas-expand-snippet'." "Log level for `yas--message' 4 means trace most anything, 0 means nothing.") (defun yas--message (level message &rest args) + "When LEVEL is above `yas-verbosity-level', log MESSAGE and ARGS." (when (> yas-verbosity level) - (message (apply #'yas--format message args)))) + (message "%s" (apply #'yas--format message args)))) (defun yas--format (format-control &rest format-args) (apply #'format (concat "[yas] " format-control) format-args)) @@ -4361,7 +4519,8 @@ handle the end-of-buffer error fired in it by calling (define-key (symbol-value (make-local-variable 'yas-keymap)) k 'self-insert-command)))) -;;; Backward compatibility to to yasnippet <= 0.7 +;;; Backward compatibility to yasnippet <= 0.7 + (defvar yas--exported-syms '(;; `defcustom's ;; yas-snippet-dirs @@ -4369,10 +4528,6 @@ handle the end-of-buffer error fired in it by calling yas-indent-line yas-also-auto-indent-first-line yas-snippet-revival - yas-trigger-key - yas-next-field-key - yas-prev-field-key - yas-skip-and-clear-key yas-triggers-in-field yas-fallback-behavior yas-choose-keys-first @@ -4394,6 +4549,7 @@ handle the end-of-buffer error fired in it by calling yas-after-exit-snippet-hook yas-before-expand-snippet-hook yas-buffer-local-condition + yas-dont-activate ;; prompting functions ;; @@ -4455,6 +4611,7 @@ handle the end-of-buffer error fired in it by calling yas-unimplemented yas-define-condition-cache yas-hippie-try-expand + yas-active-keys ;; debug definitions ;; yas-debug-snippet-vars @@ -4470,9 +4627,17 @@ handle the end-of-buffer error fired in it by calling ;; yas-saving-variables ;; yas-call-with-snippet-dirs ;; yas-with-snippet-dirs -)) +) + "Exported yasnippet symbols. + +i.e. ones that I will try to keep in future yasnippet versions +and ones that other elisp libraries can more or less safely rely +upon.") + +(defvar yas--dont-backport '(yas-active-keys) + "Exported symbols that don't map back to \"yas/*\" variants.") -(dolist (sym yas--exported-syms) +(dolist (sym (set-difference yas--exported-syms yas--dont-backport)) (let ((backported (intern (replace-regexp-in-string "^yas-" "yas/" (symbol-name sym))))) (when (boundp sym) (make-obsolete-variable backported sym "yasnippet 0.8") @@ -4487,4 +4652,5 @@ handle the end-of-buffer error fired in it by calling ;;; yasnippet.el ends here ;; Local Variables: ;; coding: utf-8 +;; byte-compile-warnings: (not cl-functions) ;; End: