From 08607b9bce0f864c677264e257652d92a51e7ab3 Mon Sep 17 00:00:00 2001 From: Nicholas D Steeves Date: Sun, 13 May 2018 16:22:01 -0300 Subject: Import yasnippet_0.13.0.orig.tar.xz [dgit import orig yasnippet_0.13.0.orig.tar.xz] --- .gitignore | 11 + .gitmodules | 0 .travis.yml | 46 + CONTRIBUTING.md | 37 + NEWS | 491 +++ README.mdown | 165 + Rakefile | 130 + doc/.nosearch | 0 doc/faq.org | 96 + doc/images/bg-content-left.png | Bin 0 -> 3275 bytes doc/images/bg-content-right.png | Bin 0 -> 3169 bytes doc/images/bg-content.png | Bin 0 -> 485 bytes doc/images/bg-navigation-item-hover.png | Bin 0 -> 441 bytes doc/images/bg-navigation-item.png | Bin 0 -> 502 bytes doc/images/bg-navigation.png | Bin 0 -> 104 bytes doc/images/body.png | Bin 0 -> 712 bytes doc/images/customization-group.png | Bin 0 -> 60007 bytes doc/images/dropdown-menu.png | Bin 0 -> 31811 bytes doc/images/external.png | Bin 0 -> 165 bytes doc/images/ido-menu.png | Bin 0 -> 58102 bytes doc/images/menu-1.png | Bin 0 -> 68953 bytes doc/images/menu-2.png | Bin 0 -> 60421 bytes doc/images/menu-groups.png | Bin 0 -> 84358 bytes doc/images/menu-parent.png | Bin 0 -> 73275 bytes doc/images/minor-mode-indicator.png | Bin 0 -> 5940 bytes doc/images/x-menu.png | Bin 0 -> 34263 bytes doc/index.org | 47 + doc/nav-menu.html.inc | 16 + doc/org-setup.inc | 11 + doc/snippet-development.org | 461 +++ doc/snippet-expansion.org | 281 ++ doc/snippet-menu.org | 68 + doc/snippet-organization.org | 132 + doc/snippet-reference.org | 12 + doc/stylesheets/manual.css | 70 + doc/yas-doc-helper.el | 223 ++ yasnippet-debug.el | 355 +++ yasnippet-tests.el | 1568 ++++++++++ yasnippet.el | 5138 +++++++++++++++++++++++++++++++ 39 files changed, 9358 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md create mode 100644 NEWS create mode 100644 README.mdown create mode 100644 Rakefile create mode 100644 doc/.nosearch create mode 100644 doc/faq.org create mode 100644 doc/images/bg-content-left.png create mode 100644 doc/images/bg-content-right.png create mode 100644 doc/images/bg-content.png create mode 100644 doc/images/bg-navigation-item-hover.png create mode 100644 doc/images/bg-navigation-item.png create mode 100644 doc/images/bg-navigation.png create mode 100644 doc/images/body.png create mode 100644 doc/images/customization-group.png create mode 100644 doc/images/dropdown-menu.png create mode 100644 doc/images/external.png create mode 100644 doc/images/ido-menu.png create mode 100644 doc/images/menu-1.png create mode 100644 doc/images/menu-2.png create mode 100644 doc/images/menu-groups.png create mode 100644 doc/images/menu-parent.png create mode 100644 doc/images/minor-mode-indicator.png create mode 100644 doc/images/x-menu.png create mode 100644 doc/index.org create mode 100644 doc/nav-menu.html.inc create mode 100644 doc/org-setup.inc create mode 100644 doc/snippet-development.org create mode 100644 doc/snippet-expansion.org create mode 100644 doc/snippet-menu.org create mode 100644 doc/snippet-organization.org create mode 100644 doc/snippet-reference.org create mode 100644 doc/stylesheets/manual.css create mode 100644 doc/yas-doc-helper.el create mode 100644 yasnippet-debug.el create mode 100644 yasnippet-tests.el create mode 100644 yasnippet.el diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ce1c40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +authors.txt +doc/gh-pages +doc/*.html +doc/html-revision +pkg/ +extras/imported/** +!extras/imported/*/.yas-setup.el +.yas-compiled-snippets.el +*.elc +ert-x.* +ert.* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9a999ec --- /dev/null +++ b/.travis.yml @@ -0,0 +1,46 @@ +language: generic +sudo: false +git: + submodules: false + +env: + global: + - Wlexical=t + - Werror=t + - tests_Werror=t # For yasnippet-tests.el + matrix: + - EMACS_VERSION=23.4 + # 24.3 gives a bunch of 'value returned from (car value-N) is + # unused' warnings. + - EMACS_VERSION=24.3 tests_Werror=nil + - EMACS_VERSION=24.5 + - EMACS_VERSION=25.3 + - EMACS_VERSION=26-prerelease + + +install: + - curl -LO https://github.com/npostavs/emacs-travis/releases/download/bins/emacs-bin-${EMACS_VERSION}.tar.gz + - tar -xaf emacs-bin-${EMACS_VERSION}.tar.gz -C / + # Configure $PATH: Emacs installed to /tmp/emacs + - export PATH=/tmp/emacs/bin:${PATH} + - if ! emacs -Q --batch --eval "(require 'cl-lib)" ; then + curl -Lo cl-lib.el http://elpa.gnu.org/packages/cl-lib-0.6.1.el ; + export warnings="'(not cl-functions)" ; + fi + - if ! emacs -Q --batch --eval "(require 'ert)" ; then + curl -LO https://raw.githubusercontent.com/ohler/ert/c619b56c5bc6a866e33787489545b87d79973205/lisp/emacs-lisp/ert.el && + curl -LO https://raw.githubusercontent.com/ohler/ert/c619b56c5bc6a866e33787489545b87d79973205/lisp/emacs-lisp/ert-x.el ; + fi + - emacs --version + +script: + - rake yasnippet.elc + - rake yasnippet-debug.elc + - rake yasnippet-tests.elc Werror=$tests_Werror + - rake tests + +notifications: + email: + # Default is change, but that includes a new branch's 1st success. + on_success: never + on_failure: always # The default. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bf3b2d3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Submitting Bug Reports or Patches + +As a GNU ELPA package, bugs or patches may be submitted to the main +Emacs bug list, bug-gnu-emacs@gnu.org. Alternatively, you may use the +[Github issue tracker][issues]. + +Please read [Important note regarding bug reporting][bugnote]. + +# Contributing to Yasnippet + +## Copyright Assignment + +Yasnippet is part of GNU ELPA, so it falls under the same copyright +assignment policy as the rest of Emacs (see "Copyright Assignment" in +https://www.gnu.org/software/emacs/CONTRIBUTE). A copyright assignment +for Emacs also covers Yasnippet. + +## Commit message format + +The commit message format roughly follows Emacs conventions. There is +no separate Changelog file. + + Capitalize the first sentence, no period at the end + + Please make sure the summary line can be understood without having + to lookup bug numbers. It may be followed by a paragraph with a + longer explanation. The changelog style entry goes at the end of + the message. + * foo.el (a-function): Terse summary of per-function changes. Use + double spacing between sentences (set `sentence-end-double-space' + to t). + +For trivial changes, a message consisting of just the changelog entry +(e.g., `* foo.el (a-function): Fix docstring typo.`) is fine. + +[bugnote]: https://github.com/joaotavora/yasnippet#important-note-regarding-bug-reporting +[issues]: https://github.com/joaotavora/yasnippet/issues diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..6bea12f --- /dev/null +++ b/NEWS @@ -0,0 +1,491 @@ +Yasnippet NEWS -- history of user-visible changes. + +Copyright (C) 2017-2018 Free Software Foundation, Inc. +See the end of the file for license conditions. + +* 0.13.0 (May 13, 2018) + +** Changes + +*** Snippets for Yasnippet must now be installed separately. The +submodule linking to yasnippet-snippets was removed, as were the +"classic" snippets that came with the GNU ELPA package. The latter +can now be installed via the 'yasnippet-classic-snippets' package from +GNU ELPA. +See Github #848, #858, #834, #775. + +*** 'snippet-mode' no longer derives from 'text-mode'. +It will derive from 'prog-mode' where available (Emacs 24.1 and newer) +or 'fundamental-mode' otherwise. See Github #826. + +*** The default value of 'yas-key-syntaxes' is changed +Longer snippet abbrev keys are now preferred over shorter ones. +See Github #805. + +*** New snippets are now created for the current major mode by default +Previously, extra activated modes could be guessed first. +See Github #875. + +*** Yasnippet supports 'unload-feature' via 'yasnippet-unload-function' +See Github #753, #891. + +*** New command 'yas-skip-and-clear-field' conditionally bound to 'C-d' +replaces obsoleted 'yas-skip-and-clear-or-delete-char'. The new +function may be bound to any key via the conditional binding value +'yas-maybe-skip-and-clear-field', instead of hardcoding the +'delete-char' fallback action. See Github #408, #892. + +*** 'yas-lookup-snippet' now returns a struct +This allows 'yas-expand-snippet' to take looked up snippet's +environment into account. 'yas-expand-snippet' handles both +structured snippets, and plain text snippet bodies. +See Github #897. + +** Fixed bugs + +*** Avoid crashing due to Emacs Bug#30931 +This prevents yasnippet's routines from triggering the bug, although +it is still possible to trigger it independently. + +*** Don't enable undo when it's disabled + +*** yas-also-auto-indent-first-line is once again respected +Yasnippet was behaving as if it was always t for single line snippets. +See Github #912. + +*** Fixed handling of fixed indent with fields at beginning of line +See Github #906, #908. + +*** Fixed incorrect snippets leaving "bad memory" +and possibly corrupting future expansions. +See Github #800. + +*** 'global-whitespace-mode' now functions in new snippet buffers. +To fix this, the buffer name for new snippet buffers is now '+new +snippet+' instead of '*new snippet*'. See Github #842. + +*** Nest snippet expansion may clear default field text +See Github #844. + +*** Fixed undo list corruption snippet expand+indent. +See Github #869. + +*** The '# --' marker in snippets now allows trailing whitespace. +See Github #862. + +*** Fixed handling of nested simple $n fields +See Github #824, #894. + + +* 0.12.2 (Aug 28, 2017) + +** The new option 'yas-also-auto-indent-empty-lines' allows restoring +the old indent behavior. See Github #850, #710, #685, #679. + +** Keybinding triggered snippets once again deactivate the mark. +See Github #840. + + +* 0.12.1 (Jul 23, 2017) + +This is a quick bugfix release. + +** Compilation errors in yasnippet-tests.el and yasnippet-debug.el are fixed. + +** A snippet-local setting of 'yas-indent-line' is now respected +during indentation triggered by auto-fill as well. See Github #838. + + +* 0.12.0 (Jul 17, 2017) + +** Changes and New Features + +*** Snippets can now expand in strings & comments by default again. +'yas-buffer-local-condition' is now a defcustom See Github #774. + +*** 'yas-after-exit-snippet-hook' can now be bound in 'expand-env' of +snippets. See Github #28, #702, #779, #786. + +*** Snippets under directories in 'yas-snippet-dirs' are now in +snippet-mode automatically. + +*** Snippets can now be expanded in org source blocks, if +'org-src-tab-acts-natively' and 'org-src-fontify-natively' are set. +See Github #761. + +*** 'yas-fallback-behavior' is now obsolete, 'yas-expand' is now bound +conditionally with an extended menu item, 'yas-maybe-expand'. +Therefore users wanting to bind 'yas-expand' to a different key, SPC +for example, should do + + (define-key yas-minor-mode-map (kbd "SPC") yas-maybe-expand) + +See Github #760, #808. + +*** The documentation build output is now reproducible. The timestamp +now depends on the commit date, or the environment variable +SOURCE_DATE_EPOCH is that is set. + +*** 'yas-indent-line' and 'expand-env' are now respected during mirror +updates. See Github #743. + +*** New function 'yas-active-snippets'. Renamed from +'yas--snippets-at-point', which remains as an obsolete alias. See +Github #727. + +*** New custom option 'yas-overlay-priority'. This is can be used to +give the snippet navigation keymaps higher priority than keymaps from +overlays created by other packages, like 'auto-complete'. See Github +#828. + +** Fixed bugs + +*** Snippets having ${0:soon-to-be-deleted} with no other fields now +correctly put the field 0 text in the active region after exiting. +See Github #653. + +*** Fix undo of snippet insertion which also triggers indentation. +See Github #821. + +*** Fixed a bug causing whitespace loss between mirrors. + +*** Fixed several bugs causing problems when combining Yasnippet with +other modes and packages, like 'auto-fill-mode', 'c++-mode', +'rust-mode', and 'lentic'. + +**** Fix another bug with auto-fill-mode. +See Github #784, #794. + +**** Fix a bug in parsing of snippet fields for modes that use the +'syntax-table' text property, 'c++-mode' is one example of this. See +Github #815. + +**** 'syntax-propertize-function' is now restored before indenting the +snippet. This improves compatibility with modes which rely on it for +indentation, like 'rust-mode'. See Github #782, #818. + +**** Avoid trying to delete a snippet which is already deleted. This +prevents an error when using 'rust-mode's 'rust-format-buffer' +command. + +**** Ensure inhibit-modification-hooks is nil while modifying buffer. +This fixes problems for packages relying on modification hooks, like +'lentic'. See Github #756, #712. + + +* 0.11.0 (Oct 26, 2016) +** Changes and New Features + +*** Modifying buffer in backquoted expressions is deprecated! +Backquoted expressions should only return a string that will be +inserted. Snippets which modify the buffer as a side-effect will now +trigger a warning. + +*** The verbosity levels for messages have been adjusted. +While the default verbosity level was increased by 1 (it was only +lower before due to a bug), several messages now only print at the +level 4, so the overall effect should be less messages by default. + +*** Saving and loading snippets has been streamlined. Saving the +snippet to a file will now automatically load it. Additionally, the +buffer will be renamed from "*new snippet*" to whatever the snippet is +named. See also Github #718, #733, and #734. + +*** `yas-escape-text' no longer signals an error when given nil. + +*** `yas-describe-tables' is split into simpler commands. +**** `yas-describe-tables' takes a prefix arg to show non-active tables. +**** New command `yas-describe-tables-by-namehash' shows table by namehash. + +*** Use the region contents as the snippet body of new snippets. + +*** The dependency on `cl' is removed. +Yasnippet now only requires `cl-lib'. + +** Fixed Bugs + +*** Fix field navigation (tabbing) in the backwards direction. +See Github #722 + +*** Add support for deprecated yas/ symbols in `yas-define-menu'. +It was left out by accident. This support is conditional on +`yas-alias-to-yas/prefix-p', just like other obsolete yas/ bindings. + +*** Fix overriding of snippet by a new snippet with same uuid. +See Github #714. + +*** Fix handling of snippets with 2 mirros on the same line. +See Github #712. + + +* 0.10.0 (June 11th, 2016) + +** Changes and New Features + +*** Yasnippet now prints far fewer message by default. +See Github #682, #683. + +*** `yas-wrap-around-region' can be set to a register. +The register's content will be used. This is like the old `cua' +option, but can be used with any register and doesn't require enabling +cua-mode. + +*** Clearing of snippet fields is now decided by the command's effect. +The `delete-selection' property is no longer consulted. See Github #662. + +*** Empty lines in snippet expansion are no longer indented. +See Github #679. + +*** All lines from mirror output are now indented. +See Github #665. + +*** New variable yas-alias-to-yas/prefix-p +See Github #696, #699. + +*** New function yas-next-field-will-exit-p +See Github #561. + +*** `snippet-mode' is now autoloaded. + +** Fixed Bugs + +*** Fix incompatibility with Emacs 25 and haskell-mode. +This should also help other modes with a non-nil syntax-propertize +function. See Github #687. + +*** Text property changes no longer disable snippets. +This prevents cc-mode based modes from causing premature exit of +snippets. See Github #677. + +*** Fields are now transformed correctly after `yas-next-field'. +See Github #381. + +*** The $> construct is now escaped correctly, and documented. +See Github #640. + +*** Avoid corruption of snippet content when loading from files. +See Github #707 and Emacs bug #23659. + +*** `yas-wrap-around-region' now works for snippets with fields +farther down the buffer than $0. See Github #636. + +*** The active region is deleted when using `yas-expand'. +This makes it consistent with `yas-insert-snippet'. See Github #523. + +*** Fix mirror+autofill interaction. +See Github #643 and http://emacs.stackexchange.com/q/19206/5296. + +*** Snippet insertion no longer adds irrelevant strings to kill ring. +See Github #675. + + +* 0.9.1 (April 3rd, 2016) + +** Changes and New Features + +*** Noam Postavsky is now the official yasnippet maintainer. + +*** `yas-visit-snippet-file' now works for compiled snippets (see Github #597). + +*** New function `yas-lookup-snippet' (see Github #595, #596). + +*** .emacs.d/snippets directory is now created automatically. +If that value is present in `yas-snippet-dirs' (see Github #648). + +*** Default value for `yas-snippet-dirs' now uses `user-emacs-directory' +instead of hardcoding "~/emacs.d" (see Github #632). + +*** `yas-visit-snippet-file' no longer overrides `yas-prompt-functions', +see Github #576. + +*** The defaults for prompting have changed. +`yas-x-prompt' is no longer present in the default value of +`yas-prompt-functions'. + +The new function `yas-maybe-ido-prompt' (which performs ido prompting +if `ido-mode' is enabled) replaces `yas-ido-prompt' (which always +performs ido prompting). Previously the behaviour was dependent on +the Emacs version being used. + +*** The default value of `yas-buffer-local-condition' now works for `yas-insert-snippet' too. +See Github #305. + +*** The default value of `yas-new-snippet-default' no longer inserts `require-final-newline: nil'. +It was redundant, since `mode: snippet' already accomplishes the same. +`binding: ${4:direct-keybinding}}' is also removed, as it is hardly +ever wanted. + +*** Snippet fields are only cleared by commands with `delete-selection' property, +See Github #515, #644. + +*** `yas-initialize' (and backward compat alias `yas/initialize') are restored, +but marked obsolete, use (yas-global-mode +1) instead. See Github +#546, #569. + +*** `yas-key-syntaxes' is much more powerful and accepts functions. +Enables use cases when discovering keys based on buffer syntax is not +good enough. See issue #497. + +*** Documentation rewritten in org-mode and updated. +A tremendous effort by Noam Postavsky. Hopefully easier to maintain +and navigate. Available at . + +*** Snippets are now maintained in their own repo. +Snippets live in Andrea Crotti's +. See README.md +for more details. + +*** Textmate snippet importer moved to separate `yasmate' repo. +URL is . See README.md for +more details. + +*** `yas-snippet-dirs' now allows symbols as aliases to directories. +The added level of indirection should allow more esoteric +configurations (see Github #495). + +*** `yas-reload-all' can now jit-load when called interactively. + +*** New `yas-after-reload-hook' run after `yas-reload-all'. +See for the discussion +leading up to this change. + +*** New functions `yas-activate-extra-mode' and `yas-deactivate-extra-mode'. +These are preferable to setting `yas-extra-modes' directly in the mode +hook (see issue #420 for more information). + +*** New variable `yas-new-snippet-default'. +The default snippet suggested on `yas-new-snippet' can now be +customized. + +** Fixed bugs + +*** `yas-expand' now sets `this-command' when falling back to another command. +Previously it was setting `this-original-command', which does not +match the documented semantics. See Github #587. + +*** Github #537: Be lenient to extensions operating on snippet fields. + +*** Github #619: Parents of extra modes are now activated too. + +*** Github #549: `yas-verbosity' is now applied to `load' calls too. + +*** Github #607; avoid obscure Emacs bug triggered by overlays in *Messages* buffer. +It was triggered by yasnippet+flycheck+highlight-parentheses. See +also + +*** Github #617; fix x prompt when snippet inserts many lines. + +*** Github #618; avoid breakage if `scan-sexp' modifies match data. +Which it does in Emacs 25. + +*** Github #562: Deleting inner numberless snippet caused an error. + +*** Github #418, #536: Fix navigation to zero-length fields at snippet end. + +*** Github #527, #525 and #526: Attempt to prevent "fallback loops" +when interactiing with other extensions that use similar fallback +mechanisms. + + +* 0.8.0 (August 2012) + +** Changes and New Features + +*** All YASnippet symbols now prefixed with `yas-'. Keep old `yas/' versions as aliases. + +*** Yasnippet is now Free Software Foundation's copyright. + +*** `yas-dont-activate' can be a list of functions. + +*** Snippets are loaded just-in-time . +Thanks to Matthew Fidler for a lot of input with the implementation. + +*** yasnippet-bundle.el is no longer available. +Use `yas-compile-directory' instead if you need the speed advantage. + +*** New functions `yas-compile-directory' and `yas-recompile-all'. +This feature is still undocumented. Generate a +.yas-compiled-snippets.el file in the directory passed where snippets +are compiled into emacs-lisp code. + +*** New `yas-verbosity' variable. + +*** Interactively calling `yas-exit-snippet' exits most recently inserted snippet. + +*** Using filenames as snippet triggers is deprecated. + +*** Default value of `yas-show-menu-p' is `abbreviate'. + +*** `yas-visit-snippet' ignores `yas-prompt-functions'. + +*** `yas-buffer-local-condition' restricted to trigger-key expansions by default. + +*** `yas-load-snippet-buffer' (`C-c C-c') in `snippet-mode' has been much improved. + +*** New variable `yas-expand-only-for-last-commands', thanks Erik Postma. + +*** New variable `yas-extra-modes' aliases old `yas/mode-symbol'. + +*** New `yas-describe-tables' command. + +*** New `yas-define-condition-cache' macro. +This defines an optimised function for placing in a `# condition:' +directive that will run at most once per snippet-expansion attempt. + +*** Mirrors can occur inside fields. + +*** New `# type: command' directive. +This feature is still largely undocumented. + +*** A hidden .yas-setup.el is loaded if found in a snippet directory. + +*** `yas-wrap-around-region' can now also be `cua' (undocumented feature). + +*** Make menu groups automatically using new .yas-make-groups file. + +*** Per-snippet keybindings using `# keybinding:' directive. + +*** More friendly `yas-expand-snippet' calling convention. +This breaks backward compatibility. + +*** The `# env:' directive is now `# expand-env:'. + +*** Setup multiple parent modes using new .yas-parents file. + +** Fixed bugs + +*** Github #281: jit-load snippets in the correct order. + +*** Github #245: primary field transformations work inside nested fields. + +*** Github #242: stop using the deprecated `assoc' library. + +*** Github #233: show direct snippet keybindings in the menu. + +*** Github #194, Googlecode 192: Compatibility with `fci-mode'. + +*** Github #147, Googlecode 145: Snippets comments were getting inserted. + +*** Github #141, Googlecode 139: searching buffer in embedded elisp works slightly better. +Issue is still open for analysis, though + +*** Github #95, Googlecode 193: no more stack overflow in org-mode. + + +---------------------------------------------------------------------- +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + + +Local variables: +coding: utf-8 +mode: outline +paragraph-separate: "[ ]*$" +end: diff --git a/README.mdown b/README.mdown new file mode 100644 index 0000000..76772c8 --- /dev/null +++ b/README.mdown @@ -0,0 +1,165 @@ +[![Build Status](https://travis-ci.org/joaotavora/yasnippet.png)](https://travis-ci.org/joaotavora/yasnippet) + +# Intro + +**YASnippet** is a template system for Emacs. It allows you to +type an abbreviation and automatically expand it into function +templates. Bundled language templates include: C, C++, C#, Perl, +Python, Ruby, SQL, LaTeX, HTML, CSS and more. The snippet syntax +is inspired from [TextMate's][textmate-snippets] syntax, you can +even [import](#import) most TextMate templates to +YASnippet. Watch [a demo on YouTube][youtube-demo]. + +[textmate-snippets]: http://manual.macromates.com/en/snippets +[youtube-demo]: http://www.youtube.com/watch?v=ZCGmZK4V7Sg + +# Installation + +## Install the most recent version + +Clone this repository somewhere + + $ cd ~/.emacs.d/plugins + $ git clone --recursive https://github.com/joaotavora/yasnippet + +Add the following in your `.emacs` file: + + (add-to-list 'load-path + "~/.emacs.d/plugins/yasnippet") + (require 'yasnippet) + (yas-global-mode 1) + +Add your own snippets to `~/.emacs.d/snippets` by placing files there or invoking `yas-new-snippet`. + +## Install with `package-install` + +In a recent emacs `M-x list-packages` is the recommended way to list and install packages. +[MELPA][melpa] keeps a very recent snapshot of YASnippet, see http://melpa.org/#installing. + +## Install with el-get + +El-get is a nice way to get the most recent version, too. See +https://github.com/dimitri/el-get for instructions. + +## Use `yas-minor-mode` on a per-buffer basis + +To use YASnippet as a non-global minor mode, don't call +`yas-global-mode`; instead call `yas-reload-all` to load the snippet +tables and then call `yas-minor-mode` from the hooks of major-modes +where you want YASnippet enabled. + + (yas-reload-all) + (add-hook 'prog-mode-hook #'yas-minor-mode) + +# Where are the snippets? + + + +YASnippet no longer bundles snippets directly, but it's very easy to +get some! + +1. [yasnippet-snippets] - a snippet collection package maintained by + [AndreaCrotti](https://github.com/AndreaCrotti). + + It can be installed with `M-x install-package RET + yasnippet-snippets` if you have added MELPA to your package + sources. + +2. [yasmate] a tool which is dedicated to converting textmate bundles + into yasnippet snippets. + + To use these snippets you have to run the tool first, so + [see its doc][yasmate]), and then point the `yas-snippet-dirs` + variable to the `.../yasmate/snippets` subdir. + + If you have a working ruby environment, you can probably get lucky + directly with `rake convert-bundles`. + +3. [textmate-to-yas.el] + + This is another textmate bundle converting tool using Elisp + instead of Ruby. + +Naturally, you can point `yas-snippet-dirs` to good snippet collections out +there. If you have created snippets for a mode, or multiple modes, +consider creating a repository to host them, then tell users that it +should be added like this to `yas-snippet-dirs`: + + (setq yas-snippet-dirs + '("~/.emacs.d/snippets" ;; personal snippets + "/path/to/some/collection/" ;; foo-mode and bar-mode snippet collection + "/path/to/yasnippet/yasmate/snippets" ;; the yasmate collection + )) + + (yas-global-mode 1) ;; or M-x yas-reload-all if you've started YASnippet already. + +# Manual, issues etc + +There's comprehensive [documentation][docs] on using and customising +YASnippet. + +There's a [list of support issues][support-issues], with solutions to +common problems and practical snippet examples. + +The [Github issue tracker][issues] is where most YASnippet-related +discussion happens. Nevertheless, since YASnippet is a part of Emacs, +you may alternatively report bugs to the main Emacs bug list, +bug-gnu-emacs@gnu.org, putting "yasnippet" somewhere in the subject. + +## Important note regarding bug reporting + +Your bug reports are very valuable. + +The most important thing when reporting bugs is making sure that we have +a way to reproduce the problem exactly like it happened to you. + +To do this, we need to rule out interference from external factors +like other Emacs extensions or your own customisations. + +Here's an example report that "sandboxes" an Emacs session just for +reproducing a bug. + +``` +$ emacs --version +Emacs 24.3 +$ cd /tmp/ +$ git clone https://github.com/joaotavora/yasnippet.git yasnippet-bug +$ cd yasnippet-bug +$ git log -1 --oneline +6053db0 Closes #527: Unbreak case where yas-fallback-behaviour is a list +$ HOME=$PWD emacs -L . # This "sandboxes" your emacs, melpa configuration, etc + +(require 'yasnippet) +(yas-global-mode 1) + +When I open a foo-mode file I don't see foo-mode under the "YASnippet" menu! +OR +When loading yasnippet I see "Error: failed to frobnicate"! +``` + +Using `emacs -Q` or temporarily moving your `.emacs` init file to the side +is another way to achieve good reproducibility. + +Here's a +[another example](https://github.com/joaotavora/yasnippet/issues/318) +of a bug report. It has everything needed for a successful analysis +and speedy resolution. + +Also, don't forget to state the Emacs version (use `M-x emacs-version`) and +the yasnippet version you are using (if using the latest from github, +do `git log -1` in the dir). + +Any more info is welcome, but don't just paste a backtrace or an error +message string you got, unless we ask for it. + +Finally, thank you very much for using YASnippet! + +[docs]: http://joaotavora.github.io/yasnippet/ +[issues]: https://github.com/joaotavora/yasnippet/issues +[support-issues]: https://github.com/joaotavora/yasnippet/issues?q=label%3Asupport +[googlecode tracker]: http://code.google.com/p/yasnippet/issues/list +[forum]: http://groups.google.com/group/smart-snippet +[melpa]: http://melpa.milkbox.net/ +[yasmate]: http://github.com/joaotavora/yasmate +[textmate-to-yas.el]: https://github.com/mattfidler/textmate-to-yas.el +[yasnippet-snippets]: http://github.com/AndreaCrotti/yasnippet-snippets diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..c63d269 --- /dev/null +++ b/Rakefile @@ -0,0 +1,130 @@ +# -*- Ruby -*- + +require 'fileutils' + +$EMACS = ENV["EMACS"] +if not $EMACS or $EMACS == 't' + $EMACS = "emacs" +end + +def find_version + File.read("yasnippet.el", :encoding => "UTF-8") =~ /;; Package-version: *([0-9.]+?) *$/ + $version = $1 +end +find_version +FileUtils.mkdir_p('pkg') + +desc "run tests in batch mode" +task :tests do + sh "#{$EMACS} -Q -L . -l yasnippet-tests.el" + + " --batch -f ert-run-tests-batch-and-exit" +end + +desc "run test in interactive mode" +task :itests do + sh "#{$EMACS} -Q -L . -l yasnippet-tests.el" + + " --eval \"(call-interactively 'ert)\"" +end + +desc "create a release package" +task :package do + release_dir = "pkg/yasnippet-#{$version}" + FileUtils.mkdir_p(release_dir) + files = ['snippets', 'yasnippet.el'] + FileUtils.cp_r files, release_dir + File.open(File.join(release_dir,'yasnippet-pkg.el'), 'w') do |file| + file.puts < [:package, 'doc:archive'] do + raise "Not implemented for github yet!" +end + +# rake doc[../htmlize] +# +# To do this interactively, load doc/yas-doc-helper, open one of the +# org files, and do `C-c C-e P'. +desc "Generate document" +task :doc, [:htmlize] do |t, args| + load_path = '-L .' + if args[:htmlize] + load_path += " -L #{args[:htmlize]}" + end + sh "#{$EMACS} -Q #{load_path} --batch -l doc/yas-doc-helper.el" + + " -f yas--generate-html-batch" +end + +namespace :doc do + task :archive do + release_dir = "pkg/yasnippet-#{$version}" + FileUtils.mkdir_p(release_dir) + sh "tar cjf pkg/yasnippet-doc-#{$version}.tar.bz2 " + + "--exclude=doc/.svn --exclude=doc/images/.svn doc/*.html doc/images" + end + + task :upload do + if File.exists? 'doc/gh-pages' + Dir.chdir 'doc/gh-pages' do + sh "git checkout gh-pages" + end + Dir.glob("doc/*.{html,css}").each do |file| + FileUtils.cp file, 'doc/gh-pages' + end + Dir.glob("doc/images/*").each do |file| + FileUtils.cp file, 'doc/gh-pages/images' + end + Dir.glob("doc/stylesheets/*.css").each do |file| + FileUtils.cp file, 'doc/gh-pages/stylesheets' + end + curRev = `git describe`.chomp() + expRev = IO.read('doc/html-revision').chomp() + if curRev != expRev + raise ("The HTML rev: #{expRev},\n" + + "current rev: #{curRev}!\n") + end + Dir.chdir 'doc/gh-pages' do + sh "git commit -a -m 'Automatic documentation update.\n\n" + + "From #{curRev.chomp()}'" + sh "git push" + end + end + end +end + +desc "Compile yasnippet.el into yasnippet.elc" + +rule '.elc' => '.el' do |t| + cmdline = $EMACS + ' --batch -L .' + if ENV['warnings'] + cmdline += " --eval \"(setq byte-compile-warnings #{ENV['warnings']})\"" + end + if ENV['Werror'] + cmdline += " --eval \"(setq byte-compile-error-on-warn #{ENV['Werror']})\"" + end + if ENV['Wlexical'] + cmdline += " --eval \"(setq byte-compile-force-lexical-warnings #{ENV['Wlexical']})\"" + end + cmdline +=" -f batch-byte-compile #{t.source}" + + sh cmdline +end +task :compile => FileList["yasnippet.el"].ext('elc') +task :compile_all => FileList["*.el"].ext('elc') + +task :default => :doc + +desc "use yasmate to convert textmate bundles" +task :convert_bundles do + cd "yasmate" + sh "rake convert_bundles" + end diff --git a/doc/.nosearch b/doc/.nosearch new file mode 100644 index 0000000..e69de29 diff --git a/doc/faq.org b/doc/faq.org new file mode 100644 index 0000000..9994e7b --- /dev/null +++ b/doc/faq.org @@ -0,0 +1,96 @@ +#+SETUPFILE: org-setup.inc + +#+TITLE: Frequently Asked Questions + +- *Note*: In addition to the questions and answers presented here, + you might also with to visit the list of [[https://github.com/joaotavora/yasnippet/issues?q=label%3Asupport][solved support issues]] in + the Github issue tracker. It might be more up-to-date than this + list. + +* Why is there an extra newline? + +If there is a newline at the end of a snippet definition file, +YASnippet will add a newline when expanding that snippet. When editing +or saving a snippet file, please be careful not to accidentally add a +terminal newline. + +Note that some editors will automatically add a newline for you. In +Emacs, if you set =require-final-newline= to =t=, it will add the +final newline automatically. + +* Why doesn't TAB navigation work with flyspell + +A workaround is to inhibit flyspell overlays while the snippet is +active: + +#+BEGIN_SRC emacs-lisp + (add-hook 'flyspell-incorrect-hook + #'(lambda (dummy1 dummy2 dymmy3) + (and yas-active-field-overlay + (overlay-buffer yas-active-field-overlay)))) +#+END_SRC + +This is apparently related to overlay priorities. For some reason, the +=keymap= property of flyspell's overlays always takes priority over the +same property in YASnippet's overlays, even if one sets the latter's +=priority= property to something big. If you know emacs-lisp and can +solve this problem, drop a line in the +[[http://groups.google.com/group/smart-snippet][discussion group]]. + +* How do I use alternative keys, i.e. not TAB? + +Edit the keymaps [[sym:yas-minor-mode-map][=yas-minor-mode-map=]] and +[[sym:yas-keymap][=yas-keymap=]] as you would any other keymap: + +#+begin_src emacs-lisp :exports code + (define-key yas-minor-mode-map (kbd "") nil) + (define-key yas-minor-mode-map (kbd "TAB") nil) + (define-key yas-minor-mode-map (kbd "") yas-maybe-expand) + + ;;keys for navigation + (define-key yas-keymap [(tab)] nil) + (define-key yas-keymap (kbd "TAB") nil) + (define-key yas-keymap [(shift tab)] nil) + (define-key yas-keymap [backtab] nil) + (define-key yas-keymap (kbd "") 'yas-next-field-or-maybe-expand) + (define-key yas-keymap (kbd "") 'yas-prev) +#+end_src + +* How do I turn off the minor mode where in some buffers? + +The best way, since version 0.6.1c, is to set the default value of the +variable [[sym:yas-dont-activate][=yas-dont-activate=]] to a lambda function like so: + +#+BEGIN_SRC emacs-lisp + (set-default 'yas-dont-activate + #'(lambda () + (and yas-root-directory + (null (yas-get-snippet-tables))))) +#+END_SRC + +This is also the default value starting for that version. It skips the +minor mode in buffers where it is not applicable (no snippet tables), +but only once you have setup your yas-root-directory. + +* How do I define an abbrev key containing characters not supported by the filesystem? + +- *Note*: This question applies if you're still defining snippets + whose key /is/ the filename. This is behavior still provided by + version 0.6 for backward compatibilty, but is somewhat + deprecated... + +For example, you want to define a snippet by the key =<= which is not a +valid character for filename on Windows. This means you can't use the +filename as a trigger key in this case. + +You should rather use the =# key:= directive to specify the key of the +defined snippet explicitly and name your snippet with an arbitrary valid +filename, =lt.YASnippet= for example, using =<= for the =# key:= +directive: + +#+BEGIN_SRC snippet + # key: < + # name: <...> + # -- + <${1:div}>$0 +#+END_SRC diff --git a/doc/images/bg-content-left.png b/doc/images/bg-content-left.png new file mode 100644 index 0000000..a64b346 Binary files /dev/null and b/doc/images/bg-content-left.png differ diff --git a/doc/images/bg-content-right.png b/doc/images/bg-content-right.png new file mode 100644 index 0000000..f07ebb5 Binary files /dev/null and b/doc/images/bg-content-right.png differ diff --git a/doc/images/bg-content.png b/doc/images/bg-content.png new file mode 100644 index 0000000..d55828e Binary files /dev/null and b/doc/images/bg-content.png differ diff --git a/doc/images/bg-navigation-item-hover.png b/doc/images/bg-navigation-item-hover.png new file mode 100644 index 0000000..c783d71 Binary files /dev/null and b/doc/images/bg-navigation-item-hover.png differ diff --git a/doc/images/bg-navigation-item.png b/doc/images/bg-navigation-item.png new file mode 100644 index 0000000..d2452ac Binary files /dev/null and b/doc/images/bg-navigation-item.png differ diff --git a/doc/images/bg-navigation.png b/doc/images/bg-navigation.png new file mode 100644 index 0000000..18b9559 Binary files /dev/null and b/doc/images/bg-navigation.png differ diff --git a/doc/images/body.png b/doc/images/body.png new file mode 100644 index 0000000..b361e7b Binary files /dev/null and b/doc/images/body.png differ diff --git a/doc/images/customization-group.png b/doc/images/customization-group.png new file mode 100644 index 0000000..b10827f Binary files /dev/null and b/doc/images/customization-group.png differ diff --git a/doc/images/dropdown-menu.png b/doc/images/dropdown-menu.png new file mode 100644 index 0000000..57d482e Binary files /dev/null and b/doc/images/dropdown-menu.png differ diff --git a/doc/images/external.png b/doc/images/external.png new file mode 100644 index 0000000..419c06f Binary files /dev/null and b/doc/images/external.png differ diff --git a/doc/images/ido-menu.png b/doc/images/ido-menu.png new file mode 100644 index 0000000..df392c5 Binary files /dev/null and b/doc/images/ido-menu.png differ diff --git a/doc/images/menu-1.png b/doc/images/menu-1.png new file mode 100644 index 0000000..d2e6a51 Binary files /dev/null and b/doc/images/menu-1.png differ diff --git a/doc/images/menu-2.png b/doc/images/menu-2.png new file mode 100644 index 0000000..abb8a72 Binary files /dev/null and b/doc/images/menu-2.png differ diff --git a/doc/images/menu-groups.png b/doc/images/menu-groups.png new file mode 100644 index 0000000..fcedda8 Binary files /dev/null and b/doc/images/menu-groups.png differ diff --git a/doc/images/menu-parent.png b/doc/images/menu-parent.png new file mode 100644 index 0000000..f0fa10c Binary files /dev/null and b/doc/images/menu-parent.png differ diff --git a/doc/images/minor-mode-indicator.png b/doc/images/minor-mode-indicator.png new file mode 100644 index 0000000..3743455 Binary files /dev/null and b/doc/images/minor-mode-indicator.png differ diff --git a/doc/images/x-menu.png b/doc/images/x-menu.png new file mode 100644 index 0000000..3bc9a15 Binary files /dev/null and b/doc/images/x-menu.png differ diff --git a/doc/index.org b/doc/index.org new file mode 100644 index 0000000..c5e0be3 --- /dev/null +++ b/doc/index.org @@ -0,0 +1,47 @@ +#+SETUPFILE: org-setup.inc +#+TITLE: Yet another snippet extension + +The YASnippet documentation has been split into separate parts: + +0. [[https://github.com/joaotavora/yasnippet/blob/master/README.mdown][README]] + + Contains an introduction, installation instructions and other important + notes. + +1. [[file:snippet-organization.org][Organizing Snippets]] + + Describes ways to organize your snippets in the hard disk. + +2. [[file:snippet-expansion.org][Expanding Snippets]] + + Describes how YASnippet chooses snippets for expansion at point. + + Maybe, you'll want some snippets to be expanded in a particular mode, + or only under certain conditions, or be prompted using =ido=, etc... + +3. [[file:snippet-development.org][Writing Snippets]] + + Describes the YASnippet definition syntax, which is very close (but + not equivalent) to Textmate's. Includes a section about converting + TextMate snippets. + +4. [[file:snippet-menu.org][The YASnippet menu]] + + Explains how to use the YASnippet menu to explore, learn and modify + snippets. + +5. [[file:faq.org][Frequently asked questions]] + + Answers to frequently asked questions. + +6. [[file:snippet-reference.org][YASnippet Symbol Reference]] + + An automatically generated listing of all YASnippet commands, + (customization) variables, and functions. + + +# Local Variables: +# mode: org +# fill-column: 80 +# coding: utf-8 +# End: diff --git a/doc/nav-menu.html.inc b/doc/nav-menu.html.inc new file mode 100644 index 0000000..3e74cf8 --- /dev/null +++ b/doc/nav-menu.html.inc @@ -0,0 +1,16 @@ + diff --git a/doc/org-setup.inc b/doc/org-setup.inc new file mode 100644 index 0000000..60b9382 --- /dev/null +++ b/doc/org-setup.inc @@ -0,0 +1,11 @@ +# -*- mode: org -*- + +#+STARTUP: showall + +#+LINK: sym file:snippet-reference.org::#%s + +#+OPTIONS: author:nil num:nil timestamp:nil +#+AUTHOR: +# org < 8.0 use +STYLE, after use +HTML_HEAD +#+STYLE: +#+HTML_HEAD: diff --git a/doc/snippet-development.org b/doc/snippet-development.org new file mode 100644 index 0000000..9112cd0 --- /dev/null +++ b/doc/snippet-development.org @@ -0,0 +1,461 @@ +#+SETUPFILE: org-setup.inc + +#+TITLE: Writing snippets + +* Snippet development + +** Quickly finding snippets + +There are some ways you can quickly find a snippet file or create a new one: + +- =M-x yas-new-snippet=, key bindind: =C-c & C-n= + + Creates a new buffer with a template for making a new snippet. The + buffer is in =snippet-mode= (see [[snippet-mode][below]]). When you are done editing + the new snippet, use [[yas-load-snippet-buffer-and-close][=C-c C-c=]] to save it. + +- =M-x yas-visit-snippet-file=, key binding: =C-c & C-v= + + Prompts you for possible snippet expansions like + [[sym:yas-insert-snippet][=yas-insert-snippet=]], but instead of expanding it, takes you directly + to the snippet definition's file, if it exists. + +Once you find this file it will be set to =snippet-mode= (see [[snippet-mode][ahead]]) +and you can start editing your snippet. + +** Using the =snippet-mode= major mode <> + +There is a major mode =snippet-mode= to edit snippets. You can set the +buffer to this mode with =M-x snippet-mode=. It provides reasonably +useful syntax highlighting. + +Three commands are defined in this mode: + +- =M-x yas-load-snippet-buffer=, key binding: =C-c C-l= + + Prompts for a snippet table (with a default based on snippet's + major mode) and loads the snippet currently being edited. + +- =M-x yas-load-snippet-buffer-and-close=, key binding: =C-c C-c= + <> + + Like =yas-load-snippet-buffer=, but also saves the snippet and + calls =quit-window=. The destination is decided based on the + chosen snippet table and snippet collection directly (defaulting to + the first directory in =yas-snippet-dirs= (see [[file:snippet-organization.org][Organizing Snippets]] + for more detail on how snippets are organized). + +- =M-x yas-tryout-snippet=, key binding: =C-c C-t= + + When editing a snippet, this opens a new empty buffer, sets it to + the appropriate major mode and inserts the snippet there, so you + can see what it looks like. + +There are also /snippets for writing snippets/: =vars=, =$f= and =$m= +:-). + +* File content + +A file defining a snippet generally contains the template to be +expanded. + +Optionally, if the file contains a line of =# --=, the lines above it +count as comments, some of which can be /directives/ (or meta data). +Snippet directives look like =# property: value= and tweak certain +snippets properties described below. If no =# --= is found, the whole +file is considered the snippet template. + +Here's a typical example: + +#+BEGIN_SRC snippet + # contributor: pluskid + # name: __...__ + # -- + __${init}__ +#+END_SRC + +Here's a list of currently supported directives: + +** =# key:= snippet abbrev + +This is the probably the most important directive, it's the +abbreviation you type to expand a snippet just before hitting the key +that runs [[sym:yas-expand][=yas-expand=]]. If you don't specify this, +the snippet will not be expandable through the trigger mechanism. + +** =# name:= snippet name + +This is a one-line description of the snippet. It will be displayed in +the menu. It's a good idea to select a descriptive name for a snippet -- +especially distinguishable among similar snippets. + +If you omit this name, it will default to the file name the snippet +was loaded from. + +** =# condition:= snippet condition + +This is a piece of Emacs-lisp code. If a snippet has a condition, then +it will only be expanded when the condition code evaluate to some +non-nil value. + +See also [[sym:yas-buffer-local-condition][=yas-buffer-local-condition=]] in +[[./snippet-expansion.org][Expanding snippets]] + +** =# group:= snippet menu grouping + +When expanding/visiting snippets from the menu-bar menu, snippets for a +given mode can be grouped into sub-menus . This is useful if one has too +many snippets for a mode which will make the menu too long. + +The =# group:= property only affect menu construction (See +[[./snippet-menu.org][the YASnippet menu]]) and the same effect can be +achieved by grouping snippets into sub-directories and using the +=.yas-make-groups= special file (for this see +[[./snippet-organization.org][Organizing Snippets]] + +Refer to the bundled snippets for =ruby-mode= for examples on the +=# group:= directive. Group can also be nested, e.g. +=control structure.loops= tells that the snippet is under the =loops= +group which is under the =control structure= group. + +** =# expand-env:= expand environment + +This is another piece of Emacs-lisp code in the form of a =let= /varlist +form/, i.e. a list of lists assigning values to variables. It can be +used to override variable values while the snippet is being expanded. + +Interesting variables to override are [[sym:yas-wrap-around-region][=yas-wrap-around-region=]] and +[[sym:yas-indent-line][=yas-indent-line=]] (see [[./snippet-expansion.org][Expanding Snippets]]). + +As an example, you might normally have [[sym:yas-indent-line][=yas-indent-line=]] set to '=auto= +and [[sym:yas-wrap-around-region][=yas-wrap-around-region=]] set to =t=, but for this particularly +brilliant piece of ASCII art these values would mess up your hard work. +You can then use: + +#+BEGIN_SRC snippet + # name: ASCII home + # expand-env: ((yas-indent-line 'fixed) (yas-wrap-around-region 'nil)) + # -- + welcome to my + X humble + / \ home, + / \ $0 + / \ + /-------\ + | | + | +-+ | + | | | | + +--+-+--+ +#+END_SRC + +** =# binding:= direct keybinding + +You can use this directive to expand a snippet directly from a normal +Emacs keybinding. The keybinding will be registered in the Emacs keymap +named after the major mode the snippet is active for. + +Additionally a variable [[sym:yas-prefix][=yas-prefix=]] is set to to the prefix argument +you normally use for a command. This allows for small variations on the +same snippet, for example in this "html-mode" snippet. + +#+BEGIN_SRC snippet + # name:

...

+ # binding: C-c C-c C-m + # -- +

`(when yas-prefix "\n")`$0`(when yas-prefix "\n")`

+#+END_SRC + +This binding will be recorded in the keymap =html-mode-map=. To expand a +paragraph tag newlines, just press =C-u C-c C-c C-m=. Omitting the =C-u= +will expand the paragraph tag without newlines. + +** =# type:= =snippet= or =command= + +If the =type= directive is set to =command=, the body of the snippet +is interpreted as lisp code to be evaluated when the snippet is +triggered. + +If it's =snippet= (the default when there is no =type= directive), the +snippet body will be parsed according to the [[Template Syntax]], +described below. + +** =# uuid:= unique identifier + +This provides to a way to identify a snippet, independent of its name. +Loading a second snippet file with the same uuid would replace the +previous snippet. + +** =# contributor:= snippet author + +This is optional and has no effect whatsoever on snippet functionality, +but it looks nice. + +* Template Syntax + +The syntax of the snippet template is simple but powerful, very similar +to TextMate's. + +** Plain Text + +Arbitrary text can be included as the content of a template. They are +usually interpreted as plain text, except =$= and =`=. You need to +use =\= to escape them: =\$= and =\`=. The =\= itself may also needed to be +escaped as =\\= sometimes. + +** Embedded Emacs-lisp code + +Emacs-Lisp code can be embedded inside the template, written inside +back-quotes (=`=). The lisp forms are evaluated when the snippet is +being expanded. The evaluation is done in the same buffer as the +snippet being expanded. + +Here's an example for c-mode to calculate the header file guard +dynamically: + +#+BEGIN_SRC snippet + #ifndef ${1:_`(upcase (file-name-nondirectory (file-name-sans-extension (buffer-file-name))))`_H_} + #define $1 + + $0 + + #endif /* $1 */ +#+END_SRC + +From version 0.6, snippets expansions are run with some special +Emacs-lisp variables bound. One of this is [[sym:yas-selected-text][=yas-selected-text=]]. You can +therefore define a snippet like: + +#+BEGIN_SRC snippet + for ($1;$2;$3) { + `yas-selected-text`$0 + } +#+END_SRC + +to "wrap" the selected region inside your recently inserted snippet. +Alternatively, you can also customize the variable +[[sym:yas-wrap-around-region][=yas-wrap-around-region=]] to =t= which will do this automatically. + +*** Note: backquote expressions should not modify the buffer + +Please note that the lisp forms in backquotes should *not* modify the +buffer, doing so will trigger a warning. For example, instead of +doing + +#+BEGIN_SRC snippet + Timestamp: `(insert (current-time-string))` +#+END_SRC + +do this: +#+BEGIN_SRC snippet + Timestamp: `(current-time-string)` +#+END_SRC + +The warning may be suppressed with the following code in your init file: +#+BEGIN_SRC emacs-lisp + (add-to-list 'warning-suppress-types '(yasnippet backquote-change)) +#+END_SRC + + +** Tab stop fields + +Tab stops are fields that you can navigate back and forth by =TAB= and +=S-TAB=. They are written by =$= followed with a number. =$0= has the +special meaning of the /exit point/ of a snippet. That is the last place +to go when you've traveled all the fields. Here's a typical example: + +#+BEGIN_SRC snippet + + $0 + +#+END_SRC +** Placeholder fields + +Tab stops can have default values -- a.k.a placeholders. The syntax is +like this: + +#+BEGIN_SRC snippet + ${N:default value} +#+END_SRC + +They act as the default value for a tab stop. But when you first +type at a tab stop, the default value will be replaced by your typing. +The number can be omitted if you don't want to create [[mirrors-fields][mirrors]] or +[[mirror-transformations][transformations]] for this field. + +** Mirrors <> + +We refer the tab stops with placeholders as a /field/. A field can +have mirrors. *All* mirrors get updated whenever you update any field +text. Here's an example: + +#+BEGIN_SRC snippet + \begin{${1:enumerate}} + $0 + \end{$1} +#+END_SRC + +When you type "document" at =${1:enumerate}=, the word "document" will +also be inserted at =\end{$1}=. The best explanation is to see the +screencast([[http://www.youtube.com/watch?v=vOj7btx3ATg][YouTube]] or [[http://yasnippet.googlecode.com/files/yasnippet.avi][avi video]]). + +The tab stops with the same number to the field act as its mirrors. If +none of the tab stops has an initial value, the first one is selected as +the field and others mirrors. + +** Mirrors with transformations <> + +If the value of an =${n:=-construct starts with and contains =$(=, +then it is interpreted as a mirror for field =n= with a +transformation. The mirror's text content is calculated according to +this transformation, which is Emacs-lisp code that gets evaluated in +an environment where the variable [[sym:yas-text][=yas-text=]] is bound to the text +content (string) contained in the field =n=. Here's an example for +Objective-C: + +#+BEGIN_SRC snippet + - (${1:id})${2:foo} + { + return $2; + } + + - (void)set${2:$(capitalize yas-text)}:($1)aValue + { + [$2 autorelease]; + $2 = [aValue retain]; + } + $0 +#+END_SRC + +Look at =${2:$(capitalize yas-text)}=, it is a mirror with +transformation instead of a field. The actual field is at the first +line: =${2:foo}=. When you type text in =${2:foo}=, the transformation +will be evaluated and the result will be placed there as the +transformed text. So in this example, if you type "baz" in the field, +the transformed text will be "Baz". This example is also available in +the screencast. + +Another example is for =rst-mode=. In reStructuredText, the document +title can be some text surrounded by "===" below and above. The "===" +should be at least as long as the text. So + +#+BEGIN_SRC rst + ===== + Title + ===== +#+END_SRC + +is a valid title but + +#+BEGIN_SRC rst + === + Title + === +#+END_SRC + +is not. Here's an snippet for rst title: + +#+BEGIN_SRC snippet + ${1:$(make-string (string-width yas-text) ?\=)} + ${1:Title} + ${1:$(make-string (string-width yas-text) ?\=)} + + $0 +#+END_SRC + +Note that a mirror with a transform is not restricted to the text of +the field it is mirroring. By making use of [[sym:yas-field-value][=yas-field-value=]], a +mirror can look at any of the snippet's field (as mentioned above, all +mirrors are updated when any field is updated). Here is an example +which shows a "live" result of calling format: + +#+BEGIN_SRC snippet +(format "${1:formatted %s}" "${2:value}") +=> "${1:$(ignore-errors (format (yas-field-value 1) (yas-field-value 2)))}" +#+END_SRC + +To keep the example simple, it uses =ignore-errors= to suppress errors +due to incomplete format codes. + +** Fields with transformations + +From version 0.6 on, you can also have lisp transformation inside +fields. These work mostly like mirror transformations. However, they +are evaluated when you first enter the field, after each change you +make to the field and also just before you exit the field. + +The syntax is also a tiny bit different, so that the parser can +distinguish between fields and mirrors. In the following example + +: #define "${1:mydefine$(upcase yas-text)}" + +=mydefine= gets automatically upcased to =MYDEFINE= once you enter the +field. As you type text, it gets filtered through the transformation +every time. + +Note that to tell this kind of expression from a mirror with a +transformation, YASnippet needs extra text between the =:= and the +transformation's =$=. If you don't want this extra-text, you can use two +=$='s instead. + +: #define "${1:$$(upcase yas-text)}" + +Please note that as soon as a transformation takes place, it changes the +value of the field and sets it its internal modification state to +=true=. As a consequence, the auto-deletion behaviour of normal fields +does not take place. This is by design. + +** Choosing fields value from a list and other tricks + +As mentioned, the field transformation is invoked just after you enter +the field, and with some useful variables bound, notably +[[sym:yas-modified-p][=yas-modified-p=]] and [[sym:yas-moving-away-p][=yas-moving-away-p=]]. Because of this feature you +can place a transformation in the primary field that lets you select +default values for it. + +The [[sym:yas-choose-value][=yas-choose-value=]] does this work for you. For example: + +#+BEGIN_SRC snippet +
+ $0 +
+#+END_SRC + +See the definition of [[sym:yas-choose-value][=yas-choose-value=]] to see how it was written using +the two variables. + +Here's another use, for LaTeX-mode, which calls reftex-label just as you +enter snippet field 2. This one makes use of [[sym:yas-modified-p][=yas-modified-p=]] directly. + +#+BEGIN_SRC snippet + \section{${1:"Titel der Tour"}}% + \index{$1}% + \label{{2:"waiting for reftex-label call..."$(unless yas-modified-p (reftex-label nil 'dont- + insert))}}% +#+END_SRC + +The function [[sym:yas-verify-value][=yas-verify-value=]] has another neat trick, and makes use +of [[sym:yas-moving-away-p][=yas-moving-away-p=]]. Try it and see! Also, check out this [[http://groups.google.com/group/smart-snippet/browse_thread/thread/282a90a118e1b662][thread]] + +** Nested placeholder fields + +From version 0.6 on, you can also have nested placeholders of the type: + +#+BEGIN_SRC snippet + $0 +#+END_SRC + +This allows you to choose if you want to give this =div= an =id= +attribute. If you tab forward after expanding, it will let you change +"some\_id" to whatever you like. Alternatively, you can just press =C-d= +(which executes [[sym:yas-skip-and-clear-or-delete-char][=yas-skip-and-clear-or-delete-char=]]) and go straight to +the exit marker. + +By the way, =C-d= will only clear the field if you cursor is at the +beginning of the field /and/ it hasn't been changed yet. Otherwise, it +performs the normal Emacs =delete-char= command. + +** Indentation markers + +If [[sym:yas-indent-line][=yas-indent-line=]] is *not* set to '=auto=, it's still possible to +indent specific lines by adding an indentation marker, =$>=, somewhere +on the line. diff --git a/doc/snippet-expansion.org b/doc/snippet-expansion.org new file mode 100644 index 0000000..a699d57 --- /dev/null +++ b/doc/snippet-expansion.org @@ -0,0 +1,281 @@ +#+SETUPFILE: org-setup.inc + +#+TITLE: Expanding snippets + + This section describes how YASnippet chooses snippets for expansion at point. + + Maybe, you'll want some snippets to be expanded in a particular + mode, or only under certain conditions, or be prompted using + +* Triggering expansion + + You can use YASnippet to expand snippets in different ways: + + - When [[sym:yas-minor-mode][=yas-minor-mode=]] is active: + - Type the snippet's *trigger key* then calling [[sym:yas-expand][=yas-expand=]] + (bound to =TAB= by default). + + - Use the snippet's *keybinding*. + + - By expanding directly from the "YASnippet" menu in the menu-bar + + - Using hippie-expand + + - Call [[sym:yas-insert-snippet][=yas-insert-snippet=]] (use =M-x yas-insert-snippet= or its + keybinding =C-c & C-s=). + + - Use m2m's excellent auto-complete + TODO: example for this + + - Expanding from emacs-lisp code + +** Trigger key + +[[sym:yas-expand][=yas-expand=]] tries to expand a /snippet abbrev/ (also known as +/snippet key/) before point. YASnippet also provides a /conditional +binding/ for this command: the variable [[sym:yas-expand][=yas-maybe-expand=]] contains a +special value which, when bound in a keymap, tells Emacs to call +[[sym:yas-expand][=yas-expand=]] if and only if there is a snippet abbrev before point. +If there is no snippet to expand, Emacs will behave as if [[sym:yas-expand][=yas-expand=]] +is unbound and so will run whatever command is bound to that key +normally. + +When [[sym:yas-minor-mode][=yas-minor-mode=]] is enabled, it binds [[sym:yas-maybe-expand][=yas-maybe-expand=]] to =TAB= +and == by default, however, you can freely remove those bindings: + +#+begin_src emacs-lisp :exports code + (define-key yas-minor-mode-map (kbd "") nil) + (define-key yas-minor-mode-map (kbd "TAB") nil) +#+end_src + +And set your own: + +#+begin_src emacs-lisp :exports code + ;; Bind `SPC' to `yas-expand' when snippet expansion available (it + ;; will still call `self-insert-command' otherwise). + (define-key yas-minor-mode-map (kbd "SPC") yas-maybe-expand) + ;; Bind `C-c y' to `yas-expand' ONLY. + (define-key yas-minor-mode-map (kbd "C-c y") #'yas-expand) +#+end_src + + +To enable the YASnippet minor mode in all buffers globally use the +command [[sym:yas-global-mode][=yas-global-mode=]]. This will enable a modeline indicator, +=yas=: + +[[./images/minor-mode-indicator.png]] + +When you use [[sym:yas-global-mode][=yas-global-mode=]] you can also selectively disable +YASnippet in some buffers by calling [[sym:yas-minor-mode][=yas-minor-mode=]] with a negative +argument in the buffer's mode hook. + +*** Fallback behaviour + +YASnippet used to support a more complicated way of sharing +keybindings before [[sym:yas-expand][=yas-maybe-expand=]] was added. This is now +obsolete. + +** Insert at point + +The command [[sym:yas-insert-snippet][=yas-insert-snippet=]] lets you insert snippets at point +/for your current major mode/. It prompts you for the snippet key +first, and then for a snippet template if more than one template +exists for the same key. + +The list presented contains the snippets that can be inserted at point, +according to the condition system. If you want to see all applicable +snippets for the major mode, prefix this command with =C-u=. + +The prompting methods used are again controlled by +[[sym:yas-prompt-functions][=yas-prompt-functions=]]. + +*** Inserting region or register contents into snippet + +It's often useful to inject already written text in the middle of a +snippet. The variable [[sym:yas-wrap-around-region][=yas-wrap-around-region=]] when to t substitute +the region contents into the =$0= placeholder of a snippet expanded by +[[sym:yas-insert-snippet][=yas-insert-snippet=]]. Setting it to a character value (e.g. =?0=) +will insert the contents of corresponding register. + +Older (versions 0.9.1 and below) of Yasnippet, supported a setting of +=cua= that is equivalent to =?0= but only worked with =cua-mode= +turned on. This setting is still supported for backwards +compatibility, but is now entirely equivalent to =?0=. + +** Snippet keybinding + +See the section of the =# binding:= directive in +[[./snippet-development.org][Writing Snippets]]. + +** Expanding from the menu + +See [[./snippet-menu.org][the YASnippet Menu]]. + +** Expanding with =hippie-expand= + +To integrate with =hippie-expand=, just put +[[sym:yas-hippie-try-expand][=yas-hippie-try-expand=]] in +=hippie-expand-try-functions-list=. This probably makes more sense +when placed at the top of the list, but it can be put anywhere you +prefer. + +** Expanding from emacs-lisp code + +Sometimes you might want to expand a snippet directly from your own +elisp code. You should call [[sym:yas-expand-snippet][=yas-expand-snippet=]] instead of +[[sym:yas-expand][=yas-expand=]] in this case. [[sym:yas-expand-snippet][=yas-expand-snippet=]] takes a string in +snippet template syntax, if you want to expand an existing snippet you +can use [[sym:yas-lookup-snippet][=yas-lookup-snippet=]] to find its contents by name. + +As with expanding from the menubar, the condition system and multiple +candidates doesn't affect expansion (the condition system does affect +[[sym:yas-lookup-snippet][=yas-lookup-snippet=]] though). In fact, expanding from the YASnippet +menu has the same effect of evaluating the follow code: + +#+BEGIN_SRC emacs-lisp + (yas-expand-snippet template) +#+END_SRC + +See the internal documentation on [[sym:yas-expand-snippet][=yas-expand-snippet=]] and +[[sym:yas-lookup-snippet][=yas-lookup-snippet=]] for more information. + +* Controlling expansion + +** Eligible snippets + +YASnippet does quite a bit of filtering to find out which snippets are +eligible for expanding at the current cursor position. + +In particular, the following things matter: + +- Currently loaded snippets tables + + These are loaded from a directory hierarchy in your file system. See + [[./snippet-organization.org][Organizing Snippets]]. They are named + after major modes like =html-mode=, =ruby-mode=, etc... + +- Major mode of the current buffer + + If the currrent major mode matches one of the loaded snippet tables, + then all that table's snippets are considered for expansion. Use + =M-x describe-variable RET major-mode RET= to find out which major + mode you are in currently. + +- Parent tables + + Snippet tables defined as the parent of some other eligible table are + also considered. This works recursively, i.e. parents of parents of + eligible tables are also considered. + +- Buffer-local list of extra modes + + Use [[sym:yas-activate-extra-mode][=yas-activate-extra-mode=]] to + consider snippet tables whose name does not correspond to a major + mode. Typically, you call this from a minor mode hook, for example: + +#+BEGIN_SRC emacs-lisp + ;; When entering rinari-minor-mode, consider also the snippets in the + ;; snippet table "rails-mode" + (add-hook 'rinari-minor-mode-hook + #'(lambda () + (yas-activate-extra-mode 'rails-mode))) +#+END_SRC + +- Buffer-local [[sym:yas-buffer-local-condition][=yas-buffer-local-condition=]] variable + + This variable provides finer grained control over what snippets can + be expanded in the current buffer. For example, the constant + [[sym:yas-not-string-or-comment-condition][=yas-not-string-or-comment-condition=]] has a value that disables + snippet expansion inside comments or string literals. See [[condition-system][the + condition system]] for more info. + +** The condition system <> + +Consider this scenario: you are an old Emacs hacker. You like the +abbrev-way and bind [[sym:yas-expand][=yas-expand=]] to =SPC=. However, you don't want +=if= to be expanded as a snippet when you are typing in a comment +block or a string (e.g. in =python-mode=). + +If you use the =# condition := directive (see [[./snippet-development.org][Writing Snippets]]) you +could just specify the condition for =if= to be =(not +(python-syntax-comment-or-string-p))=. But how about =while=, =for=, +etc? Writing the same condition for all the snippets is just boring. +So you can instead set [[sym:yas-buffer-local-condition][=yas-buffer-local-condition=]] to =(not +(python-syntax-comment-or-string-p))= in =python-mode-hook=. + +Then, what if you really want some particular snippet to expand even +inside a comment? Set [[sym:yas-buffer-local-condition][=yas-buffer-local-condition=]] like this + +#+BEGIN_SRC emacs-lisp + (add-hook 'python-mode-hook + (lambda () + (setq yas-buffer-local-condition + '(if (python-syntax-comment-or-string-p) + '(require-snippet-condition . force-in-comment) + t)))) +#+END_SRC + +... and for a snippet that you want to expand in comments, specify a +condition which evaluates to the symbol =force-in-comment=. Then it +can be expanded as you expected, while other snippets like =if= still +can't expanded in comments. + +For the full set of possible conditions, see the documentation for +[[sym:yas-buffer-local-condition][=yas-buffer-local-condition=]]. + +** Multiples snippet with the same key + +The rules outlined [[Eligible%20snippets][above]] can return more than +one snippet to be expanded at point. + +When there are multiple candidates, YASnippet will let you select one. +The UI for selecting multiple candidate can be customized through +[[sym:yas-prompt-functions][=yas-prompt-functions=]] , which defines your preferred methods of being +prompted for snippets. + +You can customize it with +=M-x customize-variable RET yas-prompt-functions RET=. Alternatively you +can put in your emacs-file: + +#+BEGIN_SRC emacs-lisp + (setq yas-prompt-functions '(yas-x-prompt yas-dropdown-prompt)) +#+END_SRC + +Currently there are some alternatives solution with YASnippet. + +*** Use the X window system + +[[./images/x-menu.png]] + +The function [[sym:yas-x-prompt][=yas-x-prompt=]] can be used to show a popup menu for you to +select. This menu will be part of you native window system widget, which +means: + +- It usually looks beautiful. E.g. when you compile Emacs with gtk + support, this menu will be rendered with your gtk theme. +- Your window system may or may not allow to you use =C-n=, =C-p= to + navigate this menu. +- This function can't be used when in a terminal. + +*** Minibuffer prompting + +[[./images/ido-menu.png]] + +You can use functions [[sym:yas-completing-prompt][=yas-completing-prompt=]] for the classic emacs +completion method or [[sym:yas-ido-prompt][=yas-ido-prompt=]] for a much nicer looking method. +The best way is to try it. This works in a terminal. + +*** Use =dropdown-menu.el= + +[[./images/dropdown-menu.png]] + +The function [[sym:yas-dropdown-prompt][=yas-dropdown-prompt=]] can also be placed in the +[[sym:yas-prompt-functions][=yas-prompt-functions=]] list. + +This works in both window system and terminal and is customizable, you +can use =C-n=, =C-p= to navigate, =q= to quit and even press =6= as a +shortcut to select the 6th candidate. + +*** Roll your own + +See the documentation on variable [[sym:yas-prompt-functions][=yas-prompt-functions=]] diff --git a/doc/snippet-menu.org b/doc/snippet-menu.org new file mode 100644 index 0000000..fee3a19 --- /dev/null +++ b/doc/snippet-menu.org @@ -0,0 +1,68 @@ +#+SETUPFILE: org-setup.inc + +#+TITLE: YASnippet menu + +When [[sym:yas-minor-mode][=yas-minor-mode=]] is active, YASnippet will setup a menu just after +the "Buffers" menu in the menubar. + +In this menu, you can find + +- The currently loaded snippet definitions, organized by major mode, + and optional grouping. + +- A rundown of the most common commands, (followed by their + keybindings) including commands to load directories and reload all + snippet definitions. + +- A series of submenus for customizing and exploring YASnippet + behavior. + +[[./images/menu-1.png]] + +* Loading snippets from menu + +Invoking "Load snippets..." from the menu invokes [[sym:yas-load-directory][=yas-load-directory=]] +and prompts you for a snippet directory hierarchy to load. + +Also useful is the "Reload everything" item to invoke [[sym:yas-reload-all][=yas-reload-all=]] +which uncondionally reloads all the snippets directories defined in +[[sym:yas-snippet-dirs][=yas-snippet-dirs=]] and rebuilds the menus. + +* Snippet menu behavior + +YASnippet will list in this section all the loaded snippet definitions +organized by snippet table name. + +You can use this section to explore currently loaded snippets. If you +click on one of them, the default behavior is to expand it, +unconditionally, inside the current buffer. + +You can however, customize variable [[sym:yas-visit-from-menu][=yas-visit-from-menu=]] to be =t= +which will take you to the snippet definition file when you select it +from the menu. + +If you want the menu show only snippet tables whose name corresponds to +a "real" major mode. You do this by setting [[sym:yas-use-menu][=yas-use-menu=]] to +'=real-modes=. + +Finally, to have the menu show only the tables for the currently active +mode, set [[sym:yas-use-menu][=yas-use-menu=]] to =abbreviate=. + +These customizations can also be found in the menu itself, under the +"Snippet menu behavior" submenu. + +* Controlling indenting + +The "Indenting" submenu contains options to control the values of +[[sym:yas-indent-line][=yas-indent-line=]] and [[sym:yas-also-auto-indent-first-line][=yas-also-auto-indent-first-line=]]. See +[[./snippet-development.org][Writing snippets]]. + +* Prompting method + +The "Prompting method" submenu contains options to control the value of +[[sym:yas-prompt-functions][=yas-prompt-functions=]]. See [[./snippet-expansion.org][Expanding snippets]]. + +* Misc + +The "Misc" submenu contains options to control the values of more +variables. diff --git a/doc/snippet-organization.org b/doc/snippet-organization.org new file mode 100644 index 0000000..22d3e46 --- /dev/null +++ b/doc/snippet-organization.org @@ -0,0 +1,132 @@ +#+SETUPFILE: org-setup.inc + +#+TITLE: Organizing snippets + +* Basic structure + + Snippet collections can be stored in plain text files. They are + arranged by sub-directories naming *snippet tables*. These mostly + name Emacs major mode names. + + #+begin_example + . + |-- c-mode + | `-- printf + |-- java-mode + | `-- println + `-- text-mode + |-- email + `-- time + #+end_example + + The collections are loaded into *snippet tables* which the + triggering mechanism (see [[file:snippet-expansion.org][Expanding Snippets]]) looks up and + (hopefully) causes the right snippet to be expanded for you. + +* Setting up =yas-snippet-dirs= + + The emacs variable [[sym:yas-snippet-dirs][=yas-snippet-dirs=]] tells YASnippet + which collections to consider. It's used when you activate + [[sym:yas-global-mode][=yas-global-mode=]] or call + [[sym:yas-reload-all][=yas-reload-all=]] interactively. + + The default considers: + + - a personal collection that lives in =~/.emacs.d/snippets= + - the bundled collection, taken as a relative path to =yasnippet.el= localtion + + When you come across other snippet collections, do the following to try them + out: + + #+begin_src emacs-lisp :exports code + ;; Develop in ~/emacs.d/mysnippets, but also + ;; try out snippets in ~/Downloads/interesting-snippets + (setq yas-snippet-dirs '("~/emacs.d/mysnippets" + "~/Downloads/interesting-snippets")) + + ;; OR, keeping YASnippet defaults try out ~/Downloads/interesting-snippets + (setq yas-snippet-dirs (append yas-snippet-dirs + '("~/Downloads/interesting-snippets"))) + #+end_src + + Collections appearing earlier in the list override snippets with same names + appearing in collections later in the list. [[sym:yas-new-snippet][=yas-new-snippet=]] always stores + snippets in the first collection. + +* The =.yas-parents= file + + It's very useful to have certain modes share snippets between + themselves. To do this, choose a mode subdirectory and place a + =.yas-parents= containing a whitespace-separated list of other mode + names. When you reload those modes become parents of the original + mode. + + #+begin_example + . + |-- c-mode + | |-- .yas-parents # contains "cc-mode text-mode" + | `-- printf + |-- cc-mode + | |-- for + | `-- while + |-- java-mode + | |-- .yas-parents # contains "cc-mode text-mode" + | `-- println + `-- text-mode + |-- email + `-- time + #+end_example + + +* TODO The =.yas-make-groups= file + + If you place an empty plain text file =.yas-make-groups= inside one + of the mode directories, the names of these sub-directories are + considered groups of snippets and [[file:snippet-menu.org][the menu]] is organized much more + cleanly: + + [[./images/menu-groups.png]] + + Another way to achieve this is to place a =# group:= directive + inside the snippet definition. See [[./snippet-development.org][Writing Snippets]]. + + #+begin_example + $ tree ruby-mode/ + ruby-mode/ + |-- .yas-make-groups + |-- collections + | |-- each + | `-- ... + |-- control structure + | |-- forin + | `-- ... + |-- definitions + | `-- ... + `-- general + `-- ... + #+end_example + + Yet another way to create a nice snippet menu is to write into + =.yas-make-groups= a menu definition. TODO + +* The =.yas-setup.el= file + + If there is file named =.yas-setup.el= in a mode's snippet + subdirectory, it is loaded along with the snippets. Utility + functions used by the snippets can be put here. + +* The =.yas-compiled-snippet.el= file + + You may compile a top-level snippet directory with the + =yas-compile-directory= function, which will create a + =.yas-compiled-snippets.el= file under each mode subdirectory, + which contains definitions for all snippets in the subdirectory. + Compilation helps improve loading time. + + Alternatively, you may compile all directories in the list + =yas-snippet-dirs= with the =yas-recompile-all= function. + +* The =.yas-skip= file + + A =.yas-skip= file in a mode's snippet subdirectory tells YASnippet + not to load snippets from there. diff --git a/doc/snippet-reference.org b/doc/snippet-reference.org new file mode 100644 index 0000000..a38fca5 --- /dev/null +++ b/doc/snippet-reference.org @@ -0,0 +1,12 @@ +#+SETUPFILE: org-setup.inc + +#+TITLE: Reference + +#+BEGIN_SRC emacs-lisp :exports results :results value raw +(yas--document-symbols 1 `("Interactive functions" . ,#'interactive-form) + `("Customization variables" . ,#'(lambda (sym) + (and (boundp sym) + (get sym 'standard-value)))) + `("Useful functions" . ,#'fboundp) + `("Useful variables" . ,#'boundp)) +#+END_SRC diff --git a/doc/stylesheets/manual.css b/doc/stylesheets/manual.css new file mode 100644 index 0000000..74bfe16 --- /dev/null +++ b/doc/stylesheets/manual.css @@ -0,0 +1,70 @@ +.center { margin-left: auto; margin-right: auto; text-align: center; } +.current { + font-weight: bold; + background-color: #E0E8F0; +} + +body { background-color: #E4F0F4 } +div#content { + max-width: 20cm; + margin-left: auto; + margin-right: auto; +} + +nav li { + vertical-align: top; + + display: inline; + list-style-type: none; + padding: 0.5em; +} +nav > ul > li { + display: inline-block; +} +.nopad { + padding: 0; +} +li.border { + border: solid; + border-width: 1px; +} + +pre, code{ background-color: #F3F5F7; } +code { + /* http://neugierig.org/software/chromium/notes/2009/09/monospace-fonts-workaround.html */ + font-family: WorkAroundWebKitAndMozilla, monospace; + white-space: nowrap; +} + +/* Styles for htmlize.el fontification. */ + +.org-comment { color: #005000; } /* font-lock-comment-face */ +.org-keyword { font-weight: bold; } /* font-lock-keyword-face */ +.org-string { color: #8b0000; } /* font-lock-string-face */ +.org-warning { color: #ff8c00; + font-weight: bold; } /* warning */ +.org-warning-1 { color: #ff0000; + font-weight: bold; } /* font-lock-warning-face */ +.org-preprocessor { color: #483d8b; } /* font-lock-preprocessor-face */ +.org-constant { color: #008b8b; } /* font-lock-constant-face */ +.org-function-name { color: #0000ff; } /* font-lock-function-name-face */ +.org-type { color: #228b22; } /* font-lock-type-face */ +.org-variable-name { color: #a0522d; } /* font-lock-variable-name-face */ + +.org-rst-adornment { color: #a020f0; } /* rst-adornment */ +.org-rst-block { color: #a020f0; } /* rst-block */ +.org-rst-comment { color: #b22222; } /* rst-comment */ +.org-rst-definition { color: #0000ff; } /* rst-definition */ +.org-rst-directive { color: #483d8b; } /* rst-directive */ +.org-rst-emphasis1 { font-style: italic; } /* rst-emphasis1 */ +.org-rst-emphasis2 { font-weight: bold; } /* rst-emphasis2 */ +.org-rst-external { color: #228b22; } /* rst-external */ +.org-rst-level-1 { background-color: #d9d9d9; } /* rst-level-1 */ +.org-rst-level-2 { background-color: #c7c7c7; } /* rst-level-2 */ +.org-rst-level-3 { background-color: #b5b5b5; } /* rst-level-3 */ +.org-rst-level-4 { background-color: #a3a3a3; } /* rst-level-4 */ +.org-rst-level-5 { background-color: #919191; } /* rst-level-5 */ +.org-rst-level-6 { background-color: #7f7f7f; } /* rst-level-6 */ +.org-rst-literal { color: #8b2252; } /* rst-literal */ +.org-rst-reference { color: #a0522d; } /* rst-reference */ +.org-rst-transition { color: #a020f0; } /* rst-transition */ diff --git a/doc/yas-doc-helper.el b/doc/yas-doc-helper.el new file mode 100644 index 0000000..f48628f --- /dev/null +++ b/doc/yas-doc-helper.el @@ -0,0 +1,223 @@ +;;; yas-doc-helper.el --- Help generate documentation for YASnippet + +;; Copyright (C) 2012, 2013 Free Software Foundation, Inc. + +;; Author: João Távora +;; Keywords: convenience + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Some functions to help generate YASnippet docs + +;;; Code: + +(eval-when-compile + (require 'cl)) +(require 'org) +(or (require 'org-publish nil t) + (require 'ox-publish)) +(require 'yasnippet) ; docstrings must be loaded + +(defun yas--org-raw-html (tag content &optional attrs) + ;; in version 8.0 org-mode changed the export syntax, see + ;; http://orgmode.org/worg/org-8.0.html#sec-8-1 + (format (if (version< org-version "8.0.0") + "@<%s>%s@" ; old: @ + "@@html:<%s>@@%s@@html:@@") ; new: @@html:@@ + (concat tag (if attrs " ") attrs) + content tag)) + +(defun yas--document-symbol (symbol level) + (let* ((stars (make-string level ?*)) + (args (and (fboundp symbol) + (mapcar #'symbol-name (help-function-arglist symbol t)))) + (heading (cond ((fboundp symbol) + (format + "%s %s (%s)\n" stars (yas--org-raw-html "code" symbol "class='function'") + (mapconcat (lambda (a) + (format (if (string-prefix-p "&" a) + "/%s/" "=%s=") + a)) + args " "))) + (t + (format "%s %s\n" stars + (yas--org-raw-html "code" symbol "class='variable'"))))) + (after-heading (format ":PROPERTIES:\n:CUSTOM_ID: %s\n:END:" symbol)) + (text-quoting-style 'grave) + (body (or (cond ((fboundp symbol) + (let ((doc-synth (car-safe (get symbol 'function-documentation)))) + (if (functionp doc-synth) + (funcall doc-synth nil) + (documentation symbol t)))) + ((boundp symbol) + (documentation-property symbol 'variable-documentation t)) + (t + (format "*WARNING*: no symbol named =%s=" symbol))) + (format "*WARNING*: no doc for symbol =%s=" symbol))) + (case-fold-search nil)) + ;; Do some transformations on the body: + ;; ARGxxx becomes @arg@xxx + ;; FOO becomes /foo/ + ;; `bar' becomes [[#bar][=bar=]] + ;; (...) becomes #+BEGIN_SRC elisp (...) #+END_SRC + ;; Info node `(some-manual) Node Name' becomes + ;; [[https://www.gnu.org/software/emacs/manual/html_node/some-manual/Node-Name.html] + ;; [(some-manual) Node Name]] + ;; + ;; This is fairly fragile, though it seems to be working for + ;; now... + (setq body (replace-regexp-in-string + "\\<\\([A-Z][-A-Z0-9]+\\)\\(\\sw+\\)?\\>" + #'(lambda (match) + (let* ((match1 (match-string 1 match)) + (prefix (downcase match1)) + (suffix (match-string 2 match)) + (fmt (cond + ((member prefix args) + (yas--org-raw-html "code" "%s")) + ((null suffix) "/%s/")))) + (if fmt (format fmt prefix) + match1))) + body t t 1) + body (replace-regexp-in-string + "\\\\{[^}]+}" + (lambda (match) + (concat "#+BEGIN_EXAMPLE\n" + (substitute-command-keys match) + "#+END_EXAMPLE\n")) + body t t) + body (substitute-command-keys body) + body (replace-regexp-in-string + "Info node `(\\([-a-z]+\\)) \\([A-Za-z0-9 ]+\\)'" + (lambda (match) + (let* ((manual (match-string 1 match)) + (node (match-string 2 match)) + (html-node (replace-regexp-in-string " " "-" node t t))) + (format "Info node\ + [[https://www.gnu.org/software/emacs/manual/html_node/%s/%s.html][(%s) %s]]" + manual html-node manual node))) + body t t) + body (replace-regexp-in-string + "`\\([-a-z]+\\)'" + #'(lambda (match) + (let* ((name (downcase (match-string 1 match))) + (sym (intern-soft name))) + (if (memq sym yas--exported-syms) + (format "[[#%s][=%s=]]" name name) + (format "=%s=" name)))) + body t t) + body (replace-regexp-in-string + "\n\n +(.+\\(?:\n +.+\\)*" + (lambda (match) + (concat "\n#+BEGIN_SRC elisp\n" + match + "\n#+END_SRC\n")) + body t t)) + ;; output the paragraph + (concat heading after-heading "\n" body))) + +(defun yas--document-symbols (level &rest names-and-predicates) + (let ((sym-lists (make-vector (length names-and-predicates) nil)) + (stars (make-string level ?*))) + (loop for sym in yas--exported-syms + do (loop for test in (mapcar #'cdr names-and-predicates) + for i from 0 + do (when (funcall test sym) + (push sym (aref sym-lists i)) + (return)))) + (loop for slist across sym-lists + for name in (mapcar #'car names-and-predicates) + concat (format "\n%s %s\n" stars name) + concat (mapconcat (lambda (sym) + (yas--document-symbol sym (1+ level))) + slist "\n\n")))) + +(defun yas--internal-link-snippet () + (interactive) + (yas-expand-snippet "[[#$1][=${1:`yas/selected-text`}=]]")) + +(define-key org-mode-map [M-f8] 'yas--internal-link-snippet) + +;; This lets all the org files be exported to HTML with +;; `org-publish-current-project' (C-c C-e P). + +(defun yas--make-preamble (props) + "Return contents of nav-menu-html.inc. +But replace link to \"current\" page with a span element." + (with-temp-buffer + (let ((dir (file-name-directory (plist-get props :input-file)))) + (insert-file-contents (expand-file-name "nav-menu.html.inc" dir)) + (goto-char (point-min)) + (search-forward (concat "")) + (replace-match "") + (search-forward "") + (replace-match "") + (buffer-string)))) + +(let* ((dir (if load-file-name (file-name-directory load-file-name) + default-directory)) + (src-epoch (getenv "SOURCE_DATE_EPOCH")) + ;; Presence of SOURCE_DATE_EPOCH indicates a reproducible + ;; build, don't depend on git. + (rev (unless src-epoch + (ignore-errors + (car (process-lines "git" "describe" "--dirty"))))) + (date (format-time-string + "(%Y-%m-%d %H:%M:%S)" + (seconds-to-time + (string-to-number + (or (if rev (car (process-lines "git" "show" "--format=%ct")) + src-epoch) + "0"))) + t)) + (proj-plist + `(,@(when (fboundp 'org-html-publish-to-html) + '(:publishing-function org-html-publish-to-html)) + :base-directory ,dir :publishing-directory ,dir + :html-preamble yas--make-preamble + ;;:with-broken-links mark + :html-postamble + ,(concat "

Generated by %c from " + (or rev yas--version) " " date "

\n" + "

%v

\n"))) + (project (assoc "yasnippet" org-publish-project-alist))) + (when rev ;; Rakefile :doc:upload uses "html-revision". + (with-temp-file (expand-file-name "html-revision" dir) + (princ rev (current-buffer)))) + (if project + (setcdr project proj-plist) + (push `("yasnippet" . ,proj-plist) + org-publish-project-alist))) + +(defun yas--generate-html-batch () + (let ((org-publish-use-timestamps-flag nil) + (org-export-copy-to-kill-ring nil) + (org-confirm-babel-evaluate nil) + (make-backup-files nil) + (org-html-htmlize-output-type 'css)) + (org-publish "yasnippet" 'force))) + + + +(provide 'yas-doc-helper) +;; Local Variables: +;; indent-tabs-mode: nil +;; coding: utf-8 +;; End: +;;; yas-doc-helper.el ends here diff --git a/yasnippet-debug.el b/yasnippet-debug.el new file mode 100644 index 0000000..abce89c --- /dev/null +++ b/yasnippet-debug.el @@ -0,0 +1,355 @@ +;;; yasnippet-debug.el --- debug functions for yasnippet -*- lexical-binding: t -*- + +;; Copyright (C) 2010, 2013-2014, 2017-2018 Free Software Foundation, Inc. + +;; Author: João Távora +;; Keywords: emulations, convenience + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Some debug functions. When loaded from the command line, provides +;; quick way to test out snippets in a fresh Emacs instance. +;; +;; emacs -Q -l yasnippet-debug [-v[v]] +;; [-M:] [-M.] [-S:[]] +;; [-- ...] +;; +;; See the source in `yas-debug-process-command-line' for meaning of +;; args. +;; +;;; Code: + +(defconst yas--loaddir + (file-name-directory (or load-file-name buffer-file-name)) + "Directory that yasnippet was loaded from.") + +(require 'yasnippet (if (boundp 'yas--loaddir) + ;; Don't require '-L ' when debugging. + (expand-file-name "yasnippet" yas--loaddir))) +(require 'cl-lib) +(eval-when-compile + (unless (fboundp 'cl-flet) + (defalias 'cl-flet 'flet))) +(require 'color nil t) +(require 'edebug) +(eval-when-compile + (require 'subr-x nil t) + (cond ((fboundp 'when-let*) nil) ; Introduced in 26. + ((fboundp 'when-let) ; Introduced in 25.1, + (defalias 'when-let* 'when-let)) ; deprecated in 26. + (t (defmacro when-let* (key-vals &rest body) + (declare (indent 1) (debug ((symbolp form) body))) + (let ((key-val (pop key-vals))) + (if key-val + `(let ((,(car key-val) ,(cadr key-val))) + (if ,(car key-val) + (when-let* ,key-vals + ,@body))) + `(progn ,@body))))))) + +(defvar yas-debug-live-indicators + (make-hash-table :test #'eq)) + +(defun yas-debug-live-colors () + (let ((colors ())) + (maphash (lambda (_k v) (push (nth 1 (car v)) colors)) yas-debug-live-indicators) + colors)) + +(defvar yas-debug-recently-live-indicators) + +(defun yas-debug-get-live-indicator (location) + (require 'color) + (when (boundp 'yas-debug-recently-live-indicators) + (push location yas-debug-recently-live-indicators)) + (let (beg end) + (if (markerp location) + (setq beg (setq end (marker-position location))) + (setq beg (yas-debug-ov-fom-start location) + end (yas-debug-ov-fom-end location))) + (or (when-let* ((color-ov (gethash location yas-debug-live-indicators))) + (if (and beg end) (move-overlay (cdr color-ov) beg end) + (delete-overlay (cdr color-ov))) + color-ov) + (let* ((live-colors (yas-debug-live-colors)) + (color + (cl-loop with best-color = nil with max-dist = -1 + for color = (format "#%06X" (random #x1000000)) + for comp = (if (fboundp 'color-complement) + (apply #'color-rgb-to-hex (color-complement color)) + color) + if (< (color-distance color (face-foreground 'default)) + (color-distance comp (face-foreground 'default))) + do (setq color comp) + for dist = (cl-loop for c in live-colors + minimize (color-distance c color)) + if (or (not live-colors) (> dist max-dist)) + do (setq best-color color) (setq max-dist dist) + repeat (if live-colors 100 1) + finally return `(:background ,best-color))) + (ov (make-overlay beg end))) + (if (markerp location) + (overlay-put ov 'before-string (propertize "↓" 'face color)) + (overlay-put ov 'before-string (propertize "↘" 'face color)) + (overlay-put ov 'after-string (propertize "↙" 'face color))) + (puthash location (cons color ov) yas-debug-live-indicators))))) + +(defun yas-debug-live-marker (marker) + (let* ((color-ov (yas-debug-get-live-indicator marker)) + (color (car color-ov)) + (ov (cdr color-ov)) + (decorator (overlay-get ov 'before-string)) + (str (format "at %d" (+ marker)))) + (if (markerp marker) + (propertize str + 'cursor-sensor-functions + `(,(lambda (_window _oldpos dir) + (overlay-put + ov 'before-string + (propertize decorator + 'face (if (eq dir 'entered) + 'mode-line-highlight color))))) + 'face color) + str))) + +(defun yas-debug-ov-fom-start (ovfom) + (cond ((overlayp ovfom) (overlay-start ovfom)) + ((integerp ovfom) ovfom) + (t (yas--fom-start ovfom)))) +(defun yas-debug-ov-fom-end (ovfom) + (cond ((overlayp ovfom) (overlay-end ovfom)) + ((integerp ovfom) ovfom) + (t (yas--fom-end ovfom)))) + +(defun yas-debug-live-range (range) + (let* ((color-ov (yas-debug-get-live-indicator range)) + (color (car color-ov)) + (ov (cdr color-ov)) + (decorator-beg (overlay-get ov 'before-string)) + (decorator-end (overlay-get ov 'after-string)) + (beg (yas-debug-ov-fom-start range)) + (end (yas-debug-ov-fom-end range))) + (if (and beg end (not (integerp beg)) (not (integerp end))) + (propertize (format "from %d to %d" (+ beg) (+ end)) + 'cursor-sensor-functions + `(,(lambda (_window _oldpos dir) + (let ((face (if (eq dir 'entered) + 'mode-line-highlight color))) + (overlay-put ov 'before-string + (propertize decorator-beg 'face face)) + (overlay-put ov 'after-string + (propertize decorator-end 'face face))))) + 'face color) + ""))) + +(defmacro yas-debug-with-tracebuf (outbuf &rest body) + (declare (indent 1)) + (let ((tracebuf-var (make-symbol "tracebuf"))) + `(let ((,tracebuf-var (or ,outbuf (get-buffer-create "*YASnippet trace*")))) + (unless (eq ,tracebuf-var (current-buffer)) + (cl-flet ((printf (fmt &rest args) + (with-current-buffer ,tracebuf-var + (insert (apply #'format fmt args))))) + (unless ,outbuf + (with-current-buffer ,tracebuf-var + (erase-buffer) + (when (fboundp 'cursor-sensor-mode) + (cursor-sensor-mode +1)) + (setq truncate-lines t))) + (setq ,outbuf ,tracebuf-var) + (save-restriction + (widen) + ,@body)))))) + + +(defun yas-debug-snippet (snippet &optional outbuf) + (yas-debug-with-tracebuf outbuf + (when-let* ((overlay (yas--snippet-control-overlay snippet))) + (printf "\tsid: %d control overlay %s\n" + (yas--snippet-id snippet) + (yas-debug-live-range overlay))) + (when-let* ((active-field (yas--snippet-active-field snippet))) + (unless (consp (yas--field-start active-field)) + (printf "\tactive field: #%d %s %s covering \"%s\"\n" + (or (yas--field-number active-field) -1) + (if (yas--field-modified-p active-field) "**" "--") + (yas-debug-live-range active-field) + (buffer-substring-no-properties (yas--field-start active-field) (yas--field-end active-field))))) + (when-let* ((exit (yas--snippet-exit snippet))) + (printf "\tsnippet-exit: %s next: %s\n" + (yas-debug-live-marker (yas--exit-marker exit)) + (yas--exit-next exit))) + (dolist (field (yas--snippet-fields snippet)) + (unless (consp (yas--field-start field)) + (printf "\tfield: %d %s %s covering \"%s\" next: %s%s\n" + (or (yas--field-number field) -1) + (if (yas--field-modified-p field) "**" "--") + (yas-debug-live-range field) + (buffer-substring-no-properties (yas--field-start field) (yas--field-end field)) + (yas--debug-format-fom-concise (yas--field-next field)) + (if (yas--field-parent-field field) + (format " parent: %s" + (yas--debug-format-fom-concise + (yas--field-parent-field field))) + ""))) + (dolist (mirror (yas--field-mirrors field)) + (unless (consp (yas--mirror-start mirror)) + (printf "\t\tmirror: %s covering \"%s\" next: %s\n" + (yas-debug-live-range mirror) + (buffer-substring-no-properties (yas--mirror-start mirror) (yas--mirror-end mirror)) + (yas--debug-format-fom-concise (yas--mirror-next mirror)))))))) + +(defvar yas-debug-target-buffer nil) +(defvar yas-debug-target-snippets nil nil) +(make-variable-buffer-local 'yas-debug-target-snippets) + +(defvar yas-debug-undo nil) + +(defun yas-toggle-debug-undo (value) + (interactive (list (not yas-debug-undo))) + (setq yas-debug-undo value) + (yas--message 3 "debug undo %sabled" (if yas-debug-undo "en" "dis"))) + +(defadvice yas--snippet-parse-create (before yas-debug-target-snippet (snippet)) + (add-to-list 'yas-debug-target-snippets snippet)) + +(defadvice yas--commit-snippet (after yas-debug-untarget-snippet (snippet)) + (setq yas-debug-target-snippets + (remq snippet yas-debug-target-snippets)) + (maphash (lambda (k color-ov) + (delete-overlay (cdr color-ov))) + yas-debug-live-indicators) + (clrhash yas-debug-live-indicators)) + +(defun yas-debug-snippets (&optional outbuf hook) + "Print debug information on active snippets to buffer OUTBUF. +If OUTBUF is nil, use a buffer named \"*YASsnippet trace*\". +If HOOK is non-nil, install `yas-debug-snippets' in +`post-command-hook' to update the information on every command +after this one. If it is `snippet-navigation' then install hook +buffer-locally, otherwise install it globally. If HOOK is +`edebug-create', also instrument the function +`yas--snippet-parse-create' with `edebug' and show its source." + (interactive (list nil t)) + (condition-case err + (yas-debug-with-tracebuf outbuf + (unless (buffer-live-p yas-debug-target-buffer) + (setq yas-debug-target-buffer nil)) + (with-current-buffer (or yas-debug-target-buffer (current-buffer)) + (when yas-debug-target-snippets + (setq yas-debug-target-snippets + (cl-delete-if-not #'yas--snippet-p yas-debug-target-snippets))) + (let ((yas-debug-recently-live-indicators nil)) + (dolist (snippet (or yas-debug-target-snippets + (yas-active-snippets))) + (printf "snippet %d\n" (yas--snippet-id snippet)) + (yas-debug-snippet snippet outbuf)) + (maphash (lambda (loc color-ov) + (unless (memq loc yas-debug-recently-live-indicators) + (delete-overlay (cdr color-ov)) + (remhash loc yas-debug-live-indicators))) + yas-debug-live-indicators)) + (when (and yas-debug-undo (listp buffer-undo-list)) + (printf "Undo list has %s elements:\n" (length buffer-undo-list)) + (cl-loop for undo-elem in buffer-undo-list + do (printf "%S\n" undo-elem)))) + (when hook + (setq yas-debug-target-buffer (current-buffer)) + (ad-enable-advice 'yas--snippet-parse-create 'before 'yas-debug-target-snippet) + (ad-activate 'yas--snippet-parse-create) + (ad-enable-advice 'yas--commit-snippet 'after 'yas-debug-untarget-snippet) + (ad-activate 'yas--commit-snippet) + (add-hook 'post-command-hook #'yas-debug-snippets + nil (eq hook 'snippet-navigation)) + ;; Window management is slapped together, it does what I + ;; want when the caller has a single window open. Good + ;; enough for now. + (when (eq hook 'edebug-create) + (edebug-instrument-function 'yas--snippet-parse-create) + (let ((buf-point (find-function-noselect 'yas--snippet-parse-create))) + (with-current-buffer (car buf-point) + (goto-char (cdr buf-point))))) + outbuf)) + ((debug error) (signal (car err) (cdr err))))) + +(defun yas-debug-snippet-create () + (yas-debug-snippets nil 'create)) + +(defun yas--debug-format-fom-concise (fom) + (when fom + (cond ((yas--field-p fom) + (format "field %s from %d to %d" + (yas--field-number fom) + (+ (yas--field-start fom)) + (+ (yas--field-end fom)))) + ((yas--mirror-p fom) + (format "mirror from %d to %d" + (+ (yas--mirror-start fom)) + (+ (yas--mirror-end fom)))) + (t + (format "snippet exit at %d" + (+ (yas--fom-start fom))))))) + +(defun yas-debug-process-command-line (&optional options) + "Implement command line processing." + (setq yas-verbosity 99) + (setq yas-triggers-in-field t) + (setq debug-on-error t) + (let* ((snippet-mode 'fundamental-mode) + (snippet-key nil)) + (unless options + (setq options (cl-loop for opt = (pop command-line-args-left) + while (and opt (not (equal opt "--")) + (string-prefix-p "-" opt)) + collect opt))) + (when-let* ((mode (cl-member "-M:" options :test #'string-prefix-p))) + (setq snippet-mode (intern (concat (substring (car mode) 3) "-mode")))) + (when-let* ((mode (cl-member "-M." options :test #'string-prefix-p))) + (setq snippet-mode + (cdr (cl-assoc (substring (car mode) 2) auto-mode-alist + :test (lambda (ext regexp) (string-match-p regexp ext)))))) + (switch-to-buffer (get-buffer-create "*yas test*")) + (funcall snippet-mode) + (when-let* ((snippet-file (cl-member "-S:" options :test #'string-prefix-p))) + (setq snippet-file (substring (car snippet-file) 3)) + (if (file-exists-p snippet-file) + (with-temp-buffer + (insert-file-contents snippet-file) + (let ((snippet-deflist (yas--parse-template snippet-file))) + (yas-define-snippets snippet-mode (list snippet-deflist)) + (setq snippet-key (car snippet-deflist)))) + (yas-reload-all) + (let ((template (yas--lookup-snippet-1 snippet-file snippet-mode))) + (if template + (setq snippet-key (yas--template-key template)) + (error "No such snippet `%s'" snippet-file))))) + (display-buffer (find-file-noselect + (expand-file-name "yasnippet.el" yas--loaddir))) + (when-let* ((verbosity (car (or (member "-v" options) (member "-vv" options))))) + (set-window-buffer + (split-window) (yas-debug-snippets + nil (if (equal verbosity "-vv") 'edebug-create t)))) + (yas-minor-mode +1) + (when snippet-key (insert snippet-key)))) + +(when command-line-args-left + (yas-debug-process-command-line)) + +(provide 'yasnippet-debug) +;; Local Variables: +;; indent-tabs-mode: nil +;; autoload-compute-prefixes: nil +;; End: +;;; yasnippet-debug.el ends here diff --git a/yasnippet-tests.el b/yasnippet-tests.el new file mode 100644 index 0000000..6048467 --- /dev/null +++ b/yasnippet-tests.el @@ -0,0 +1,1568 @@ +;;; yasnippet-tests.el --- some yasnippet tests -*- lexical-binding: t -*- + +;; Copyright (C) 2012-2015, 2017-2018 Free Software Foundation, Inc. + +;; Author: João Távora +;; Keywords: emulations, convenience + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Test basic snippet mechanics and the loading system + +;; To test this in emacs22 mac osx: +;; curl -L -O https://github.com/mirrors/emacs/raw/master/lisp/emacs-lisp/ert.el +;; curl -L -O https://github.com/mirrors/emacs/raw/master/lisp/emacs-lisp/ert-x.el +;; /usr/bin/emacs -nw -Q -L . -l yasnippet-tests.el --batch -e ert + +;;; Code: + +(require 'yasnippet) +(require 'ert) +(require 'ert-x) +(require 'cl-lib) +(require 'org) + + +;;; Helper macros and function + +(defmacro yas-with-snippet-dirs (dirs &rest body) + (declare (indent defun) (debug t)) + `(yas-call-with-snippet-dirs + ,dirs #'(lambda () ,@body))) + +(defun yas-should-expand (keys-and-expansions) + (dolist (key-and-expansion keys-and-expansions) + (yas-exit-all-snippets) + (erase-buffer) + (insert (car key-and-expansion)) + (ert-simulate-command '(yas-expand)) + (unless (string= (yas--buffer-contents) (cdr key-and-expansion)) + (ert-fail (format "\"%s\" should have expanded to \"%s\" but got \"%s\"" + (car key-and-expansion) + (cdr key-and-expansion) + (yas--buffer-contents))))) + (yas-exit-all-snippets)) + +(defun yas--collect-menu-items (menu-keymap) + (let ((yas--menu-items ())) + (map-keymap (lambda (_binding definition) + (when (eq (car-safe definition) 'menu-item) + (push definition yas--menu-items))) + menu-keymap) + yas--menu-items)) + +(defun yas-should-not-expand (keys) + (dolist (key keys) + (yas-exit-all-snippets) + (erase-buffer) + (insert key) + (ert-simulate-command '(yas-expand)) + (unless (string= (yas--buffer-contents) key) + (ert-fail (format "\"%s\" should have stayed put, but instead expanded to \"%s\"" + key + (yas--buffer-contents)))))) + +(defun yas-mock-insert (string) + (dotimes (i (length string)) + (let ((last-command-event (aref string i))) + (ert-simulate-command '(self-insert-command 1))))) + +(defun yas-mock-yank (string) + (let ((interprogram-paste-function (lambda () string))) + (ert-simulate-command '(yank nil)))) + +(defun yas--key-binding (key) + "Like `key-binding', but override `this-command-keys-vector'. +This lets `yas--maybe-expand-from-keymap-filter' work as expected." + (cl-letf (((symbol-function 'this-command-keys-vector) + (lambda () (cl-coerce key 'vector)))) + (key-binding key))) + +(defun yas-make-file-or-dirs (ass) + (let ((file-or-dir-name (car ass)) + (content (cdr ass))) + (cond ((listp content) + (make-directory file-or-dir-name 'parents) + (let ((default-directory (concat default-directory "/" file-or-dir-name))) + (mapc #'yas-make-file-or-dirs content))) + ((stringp content) + (with-temp-buffer + (insert content) + (write-region nil nil file-or-dir-name nil 'nomessage))) + (t + (message "[yas] oops don't know this content"))))) + + +(defun yas-variables () + (let ((syms)) + (mapatoms #'(lambda (sym) + (if (and (string-match "^yas-[^/]" (symbol-name sym)) + (boundp sym)) + (push sym syms)))) + syms)) + +(defun yas-call-with-saving-variables (fn) + (let* ((vars (yas-variables)) + (saved-values (mapcar #'symbol-value vars))) + (unwind-protect + (funcall fn) + (cl-loop for var in vars + for saved in saved-values + do (set var saved))))) + +(defun yas-call-with-snippet-dirs (dirs fn) + (let* ((default-directory (make-temp-file "yasnippet-fixture" t)) + (yas-snippet-dirs (mapcar (lambda (d) (expand-file-name (car d))) dirs))) + (with-temp-message "" + (unwind-protect + (progn + (mapc #'yas-make-file-or-dirs dirs) + (funcall fn)) + (when (>= emacs-major-version 24) + (delete-directory default-directory 'recursive)))))) + +;;; Older emacsen +;;; +(unless (fboundp 'special-mode) + ;; FIXME: Why provide this default definition here?!? + (defalias 'special-mode 'fundamental)) + +(unless (fboundp 'string-suffix-p) + ;; introduced in Emacs 24.4 + (defun string-suffix-p (suffix string &optional ignore-case) + "Return non-nil if SUFFIX is a suffix of STRING. +If IGNORE-CASE is non-nil, the comparison is done without paying +attention to case differences." + (let ((start-pos (- (length string) (length suffix)))) + (and (>= start-pos 0) + (eq t (compare-strings suffix nil nil + string start-pos nil ignore-case)))))) + + +;;; Snippet mechanics + +(defun yas--buffer-contents () + (buffer-substring-no-properties (point-min) (point-max))) + +(ert-deftest field-navigation () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${1:brother} from ${2:another} ${3:mother}") + (should (string= (yas--buffer-contents) + "brother from another mother")) + (should (looking-at "brother")) + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (looking-at "another")) + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (looking-at "mother")) + (ert-simulate-command '(yas-prev-field)) + (should (looking-at "another")) + (ert-simulate-command '(yas-prev-field)) + (should (looking-at "brother")))) + +(ert-deftest simple-mirror () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${1:brother} from another $1") + (should (string= (yas--buffer-contents) + "brother from another brother")) + (yas-mock-insert "bla") + (should (string= (yas--buffer-contents) + "bla from another bla")))) + +(ert-deftest mirror-with-transformation () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${1:brother} from another ${1:$(upcase yas-text)}") + (should (string= (yas--buffer-contents) + "brother from another BROTHER")) + (yas-mock-insert "bla") + (should (string= (yas--buffer-contents) + "bla from another BLA")))) + +(ert-deftest mirror-with-transformation-and-autofill () + "Test interaction of autofill with mirror transforms" + (let ((words "one two three four five") + filled-words) + (with-temp-buffer + (c-mode) ; In `c-mode' filling comments works by narrowing. + (yas-minor-mode +1) + (setq fill-column 10) + (auto-fill-mode +1) + (yas-expand-snippet "/* $0\n */") + (yas-mock-insert words) + (setq filled-words (delete-and-extract-region (point-min) (point-max))) + (yas-expand-snippet "/* $1\n */\n$2$2") + (should (string= (yas--buffer-contents) + "/* \n */\n")) + (yas-mock-insert words) + (should (string= (yas--buffer-contents) + (concat filled-words "\n")))))) + +(ert-deftest auto-fill-with-multiparagraph () + "Test auto-fill protection on snippet spanning multiple paragraphs" + (with-temp-buffer + (yas-minor-mode +1) + (auto-fill-mode +1) + (yas-expand-snippet "foo$1\n\n$2bar") + (yas-mock-insert " ") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (looking-at "bar")))) + +(ert-deftest primary-field-transformation () + (with-temp-buffer + (yas-minor-mode 1) + (let ((snippet "${1:$$(upcase yas-text)}${1:$(concat \"bar\" yas-text)}")) + (yas-expand-snippet snippet) + (should (string= (yas--buffer-contents) "bar")) + (yas-mock-insert "foo") + (should (string= (yas--buffer-contents) "FOObarFOO"))))) + +(ert-deftest nested-placeholders-kill-superfield () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "brother from ${2:another ${3:mother}}!") + (should (string= (yas--buffer-contents) + "brother from another mother!")) + (yas-mock-insert "bla") + (should (string= (yas--buffer-contents) + "brother from bla!")))) + +(ert-deftest nested-placeholders-use-subfield () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "brother from ${2:another ${3:mother}}!") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "bla") + (should (string= (yas--buffer-contents) + "brother from another bla!")))) + +(ert-deftest mirrors-adjacent-to-fields-with-nested-mirrors () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "<%= f.submit \"${1:Submit}\"${2:$(and (yas-text) \", :disable_with => '\")}${2:$1ing...}${2:$(and (yas-text) \"'\")} %>") + (should (string= (yas--buffer-contents) + "<%= f.submit \"Submit\", :disable_with => 'Submiting...' %>")) + (yas-mock-insert "Send") + (should (string= (yas--buffer-contents) + "<%= f.submit \"Send\", :disable_with => 'Sending...' %>")))) + +(ert-deftest deep-nested-mirroring-issue-351 () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${1:FOOOOOOO}${2:$1}${3:$2}${4:$3}") + (yas-mock-insert "abc") + (should (string= (yas--buffer-contents) "abcabcabcabc")))) + +(ert-deftest delete-numberless-inner-snippet-issue-562 () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${3:${test}bla}$0${2:ble}") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (looking-at "testblable")) + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (ert-simulate-command '(yas-skip-and-clear-field)) + (should (looking-at "ble")) + (should (null (yas-active-snippets))))) + +(ert-deftest delete-nested-simple-field-issue-824 () + "Test deleting a field with a nested simple field in it." + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${3:so-$4and}$0${2:-so}") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (looking-at "so-and-so")) + (ert-simulate-command '(yas-skip-and-clear-or-delete-char)) + (should (looking-at "-so")) + (should (null (yas-active-snippets))))) + +(ert-deftest ignore-trailing-whitespace () + (should (equal + (with-temp-buffer + (insert "# key: foo\n# --\nfoo") + (yas--parse-template)) + (with-temp-buffer + (insert "# key: foo \n# --\nfoo") + (yas--parse-template))))) + +;; (ert-deftest in-snippet-undo () +;; (with-temp-buffer +;; (yas-minor-mode 1) +;; (yas-expand-snippet "brother from ${2:another ${3:mother}}!") +;; (ert-simulate-command '(yas-next-field-or-maybe-expand)) +;; (yas-mock-insert "bla") +;; (ert-simulate-command '(undo)) +;; (should (string= (yas--buffer-contents) +;; "brother from another mother!")))) + +(ert-deftest undo-redo () + "Check redoing of snippet undo." + (yas-with-snippet-dirs '((".emacs.d/snippets" + ("emacs-lisp-mode" ("x" . "${1:one},and done")))) + (with-temp-buffer + (emacs-lisp-mode) + (yas-reload-all) + (yas-minor-mode 1) + (yas-expand-snippet "x$0") + (let ((pre-expand-string (buffer-string))) + (setq buffer-undo-list nil) + (ert-simulate-command '(yas-expand)) + (push nil buffer-undo-list) + (ert-simulate-command '(yas-next-field)) ; $1 -> exit snippet. + (should (string-match-p "\\`one,and done" (buffer-string))) + (push nil buffer-undo-list) + (ert-simulate-command '(undo)) ; Revive snippet. + (ert-simulate-command '(undo)) ; Undo expansion. + (should (string= (buffer-string) pre-expand-string)) + (ert-simulate-command '(move-end-of-line 1)) + (push nil buffer-undo-list) + (ert-simulate-command '(undo)) ; Redo (re-expand snippet). + (should (string-match-p "\\`one,and done" (buffer-string))))))) + +(defun yas-test-expand-and-undo (mode snippet-entry initial-contents) + (yas-with-snippet-dirs + `((".emacs.d/snippets" (,(symbol-name mode) ,snippet-entry))) + (with-temp-buffer + (funcall mode) + (yas-reload-all) + (yas-minor-mode 1) + (yas-expand-snippet initial-contents) + (let ((pre-expand-string (buffer-string))) + (setq buffer-undo-list ()) + (ert-simulate-command '(yas-expand)) + ;; Need undo barrier, I think command loop puts it normally. + (push nil buffer-undo-list) + (ert-simulate-command '(undo)) + (should (string= (buffer-string) pre-expand-string)))))) + +(ert-deftest undo-indentation-1 () + "Check undoing works when only line of snippet is indented." + (let ((yas-also-auto-indent-first-line t)) + (yas-test-expand-and-undo + 'emacs-lisp-mode '("s" . "(setq $0)") "(let\n(while s$0"))) + +(ert-deftest undo-indentation-2 () + "Check undoing works when only line of snippet is indented." + (let ((yas-also-auto-indent-first-line t) + (indent-tabs-mode nil)) + (yas-test-expand-and-undo + 'emacs-lisp-mode '("t" . "; TODO") "t$0"))) + +(ert-deftest undo-indentation-multiline-1 () + "Check undoing works when 1st line of multi-line snippet is indented." + (let ((yas-also-auto-indent-first-line t) + (indent-tabs-mode nil)) + (yas-test-expand-and-undo + 'js-mode '("if" . "if ($1) {\n\n}\n") + "if$0\nabc = 123456789 + abcdef;"))) + + +(ert-deftest undo-indentation-multiline-2 () + "Check undoing works when 2nd line of multi-line snippet is indented." + (let ((yas-also-auto-indent-first-line t) + (indent-tabs-mode nil)) + (yas-test-expand-and-undo + 'js-mode '("if" . "if (true) {\n${1:foo};\n}\n") + "if$0\nabc = 123456789 + abcdef;"))) + +(ert-deftest dont-clear-on-partial-deletion-issue-515 () + "Ensure fields are not cleared when user doesn't really mean to." + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "my ${1:kid brother} from another ${2:mother}") + + (ert-simulate-command '(kill-word 1)) + (ert-simulate-command '(delete-char 1)) + + (should (string= (yas--buffer-contents) + "my brother from another mother")) + (should (looking-at "brother")) + + (ert-simulate-command '(yas-next-field)) + (should (looking-at "mother")) + (ert-simulate-command '(yas-prev-field)) + (should (looking-at "brother")))) + +(ert-deftest do-clear-on-yank-issue-515 () + "A yank should clear an unmodified field." + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "my ${1:kid brother} from another ${2:mother}") + (yas-mock-yank "little sibling") + (should (string= (yas--buffer-contents) + "my little sibling from another mother")) + (ert-simulate-command '(yas-next-field)) + (ert-simulate-command '(yas-prev-field)) + (should (looking-at "little sibling")))) + +(ert-deftest basic-indentation () + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) 'auto) + (set (make-local-variable 'yas-also-auto-indent-first-line) t) + (yas-expand-snippet "def ${1:method}${2:(${3:args})}\n$0\nend") + ;; Note that empty line is not indented. + (should (string= "def method(args) + +end" (buffer-string))) + (cl-loop repeat 3 do (ert-simulate-command '(yas-next-field))) + (yas-mock-insert (make-string (random 5) ?\ )) ; purposedly mess up indentation + (yas-expand-snippet "class << ${self}\n $0\nend") + (ert-simulate-command '(yas-next-field)) + (should (string= "def method(args) + class << self + + end +end" (buffer-string))) + (should (= 4 (current-column))))) + +(ert-deftest yas-also-indent-empty-lines () + "Respect `yas-also-indent-empty-lines' setting." + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) 'auto) + (set (make-local-variable 'yas-also-auto-indent-first-line) t) + (set (make-local-variable 'yas-also-indent-empty-lines) t) + (yas-expand-snippet "def foo\n\nend") + (should (string= "def foo\n \nend" (buffer-string))) + ;; Test that it keeps working without setting + ;; `yas-also-auto-indent-first-line'. + (setq yas-also-auto-indent-first-line nil) + (erase-buffer) + (yas-expand-snippet "def foo\n\nend") + (should (string= "def foo\n \nend" (buffer-string))))) + +(ert-deftest yas-indent-first-line () + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) 'auto) + (set (make-local-variable 'yas-also-auto-indent-first-line) nil) + (set (make-local-variable 'yas-also-indent-empty-lines) nil) + (yas-expand-snippet "def foo\n$0\nend\n") + ;; First (and only) line should not indent. + (yas-expand-snippet "#not indented") + (should (equal "def foo\n#not indented\nend\n" (buffer-string))))) + +(ert-deftest yas-indent-first-line-fixed () + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) 'fixed) + (set (make-local-variable 'yas-also-auto-indent-first-line) nil) + (set (make-local-variable 'yas-also-indent-empty-lines) nil) + (yas-expand-snippet " def foo\n $0\n end\n") + ;; First (and only) line should not indent. + (yas-expand-snippet "#not more indented") + (should (equal " def foo\n #not more indented\n end\n" (buffer-string))))) + +(ert-deftest indentation-markers () + "Test a snippet with indentation markers (`$<')." + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) nil) + (yas-expand-snippet "def ${1:method}${2:(${3:args})}\n$>Indent\nNo indent\\$>\nend") + (should (string= "def method(args) + Indent +No indent$> +end" (buffer-string))))) + +(ert-deftest single-line-multi-mirror-indentation () + "Make sure not to indent with multiple mirrors per line." + ;; See also Github issue #712. + (with-temp-buffer + (text-mode) + (yas-minor-mode 1) + (yas-expand-snippet "${1:XXXXX} -------- +$1 ---------------- $1 ---- +$1 ------------------------") + (should (string= (yas--buffer-contents) "XXXXX -------- +XXXXX ---------------- XXXXX ---- +XXXXX ------------------------")))) + +(ert-deftest single-line-multi-mirror-indentation-2 () + "Like `single-line-multi-mirror-indentation' but 2 mirrors interleaved." + ;; See also Github issue #768. + (with-temp-buffer + (c-mode) + (yas-minor-mode 1) + (yas-expand-snippet "${1:one} ${2:two};\n$1 $2_;\n$2 $1_;\n") + (should (string= (yas--buffer-contents) + "one two;\none two_;\ntwo one_;\n")))) + +(ert-deftest indent-org-property () + "Handling of `org-mode' property indentation, see `org-property-format'." + ;; This is an interesting case because `org-indent-line' calls + ;; `replace-match' for properties. + (with-temp-buffer + (org-mode) + (yas-minor-mode +1) + (yas-expand-snippet "* Test ${1:test}\n:PROPERTIES:\n:ID: $1-after\n:END:") + (yas-mock-insert "foo bar") + (ert-simulate-command '(yas-next-field)) + (goto-char (point-min)) + (let ((expected (with-temp-buffer + (insert (format (concat "* Test foo bar\n" + " " org-property-format "\n" + " " org-property-format "\n" + " " org-property-format) + ":PROPERTIES:" "" + ":ID:" "foo bar-after" + ":END:" "")) + (delete-trailing-whitespace) + (buffer-string)))) + ;; Some org-mode versions leave trailing whitespace, some don't. + (delete-trailing-whitespace) + (should (equal expected (buffer-string)))))) + +(ert-deftest indent-cc-mode () + "Handling of cc-mode's indentation." + ;; This is an interesting case because cc-mode deletes all the + ;; indentation before recreating it. + (with-temp-buffer + (c++-mode) + (yas-minor-mode +1) + (yas-expand-snippet "\ +int foo() +{ + if ($1) { + delete $1; + $1 = 0; + } +}") + (yas-mock-insert "var") + (should (string= "\ +int foo() +{ + if (var) { + delete var; + var = 0; + } +}" (buffer-string))))) + +(ert-deftest indent-snippet-mode () + "Handling of snippet-mode indentation." + ;; This is an interesting case because newlines match [[:space:]] in + ;; snippet-mode. + (with-temp-buffer + (snippet-mode) + (yas-minor-mode +1) + (yas-expand-snippet "# -*- mode: snippet -*-\n# name: $1\n# key: $1\n# --\n") + (yas-mock-insert "foo") + (should (string= "# -*- mode: snippet -*-\n# name: foo\n# key: foo\n# --\n" + (buffer-string))))) + +(ert-deftest indent-mirrors-on-update () + "Check that mirrors are always kept indented." + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (yas-expand-snippet "def $1\n$1\nend") + (yas-mock-insert "xxx") + ;; Assuming 2 space indent. + (should (string= "def xxx\n xxx\nend" (buffer-string))))) + + +(ert-deftest snippet-with-multiline-mirrors-issue-665 () + "In issue 665, a multi-line mirror is attempted." + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (yas-expand-snippet "def initialize(${1:params})\n$2${1:$( +mapconcat #'(lambda (arg) + (format \"@%s = %s\" arg arg)) + (split-string yas-text \", \") + \"\n\")}\nend") + (yas-mock-insert "bla, ble, bli") + (ert-simulate-command '(yas-next-field)) + (let ((expected (mapconcat #'identity + '("@bla = bla" + ;; assume ruby is always indented to 2 spaces + " @ble = ble" + " @bli = bli") + "\n"))) + (should (looking-at expected)) + (yas-mock-insert "blo") + (ert-simulate-command '(yas-prev-field)) + (ert-simulate-command '(yas-next-field)) + (should (looking-at (concat "blo" expected)))))) + + +;;; Snippet expansion and character escaping +;;; Thanks to @zw963 (Billy) for the testing +;;; +(ert-deftest escape-dollar () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "bla\\${1:bla}ble") + (should (string= (yas--buffer-contents) "bla${1:bla}ble")))) + +(ert-deftest escape-closing-brace () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "bla${1:bla\\}}ble") + (should (string= (yas--buffer-contents) "blabla}ble")) + (should (string= (yas-field-value 1) "bla}")))) + +(ert-deftest escape-backslashes () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "bla\\ble") + (should (string= (yas--buffer-contents) "bla\\ble")))) + +(ert-deftest escape-backquotes () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "bla`(upcase \"foo\\`bar\")`ble") + (should (string= (yas--buffer-contents) "blaFOO`BARble")))) + +(ert-deftest escape-some-elisp-with-strings () + "elisp with strings and unbalance parens inside it" + (with-temp-buffer + (yas-minor-mode 1) + ;; The rules here is: to output a literal `"' you need to escape + ;; it with one backslash. You don't need to escape them in + ;; embedded elisp. + (yas-expand-snippet "soon \\\"`(concat (upcase \"(my arms\")\"\\\" were all around her\")`") + (should (string= (yas--buffer-contents) "soon \"(MY ARMS\" were all around her")))) + +(ert-deftest escape-some-elisp-with-backslashes () + (with-temp-buffer + (yas-minor-mode 1) + ;; And the rule here is: to output a literal `\' inside a string + ;; inside embedded elisp you need a total of six `\' + (yas-expand-snippet "bla`(upcase \"hey\\\\\\yo\")`ble") + (should (string= (yas--buffer-contents) "blaHEY\\YOble")))) + +(ert-deftest be-careful-when-escaping-in-yas-selected-text () + (with-temp-buffer + (yas-minor-mode 1) + (let ((yas-selected-text "He\\\\o world!")) + (yas-expand-snippet "Look ma! `(yas-selected-text)`") + (should (string= (yas--buffer-contents) "Look ma! He\\\\o world!"))) + (yas-exit-all-snippets) + (erase-buffer) + (let ((yas-selected-text "He\"o world!")) + (yas-expand-snippet "Look ma! `(yas-selected-text)`") + (should (string= (yas--buffer-contents) "Look ma! He\"o world!"))) + (yas-exit-all-snippets) + (erase-buffer) + (let ((yas-selected-text "He\"\)\\o world!")) + (yas-expand-snippet "Look ma! `(yas-selected-text)`") + (should (string= (yas--buffer-contents) "Look ma! He\"\)\\o world!"))) + (yas-exit-all-snippets) + (erase-buffer))) + +(ert-deftest be-careful-when-escaping-in-yas-selected-text-2 () + (with-temp-buffer + (yas-minor-mode 1) + (let ((yas-selected-text "He)}o world!")) + (yas-expand-snippet "Look ma! ${1:`(yas-selected-text)`} OK?") + (should (string= (yas--buffer-contents) "Look ma! He)}o world! OK?"))))) + +(ert-deftest insert-snippet-with-backslashes-in-active-field () + ;; This test case fails if `yas--inhibit-overlay-hooks' is not bound + ;; in `yas-expand-snippet' (see Github #844). + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${1:$$(if (not yas-modified-p) \"a\")}") + (yas-expand-snippet "\\\\alpha"))) + +(ert-deftest expand-with-unused-yas-selected-text () + (with-temp-buffer + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "expanded `yas-selected-text`foo")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should (equal (buffer-string) "expanded foo"))))) + +(ert-deftest yas-expand-command-snippet () + (with-temp-buffer + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "\ +# type: command +# -- +\(insert \"expanded foo\")")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should (equal (buffer-string) "expanded foo"))))) + +(ert-deftest example-for-issue-271 () + (with-temp-buffer + (yas-minor-mode 1) + (let ((yas-selected-text "aaa") + (snippet "if ${1:condition}\n`yas-selected-text`\nelse\n$3\nend")) + (yas-expand-snippet snippet) + (yas-next-field) + (yas-mock-insert "bbb") + (should (string= (yas--buffer-contents) "if condition\naaa\nelse\nbbb\nend"))))) + +(ert-deftest yas-no-memory-of-bad-snippet () + "Expanding an incorrect snippet should not influence future expansions." + ;; See https://github.com/joaotavora/yasnippet/issues/800. + (with-temp-buffer + (yas-minor-mode 1) + (should-error (yas-expand-snippet "```foo\n\n```")) + (erase-buffer) ; Bad snippet may leave wrong text. + ;; But expanding the corrected snippet should work fine. + (yas-expand-snippet "\\`\\`\\`foo\n\n\\`\\`\\`") + (should (equal (buffer-string) "```foo\n\n```")))) + +(defmacro yas--with-font-locked-temp-buffer (&rest body) + "Like `with-temp-buffer', but ensure `font-lock-mode'." + (declare (indent 0) (debug t)) + (let ((temp-buffer (make-symbol "temp-buffer"))) + ;; NOTE: buffer name must not start with a space, otherwise + ;; `font-lock-mode' doesn't turn on. + `(let ((,temp-buffer (generate-new-buffer "*yas-temp*"))) + (with-current-buffer ,temp-buffer + ;; pretend we're interactive so `font-lock-mode' turns on + (let ((noninteractive nil) + ;; turn on font locking after major mode change + (change-major-mode-after-body-hook #'font-lock-mode)) + (unwind-protect + (progn (require 'font-lock) + ;; turn on font locking before major mode change + (font-lock-mode +1) + ,@body) + (and (buffer-name ,temp-buffer) + (kill-buffer ,temp-buffer)))))))) + +(defmacro yas-saving-variables (&rest body) + (declare (debug t)) + `(yas-call-with-saving-variables #'(lambda () ,@body))) + +(ert-deftest example-for-issue-474 () + (yas--with-font-locked-temp-buffer + (c-mode) + (yas-minor-mode 1) + (insert "#include \n") + (let ((yas-good-grace nil)) (yas-expand-snippet "`\"TODO: \"`")) + (should (string= (yas--buffer-contents) "#include \nTODO: ")))) + +(ert-deftest example-for-issue-404 () + (yas--with-font-locked-temp-buffer + (c++-mode) + (yas-minor-mode 1) + (insert "#include \n") + (let ((yas-good-grace nil)) (yas-expand-snippet "main")) + (should (string= (yas--buffer-contents) "#include \nmain")))) + +(ert-deftest example-for-issue-404-c-mode () + (yas--with-font-locked-temp-buffer + (c-mode) + (yas-minor-mode 1) + (insert "#include \n") + (let ((yas-good-grace nil)) (yas-expand-snippet "main")) + (should (string= (yas--buffer-contents) "#include \nmain")))) + +(ert-deftest middle-of-buffer-snippet-insertion () + (with-temp-buffer + (yas-minor-mode 1) + (insert "beginning") + (save-excursion (insert "end")) + (yas-expand-snippet "-middle-") + (should (string= (yas--buffer-contents) "beginning-middle-end")))) + +(ert-deftest another-example-for-issue-271 () + ;; expect this to fail in batch mode since `region-active-p' doesn't + ;; used by `yas-expand-snippet' doesn't make sense in that context. + ;; + :expected-result (if noninteractive + :failed + :passed) + (with-temp-buffer + (yas-minor-mode 1) + (let ((snippet "\\${${1:1}:`yas-selected-text`}")) + (insert "aaabbbccc") + (set-mark 4) + (goto-char 7) + (yas-expand-snippet snippet) + (should (string= (yas--buffer-contents) "aaa${1:bbb}ccc"))))) + +(ert-deftest string-match-with-subregexp-in-embedded-elisp () + (with-temp-buffer + (yas-minor-mode 1) + ;; the rule here is: To use regexps in embedded `(elisp)` expressions, write + ;; it like you would normal elisp, i.e. no need to escape the backslashes. + (let ((snippet "`(if (string-match \"foo\\\\(ba+r\\\\)foo\" \"foobaaaaaaaaaarfoo\") + \"ok\" + \"fail\")`")) + (yas-expand-snippet snippet)) + (should (string= (yas--buffer-contents) "ok")))) + +(ert-deftest string-match-with-subregexp-in-mirror-transformations () + (with-temp-buffer + (yas-minor-mode 1) + ;; the rule here is: To use regexps in embedded `(elisp)` expressions, + ;; escape backslashes once, i.e. to use \\( \\) constructs, write \\\\( \\\\). + (let ((snippet "$1${1:$(if (string-match \"foo\\\\\\\\(ba+r\\\\\\\\)baz\" yas-text) + \"ok\" + \"fail\")}")) + (yas-expand-snippet snippet) + (should (string= (yas--buffer-contents) "fail")) + (yas-mock-insert "foobaaar") + (should (string= (yas--buffer-contents) "foobaaarfail")) + (yas-mock-insert "baz") + (should (string= (yas--buffer-contents) "foobaaarbazok"))))) + + +;;; Misc tests +;;; +(ert-deftest protection-overlay-no-cheating () + "Protection overlays at the very end of the buffer are dealt + with by cheatingly inserting a newline! + +TODO: correct this bug!" + :expected-result :failed + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${2:brother} from another ${1:mother}") + (should (string= (yas--buffer-contents) + "brother from another mother") ;; no newline should be here! + ))) + +(defvar yas-tests--ran-exit-hook nil) + +(ert-deftest snippet-exit-hooks () + (with-temp-buffer + (yas-saving-variables + (let ((yas-tests--ran-exit-hook nil) + (yas-triggers-in-field t)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "\ +# expand-env: ((yas-after-exit-snippet-hook (lambda () (setq yas-tests--ran-exit-hook t)))) +# -- +FOO ${1:f1} ${2:f2}") + ("sub" . "\ +# expand-env: ((yas-after-exit-snippet-hook (lambda () (setq yas-tests--ran-exit-hook 'sub)))) +# -- +SUB")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should-not yas-tests--ran-exit-hook) + (yas-mock-insert "sub") + (ert-simulate-command '(yas-expand)) + (ert-simulate-command '(yas-next-field)) + (should-not yas-tests--ran-exit-hook) + (ert-simulate-command '(yas-next-field)) + (should (eq yas-tests--ran-exit-hook t))))))) + +(ert-deftest snippet-exit-hooks-bindings () + "Check that `yas-after-exit-snippet-hook' is handled correctly +in the case of a buffer-local variable and being overwritten by +the expand-env field." + (with-temp-buffer + (yas-saving-variables + (let ((yas-tests--ran-exit-hook nil) + (yas-triggers-in-field t) + (yas-after-exit-snippet-hook nil)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "foobar\n") + ("baz" . "\ +# expand-env: ((yas-after-exit-snippet-hook (lambda () (setq yas-tests--ran-exit-hook 'letenv)))) +# -- +foobaz\n")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (add-hook 'yas-after-exit-snippet-hook (lambda () (push 'global yas-tests--ran-exit-hook))) + (add-hook 'yas-after-exit-snippet-hook (lambda () (push 'local yas-tests--ran-exit-hook)) nil t) + (insert "baz") + (ert-simulate-command '(yas-expand)) + (should (eq 'letenv yas-tests--ran-exit-hook)) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should (eq 'global (nth 0 yas-tests--ran-exit-hook))) + (should (eq 'local (nth 1 yas-tests--ran-exit-hook)))))))) + +(ert-deftest snippet-mirror-bindings () + "Check that variables defined with the expand-env field are +accessible from mirror transformations." + (with-temp-buffer + (yas-saving-variables + (let ((yas-triggers-in-field t) + (yas-good-grace nil)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("baz" . "\ +# expand-env: ((func #'upcase)) +# -- +hello ${1:$(when (stringp yas-text) (funcall func yas-text))} foo${1:$$(concat \"baz\")}$0")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "baz") + (ert-simulate-command '(yas-expand)) + (should (string= (yas--buffer-contents) "hello BAZ foobaz\n"))))))) + +(defvar yas--barbaz) +(defvar yas--foobarbaz) + +;; See issue #497. To understand this test, follow the example of the +;; `yas-key-syntaxes' docstring. +;; +(ert-deftest complicated-yas-key-syntaxes () + (with-temp-buffer + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo-barbaz" . "# condition: yas--foobarbaz\n# --\nOKfoo-barbazOK") + ("barbaz" . "# condition: yas--barbaz\n# --\nOKbarbazOK") + ("baz" . "OKbazOK") + ("'quote" . "OKquoteOK")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode-on) + (let ((yas-key-syntaxes '("w" "w_"))) + (let ((yas--barbaz t)) + (yas-should-expand '(("foo-barbaz" . "foo-OKbarbazOK") + ("barbaz" . "OKbarbazOK")))) + (let ((yas--foobarbaz t)) + (yas-should-expand '(("foo-barbaz" . "OKfoo-barbazOK")))) + (let ((yas-key-syntaxes + (cons #'(lambda (_start-point) + (unless (eq ?- (char-before)) + (backward-char) + 'again)) + yas-key-syntaxes)) + (yas--foobarbaz t)) + (yas-should-expand '(("foo-barbaz" . "foo-barOKbazOK"))))) + (let ((yas-key-syntaxes '(yas-try-key-from-whitespace))) + (yas-should-expand '(("xxx\n'quote" . "xxx\nOKquoteOK") + ("xxx 'quote" . "xxx OKquoteOK")))) + (let ((yas-key-syntaxes '(yas-shortest-key-until-whitespace)) + (yas--foobarbaz t) (yas--barbaz t)) + (yas-should-expand '(("foo-barbaz" . "foo-barOKbazOK"))) + (setq yas-key-syntaxes '(yas-longest-key-from-whitespace)) + (yas-should-expand '(("foo-barbaz" . "OKfoo-barbazOK") + ("foo " . "foo ")))))))) + +(ert-deftest nested-snippet-expansion-1 () + (with-temp-buffer + (yas-minor-mode +1) + (let ((yas-triggers-in-field t)) + (yas-expand-snippet "Parent $1 Snippet") + (yas-expand-snippet "(Child $1 $2 Snippet)") + (let ((snippets (yas-active-snippets))) + (should (= (length snippets) 2)) + (should (= (length (yas--snippet-fields (nth 0 snippets))) 2)) + (should (= (length (yas--snippet-fields (nth 1 snippets))) 1)))))) + +(ert-deftest nested-snippet-expansion-2 () + (let ((yas-triggers-in-field t)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("text-mode" + ("nest" . "one($1:$1) two($2).$0")))) + (yas-reload-all) + (text-mode) + (yas-minor-mode +1) + (insert "nest") + (ert-simulate-command '(yas-expand)) + (yas-mock-insert "nest") + (ert-simulate-command '(yas-expand)) + (yas-mock-insert "x") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "y") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "z") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (string= (buffer-string) + "one(one(x:x) two(y).:one(x:x) two(y).) two(z)."))))) + +(ert-deftest nested-snippet-expansion-3 () + (let ((yas-triggers-in-field t)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("text-mode" + ("rt" . "\ +\\sqrt${1:$(if (string-equal \"\" yas/text) \"\" \"[\")}${1:}${1:$(if (string-equal \"\" yas/text) \"\" \"]\")}{$2}$0")))) + (yas-reload-all) + (text-mode) + (yas-minor-mode +1) + (insert "rt") + (ert-simulate-command '(yas-expand)) + (yas-mock-insert "3") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "rt") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "5") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "2") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (string= (buffer-string) "\\sqrt[3]{\\sqrt[5]{2}}"))))) + + +;;; Loading +;;; + +(defmacro yas-with-overriden-buffer-list (&rest body) + (declare (debug t)) + (let ((saved-sym (make-symbol "yas--buffer-list"))) + `(let ((,saved-sym (symbol-function 'buffer-list))) + (cl-letf (((symbol-function 'buffer-list) + (lambda () + (cl-remove-if (lambda (buf) + (with-current-buffer buf + (eq major-mode 'lisp-interaction-mode))) + (funcall ,saved-sym))))) + ,@body)))) + + +(defmacro yas-with-some-interesting-snippet-dirs (&rest body) + (declare (debug t)) + `(yas-saving-variables + (yas-with-overriden-buffer-list + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("c-mode" + (".yas-parents" . "cc-mode") + ("printf" . "printf($1);")) ;; notice the overriding for issue #281 + ("emacs-lisp-mode" ("ert-deftest" . "(ert-deftest ${1:name} () $0)")) + ("lisp-interaction-mode" (".yas-parents" . "emacs-lisp-mode"))) + ("library/snippets" + ("c-mode" + (".yas-parents" . "c++-mode") + ("printf" . "printf")) + ("cc-mode" ("def" . "# define")) + ("emacs-lisp-mode" ("dolist" . "(dolist)")) + ("lisp-interaction-mode" ("sc" . "brother from another mother")))) + ,@body)))) + +(ert-deftest snippet-lookup () + "Test `yas-lookup-snippet'." + (yas-with-some-interesting-snippet-dirs + (yas-reload-all 'no-jit) + (should (equal (yas--template-content (yas-lookup-snippet "printf" 'c-mode)) + "printf($1);")) + (should (equal (yas--template-content (yas-lookup-snippet "def" 'c-mode)) + "# define")) + (should-not (yas-lookup-snippet "no such snippet" nil 'noerror)) + (should-not (yas-lookup-snippet "printf" 'emacs-lisp-mode 'noerror)))) + +(ert-deftest yas-lookup-snippet-with-env () + (with-temp-buffer + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "\ +# expand-env: ((foo \"bar\")) +# -- +`foo`")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (yas-expand-snippet (yas-lookup-snippet "foo")) + (should (equal (buffer-string) "bar"))))) + +(ert-deftest basic-jit-loading () + "Test basic loading and expansion of snippets" + (yas-with-some-interesting-snippet-dirs + (yas-reload-all) + (yas--basic-jit-loading-1))) + +(ert-deftest basic-jit-loading-with-compiled-snippets () + "Test basic loading and expansion of compiled snippets" + (yas-with-some-interesting-snippet-dirs + (yas-reload-all) + (yas-recompile-all) + (cl-letf (((symbol-function 'yas--load-directory-2) + (lambda (&rest _dummies) + (ert-fail "yas--load-directory-2 shouldn't be called when snippets have been compiled")))) + (yas-reload-all) + (yas--basic-jit-loading-1)))) + +(ert-deftest snippet-load-uuid () + "Test snippets with same uuid override old ones." + (yas-saving-variables + (yas-define-snippets + 'text-mode + '(("1" "one" "one" nil nil nil nil "C-c 1" "uuid-1") + ("2" "two" "two" nil nil nil nil nil "uuid-2"))) + (with-temp-buffer + (text-mode) + (yas-minor-mode +1) + (should (equal (yas--template-content (yas-lookup-snippet "one")) + "one")) + (should (eq (yas--key-binding "\C-c1") 'yas-expand-from-keymap)) + (yas-define-snippets + 'text-mode '(("_1" "one!" "won" nil nil nil nil nil "uuid-1"))) + (should (null (yas-lookup-snippet "one" nil 'noerror))) + (should (null (yas--key-binding "\C-c1"))) + (should (equal (yas--template-content(yas-lookup-snippet "won")) + "one!"))))) + +(ert-deftest snippet-save () + "Make sure snippets can be saved correctly." + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("text-mode"))) + (cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest _) t)) + ((symbol-function 'read-file-name) + (lambda (_prompt &optional _dir _default _mustmatch initial _predicate) + (expand-file-name initial))) + ((symbol-function 'completing-read) + (lambda (_prompt collection &rest _) + (or (car collection) "")))) + (with-temp-buffer + (text-mode) + (yas-minor-mode +1) + (save-current-buffer + (yas-new-snippet t) + (with-current-buffer yas-new-snippet-buffer-name + (snippet-mode) + (insert "# name: foo\n# key: bar\n# --\nsnippet foo") + (call-interactively 'yas-load-snippet-buffer-and-close))) + (save-current-buffer + (yas-new-snippet t) + (with-current-buffer yas-new-snippet-buffer-name + (snippet-mode) + (insert "# name: bar\n# key: bar\n# --\nsnippet bar") + (call-interactively 'yas-load-snippet-buffer-and-close))) + (should (file-readable-p + (expand-file-name "foo" (car yas-snippet-dirs)))) + (should (file-readable-p + (expand-file-name "bar" (car yas-snippet-dirs))))))))) + +(ert-deftest visiting-compiled-snippets () + "Test snippet visiting for compiled snippets." + (yas-with-some-interesting-snippet-dirs + (yas-recompile-all) + (yas-reload-all 'no-jit) ; must be loaded for `yas-lookup-snippet' to work. + (cl-letf (((symbol-function 'find-file-noselect) + (lambda (filename &rest _) + (throw 'yas-snippet-file filename)))) + (should (string-suffix-p + "cc-mode/def" + (catch 'yas-snippet-file + (yas--visit-snippet-file-1 + (yas--lookup-snippet-1 "def" 'cc-mode)))))))) + +(ert-deftest loading-with-cyclic-parenthood () + "Test loading when cyclic parenthood is setup." + (yas-saving-variables + (yas-with-snippet-dirs '((".emacs.d/snippets" + ("c-mode" + (".yas-parents" . "cc-mode")) + ("cc-mode" + (".yas-parents" . "yet-another-c-mode and-that-one")) + ("yet-another-c-mode" + (".yas-parents" . "c-mode and-also-this-one lisp-interaction-mode")))) + (yas-reload-all) + (with-temp-buffer + (let* ((major-mode 'c-mode) + (expected `(c-mode + cc-mode + yet-another-c-mode + and-also-this-one + and-that-one + ;; prog-mode doesn't exist in emacs 23.4 + ,@(if (fboundp 'prog-mode) + '(prog-mode)) + emacs-lisp-mode + lisp-interaction-mode)) + (observed (yas--modes-to-activate))) + (should (equal major-mode (car observed))) + (should (equal (sort expected #'string<) (sort observed #'string<)))))))) + +(ert-deftest extra-modes-parenthood () + "Test activation of parents of `yas--extra-modes'." + (yas-saving-variables + (yas-with-snippet-dirs '((".emacs.d/snippets" + ("c-mode" + (".yas-parents" . "cc-mode")) + ("yet-another-c-mode" + (".yas-parents" . "c-mode and-also-this-one lisp-interaction-mode")))) + (yas-reload-all) + (with-temp-buffer + (yas-activate-extra-mode 'c-mode) + (yas-activate-extra-mode 'yet-another-c-mode) + (yas-activate-extra-mode 'and-that-one) + (let* ((expected-first `(and-that-one + yet-another-c-mode + c-mode + ,major-mode)) + (expected-rest `(cc-mode + ;; prog-mode doesn't exist in emacs 23.4 + ,@(if (fboundp 'prog-mode) + '(prog-mode)) + emacs-lisp-mode + and-also-this-one + lisp-interaction-mode)) + (observed (yas--modes-to-activate))) + (should (equal expected-first + (cl-subseq observed 0 (length expected-first)))) + (should (equal (sort expected-rest #'string<) + (sort (cl-subseq observed (length expected-first)) #'string<)))))))) + +(defalias 'yas--phony-c-mode 'c-mode) + +(ert-deftest issue-492-and-494 () + (define-derived-mode yas--test-mode yas--phony-c-mode "Just a test mode") + (yas-with-snippet-dirs '((".emacs.d/snippets" + ("yas--test-mode"))) + (yas-reload-all) + (with-temp-buffer + (let* ((major-mode 'yas--test-mode) + (expected `(c-mode + ,@(if (fboundp 'prog-mode) + '(prog-mode)) + yas--phony-c-mode + yas--test-mode)) + (observed (yas--modes-to-activate))) + (should (null (cl-set-exclusive-or expected observed))) + (should (= (length expected) + (length observed))))))) + +(define-derived-mode yas--test-mode c-mode "Just a test mode") +(define-derived-mode yas--another-test-mode c-mode "Another test mode") + +(ert-deftest issue-504-tricky-jit () + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("yas--another-test-mode" + (".yas-parents" . "yas--test-mode")) + ("yas--test-mode"))) + (let ((b (with-current-buffer (generate-new-buffer "*yas-test*") + (yas--another-test-mode) + (current-buffer)))) + (unwind-protect + (progn + (yas-reload-all) + (should (= 0 (hash-table-count yas--scheduled-jit-loads)))) + (kill-buffer b))))) + +(defun yas--basic-jit-loading-1 () + (with-temp-buffer + (should (= 4 (hash-table-count yas--scheduled-jit-loads))) + (should (= 0 (hash-table-count yas--tables))) + (lisp-interaction-mode) + (yas-minor-mode 1) + (should (= 2 (hash-table-count yas--scheduled-jit-loads))) + (should (= 2 (hash-table-count yas--tables))) + (should (= 1 (hash-table-count (yas--table-uuidhash (gethash 'lisp-interaction-mode yas--tables))))) + (should (= 2 (hash-table-count (yas--table-uuidhash (gethash 'emacs-lisp-mode yas--tables))))) + (yas-should-expand '(("sc" . "brother from another mother") + ("dolist" . "(dolist)") + ("ert-deftest" . "(ert-deftest name () )"))) + (c-mode) + (yas-minor-mode 1) + (should (= 0 (hash-table-count yas--scheduled-jit-loads))) + (should (= 4 (hash-table-count yas--tables))) + (should (= 1 (hash-table-count (yas--table-uuidhash (gethash 'c-mode yas--tables))))) + (should (= 1 (hash-table-count (yas--table-uuidhash (gethash 'cc-mode yas--tables))))) + (yas-should-expand '(("printf" . "printf();") + ("def" . "# define"))) + (yas-should-not-expand '("sc" "dolist" "ert-deftest")))) + + +;;; Unloading +(ert-deftest yas-unload () + "Test unloading and reloading." + (with-temp-buffer + (let ((status (call-process + (concat invocation-directory invocation-name) + nil '(t t) nil + "-Q" "--batch" "-L" yas--loaddir "-l" "yasnippet" + "--eval" + (prin1-to-string + '(condition-case err + (progn + (yas-minor-mode +1) + (unload-feature 'yasnippet) + ;; Unloading leaves `yas-minor-mode' bound, + ;; harmless, though perhaps surprising. + (when (bound-and-true-p yas-minor-mode) + (error "`yas-minor-mode' still enabled")) + (when (fboundp 'yas-minor-mode) + (error "`yas-minor-mode' still fboundp")) + (require 'yasnippet) + (unless (fboundp 'yas-minor-mode) + (error "Failed to reload"))) + (error (message "%S" (error-message-string err)) + (kill-emacs 1))))))) + (ert-info ((buffer-string)) (should (eq status 0)))))) + + +;;; Menu +;;; +(defmacro yas-with-even-more-interesting-snippet-dirs (&rest body) + (declare (debug t)) + `(yas-saving-variables + (yas-with-snippet-dirs + `((".emacs.d/snippets" + ("c-mode" + (".yas-make-groups" . "") + ("printf" . "printf($1);") + ("foo-group-a" + ("fnprintf" . "fprintf($1);") + ("snprintf" . "snprintf($1);")) + ("foo-group-b" + ("strcmp" . "strecmp($1);") + ("strcasecmp" . "strcasecmp($1);"))) + ("lisp-interaction-mode" + ("ert-deftest" . "# group: barbar\n# --\n(ert-deftest ${1:name} () $0)")) + ("fancy-mode" + ("a-guy" . "# uuid: 999\n# --\nyo!") + ("a-sir" . "# uuid: 12345\n# --\nindeed!") + ("a-lady" . "# uuid: 54321\n# --\noh-la-la!") + ("a-beggar" . "# uuid: 0101\n# --\narrrgh!") + ("an-outcast" . "# uuid: 666\n# --\narrrgh!") + (".yas-setup.el" . , (pp-to-string + '(yas-define-menu 'fancy-mode + '((yas-ignore-item "0101") + (yas-item "999") + (yas-submenu "sirs" + ((yas-item "12345"))) + (yas-submenu "ladies" + ((yas-item "54321")))) + '("666"))))))) + ,@body))) + +(ert-deftest test-yas-define-menu () + (let ((yas-use-menu t)) + (yas-with-even-more-interesting-snippet-dirs + (yas-reload-all 'no-jit) + (let ((menu-items (yas--collect-menu-items + (gethash 'fancy-mode yas--menu-table)))) + (should (eql 4 (length menu-items))) + (dolist (item '("a-guy" "a-beggar")) + (should (cl-find item menu-items :key #'cl-second :test #'string=))) + (should-not (cl-find "an-outcast" menu-items :key #'cl-second :test #'string=)) + (dolist (submenu '("sirs" "ladies")) + (should (keymapp + (cl-third + (cl-find submenu menu-items :key #'cl-second :test #'string=))))))))) + +(ert-deftest test-group-menus () + "Test group-based menus using .yas-make-groups and the group directive" + (let ((yas-use-menu t)) + (yas-with-even-more-interesting-snippet-dirs + (yas-reload-all 'no-jit) + ;; first the subdir-based groups + ;; + (let ((menu (cdr (gethash 'c-mode yas--menu-table)))) + (should (eql 3 (length menu))) + (dolist (item '("printf" "foo-group-a" "foo-group-b")) + (should (cl-find item menu :key #'cl-third :test #'string=))) + (dolist (submenu '("foo-group-a" "foo-group-b")) + (should (keymapp + (cl-fourth + (cl-find submenu menu :key #'cl-third :test #'string=)))))) + ;; now group directives + ;; + (let ((menu (cdr (gethash 'lisp-interaction-mode yas--menu-table)))) + (should (eql 1 (length menu))) + (should (cl-find "barbar" menu :key #'cl-third :test #'string=)) + (should (keymapp + (cl-fourth + (cl-find "barbar" menu :key #'cl-third :test #'string=)))))))) + +(ert-deftest test-group-menus-twisted () + "Same as similarly named test, but be mean. + +TODO: be meaner" + (let ((yas-use-menu t)) + (yas-with-even-more-interesting-snippet-dirs + ;; add a group directive conflicting with the subdir and watch + ;; behaviour + (with-temp-buffer + (insert "# group: foo-group-c\n# --\nstrecmp($1)") + (write-region nil nil (concat (car (yas-snippet-dirs)) + "/c-mode/foo-group-b/strcmp"))) + (yas-reload-all 'no-jit) + (let ((menu (cdr (gethash 'c-mode yas--menu-table)))) + (should (eql 4 (length menu))) + (dolist (item '("printf" "foo-group-a" "foo-group-b" "foo-group-c")) + (should (cl-find item menu :key #'cl-third :test #'string=))) + (dolist (submenu '("foo-group-a" "foo-group-b" "foo-group-c")) + (should (keymapp + (cl-fourth + (cl-find submenu menu :key #'cl-third :test #'string=)))))) + ;; delete the .yas-make-groups file and watch behaviour + ;; + (delete-file (concat (car (yas-snippet-dirs)) + "/c-mode/.yas-make-groups")) + (yas-reload-all 'no-jit) + (let ((menu (cdr (gethash 'c-mode yas--menu-table)))) + (should (eql 5 (length menu)))) + ;; Change a group directive and reload + ;; + (let ((menu (cdr (gethash 'lisp-interaction-mode yas--menu-table)))) + (should (cl-find "barbar" menu :key #'cl-third :test #'string=))) + + (with-temp-buffer + (insert "# group: foofoo\n# --\n(ert-deftest ${1:name} () $0)") + (write-region nil nil (concat (car (yas-snippet-dirs)) + "/lisp-interaction-mode/ert-deftest"))) + (yas-reload-all 'no-jit) + (let ((menu (cdr (gethash 'lisp-interaction-mode yas--menu-table)))) + (should (eql 1 (length menu))) + (should (cl-find "foofoo" menu :key #'cl-third :test #'string=)) + (should (keymapp + (cl-fourth + (cl-find "foofoo" menu :key #'cl-third :test #'string=)))))))) + + +;;; The infamous and problematic tab keybinding +;;; +(ert-deftest test-yas-tab-binding () + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("fundamental-mode" + ("foo" . "foobar")))) + (yas-reload-all) + (with-temp-buffer + (yas-minor-mode -1) + (insert "foo") + (should (not (eq (key-binding (yas--read-keybinding "")) 'yas-expand))) + (yas-minor-mode 1) + (should (eq (key-binding (yas--read-keybinding "")) 'yas-expand)) + (yas-expand-snippet "$1 $2 $3") + (should (eq (key-binding [(tab)]) 'yas-next-field-or-maybe-expand)) + (should (eq (key-binding (kbd "TAB")) 'yas-next-field-or-maybe-expand)) + (should (eq (key-binding [(shift tab)]) 'yas-prev-field)) + (should (eq (key-binding [backtab]) 'yas-prev-field)))))) + +(ert-deftest test-rebindings () + (let* ((yas-minor-mode-map (copy-keymap yas-minor-mode-map)) + (minor-mode-map-alist + (cons `(yas-minor-mode . ,yas-minor-mode-map) + (cl-remove 'yas-minor-mode minor-mode-map-alist + :test #'eq :key #'car)))) + (define-key yas-minor-mode-map [tab] nil) + (define-key yas-minor-mode-map (kbd "TAB") nil) + (define-key yas-minor-mode-map (kbd "SPC") 'yas-expand) + (with-temp-buffer + (yas-minor-mode 1) + (should-not (eq (key-binding (kbd "TAB")) 'yas-expand)) + (should (eq (key-binding (kbd "SPC")) 'yas-expand)) + (yas-reload-all) + (should-not (eq (key-binding (kbd "TAB")) 'yas-expand)) + (should (eq (key-binding (kbd "SPC")) 'yas-expand))))) + +(ert-deftest test-yas-in-org () + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("org-mode" + ("foo" . "foobar")))) + (yas-reload-all) + (with-temp-buffer + (org-mode) + (yas-minor-mode 1) + (insert "foo") + (should (eq (key-binding [(tab)]) 'yas-expand)) + (should (eq (key-binding (kbd "TAB")) 'yas-expand)))))) + +(ert-deftest yas-org-native-tab-in-source-block () + "Test expansion of snippets in org source blocks." + :expected-result (if (and (fboundp 'org-in-src-block-p) (version< (org-version) "9")) + :passed :failed) + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("text-mode" + ("T" . "${1:one} $1\n${2:two} $2\n<<$0>> done!")))) + (let ((text-mode-hook '(yas-minor-mode)) + (org-src-tab-acts-natively t) + ;; Org 8.x requires this in order for + ;; `org-src-tab-acts-natively' to have effect. + (org-src-fontify-natively t)) + (yas-reload-all) + ;; Org relies on font-lock to identify source blocks. + (yas--with-font-locked-temp-buffer + (org-mode) + (yas-minor-mode 1) + (insert "#+BEGIN_SRC text\nT\n#+END_SRC") + (if (fboundp 'font-lock-ensure) + (font-lock-ensure) + (jit-lock-fontify-now)) + (re-search-backward "^T$") (goto-char (match-end 0)) + (should (org-in-src-block-p)) + (ert-simulate-command `(,(key-binding (kbd "TAB")))) + (ert-simulate-command `(,(key-binding (kbd "TAB")))) + (ert-simulate-command `(,(key-binding (kbd "TAB")))) + ;; Check snippet exit location. + (should (looking-at ">> done!")) + (goto-char (point-min)) + (forward-line) + ;; Check snippet expansion, ignore leading whitespace due to + ;; `org-edit-src-content-indentation'. + (should (looking-at "\ +[[:space:]]*one one +[[:space:]]*two two +[[:space:]]*<<>> done!"))))))) + + +(ert-deftest test-yas-activate-extra-modes () + "Given a symbol, `yas-activate-extra-mode' should be able to +add the snippets associated with the given mode." + (with-temp-buffer + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("markdown-mode" + ("_" . "_Text_ ")) + ("emacs-lisp-mode" + ("car" . "(car )")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode-on) + (yas-activate-extra-mode 'markdown-mode) + (should (eq 'markdown-mode (car yas--extra-modes))) + (yas-should-expand '(("_" . "_Text_ "))) + (yas-should-expand '(("car" . "(car )"))) + (yas-deactivate-extra-mode 'markdown-mode) + (should-not (eq 'markdown-mode (car yas--extra-modes))) + (yas-should-not-expand '("_")) + (yas-should-expand '(("car" . "(car )"))))))) + + + +(provide 'yasnippet-tests) +;; Local Variables: +;; indent-tabs-mode: nil +;; autoload-compute-prefixes: nil +;; End: +;;; yasnippet-tests.el ends here diff --git a/yasnippet.el b/yasnippet.el new file mode 100644 index 0000000..d478073 --- /dev/null +++ b/yasnippet.el @@ -0,0 +1,5138 @@ +;;; yasnippet.el --- Yet another snippet extension for Emacs. + +;; Copyright (C) 2008-2018 Free Software Foundation, Inc. +;; Authors: pluskid , +;; João Távora , +;; Noam Postavsky +;; Maintainer: Noam Postavsky +;; Version: 0.13.0 +;; X-URL: http://github.com/joaotavora/yasnippet +;; Keywords: convenience, emulation +;; URL: http://github.com/joaotavora/yasnippet +;; Package-Requires: ((cl-lib "0.5")) +;; EmacsWiki: YaSnippetMode + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: +;; +;; Basic steps to setup: +;; +;; (add-to-list 'load-path +;; "~/path-to-yasnippet") +;; (require 'yasnippet) +;; (yas-global-mode 1) +;; +;; +;; Interesting variables are: +;; +;; `yas-snippet-dirs' +;; +;; 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 override other dir's snippets. Also, the first +;; directory is taken as the default for storing the user's +;; new snippets. +;; +;; The deprecated `yas/root-directory' aliases this variable +;; for backward-compatibility. +;; +;; +;; Major commands are: +;; +;; M-x yas-expand +;; +;; Try to expand snippets before point. In `yas-minor-mode', +;; this is normally bound to TAB, but you can customize it in +;; `yas-minor-mode-map'. +;; +;; M-x yas-load-directory +;; +;; Prompts you for a directory hierarchy of snippets to load. +;; +;; M-x yas-activate-extra-mode +;; +;; Prompts you for an extra mode to add snippets for in the +;; current buffer. +;; +;; M-x yas-insert-snippet +;; +;; Prompts you for possible snippet expansion if that is +;; possible according to buffer-local and snippet-local +;; expansion conditions. With prefix argument, ignore these +;; conditions. +;; +;; M-x yas-visit-snippet-file +;; +;; Prompts you for possible snippet expansions like +;; `yas-insert-snippet', but instead of expanding it, takes +;; you directly to the snippet definition's file, if it +;; exists. +;; +;; M-x yas-new-snippet +;; +;; Lets you create a new snippet file in the correct +;; subdirectory of `yas-snippet-dirs', according to the +;; active major mode. +;; +;; M-x yas-load-snippet-buffer +;; +;; When editing a snippet, this loads the snippet. This is +;; bound to "C-c C-c" while in the `snippet-mode' editing +;; mode. +;; +;; M-x yas-tryout-snippet +;; +;; When editing a snippet, this opens a new empty buffer, +;; sets it to the appropriate major mode and inserts the +;; snippet there, so you can see what it looks like. This is +;; bound to "C-c C-t" while in `snippet-mode'. +;; +;; M-x yas-describe-tables +;; +;; 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. +;; +;; 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 +;; yas-ido-prompt +;; yas-completing-prompt)) +;; +;; Also check out the customization group +;; +;; M-x customize-group RET yasnippet RET +;; +;; If you use the customization group to set variables +;; `yas-snippet-dirs' or `yas-global-mode', make sure the path to +;; "yasnippet.el" is present in the `load-path' *before* the +;; `custom-set-variables' is executed in your .emacs file. +;; +;; For more information and detailed usage, refer to the project page: +;; http://github.com/joaotavora/yasnippet + +;;; Code: + +(require 'cl-lib) +(declare-function cl-progv-after "cl-extra") ; Needed for 23.4. +(require 'easymenu) +(require 'help-mode) + +(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) +(defvar yas--current-template) + + +;;; User customizable variables + +(defgroup yasnippet nil + "Yet Another Snippet extension" + :prefix "yas-" + :group 'editing) + +(defconst yas--loaddir + (file-name-directory (or load-file-name buffer-file-name)) + "Directory that yasnippet was loaded from.") + +(defconst yas-installed-snippets-dir (expand-file-name "snippets" yas--loaddir)) +(make-obsolete-variable 'yas-installed-snippets-dir "\ +Yasnippet no longer comes with installed snippets" "0.13") + +(defconst yas--default-user-snippets-dir + (expand-file-name "snippets" user-emacs-directory)) + +(defcustom yas-snippet-dirs (list yas--default-user-snippets-dir) + "List of top-level snippet directories. + +Each element, a string or a symbol whose value is a string, +designates a top-level directory where per-mode snippet +directories can be found. + +Elements appearing earlier in the list override later elements' +snippets. + +The first directory is taken as the default for storing snippet's +created with `yas-new-snippet'. " + :type '(choice (directory :tag "Single directory") + (repeat :tag "List of directories" + (choice (directory) (variable)))) + :set #'(lambda (symbol new) + (let ((old (and (boundp symbol) + (symbol-value symbol)))) + (set-default symbol new) + (unless (or (not (fboundp 'yas-reload-all)) + (equal old new)) + (yas-reload-all))))) + +(defun yas-snippet-dirs () + "Return variable `yas-snippet-dirs' as list of strings." + (cl-loop for e in (if (listp yas-snippet-dirs) + yas-snippet-dirs + (list yas-snippet-dirs)) + collect + (cond ((stringp e) e) + ((and (symbolp e) + (boundp e) + (stringp (symbol-value e))) + (symbol-value e)) + (t + (error "[yas] invalid element %s in `yas-snippet-dirs'" e))))) + +(defcustom yas-new-snippet-default "\ +# -*- mode: snippet -*- +# name: $1 +# key: ${2:${1:$(yas--key-from-desc yas-text)}} +# -- +$0`(yas-escape-text yas-selected-text)`" + "Default snippet to use when creating a new snippet. +If nil, don't use any snippet." + :type 'string) + +(defcustom yas-prompt-functions '(yas-dropdown-prompt + yas-completing-prompt + yas-maybe-ido-prompt + yas-no-prompt) + "Functions to prompt for keys, templates, etc interactively. + +These functions are called with the following arguments: + +- PROMPT: A string to prompt the user + +- CHOICES: a list of strings or objects. + +- optional DISPLAY-FN : A function that, when applied to each of +the objects in CHOICES will return a string. + +The return value of any function you put here should be one of +the objects in CHOICES, properly formatted with DISPLAY-FN (if +that is passed). + +- To signal that your particular style of prompting is +unavailable at the moment, you can also have the function return +nil. + +- To signal that the user quit the prompting process, you can +signal `quit' with + + (signal \\='quit \"user quit!\")" + :type '(repeat function)) + +(defcustom yas-indent-line 'auto + "Controls indenting applied to a recent snippet expansion. + +The following values are possible: + +- `fixed' Indent the snippet to the current column; + +- `auto' Indent each line of the snippet with `indent-according-to-mode' + +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) + (const :tag "Fixed" fixed) + (const :tag "Auto" auto))) + +(defcustom yas-also-auto-indent-first-line nil + "Non-nil means also auto indent first line according to mode. + +Naturally this is only valid when `yas-indent-line' is `auto'." + :type 'boolean) + +(defcustom yas-also-indent-empty-lines nil + "Non-nil means also indent empty lines according to mode." + :type 'boolean) + +(defcustom yas-snippet-revival t + "Non-nil means re-activate snippet fields after undo/redo." + :type 'boolean) + +(defcustom yas-triggers-in-field nil + "If non-nil, allow stacked expansions (snippets inside snippets). + +Otherwise `yas-next-field-or-maybe-expand' just moves on to the +next field" + :type 'boolean) + +(defcustom yas-fallback-behavior 'return-nil + "This option is obsolete. +Now that the conditional keybinding `yas-maybe-expand' is +available, there's no more need for it." + :type '(choice (const :tag "Call previous command" call-other-command) + (const :tag "Do nothing" return-nil))) + +(make-obsolete-variable + 'yas-fallback-behavior + "For `call-other-command' behavior bind to the conditional +command value `yas-maybe-expand', for `return-nil' behavior bind +directly to `yas-expand'." + "0.12") + +(defcustom yas-choose-keys-first nil + "If non-nil, prompt for snippet key first, then for template. + +Otherwise prompts for all possible snippet names. + +This affects `yas-insert-snippet' and `yas-visit-snippet-file'." + :type 'boolean) + +(defcustom yas-choose-tables-first nil + "If non-nil, and multiple eligible snippet tables, prompts user for tables first. + +Otherwise, user chooses between the merging together of all +eligible tables. + +This affects `yas-insert-snippet', `yas-visit-snippet-file'" + :type 'boolean) + +(defcustom yas-use-menu 'abbreviate + "Display a YASnippet menu in the menu bar. + +When non-nil, submenus for each snippet table will be listed +under the menu \"Yasnippet\". + +- If set to `abbreviate', only the current major-mode +menu and the modes set in `yas--extra-modes' are listed. + +- If set to `full', every submenu is listed + +- If set to nil, hide the menu. + +Any other non-nil value, every submenu is listed." + :type '(choice (const :tag "Full" full) + (const :tag "Abbreviate" abbreviate) + (const :tag "No menu" nil))) + +(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) + +(defcustom yas-wrap-around-region nil + "What to insert for snippet's $0 field. + +If set to a character, insert contents of corresponding register. +If non-nil insert region contents. This can be overridden on a +per-snippet basis. A value of `cua' is considered equivalent to +`?0' for backwards compatibility." + :type '(choice (character :tag "Insert from register") + (const t :tag "Insert region contents") + (const nil :tag "Don't insert anything") + (const cua))) ; backwards compat + +(defcustom yas-good-grace t + "If non-nil, don't raise errors in elisp evaluation. + +This affects both the inline elisp in snippets and the hook +variables such as `yas-after-exit-snippet-hook'. + +If this variable's value is `inline', an error string \"[yas] +error\" is returned instead of raising the error. If this +variable's value is `hooks', a message is output to according to +`yas-verbosity-level'. If this variable's value is t, both are +active." + :type 'boolean) + +(defcustom yas-visit-from-menu nil + "If non-nil visit snippets's files from menu, instead of expanding them. + +This can only work when snippets are loaded from files." + :type 'boolean) + +(defcustom yas-expand-only-for-last-commands nil + "List of `last-command' values to restrict tab-triggering to, or nil. + +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. + +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." + :type '(repeat function)) + +(defcustom yas-alias-to-yas/prefix-p t + "If non-nil make aliases for the old style yas/ prefixed symbols. +It must be set to nil before loading yasnippet to take effect." + :type 'boolean + :group 'yasnippet) + +;; Only two faces, and one of them shouldn't even be used... +;; +(defface yas-field-highlight-face + '((t (:inherit 'region))) + "The face used to highlight the currently active field of a snippet") + +(defface yas--field-debug-face + '() + "The face used for debugging some overlays normally hidden") + + +;;; User-visible variables + +(defconst yas-maybe-skip-and-clear-field + '(menu-item "" yas-skip-and-clear-field + :filter yas--maybe-clear-field-filter) + "A conditional key definition. +This can be used as a key definition in keymaps to bind a key to +`yas-skip-and-clear-field' only when at the beginning of an +unmodified snippey field.") + +(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-maybe-skip-and-clear-field) + map) + "The active keymap while a snippet expansion is in progress.") + +(defvar yas-key-syntaxes (list #'yas-try-key-from-whitespace + "w_.()" "w_." "w_" "w") + "Syntaxes and functions to help look for trigger keys before point. + +Each element in this list specifies how to skip buffer positions +backwards and look for the start of a trigger key. + +Each element can be either a string or a function receiving the +original point as an argument. A string element is simply passed +to `skip-syntax-backward' whereas a function element is called +with no arguments and should also place point before the original +position. + +The string between the resulting buffer position and the original +point is matched against the trigger keys in the active snippet +tables. + +If no expandable snippets are found, the next element is the list +is tried, unless a function element returned the symbol `again', +in which case it is called again from the previous position and +may once more reposition point. + +For example, if `yas-key-syntaxes' has the value (\"w\" \"w_\"), +trigger keys composed exclusively of \"word\"-syntax characters +are looked for first. Failing that, longer keys composed of +\"word\" or \"symbol\" syntax are looked for. Therefore, +triggering after + +foo-bar + +will, according to the \"w\" element first try \"barbaz\". If +that isn't a trigger key, \"foo-barbaz\" is tried, respecting the +second \"w_\" element. Notice that even if \"baz\" is a trigger +key for an active snippet, it won't be expanded, unless a +function is added to `yas-key-syntaxes' that eventually places +point between \"bar\" and \"baz\". + +See also Info node `(elisp) Syntax Descriptors'.") + +(defvar yas-after-exit-snippet-hook + '() + "Hooks to run after a snippet exited. + +The hooks will be run in an environment where some variables bound to +proper values: + +`yas-snippet-beg' : The beginning of the region of the snippet. + +`yas-snippet-end' : Similar to beg. + +Attention: These hooks are not run when exiting nested/stacked snippet expansion!") + +(defvar yas-before-expand-snippet-hook + '() + "Hooks to run just before expanding a snippet.") + +(defconst yas-not-string-or-comment-condition + '(if (and (let ((ppss (syntax-ppss))) + (or (nth 3 ppss) (nth 4 ppss))) + (memq this-command '(yas-expand yas-expand-from-trigger-key + yas-expand-from-keymap))) + '(require-snippet-condition . force-in-comment) + t) + "Disables snippet expansion in strings and comments. +To use, set `yas-buffer-local-condition' to this value.") + +(defcustom yas-buffer-local-condition t + "Snippet expanding condition. + +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. + + * If it evaluates to the a cons (require-snippet-condition + . REQUIREMENT) + + * Snippets bearing no \"# condition:\" directive are not + considered + + * Snippets bearing conditions that evaluate to nil (or + produce an error) won't be considered. + + * If the snippet has a condition that evaluates to non-nil + RESULT: + + * If REQUIREMENT is t, the snippet is considered + + * If REQUIREMENT is `eq' RESULT, the snippet is + considered + + * Otherwise, the snippet is not considered. + + * If it evaluates to the symbol `always', all snippets are + considered for expansion, regardless of any conditions. + + * If it evaluates to t or some other non-nil value + + * Snippet bearing no conditions, or conditions that + evaluate to non-nil, are considered for expansion. + + * Otherwise, the snippet is not considered. + +Here's an example preventing snippets from being expanded from +inside comments, in `python-mode' only, with the exception of +snippets returning the symbol `force-in-comment' in their +conditions. + + (add-hook \\='python-mode-hook + (lambda () + (setq yas-buffer-local-condition + \\='(if (python-syntax-comment-or-string-p) + \\='(require-snippet-condition . force-in-comment) + t))))" + :type + `(choice + (const :tag "Disable snippet expansion inside strings and comments" + ,yas-not-string-or-comment-condition) + (const :tag "Expand all snippets regardless of conditions" always) + (const :tag "Expand snippets unless their condition is nil" t) + (const :tag "Disable all snippet expansion" nil) + sexp)) + +(defcustom yas-overlay-priority 100 + "Priority to use for yasnippets overlays. +This is useful to control whether snippet navigation bindings +override bindings from other packages (e.g., `company-mode')." + :type 'integer) + + +;;; Internal variables + +(defconst yas--version "0.13.0") + +(defvar yas--menu-table (make-hash-table) + "A hash table of MAJOR-MODE symbols to menu keymaps.") + +(defvar yas--escaped-characters + '(?\\ ?` ?\" ?' ?$ ?} ?{ ?\( ?\)) + "List of characters which *might* need to be escaped.") + +(defconst yas--field-regexp + "${\\([0-9]+:\\)?\\([^}]*\\)}" + "A regexp to *almost* recognize a field.") + +(defconst yas--multi-dollar-lisp-expression-regexp + "$+[ \t\n]*\\(([^)]*)\\)" + "A regexp to *almost* recognize a \"$(...)\" expression.") + +(defconst yas--backquote-lisp-expression-regexp + "`\\([^`]*\\)`" + "A regexp to recognize a \"\\=`lisp-expression\\=`\" expression." ) + +(defconst yas--transform-mirror-regexp + "${\\(?:\\([0-9]+\\):\\)?$\\([ \t\n]*([^}]*\\)" + "A regexp to *almost* recognize a mirror with a transform.") + +(defconst yas--simple-mirror-regexp + "$\\([0-9]+\\)" + "A regexp to recognize a simple mirror.") + +(defvar yas--snippet-id-seed 0 + "Contains the next id for a snippet.") + +(defvar yas--original-auto-fill-function nil + "The original value of `auto-fill-function'.") +(make-variable-buffer-local 'yas--original-auto-fill-function) + +(defvar yas--watch-auto-fill-backtrace nil) + +(defun yas--watch-auto-fill (sym newval op _where) + (when (and (or (and (eq sym 'yas--original-auto-fill-function) + (null newval) + (eq auto-fill-function 'yas--auto-fill)) + (and (eq sym 'auto-fill-function) + (eq newval 'yas--auto-fill) + (null yas--original-auto-fill-function))) + (null yas--watch-auto-fill-backtrace) + (fboundp 'backtrace-frames) ; Suppress compiler warning. + ;; If we're about to change `auto-fill-function' too, + ;; it's okay (probably). + (not (and (eq op 'makunbound) + (not (eq (default-value 'auto-fill-function) 'yas--auto-fill)) + (cl-member 'kill-all-local-variables + (backtrace-frames 'yas--watch-auto-fill) + :key (lambda (frame) (nth 1 frame)))))) + (setq yas--watch-auto-fill-backtrace + (backtrace-frames 'yas--watch-auto-fill)))) + +;; Try to get more info on #873/919 (this only works for Emacs 26+). +(when (fboundp 'add-variable-watcher) + (add-variable-watcher 'yas--original-auto-fill-function + #'yas--watch-auto-fill) + (add-variable-watcher 'auto-fill-function + #'yas--watch-auto-fill)) + +(defun yas--snippet-next-id () + (let ((id yas--snippet-id-seed)) + (cl-incf yas--snippet-id-seed) + id)) + + +;;; Minor mode stuff + +;; XXX: `last-buffer-undo-list' is somehow needed in Carbon Emacs for MacOSX +(defvar last-buffer-undo-list nil) + +(defvar yas--minor-mode-menu nil + "Holds the YASnippet menu.") + +(defvar yas--condition-cache-timestamp nil) + +(defun yas--maybe-expand-key-filter (cmd) + (when (let ((yas--condition-cache-timestamp (current-time))) + (yas--templates-for-key-at-point)) + cmd)) + +(defconst yas-maybe-expand + '(menu-item "" yas-expand :filter yas--maybe-expand-key-filter) + "A conditional key definition. +This can be used as a key definition in keymaps to bind a key to +`yas-expand' only when there is a snippet available to be +expanded.") + +(defvar yas-minor-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [(tab)] yas-maybe-expand) + (define-key map (kbd "TAB") yas-maybe-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) + map) + "The keymap used when `yas-minor-mode' is active.") + +(easy-menu-define yas--minor-mode-menu + yas-minor-mode-map + "Menu used when `yas-minor-mode' is active." + '("YASnippet" :visible yas-use-menu + "----" + ["Expand trigger" yas-expand + :help "Possibly expand tab trigger before point"] + ["Insert at point..." yas-insert-snippet + :help "Prompt for an expandable snippet and expand it at point"] + ["New snippet..." yas-new-snippet + :help "Create a new snippet in an appropriate directory"] + ["Visit snippet file..." yas-visit-snippet-file + :help "Prompt for an expandable snippet and find its file"] + "----" + ("Snippet menu behaviour" + ["Visit snippets" (setq yas-visit-from-menu t) + :help "Visit snippets from the menu" + :active t :style radio :selected yas-visit-from-menu] + ["Expand snippets" (setq yas-visit-from-menu nil) + :help "Expand snippets from the menu" + :active t :style radio :selected (not yas-visit-from-menu)] + "----" + ["Show all known modes" (setq yas-use-menu 'full) + :help "Show one snippet submenu for each loaded table" + :active t :style radio :selected (eq yas-use-menu 'full)] + ["Abbreviate according to current mode" (setq yas-use-menu 'abbreviate) + :help "Show only snippet submenus for the current active modes" + :active t :style radio :selected (eq yas-use-menu 'abbreviate)]) + ("Indenting" + ["Auto" (setq yas-indent-line 'auto) + :help "Indent each line of the snippet with `indent-according-to-mode'" + :active t :style radio :selected (eq yas-indent-line 'auto)] + ["Fixed" (setq yas-indent-line 'fixed) + :help "Indent the snippet to the current column" + :active t :style radio :selected (eq yas-indent-line 'fixed)] + ["None" (setq yas-indent-line 'none) + :help "Don't apply any particular snippet indentation after expansion" + :active t :style radio :selected (not (member yas-indent-line '(fixed auto)))] + "----" + ["Also auto indent first line" (setq yas-also-auto-indent-first-line + (not yas-also-auto-indent-first-line)) + :help "When auto-indenting also, auto indent the first line menu" + :active (eq yas-indent-line 'auto) + :style toggle :selected yas-also-auto-indent-first-line] + ) + ("Prompting method" + ["System X-widget" (setq yas-prompt-functions + (cons #'yas-x-prompt + (remove #'yas-x-prompt + yas-prompt-functions))) + :help "Use your windowing system's (gtk, mac, windows, etc...) default menu" + :active t :style radio :selected (eq (car yas-prompt-functions) + #'yas-x-prompt)] + ["Dropdown-list" (setq yas-prompt-functions + (cons #'yas-dropdown-prompt + (remove #'yas-dropdown-prompt + yas-prompt-functions))) + :help "Use a special dropdown list" + :active t :style radio :selected (eq (car yas-prompt-functions) + #'yas-dropdown-prompt)] + ["Ido" (setq yas-prompt-functions + (cons #'yas-ido-prompt + (remove #'yas-ido-prompt + yas-prompt-functions))) + :help "Use an ido-style minibuffer prompt" + :active t :style radio :selected (eq (car yas-prompt-functions) + #'yas-ido-prompt)] + ["Completing read" (setq yas-prompt-functions + (cons #'yas-completing-prompt + (remove #'yas-completing-prompt + yas-prompt-functions))) + :help "Use a normal minibuffer prompt" + :active t :style radio :selected (eq (car yas-prompt-functions) + #'yas-completing-prompt)] + ) + ("Misc" + ["Wrap region in exit marker" + (setq yas-wrap-around-region + (not yas-wrap-around-region)) + :help "If non-nil automatically wrap the selected text in the $0 snippet exit" + :style toggle :selected yas-wrap-around-region] + ["Allow stacked expansions " + (setq yas-triggers-in-field + (not yas-triggers-in-field)) + :help "If non-nil allow snippets to be triggered inside other snippet fields" + :style toggle :selected yas-triggers-in-field] + ["Revive snippets on undo " + (setq yas-snippet-revival + (not yas-snippet-revival)) + :help "If non-nil allow snippets to become active again after undo" + :style toggle :selected yas-snippet-revival] + ["Good grace " + (setq yas-good-grace + (not yas-good-grace)) + :help "If non-nil don't raise errors in bad embedded elisp in snippets" + :style toggle :selected yas-good-grace] + ) + "----" + ["Load snippets..." yas-load-directory + :help "Load snippets from a specific directory"] + ["Reload everything" yas-reload-all + :help "Cleanup stuff, reload snippets, rebuild menus"] + ["About" yas-about + :help "Display some information about YASnippet"])) + +(defvar yas--extra-modes nil + "An internal list of modes for which to also lookup snippets. + +This variable probably makes more sense as buffer-local, so +ensure your use `make-local-variable' when you set it.") +(define-obsolete-variable-alias 'yas-extra-modes 'yas--extra-modes "0.9.1") + +(defvar yas--tables (make-hash-table) + "A hash table of mode symbols to `yas--table' objects.") + +(defvar yas--parents (make-hash-table) + "A hash table of mode symbols do lists of direct parent mode symbols. + +This list is populated when reading the \".yas-parents\" files +found when traversing snippet directories with +`yas-load-directory'. + +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--direct-keymaps (list) + "Keymap alist supporting direct snippet keybindings. + +This variable is placed in `emulation-mode-map-alists'. + +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 defined direct keybindings to `yas-maybe-expand-from-keymap' +which decides on the snippet to expand.") + +(defun yas-direct-keymaps-reload () + "Force reload the direct keybinding for active snippet tables." + (interactive) + (setq yas--direct-keymaps nil) + (maphash #'(lambda (name table) + (push (cons (intern (format "yas--direct-%s" name)) + (yas--table-direct-keymap table)) + yas--direct-keymaps)) + yas--tables)) + +(defun yas--modes-to-activate (&optional mode) + "Compute list of mode symbols that are active for `yas-expand' and friends." + (defvar yas--dfs) ;We rely on dynbind. We could use `letrec' instead! + (let* ((explored (if mode (list mode) ; Building up list in reverse. + (cons major-mode (reverse yas--extra-modes)))) + (yas--dfs + (lambda (mode) + (cl-loop for neighbour + in (cl-list* (get mode 'derived-mode-parent) + ;; NOTE: `fboundp' check is redundant + ;; since Emacs 24.4. + (and (fboundp mode) (symbol-function mode)) + (gethash mode yas--parents)) + when (and neighbour + (not (memq neighbour explored)) + (symbolp neighbour)) + do (push neighbour explored) + (funcall yas--dfs neighbour))))) + (mapc yas--dfs explored) + (nreverse explored))) + +(defvar yas-minor-mode-hook nil + "Hook run when `yas-minor-mode' is turned on.") + +(defun yas--auto-fill-wrapper () + (when (and auto-fill-function + (not (eq auto-fill-function #'yas--auto-fill))) + (setq yas--original-auto-fill-function auto-fill-function) + (setq auto-fill-function #'yas--auto-fill))) + +;;;###autoload +(define-minor-mode yas-minor-mode + "Toggle YASnippet 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. + +Key bindings: +\\{yas-minor-mode-map}" + :lighter " yas" ;; The indicator for the mode line. + (cond ((and yas-minor-mode (featurep 'yasnippet)) + ;; 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 + ;; index `yas--direct-keymaps'. + ;; + ;; Also install the post-command-hook. + ;; + (cl-pushnew 'yas--direct-keymaps emulation-mode-map-alists) + (add-hook 'post-command-hook #'yas--post-command-handler nil t) + ;; Set the `yas--direct-%s' vars for direct keymap expansion + ;; + (dolist (mode (yas--modes-to-activate)) + (let ((name (intern (format "yas--direct-%s" mode)))) + (set-default name nil) + (set (make-local-variable name) t))) + ;; Perform JIT loads + (yas--load-pending-jits) + ;; Install auto-fill handler. + (yas--auto-fill-wrapper) ; Now... + (add-hook 'auto-fill-mode-hook #'yas--auto-fill-wrapper)) ; or later. + (t + ;; Uninstall the direct keymaps, post-command hook, and + ;; auto-fill handler. + (remove-hook 'post-command-hook #'yas--post-command-handler t) + (remove-hook 'auto-fill-mode-hook #'yas--auto-fill-wrapper) + (when (local-variable-p 'yas--original-auto-fill-function) + (setq auto-fill-function yas--original-auto-fill-function)) + (setq emulation-mode-map-alists + (remove 'yas--direct-keymaps emulation-mode-map-alists))))) + +(defun yas-activate-extra-mode (mode) + "Activates the snippets for the given `mode' in the buffer. + +The function can be called in the hook of a minor mode to +activate snippets associated with that mode." + (interactive + (let (modes + symbol) + (maphash (lambda (k _) + (setq modes (cons (list k) modes))) + yas--parents) + (setq symbol (completing-read + "Activate mode: " modes nil t)) + (list + (when (not (string= "" symbol)) + (intern symbol))))) + (when mode + (add-to-list (make-local-variable 'yas--extra-modes) mode) + (yas--load-pending-jits))) + +(defun yas-deactivate-extra-mode (mode) + "Deactivates the snippets for the given `mode' in the buffer." + (interactive + (list (intern + (completing-read + "Deactivate mode: " (mapcar #'list yas--extra-modes) nil t)))) + (set (make-local-variable 'yas--extra-modes) + (remove mode + yas--extra-modes))) + +(define-obsolete-variable-alias 'yas-dont-activate + 'yas-dont-activate-functions "0.9.2") +(defvar yas-dont-activate-functions (list #'minibufferp) + "Special hook to control which buffers `yas-global-mode' affects. +Functions are called with no argument, and should return non-nil to prevent +`yas-global-mode' from enabling yasnippet in this buffer. + +In Emacsen < 24, 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 >= 24, 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) + (with-no-warnings + (make-variable-buffer-local 'yas-dont-activate))) + + +(defun yas-minor-mode-on () + "Turn on YASnippet minor mode. + +Honour `yas-dont-activate-functions', which see." + (interactive) + (unless (or + ;; The old behavior used for Emacs<24 was to set + ;; `yas-dont-activate-functions' to t buffer-locally. + (not (or (listp yas-dont-activate-functions) + (functionp yas-dont-activate-functions))) + (run-hook-with-args-until-success 'yas-dont-activate-functions)) + (yas-minor-mode 1))) + +;;;###autoload +(define-globalized-minor-mode yas-global-mode yas-minor-mode yas-minor-mode-on) + +(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)) + (with-temp-buffer + (let ((prog-mode-hook nil) + (emacs-lisp-mode-hook nil)) + (ignore-errors (emacs-lisp-mode))) + (font-lock-set-defaults) + (if (eq t (car-safe font-lock-keywords)) + ;; They're "compiled", so extract the source. + (cadr font-lock-keywords) + font-lock-keywords)) + '(("\\$\\([0-9]+\\)" + (0 font-lock-keyword-face) + (1 font-lock-string-face t)) + ("\\${\\([0-9]+\\):?" + (0 font-lock-keyword-face) + (1 font-lock-warning-face t)) + ("\\(\\$(\\)" 1 font-lock-preprocessor-face) + ("}" + (0 font-lock-keyword-face))))) + +(defvar snippet-mode-map + (let ((map (make-sparse-keymap))) + (easy-menu-define nil + map + "Menu used when snippet-mode is active." + (cons "Snippet" + (mapcar #'(lambda (ent) + (when (nth 2 ent) + (define-key map (nth 2 ent) (nth 1 ent))) + (vector (nth 0 ent) (nth 1 ent) t)) + '(("Load this snippet" yas-load-snippet-buffer "\C-c\C-l") + ("Load and quit window" yas-load-snippet-buffer-and-close "\C-c\C-c") + ("Try out this snippet" yas-tryout-snippet "\C-c\C-t"))))) + map) + "The keymap used when `snippet-mode' is active.") + + + +;;;###autoload(autoload 'snippet-mode "yasnippet" "A mode for editing yasnippets" t nil) +(eval-and-compile + (if (fboundp 'prog-mode) + ;; `prog-mode' is new in 24.1. + (define-derived-mode snippet-mode prog-mode "Snippet" + "A mode for editing yasnippets" + (setq font-lock-defaults '(yas--font-lock-keywords)) + (set (make-local-variable 'require-final-newline) nil) + (set (make-local-variable 'comment-start) "#") + (set (make-local-variable 'comment-start-skip) "#+[\t ]*") + (add-hook 'after-save-hook #'yas-maybe-load-snippet-buffer nil t)) + (define-derived-mode snippet-mode fundamental-mode "Snippet" + "A mode for editing yasnippets" + (setq font-lock-defaults '(yas--font-lock-keywords)) + (set (make-local-variable 'require-final-newline) nil) + (set (make-local-variable 'comment-start) "#") + (set (make-local-variable 'comment-start-skip) "#+[\t ]*") + (add-hook 'after-save-hook #'yas-maybe-load-snippet-buffer nil t)))) + +(defun yas-snippet-mode-buffer-p () + "Return non-nil if current buffer should be in `snippet-mode'. +Meaning it's visiting a file under one of the mode directories in +`yas-snippet-dirs'." + (when buffer-file-name + (cl-member buffer-file-name (yas-snippet-dirs) + :test #'file-in-directory-p))) + +;; We're abusing `magic-fallback-mode-alist' here because +;; `auto-mode-alist' doesn't support function matchers. +(add-to-list 'magic-fallback-mode-alist + `(yas-snippet-mode-buffer-p . snippet-mode)) + + +;;; Internal structs for template management + +(cl-defstruct (yas--template + (:constructor yas--make-template) + ;; Handles `yas-define-snippets' format, plus the + ;; initial TABLE argument. + (:constructor + yas--define-snippets-2 + (table + key content + &optional xname condition group + expand-env load-file xkeybinding xuuid save-file + &aux + (name (or xname + ;; A little redundant: we always get a name + ;; from `yas--parse-template' except when + ;; there isn't a file. + (and load-file (file-name-nondirectory load-file)) + (and save-file (file-name-nondirectory save-file)) + key)) + (keybinding (yas--read-keybinding xkeybinding)) + (uuid (or xuuid name)) + (old (gethash uuid (yas--table-uuidhash table))) + (menu-binding-pair + (and old (yas--template-menu-binding-pair old))) + (perm-group + (and old (yas--template-perm-group old)))))) + "A template for a snippet." + key + content + name + condition + expand-env + load-file + save-file + keybinding + uuid + menu-binding-pair + group ;; as dictated by the #group: directive or .yas-make-groups + perm-group ;; as dictated by `yas-define-menu' + table + ) + +(cl-defstruct (yas--table (:constructor yas--make-snippet-table (name))) + "A table to store snippets for a particular mode. + +Has the following fields: + +`yas--table-name' + + A symbol name normally corresponding to a major mode, but can + also be a pseudo major-mode to be used in + `yas-activate-extra-mode', for example. + +`yas--table-hash' + + A hash table (KEY . NAMEHASH), known as the \"keyhash\". KEY is + a string or a vector, where the former is the snippet's trigger + and the latter means it's a direct keybinding. NAMEHASH is yet + another hash of (NAME . TEMPLATE) where NAME is the snippet's + name and TEMPLATE is a `yas--template' object. + +`yas--table-direct-keymap' + + A keymap for the snippets in this table that have direct + keybindings. This is kept in sync with the keyhash, i.e., all + the elements of the keyhash that are vectors appear here as + bindings to `yas-maybe-expand-from-keymap'. + +`yas--table-uuidhash' + + A hash table mapping snippets uuid's to the same `yas--template' + objects. A snippet uuid defaults to the snippet's name." + name + (hash (make-hash-table :test 'equal)) + (uuidhash (make-hash-table :test 'equal)) + (parents nil) + (direct-keymap (make-sparse-keymap))) + +(defun yas--get-template-by-uuid (mode uuid) + "Find the snippet template in MODE by its UUID." + (let* ((table (gethash mode yas--tables mode))) + (when table + (gethash uuid (yas--table-uuidhash table))))) + +;; Apropos storing/updating in TABLE, this works in two steps: +;; +;; 1. `yas--remove-template-by-uuid' removes any +;; 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?.) +;; +;; 2. `yas--add-template' adds this all over again. +;; +;; Create a new or add to an existing keyhash-namehash mapping. +;; +;; For reference on understanding this, consider three snippet +;; definitions: +;; +;; A: # name: The Foo +;; # key: foo +;; # binding: C-c M-l +;; +;; B: # name: Mrs Foo +;; # key: foo +;; +;; C: # name: The Bar +;; # binding: C-c M-l +;; +;; D: # name: Baz +;; # key: baz +;; +;; keyhash namehashes(3) yas--template structs(4) +;; ----------------------------------------------------- +;; __________ +;; / \ +;; "foo" ---> "The Foo" ---> [yas--template A] | +;; "Mrs Foo" ---> [yas--template B] | +;; | +;; [C-c M-l] ---> "The Foo" -------------------------/ +;; "The Bar" ---> [yas--template C] +;; +;; "baz" ---> "Baz" ---> [yas--template D] +;; +;; Additionally, since uuid defaults to the name, we have a +;; `yas--table-uuidhash' for TABLE +;; +;; uuidhash yas--template structs +;; ------------------------------- +;; "The Foo" ---> [yas--template A] +;; "Mrs Foo" ---> [yas--template B] +;; "The Bar" ---> [yas--template C] +;; "Baz" ---> [yas--template D] +;; +;; FIXME: the more I look at this data-structure the more I think I'm +;; stupid. There has to be an easier way (but beware lots of code +;; depends on this). +;; +(defun yas--remove-template-by-uuid (table uuid) + "Remove from TABLE a template identified by UUID." + (let ((template (gethash uuid (yas--table-uuidhash table)))) + (when template + (let* ((name (yas--template-name template)) + (empty-keys nil)) + ;; Remove the name from each of the targeted namehashes + ;; + (maphash #'(lambda (k v) + (let ((template (gethash name v))) + (when (and template + (equal uuid (yas--template-uuid template))) + (remhash name v) + (when (zerop (hash-table-count v)) + (push k empty-keys))))) + (yas--table-hash table)) + ;; Remove the namehash themselves if they've become empty + ;; + (dolist (key empty-keys) + (when (vectorp key) + (define-key (yas--table-direct-keymap table) key nil)) + (remhash key (yas--table-hash table))) + + ;; Finally, remove the uuid from the uuidhash + ;; + (remhash uuid (yas--table-uuidhash table)))))) + +(defconst yas-maybe-expand-from-keymap + '(menu-item "" yas-expand-from-keymap + :filter yas--maybe-expand-from-keymap-filter)) + +(defun yas--add-template (table template) + "Store in TABLE the snippet template TEMPLATE. + +KEY can be a string (trigger key) of a vector (direct +keybinding)." + (let ((name (yas--template-name template)) + (key (yas--template-key template)) + (keybinding (yas--template-keybinding template)) + (_menu-binding-pair (yas--template-menu-binding-pair-get-create template))) + (dolist (k (remove nil (list key keybinding))) + (puthash name + template + (or (gethash k + (yas--table-hash table)) + (puthash k + (make-hash-table :test 'equal) + (yas--table-hash table)))) + (when (vectorp k) + (define-key (yas--table-direct-keymap table) k yas-maybe-expand-from-keymap))) + + ;; Update TABLE's `yas--table-uuidhash' + (puthash (yas--template-uuid template) + template + (yas--table-uuidhash table)))) + +(defun yas--update-template (table template) + "Add or update TEMPLATE in TABLE. + +Also takes care of adding and updating to the associated menu. +Return TEMPLATE." + ;; Remove from table by uuid + ;; + (yas--remove-template-by-uuid table (yas--template-uuid template)) + ;; Add to table again + ;; + (yas--add-template table template) + ;; Take care of the menu + ;; + (yas--update-template-menu table template) + template) + +(defun yas--update-template-menu (table 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))) + ;; The snippet might have changed name or keys, so update + ;; user-visible strings + ;; + (unless (eq (cdr menu-binding-pair) :none) + ;; the menu item name + ;; + (setf (cl-cadar menu-binding-pair) (yas--template-name template)) + ;; the :keys information (also visible to the user) + (setf (cl-getf (cdr (car menu-binding-pair)) :keys) + (or (and keybinding (key-description keybinding)) + (and key (concat key yas-trigger-symbol)))))) + (unless (yas--template-menu-managed-by-yas-define-menu template) + (let ((menu-keymap + (yas--menu-keymap-get-create (yas--table-mode table) + (mapcar #'yas--table-mode + (yas--table-parents table)))) + (group (yas--template-group template))) + ;; Remove from menu keymap + ;; + (cl-assert menu-keymap) + (yas--delete-from-keymap menu-keymap (yas--template-uuid template)) + + ;; Add necessary subgroups as necessary. + ;; + (dolist (subgroup group) + (let ((subgroup-keymap (lookup-key menu-keymap (vector (make-symbol subgroup))))) + (unless (and subgroup-keymap + (keymapp subgroup-keymap)) + (setq subgroup-keymap (make-sparse-keymap)) + (define-key menu-keymap (vector (make-symbol subgroup)) + `(menu-item ,subgroup ,subgroup-keymap))) + (setq menu-keymap subgroup-keymap))) + + ;; Add this entry to the keymap + ;; + (define-key menu-keymap + (vector (make-symbol (yas--template-uuid template))) + (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)) + namehash) + alist)) + +(defun yas--fetch (table key) + "Fetch templates in TABLE by KEY. + +Return a list of cons (NAME . TEMPLATE) where NAME is a +string and TEMPLATE is a `yas--template' structure." + (let* ((keyhash (yas--table-hash table)) + (namehash (and keyhash (gethash key keyhash)))) + (when namehash + (yas--filter-templates-by-condition (yas--namehash-templates-alist namehash))))) + + +;;; Filtering/condition logic + +(defun yas--eval-condition (condition) + (condition-case err + (save-excursion + (save-restriction + (save-match-data + (eval condition)))) + (error (progn + (yas--message 1 "Error in condition evaluation: %s" (error-message-string err)) + nil)))) + + +(defun yas--filter-templates-by-condition (templates) + "Filter the templates using the applicable condition. + +TEMPLATES is a list of cons (NAME . TEMPLATE) where NAME is a +string and TEMPLATE is a `yas--template' structure. + +This function implements the rules described in +`yas-buffer-local-condition'. See that variables documentation." + (let ((requirement (yas--require-template-specific-condition-p))) + (if (eq requirement 'always) + templates + (cl-remove-if-not (lambda (pair) + (yas--template-can-expand-p + (yas--template-condition (cdr pair)) requirement)) + templates)))) + +(defun yas--require-template-specific-condition-p () + "Decide if this buffer requests/requires snippet-specific +conditions to filter out potential expansions." + (if (eq 'always yas-buffer-local-condition) + 'always + (let ((local-condition (or (and (consp yas-buffer-local-condition) + (yas--eval-condition yas-buffer-local-condition)) + yas-buffer-local-condition))) + (when local-condition + (if (eq local-condition t) + t + (and (consp local-condition) + (eq 'require-snippet-condition (car local-condition)) + (symbolp (cdr local-condition)) + (cdr local-condition))))))) + +(defun yas--template-can-expand-p (condition requirement) + "Evaluate CONDITION and REQUIREMENT and return a boolean." + (let* ((result (or (null condition) + (yas--eval-condition condition)))) + (cond ((eq requirement t) + result) + (t + (eq requirement result))))) + +(defun yas--table-templates (table) + (when table + (let ((acc (list))) + (maphash #'(lambda (_key namehash) + (maphash #'(lambda (name template) + (push (cons name template) acc)) + namehash)) + (yas--table-hash table)) + (yas--filter-templates-by-condition acc)))) + +(defun yas--templates-for-key-at-point () + "Find `yas--template' objects for any trigger keys preceding point. +Returns (TEMPLATES START END). This function respects +`yas-key-syntaxes', which see." + (save-excursion + (let ((original (point)) + (methods yas-key-syntaxes) + (templates) + (method)) + (while (and methods + (not templates)) + (unless (eq method (car methods)) + ;; TRICKY: `eq'-ness test means we can only be here if + ;; `method' is a function that returned `again', and hence + ;; don't revert back to original position as per + ;; `yas-key-syntaxes'. + (goto-char original)) + (setq method (car methods)) + (cond ((stringp method) + (skip-syntax-backward method) + (setq methods (cdr methods))) + ((functionp method) + (unless (eq (funcall method original) + 'again) + (setq methods (cdr methods)))) + (t + (setq methods (cdr methods)) + (yas--warning "Invalid element `%s' in `yas-key-syntaxes'" method))) + (let ((possible-key (buffer-substring-no-properties (point) original))) + (save-excursion + (goto-char original) + (setq templates + (cl-mapcan (lambda (table) + (yas--fetch table possible-key)) + (yas--get-snippet-tables)))))) + (when templates + (list templates (point) original))))) + +(defun yas--table-all-keys (table) + "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 and macros: + +(defun yas--remove-misc-free-from-undo (old-undo-list) + "Tries to work around Emacs Bug#30931. +Helper function for `yas--save-restriction-and-widen'." + ;; If Bug#30931 is unfixed, we get (# . INTEGER) + ;; entries in the undo list. If we call `type-of' on the + ;; Lisp_Misc_Free object then Emacs aborts, so try to find it by + ;; checking that its type is none of the expected ones. + (when (consp buffer-undo-list) + (let* ((prev buffer-undo-list) + (undo-list prev)) + (while (and (consp undo-list) + ;; Only check new entries. + (not (eq undo-list old-undo-list))) + (let ((entry (pop undo-list))) + (when (consp entry) + (let ((head (car entry))) + (unless (or (stringp head) + (markerp head) + (integerp head) + (symbolp head) + (not (integerp (cdr entry)))) + ;; (message "removing misc free %S" entry) + (setcdr prev undo-list))))) + (setq prev undo-list))))) + +(defmacro yas--save-restriction-and-widen (&rest body) + "Equivalent to (save-restriction (widen) BODY). +Also tries to work around Emacs Bug#30931." + (declare (debug (body)) (indent 0)) + ;; Disable garbage collection, since it could cause an abort. + `(let ((gc-cons-threshold most-positive-fixnum) + (old-undo-list buffer-undo-list)) + (prog1 (save-restriction + (widen) + ,@body) + (yas--remove-misc-free-from-undo old-undo-list)))) + +(defun yas--eval-for-string (form) + "Evaluate FORM and convert the result to string." + (let ((debug-on-error (and (not (memq yas-good-grace '(t inline))) + debug-on-error))) + (condition-case oops + (save-excursion + (yas--save-restriction-and-widen + (save-match-data + (let ((result (eval form))) + (when result + (format "%s" result)))))) + ((debug error) (cdr oops))))) + +(defun yas--eval-for-effect (form) + (yas--safely-call-fun (apply-partially #'eval form))) + +(defun yas--read-lisp (string &optional nil-on-error) + "Read STRING as a elisp expression and return it. + +In case STRING in an invalid expression and NIL-ON-ERROR is nil, +return an expression that when evaluated will issue an error." + (condition-case err + (read string) + (error (and (not nil-on-error) + `(error (error-message-string ,err)))))) + +(defun yas--read-keybinding (keybinding) + "Read KEYBINDING as a snippet keybinding, return a vector." + (when (and keybinding + (not (string-match "keybinding" keybinding))) + (condition-case err + (let ((res (or (and (string-match "^\\[.*\\]$" keybinding) + (read keybinding)) + (read-kbd-macro keybinding 'need-vector)))) + res) + (error + (yas--message 2 "warning: keybinding \"%s\" invalid since %s." + keybinding (error-message-string err)) + nil)))) + +(defun yas--table-get-create (mode) + "Get or create the snippet table corresponding to MODE." + (let ((table (gethash mode + yas--tables))) + (unless table + (setq table (yas--make-snippet-table (symbol-name mode))) + (puthash mode table yas--tables) + (push (cons (intern (format "yas--direct-%s" mode)) + (yas--table-direct-keymap table)) + yas--direct-keymaps)) + table)) + +(defun yas--get-snippet-tables (&optional mode) + "Get snippet tables for MODE. + +MODE defaults to the current buffer's `major-mode'. + +Return a list of `yas--table' objects. The list of modes to +consider is returned by `yas--modes-to-activate'" + (remove nil + (mapcar #'(lambda (name) + (gethash name yas--tables)) + (yas--modes-to-activate mode)))) + +(defun yas--menu-keymap-get-create (mode &optional parents) + "Get or create the menu keymap for MODE and its PARENTS. + +This may very well create a plethora of menu keymaps and arrange +them all in `yas--menu-table'" + (let* ((menu-keymap (or (gethash mode yas--menu-table) + (puthash mode (make-sparse-keymap) yas--menu-table)))) + (mapc #'yas--menu-keymap-get-create parents) + (define-key yas--minor-mode-menu (vector mode) + `(menu-item ,(symbol-name mode) ,menu-keymap + :visible (yas--show-menu-p ',mode))) + menu-keymap)) + + +;;; Template-related and snippet loading functions + +(defun yas--parse-template (&optional file) + "Parse the template in the current buffer. + +Optional FILE is the absolute file name of the file being +parsed. + +Optional GROUP is the group where the template is to go, +otherwise we attempt to calculate it from FILE. + +Return a snippet-definition, i.e. a list + + (KEY TEMPLATE NAME CONDITION GROUP VARS LOAD-FILE KEYBINDING UUID) + +If the buffer contains a line of \"# --\" then the contents above +this line are ignored. Directives can set most of these with the syntax: + +# directive-name : directive-value + +Here's a list of currently recognized directives: + + * type + * name + * contributor + * condition + * group + * key + * expand-env + * binding + * uuid" + (goto-char (point-min)) + (let* ((type 'snippet) + (name (and file + (file-name-nondirectory file))) + (key nil) + template + bound + condition + (group (and file + (yas--calculate-group file))) + expand-env + binding + uuid) + (if (re-search-forward "^# --\\s-*\n" nil t) + (progn (setq template + (buffer-substring-no-properties (point) + (point-max))) + (setq bound (point)) + (goto-char (point-min)) + (while (re-search-forward "^# *\\([^ ]+?\\) *: *\\(.*?\\)[[:space:]]*$" bound t) + (when (string= "uuid" (match-string-no-properties 1)) + (setq uuid (match-string-no-properties 2))) + (when (string= "type" (match-string-no-properties 1)) + (setq type (if (string= "command" (match-string-no-properties 2)) + 'command + 'snippet))) + (when (string= "key" (match-string-no-properties 1)) + (setq key (match-string-no-properties 2))) + (when (string= "name" (match-string-no-properties 1)) + (setq name (match-string-no-properties 2))) + (when (string= "condition" (match-string-no-properties 1)) + (setq condition (yas--read-lisp (match-string-no-properties 2)))) + (when (string= "group" (match-string-no-properties 1)) + (setq group (match-string-no-properties 2))) + (when (string= "expand-env" (match-string-no-properties 1)) + (setq expand-env (yas--read-lisp (match-string-no-properties 2) + 'nil-on-error))) + (when (string= "binding" (match-string-no-properties 1)) + (setq binding (match-string-no-properties 2))))) + (setq template + (buffer-substring-no-properties (point-min) (point-max)))) + (unless (or key binding) + (setq key (and file (file-name-nondirectory file)))) + (when (eq type 'command) + (setq template (yas--read-lisp (concat "(progn" template ")")))) + (when group + (setq group (split-string group "\\."))) + (list key template name condition group expand-env file binding uuid))) + +(defun yas--calculate-group (file) + "Calculate the group for snippet file path FILE." + (let* ((dominating-dir (locate-dominating-file file + ".yas-make-groups")) + (extra-path (and dominating-dir + (file-relative-name file dominating-dir))) + (extra-dir (and extra-path + (file-name-directory extra-path))) + (group (and extra-dir + (replace-regexp-in-string "/" + "." + (directory-file-name extra-dir))))) + group)) + +(defun yas--subdirs (directory &optional filep) + "Return subdirs or files of DIRECTORY according to FILEP." + (cl-remove-if (lambda (file) + (or (string-match "\\`\\." + (file-name-nondirectory file)) + (string-match "\\`#.*#\\'" + (file-name-nondirectory file)) + (string-match "~\\'" + (file-name-nondirectory file)) + (if filep + (file-directory-p file) + (not (file-directory-p file))))) + (directory-files directory t))) + +(defun yas--make-menu-binding (template) + (let ((mode (yas--table-mode (yas--template-table template)))) + `(lambda () (interactive) (yas--expand-or-visit-from-menu ',mode ,(yas--template-uuid template))))) + +(defun yas--expand-or-visit-from-menu (mode uuid) + (let* ((table (yas--table-get-create mode)) + (yas--current-template (and table + (gethash uuid (yas--table-uuidhash table))))) + (when yas--current-template + (if yas-visit-from-menu + (yas--visit-snippet-file-1 yas--current-template) + (let ((where (if (region-active-p) + (cons (region-beginning) (region-end)) + (cons (point) (point))))) + (yas-expand-snippet (yas--template-content yas--current-template) + (car where) + (cdr where) + (yas--template-expand-env yas--current-template))))))) + +(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 + +(defun yas--prompt-for-template (templates &optional prompt) + "Interactively choose a template from the list TEMPLATES. + +TEMPLATES is a list of `yas--template'. + +Optional PROMPT sets the prompt to use." + (when templates + (setq templates + (sort templates #'(lambda (t1 t2) + (< (length (yas--template-name t1)) + (length (yas--template-name t2)))))) + (cl-some (lambda (fn) + (funcall fn (or prompt "Choose a snippet: ") + templates + #'yas--template-name)) + yas-prompt-functions))) + +(defun yas--prompt-for-keys (keys &optional prompt) + "Interactively choose a template key from the list KEYS. + +Optional PROMPT sets the prompt to use." + (when keys + (cl-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 + (cl-some (lambda (fn) + (funcall fn (or prompt "Choose a snippet table: ") + tables + #'yas--table-name)) + yas-prompt-functions))) + +(defun yas-x-prompt (prompt choices &optional display-fn) + "Display choices in a x-window prompt." + (when (and window-system choices) + ;; Let window position be recalculated to ensure that + ;; `posn-at-point' returns non-nil. + (redisplay) + (or + (x-popup-menu + (if (fboundp 'posn-at-point) + (let ((x-y (posn-x-y (posn-at-point (point))))) + (list (list (+ (car x-y) 10) + (+ (cdr x-y) 20)) + (selected-window))) + t) + `(,prompt ("title" + ,@(cl-mapcar (lambda (c d) `(,(concat " " d) . ,c)) + choices + (if display-fn (mapcar display-fn choices) + choices))))) + (keyboard-quit)))) + +(defun yas-maybe-ido-prompt (prompt choices &optional display-fn) + (when (bound-and-true-p ido-mode) + (yas-ido-prompt prompt choices display-fn))) + +(defun yas-ido-prompt (prompt choices &optional display-fn) + (require 'ido) + (yas-completing-prompt prompt choices display-fn #'ido-completing-read)) + +(defun yas-dropdown-prompt (_prompt choices &optional display-fn) + (when (fboundp 'dropdown-list) + (let* ((formatted-choices + (if display-fn (mapcar display-fn choices) choices)) + (n (dropdown-list formatted-choices))) + (if n (nth n choices) + (keyboard-quit))))) + +(defun yas-completing-prompt (prompt choices &optional display-fn completion-fn) + (let* ((formatted-choices + (if display-fn (mapcar display-fn choices) choices)) + (chosen (funcall (or completion-fn #'completing-read) + prompt formatted-choices + nil 'require-match nil nil))) + (if (eq choices formatted-choices) + chosen + (nth (or (cl-position chosen formatted-choices :test #'string=) 0) + choices)))) + +(defun yas-no-prompt (_prompt choices &optional _display-fn) + (cl-first choices)) + + +;;; Defining snippets +;; This consists of creating and registering `yas--template' objects in the +;; correct tables. +;; + +(defvar yas--creating-compiled-snippets nil) + +(defun yas--define-snippets-1 (snippet snippet-table) + "Helper for `yas-define-snippets'." + ;; Update the appropriate table. Also takes care of adding the + ;; key indicators in the templates menu entry, if any. + (yas--update-template + snippet-table (apply #'yas--define-snippets-2 snippet-table snippet))) + +(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 LOAD-FILE KEYBINDING UUID SAVE-FILE) + +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-for-string'-ed. + +The remaining elements are strings. + +FILE is probably of very little use if you're programatically +defining snippets. + +UUID is the snippet's \"unique-id\". Loading a second snippet +file with the same uuid would replace the previous snippet. + +You can use `yas--parse-template' to return such lists based on +the current buffers contents." + (if yas--creating-compiled-snippets + (let ((print-length nil)) + (insert ";;; Snippet definitions:\n;;;\n") + (dolist (snippet snippets) + ;; Fill in missing elements with nil. + (setq snippet (append snippet (make-list (- 10 (length snippet)) nil))) + ;; Move LOAD-FILE to SAVE-FILE because we will load from the + ;; compiled file, not LOAD-FILE. + (let ((load-file (nth 6 snippet))) + (setcar (nthcdr 6 snippet) nil) + (setcar (nthcdr 9 snippet) load-file))) + (insert (pp-to-string + `(yas-define-snippets ',mode ',snippets))) + (insert "\n\n")) + ;; Normal case. + (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--template-get-file (template) + "Return TEMPLATE's LOAD-FILE or SAVE-FILE." + (or (yas--template-load-file template) + (let ((file (yas--template-save-file template))) + (when file + (yas--message 3 "%s has no load file, using save file, %s, instead." + (yas--template-name template) file)) + file))) + +(defun yas--load-yas-setup-file (file) + (if (not yas--creating-compiled-snippets) + ;; Normal case. + (load file 'noerror (<= yas-verbosity 4)) + (let ((elfile (concat file ".el"))) + (when (file-exists-p elfile) + (insert ";;; contents of the .yas-setup.el support file:\n;;;\n") + (insert-file-contents elfile) + (goto-char (point-max)))))) + +(defun yas--define-parents (mode parents) + "Add PARENTS to the list of MODE's parents." + (puthash mode (cl-remove-duplicates + (append parents + (gethash mode yas--parents))) + yas--parents)) + +(defun yas-load-directory (top-level-dir &optional use-jit interactive) + "Load snippets in directory hierarchy TOP-LEVEL-DIR. + +Below TOP-LEVEL-DIR each directory should be a mode name. + +With prefix argument USE-JIT do jit-loading of snippets." + (interactive + (list (read-directory-name "Select the root directory: " nil nil t) + current-prefix-arg t)) + (unless yas-snippet-dirs + (setq yas-snippet-dirs top-level-dir)) + (let ((impatient-buffers)) + (dolist (dir (yas--subdirs top-level-dir)) + (let* ((major-mode-and-parents (yas--compute-major-mode-and-parents + (concat dir "/dummy"))) + (mode-sym (car major-mode-and-parents)) + (parents (cdr major-mode-and-parents))) + ;; Attention: The parents and the menus are already defined + ;; here, even if the snippets are later jit-loaded. + ;; + ;; * We need to know the parents at this point since entering a + ;; given mode should jit load for its parents + ;; immediately. This could be reviewed, the parents could be + ;; discovered just-in-time-as well + ;; + ;; * We need to create the menus here to support the `full' + ;; option to `yas-use-menu' (all known snippet menus are shown to the user) + ;; + (yas--define-parents mode-sym parents) + (yas--menu-keymap-get-create mode-sym) + (let ((fun (apply-partially #'yas--load-directory-1 dir mode-sym))) + (if use-jit + (yas--schedule-jit mode-sym fun) + (funcall fun))) + ;; Look for buffers that are already in `mode-sym', and so + ;; need the new snippets immediately... + ;; + (when use-jit + (cl-loop for buffer in (buffer-list) + do (with-current-buffer buffer + (when (eq major-mode mode-sym) + (yas--message 4 "Discovered there was already %s in %s" buffer mode-sym) + (push buffer impatient-buffers))))))) + ;; ...after TOP-LEVEL-DIR has been completely loaded, call + ;; `yas--load-pending-jits' in these impatient buffers. + ;; + (cl-loop for buffer in impatient-buffers + do (with-current-buffer buffer (yas--load-pending-jits)))) + (when interactive + (yas--message 3 "Loaded snippets from %s." top-level-dir))) + +(defun yas--load-directory-1 (directory mode-sym) + "Recursively load snippet templates from DIRECTORY." + (if yas--creating-compiled-snippets + (let ((output-file (expand-file-name ".yas-compiled-snippets.el" + directory))) + (with-temp-file output-file + (insert (format ";;; Compiled snippets and support files for `%s'\n" + mode-sym)) + (yas--load-directory-2 directory mode-sym) + (insert (format ";;; Do not edit! File generated at %s\n" + (current-time-string))))) + ;; Normal case. + (unless (file-exists-p (expand-file-name ".yas-skip" directory)) + (unless (and (load (expand-file-name ".yas-compiled-snippets" directory) 'noerror (<= yas-verbosity 3)) + (progn (yas--message 4 "Loaded compiled snippets from %s" directory) t)) + (yas--message 4 "Loading snippet files from %s" directory) + (yas--load-directory-2 directory mode-sym))))) + +(defun yas--load-directory-2 (directory mode-sym) + ;; Load .yas-setup.el files wherever we find them + ;; + (yas--load-yas-setup-file (expand-file-name ".yas-setup" directory)) + (let* ((default-directory directory) + (snippet-defs nil)) + ;; load the snippet files + ;; + (with-temp-buffer + (dolist (file (yas--subdirs directory 'no-subdirs-just-files)) + (when (file-readable-p file) + ;; Erase the buffer instead of passing non-nil REPLACE to + ;; `insert-file-contents' (avoids Emacs bug #23659). + (erase-buffer) + (insert-file-contents file) + (push (yas--parse-template file) + snippet-defs)))) + (when snippet-defs + (yas-define-snippets mode-sym + snippet-defs)) + ;; now recurse to a lower level + ;; + (dolist (subdir (yas--subdirs directory)) + (yas--load-directory-2 subdir + mode-sym)))) + +(defun yas--load-snippet-dirs (&optional nojit) + "Reload the directories listed in `yas-snippet-dirs' or +prompt the user to select one." + (let (errors) + (if (null yas-snippet-dirs) + (call-interactively 'yas-load-directory) + (when (member yas--default-user-snippets-dir yas-snippet-dirs) + (make-directory yas--default-user-snippets-dir t)) + (dolist (directory (reverse (yas-snippet-dirs))) + (cond ((file-directory-p directory) + (yas-load-directory directory (not nojit)) + (if nojit + (yas--message 4 "Loaded %s" directory) + (yas--message 4 "Prepared just-in-time loading for %s" directory))) + (t + (push (yas--message 1 "Check your `yas-snippet-dirs': %s is not a directory" directory) errors))))) + errors)) + +(defun yas-reload-all (&optional no-jit interactive) + "Reload all snippets and rebuild the YASnippet menu. + +When NO-JIT is non-nil force immediate reload of all known +snippets under `yas-snippet-dirs', otherwise use just-in-time +loading. + +When called interactively, use just-in-time loading when given a +prefix argument." + (interactive (list (not current-prefix-arg) t)) + (catch 'abort + (let ((errors) + (snippet-editing-buffers + (cl-remove-if-not (lambda (buffer) + (with-current-buffer buffer + yas--editing-template)) + (buffer-list)))) + ;; Warn if there are buffers visiting snippets, since reloading will break + ;; any on-line editing of those buffers. + ;; + (when snippet-editing-buffers + (if interactive + (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)) + ;; in a non-interactive use, at least set + ;; `yas--editing-template' to nil, make it guess it next time around + (mapc #'(lambda (buffer) + (with-current-buffer buffer + (kill-local-variable 'yas--editing-template))) + (buffer-list)))) + + ;; Empty all snippet tables and parenting info + ;; + (setq yas--tables (make-hash-table)) + (setq yas--parents (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)) + + ;; Reload the directories listed in `yas-snippet-dirs' or prompt + ;; the user to select one. + ;; + (setq errors (yas--load-snippet-dirs no-jit)) + ;; Reload the direct keybindings + ;; + (yas-direct-keymaps-reload) + + (run-hooks 'yas-after-reload-hook) + (let ((no-snippets + (cl-every (lambda (table) (= (hash-table-count table) 0)) + (list yas--scheduled-jit-loads + yas--parents yas--tables)))) + (yas--message (if (or no-snippets errors) 2 3) + (if no-jit "Snippets loaded %s." + "Prepared just-in-time loading of snippets %s.") + (cond (errors + "with some errors. Check *Messages*") + (no-snippets + "(but no snippets found)") + (t + "successfully"))))))) + +(defvar yas-after-reload-hook nil + "Hooks run after `yas-reload-all'.") + +(defun yas--load-pending-jits () + (dolist (mode (yas--modes-to-activate)) + (let ((funs (reverse (gethash mode yas--scheduled-jit-loads)))) + ;; must reverse to maintain coherence with `yas-snippet-dirs' + (dolist (fun funs) + (yas--message 4 "Loading for `%s', just-in-time: %s!" mode fun) + (funcall fun)) + (remhash mode yas--scheduled-jit-loads)))) + +(defun yas-escape-text (text) + "Escape TEXT for snippet." + (when text + (replace-regexp-in-string "[\\$]" "\\\\\\&" text))) + + +;;; Snippet compilation function + +(defun yas-compile-directory (top-level-dir) + "Create .yas-compiled-snippets.el files under subdirs of TOP-LEVEL-DIR. + +This works by stubbing a few functions, then calling +`yas-load-directory'." + (interactive "DTop level snippet directory?") + (let ((yas--creating-compiled-snippets t)) + (yas-load-directory top-level-dir nil))) + +(defun yas-recompile-all () + "Compile every dir in `yas-snippet-dirs'." + (interactive) + (mapc #'yas-compile-directory (yas-snippet-dirs))) + + +;;; JIT loading +;;; + +(defvar yas--scheduled-jit-loads (make-hash-table) + "Alist of mode-symbols to forms to be evaled when `yas-minor-mode' kicks in.") + +(defun yas--schedule-jit (mode fun) + (push fun (gethash mode yas--scheduled-jit-loads))) + + + +;;; Some user level functions + +(defun yas-about () + (interactive) + (message "yasnippet (version %s) -- pluskid/joaotavora/npostavs" + (or (ignore-errors (car (let ((default-directory yas--loaddir)) + (process-lines "git" "describe" + "--tags" "--dirty")))) + (when (and (featurep 'package) + (fboundp 'package-desc-version) + (fboundp 'package-version-join)) + (defvar package-alist) + (ignore-errors + (let* ((yas-pkg (cdr (assq 'yasnippet package-alist))) + (version (package-version-join + (package-desc-version (car yas-pkg))))) + ;; Special case for MELPA's bogus version numbers. + (if (string-match "\\`20..[01][0-9][0-3][0-9][.][0-9]\\{3,4\\}\\'" + version) + (concat yas--version "-snapshot" version) + version)))) + yas--version))) + + +;;; Apropos snippet menu: +;; +;; The snippet menu keymaps are stored by mode in hash table called +;; `yas--menu-table'. They are linked to the main menu in +;; `yas--menu-keymap-get-create' and are initially created empty, +;; reflecting the table hierarchy. +;; +;; They can be populated in two mutually exclusive ways: (1) by +;; reading `yas--template-group', which in turn is populated by the "# +;; group:" directives of the snippets or the ".yas-make-groups" file +;; or (2) by using a separate `yas-define-menu' call, which declares a +;; menu structure based on snippets uuids. +;; +;; Both situations are handled in `yas--update-template-menu', which +;; uses the predicate `yas--template-menu-managed-by-yas-define-menu' +;; that can tell between the two situations. +;; +;; Note: +;; +;; * if `yas-define-menu' is used it must run before +;; `yas-define-snippets' and the UUIDS must match, otherwise we get +;; duplicate entries. The `yas--template' objects are created in +;; `yas-define-menu', holding nothing but the menu entry, +;; represented by a pair of ((menu-item NAME :keys KEYS) TYPE) and +;; stored in `yas--template-menu-binding-pair'. The (menu-item ...) +;; part is then stored in the menu keymap itself which make the item +;; appear to the user. These limitations could probably be revised. +;; +;; * The `yas--template-perm-group' slot is only used in +;; `yas-describe-tables'. +;; +(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', signaling this menu binding should be +static in the menu." + (or (yas--template-menu-binding-pair template) + (let (;; (key (yas--template-key template)) + ;; (keybinding (yas--template-keybinding template)) + ) + (setf (yas--template-menu-binding-pair template) + (cons `(menu-item ,(or (yas--template-name template) + (yas--template-uuid template)) + ,(yas--make-menu-binding template) + :keys ,nil) + type))))) +(defun yas--template-menu-managed-by-yas-define-menu (template) + "Non-nil if TEMPLATE's menu entry was included in a `yas-define-menu' call." + (cdr (yas--template-menu-binding-pair template))) + + +(defun yas--show-menu-p (mode) + (cond ((eq yas-use-menu 'abbreviate) + (cl-find mode + (mapcar #'yas--table-mode + (yas--get-snippet-tables)))) + (yas-use-menu t))) + +(defun yas--delete-from-keymap (keymap uuid) + "Recursively delete items with UUID from KEYMAP and its submenus." + + ;; XXX: This used to skip any submenus named \"parent mode\" + ;; + ;; First of all, recursively enter submenus, i.e. the tree is + ;; searched depth first so that stale submenus can be found in the + ;; higher passes. + ;; + (mapc #'(lambda (item) + (when (and (consp (cdr-safe item)) + (keymapp (nth 2 (cdr item)))) + (yas--delete-from-keymap (nth 2 (cdr item)) uuid))) + (cdr keymap)) + ;; Set the uuid entry to nil + ;; + (define-key keymap (vector (make-symbol uuid)) nil) + ;; Destructively modify keymap + ;; + (setcdr keymap (cl-delete-if (lambda (item) + (cond ((not (listp item)) nil) + ((null (cdr item))) + ((and (keymapp (nth 2 (cdr item))) + (null (cdr (nth 2 (cdr item)))))))) + (cdr keymap)))) + +(defun yas-define-menu (mode menu &optional 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 + 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 + list of groups of the snippets defined thereafter. + +OMIT-ITEMS is a list of snippet uuids that will always be +omitted from MODE's menu, even if they're manually loaded." + (let* ((table (yas--table-get-create mode)) + (hash (yas--table-uuidhash table))) + (yas--define-menu-1 table + (yas--menu-keymap-get-create mode) + menu + hash) + (dolist (uuid omit-items) + (let ((template (or (gethash uuid hash) + (puthash uuid + (yas--make-template :table table + :uuid uuid) + hash)))) + (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'." + (cl-loop + for (type name submenu) in (reverse menu) + collect (cond + ((or (eq type 'yas-item) + (and yas-alias-to-yas/prefix-p + (eq type 'yas/item))) + (let ((template (or (gethash name uuidhash) + (puthash name + (yas--make-template + :table table + :perm-group group-list + :uuid name) + uuidhash)))) + (car (yas--template-menu-binding-pair-get-create + template :stay)))) + ((or (eq type 'yas-submenu) + (and yas-alias-to-yas/prefix-p + (eq type 'yas/submenu))) + (let ((subkeymap (make-sparse-keymap))) + (yas--define-menu-1 table subkeymap submenu uuidhash + (append group-list (list name))) + `(menu-item ,name ,subkeymap))) + ((or (eq type 'yas-separator) + (and yas-alias-to-yas/prefix-p + (eq type 'yas/separator))) + '(menu-item "----")) + (t (yas--message 1 "Don't know anything about menu entry %s" type) + nil)) + into menu-entries + finally do (push (apply #'vector menu-entries) (cdr menu-keymap)))) + +(defun yas--define (mode key template &optional name condition group) + "Define a snippet. Expanding KEY into TEMPLATE. + +NAME is a description to this template. Also update the menu if +`yas-use-menu' is t. CONDITION is the condition attached to +this snippet. If you attach a condition to a snippet, then it +will only be expanded when the condition evaluated to non-nil." + (yas-define-snippets mode + (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'." + (when yas-minor-mode + (if (not first-time?) + (let ((yas-fallback-behavior 'return-nil)) + (yas-expand)) + (undo 1) + nil))) + + +;;; Apropos condition-cache: +;;; +;;; +;;; +;;; +(defmacro yas-define-condition-cache (func doc &rest body) + "Define a function FUNC with doc DOC and body BODY. +BODY is executed at most once every snippet expansion attempt, to check +expansion conditions. + +It doesn't make any sense to call FUNC programatically." + `(defun ,func () ,(if (and doc + (stringp doc)) + (concat doc +"\n\nFor use in snippets' conditions. Within each +snippet-expansion routine like `yas-expand', computes actual +value for the first time then always returns a cached value.") + (setq body (cons doc body)) + nil) + (let ((timestamp-and-value (get ',func 'yas--condition-cache))) + (if (equal (car timestamp-and-value) yas--condition-cache-timestamp) + (cdr timestamp-and-value) + (let ((new-value (progn + ,@body + ))) + (put ',func 'yas--condition-cache (cons yas--condition-cache-timestamp new-value)) + new-value))))) + +(defalias 'yas-expand 'yas-expand-from-trigger-key) +(defun yas-expand-from-trigger-key (&optional field) + "Expand a snippet before point. + +If no snippet expansion is possible, fall back to the behaviour +defined in `yas-fallback-behavior'. + +Optional argument FIELD is for non-interactive use and is an +object satisfying `yas--field-p' to restrict the expansion to." + (interactive) + (setq yas--condition-cache-timestamp (current-time)) + (let (templates-and-pos) + (unless (and yas-expand-only-for-last-commands + (not (member last-command yas-expand-only-for-last-commands))) + (setq templates-and-pos (if field + (save-restriction + (narrow-to-region (yas--field-start field) + (yas--field-end field)) + (yas--templates-for-key-at-point)) + (yas--templates-for-key-at-point)))) + (if templates-and-pos + (yas--expand-or-prompt-for-template + (nth 0 templates-and-pos) + ;; Delete snippet key and active region when expanding. + (min (if (use-region-p) (region-beginning) most-positive-fixnum) + (nth 1 templates-and-pos)) + (max (if (use-region-p) (region-end) most-negative-fixnum) + (nth 2 templates-and-pos))) + (yas--fallback)))) + +(defun yas--maybe-expand-from-keymap-filter (cmd) + (let* ((yas--condition-cache-timestamp (current-time)) + (vec (cl-subseq (this-command-keys-vector) + (if current-prefix-arg + (length (this-command-keys)) + 0))) + (templates (cl-mapcan (lambda (table) + (yas--fetch table vec)) + (yas--get-snippet-tables)))) + (if templates (or cmd templates)))) + +(defun yas-expand-from-keymap () + "Directly expand some snippets, searching `yas--direct-keymaps'." + (interactive) + (setq yas--condition-cache-timestamp (current-time)) + (let* ((templates (yas--maybe-expand-from-keymap-filter nil))) + (when templates + (yas--expand-or-prompt-for-template templates)))) + +(defun yas--expand-or-prompt-for-template (templates &optional start end) + "Expand one of TEMPLATES from START to END. + +Prompt the user if TEMPLATES has more than one element, else +expand immediately. Common gateway for +`yas-expand-from-trigger-key' and `yas-expand-from-keymap'." + (let ((yas--current-template (or (and (cl-rest templates) ;; more than one + (yas--prompt-for-template (mapcar #'cdr templates))) + (cdar templates)))) + (when yas--current-template + (yas-expand-snippet (yas--template-content yas--current-template) + start + 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 () + "Fallback after expansion has failed. + +Common gateway for `yas-expand-from-trigger-key' and +`yas-expand-from-keymap'." + (cond ((eq yas-fallback-behavior 'return-nil) + ;; return nil + nil) + ((eq yas-fallback-behavior 'yas--fallback) + (error (concat "yasnippet fallback loop!\n" + "This can happen when you bind `yas-expand' " + "outside of the `yas-minor-mode-map'."))) + ((eq yas-fallback-behavior 'call-other-command) + (let* ((yas-fallback-behavior 'yas--fallback) + ;; Also bind `yas-minor-mode' to prevent fallback + ;; loops when other extensions use mechanisms similar + ;; to `yas--keybinding-beyond-yasnippet'. (github #525 + ;; and #526) + ;; + (yas-minor-mode nil) + (beyond-yasnippet (yas--keybinding-beyond-yasnippet))) + (yas--message 4 "Falling back to %s" beyond-yasnippet) + (cl-assert (or (null beyond-yasnippet) (commandp beyond-yasnippet))) + (setq this-command beyond-yasnippet) + (when beyond-yasnippet + (call-interactively beyond-yasnippet)))) + ((and (listp yas-fallback-behavior) + (cdr yas-fallback-behavior) + (eq 'apply (car yas-fallback-behavior))) + (let ((command-or-fn (cadr yas-fallback-behavior)) + (args (cddr yas-fallback-behavior)) + (yas-fallback-behavior 'yas--fallback) + (yas-minor-mode nil)) + (if args + (apply command-or-fn args) + (when (commandp command-or-fn) + (setq this-command command-or-fn) + (call-interactively command-or-fn))))) + (t + ;; also return nil if all the other fallbacks have failed + nil))) + +(defun yas--keybinding-beyond-yasnippet () + "Get current keys's binding as if YASsnippet didn't exist." + (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) + "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'" + (when yas-choose-tables-first + (setq tables (list (yas--prompt-for-table tables)))) + (mapcar #'cdr + (if yas-choose-keys-first + (let ((key (yas--prompt-for-keys + (cl-mapcan #'yas--table-all-keys tables)))) + (when key + (cl-mapcan (lambda (table) + (yas--fetch table key)) + tables))) + (cl-remove-duplicates (cl-mapcan #'yas--table-templates tables) + :test #'equal)))) + +(defun yas--lookup-snippet-1 (name mode) + "Get the snippet called NAME in MODE's tables." + (let ((yas-choose-tables-first nil) ; avoid prompts + (yas-choose-keys-first nil)) + (cl-find name (yas--all-templates + (yas--get-snippet-tables mode)) + :key #'yas--template-name :test #'string=))) + +(defun yas-lookup-snippet (name &optional mode noerror) + "Get the snippet named NAME in MODE's tables. + +MODE defaults to the current buffer's `major-mode'. If NOERROR +is non-nil, then don't signal an error if there isn't any snippet +called NAME. + +Honours `yas-buffer-local-condition'." + (cond + ((yas--lookup-snippet-1 name mode)) + (noerror nil) + (t (error "No snippet named: %s" name)))) + +(defun yas-insert-snippet (&optional no-condition) + "Choose a snippet to expand, pop-up a list of choices according +to `yas-prompt-functions'. + +With prefix argument NO-CONDITION, bypass filtering of snippets +by condition." + (interactive "P") + (setq yas--condition-cache-timestamp (current-time)) + (let* ((yas-buffer-local-condition (or (and no-condition + 'always) + yas-buffer-local-condition)) + (templates (yas--all-templates (yas--get-snippet-tables))) + (yas--current-template (and templates + (or (and (cl-rest templates) ;; more than one template for same key + (yas--prompt-for-template templates)) + (car templates)))) + (where (if (region-active-p) + (cons (region-beginning) (region-end)) + (cons (point) (point))))) + (if yas--current-template + (yas-expand-snippet (yas--template-content yas--current-template) + (car where) + (cdr where) + (yas--template-expand-env yas--current-template)) + (yas--message 1 "No snippets can be inserted here!")))) + +(defun yas-visit-snippet-file () + "Choose a snippet to edit, selection like `yas-insert-snippet'. + +Only success if selected snippet was loaded from a file. Put the +visited file in `snippet-mode'." + (interactive) + (let* ((yas-buffer-local-condition 'always) + (templates (yas--all-templates (yas--get-snippet-tables))) + (template (and templates + (or (yas--prompt-for-template templates + "Choose a snippet template to edit: ") + (car templates))))) + + (if template + (yas--visit-snippet-file-1 template) + (message "No snippets tables active!")))) + +(defun yas--visit-snippet-file-1 (template) + "Helper for `yas-visit-snippet-file'." + (let ((file (yas--template-get-file template))) + (cond ((and file (file-readable-p file)) + (find-file-other-window file) + (snippet-mode) + (set (make-local-variable 'yas--editing-template) template)) + (file + (message "Original file %s no longer exists!" file)) + (t + (switch-to-buffer (format "*%s*"(yas--template-name template))) + (let ((type 'snippet)) + (when (listp (yas--template-content template)) + (insert (format "# type: command\n")) + (setq type 'command)) + (insert (format "# key: %s\n" (yas--template-key template))) + (insert (format "# name: %s\n" (yas--template-name template))) + (when (yas--template-keybinding template) + (insert (format "# binding: %s\n" (yas--template-keybinding template)))) + (when (yas--template-expand-env template) + (insert (format "# expand-env: %s\n" (yas--template-expand-env template)))) + (when (yas--template-condition template) + (insert (format "# condition: %s\n" (yas--template-condition template)))) + (insert "# --\n") + (insert (if (eq type 'command) + (pp-to-string (yas--template-content template)) + (yas--template-content template)))) + (snippet-mode) + (set (make-local-variable 'yas--editing-template) template) + (set (make-local-variable 'default-directory) + (car (cdr (car (yas--guess-snippet-directories (yas--template-table template)))))))))) + +(defun yas--guess-snippet-directories-1 (table) + "Guess possible snippet subdirectories for TABLE." + (cons (file-name-as-directory (yas--table-name table)) + (cl-mapcan #'yas--guess-snippet-directories-1 + (yas--table-parents table)))) + +(defun yas--guess-snippet-directories (&optional table) + "Try to guess suitable directories based on the current active +tables (or optional TABLE). + +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 (car (or (yas-snippet-dirs) + (setq yas-snippet-dirs + (list yas--default-user-snippets-dir))))) + (tables (if table (list table) + (yas--get-snippet-tables)))) + ;; HACK! the snippet table created here is actually registered! + (unless table + ;; The major mode is probably the best guess, put it first. + (let ((major-mode-table (yas--table-get-create major-mode))) + (cl-callf2 delq major-mode-table tables) + (push major-mode-table tables))) + + (mapcar #'(lambda (table) + (cons table + (mapcar #'(lambda (subdir) + (expand-file-name subdir main-dir)) + (yas--guess-snippet-directories-1 table)))) + tables))) + +(defun yas--make-directory-maybe (table-and-dirs &optional main-table-string) + "Return a dir inside TABLE-AND-DIRS, prompts for creation if none exists." + (or (cl-some (lambda (dir) (when (file-directory-p dir) dir)) + (cdr table-and-dirs)) + (let ((candidate (cl-first (cdr table-and-dirs)))) + (unless (file-writable-p (file-name-directory candidate)) + (error (yas--format "%s is not writable." candidate))) + (if (y-or-n-p (format "Guessed directory (%s) for%s%s table \"%s\" does not exist! Create? " + candidate + (if (gethash (yas--table-mode (car table-and-dirs)) + yas--tables) + "" + " brand new") + (or main-table-string + "") + (yas--table-name (car table-and-dirs)))) + (progn + (make-directory candidate 'also-make-parents) + ;; create the .yas-parents file here... + candidate))))) + +;; NOTE: Using the traditional "*new snippet*" stops whitespace mode +;; from activating (it doesn't like the leading "*"). +(defconst yas-new-snippet-buffer-name "+new-snippet+") + +(defun yas-new-snippet (&optional no-template) + "Pops a new buffer for writing a snippet. + +Expands a snippet-writing snippet, unless the optional prefix arg +NO-TEMPLATE is non-nil." + (interactive "P") + (let ((guessed-directories (yas--guess-snippet-directories)) + (yas-selected-text (or yas-selected-text + (and (region-active-p) + (buffer-substring-no-properties + (region-beginning) (region-end)))))) + + (switch-to-buffer yas-new-snippet-buffer-name) + (erase-buffer) + (kill-all-local-variables) + (snippet-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas--guessed-modes) + (mapcar (lambda (d) (yas--table-mode (car d))) + guessed-directories)) + (set (make-local-variable 'default-directory) + (car (cdr (car guessed-directories)))) + (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. + +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, +neither do the elements of PARENTS." + (let* ((file-dir (and file + (directory-file-name + (or (cl-some (lambda (special) + (locate-dominating-file file special)) + '(".yas-setup.el" + ".yas-make-groups" + ".yas-parents")) + (directory-file-name (file-name-directory file)))))) + (parents-file-name (concat file-dir "/.yas-parents")) + (major-mode-name (and file-dir + (file-name-nondirectory file-dir))) + (major-mode-sym (or (and major-mode-name + (intern major-mode-name)))) + (parents (when (file-readable-p parents-file-name) + (mapcar #'intern + (split-string + (with-temp-buffer + (insert-file-contents parents-file-name) + (buffer-substring-no-properties (point-min) + (point-max)))))))) + (when major-mode-sym + (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'.") + +(defvar yas--current-template nil + "Holds the current template being expanded into a snippet.") + +(defvar yas--guessed-modes nil + "List of guessed modes supporting `yas-load-snippet-buffer'.") + +(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 + (cl-first yas--guessed-modes) + "nothing")) + (mapcar #'symbol-name yas--guessed-modes) + nil + nil + nil + nil + (if (cl-first yas--guessed-modes) + (symbol-name (cl-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 name passed to `yas--table-get-create'. When +called interactively, prompt for the table name. +Return the `yas--template' object created" + (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,... + ;; + (yas--editing-template + (yas--define-snippets-1 (yas--parse-template (yas--template-load-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' + ;; + (t + (unless yas--guessed-modes + (set (make-local-variable 'yas--guessed-modes) (or (yas--compute-major-mode-and-parents buffer-file-name)))) + (let* ((table (yas--table-get-create table))) + (set (make-local-variable 'yas--editing-template) + (yas--define-snippets-1 (yas--parse-template buffer-file-name) + table))))) + (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)))) + yas--editing-template) + +(defun yas-maybe-load-snippet-buffer () + "Added to `after-save-hook' in `snippet-mode'." + (let* ((mode (intern (file-name-sans-extension + (file-name-nondirectory + (directory-file-name default-directory))))) + (current-snippet + (apply #'yas--define-snippets-2 (yas--table-get-create mode) + (yas--parse-template buffer-file-name))) + (uuid (yas--template-uuid current-snippet))) + (unless (equal current-snippet + (if uuid (yas--get-template-by-uuid mode uuid) + (yas--lookup-snippet-1 + (yas--template-name current-snippet) mode))) + (yas-load-snippet-buffer mode t)))) + +(defun yas-load-snippet-buffer-and-close (table &optional kill) + "Load and save the snippet, then `quit-window' if saved. +Loading is performed by `yas-load-snippet-buffer'. If the +snippet is new, ask the user whether (and where) to save it. If +the snippet already has a file, just save it. + +The prefix argument KILL is passed to `quit-window'. + +Don't use this from a Lisp program, call `yas-load-snippet-buffer' +and `kill-buffer' instead." + (interactive (list (yas--read-table) current-prefix-arg)) + (let ((template (yas-load-snippet-buffer table t))) + (when (and (buffer-modified-p) + (y-or-n-p + (format "[yas] Loaded for %s. Also save snippet buffer?" + (yas--table-name (yas--template-table template))))) + (let ((default-directory (car (cdr (car (yas--guess-snippet-directories + (yas--template-table template)))))) + (default-file-name (yas--template-name template))) + (unless (or buffer-file-name (not default-file-name)) + (setq buffer-file-name + (read-file-name "File to save snippet in: " + nil nil nil default-file-name)) + (rename-buffer (file-name-nondirectory buffer-file-name) t)) + (save-buffer))) + (quit-window kill))) + +(declare-function yas-debug-snippets "yasnippet-debug") + +(defun yas-tryout-snippet (&optional debug) + "Test current buffer's snippet template in other buffer. +DEBUG is for debugging the YASnippet engine itself." + (interactive "P") + (let* ((major-mode-and-parent (yas--compute-major-mode-and-parents buffer-file-name)) + (parsed (yas--parse-template)) + (test-mode (or (and (car major-mode-and-parent) + (fboundp (car major-mode-and-parent)) + (car major-mode-and-parent)) + (cl-first yas--guessed-modes) + (intern (read-from-minibuffer (yas--format "Please input a mode: "))))) + (yas--current-template + (and parsed + (fboundp test-mode) + (yas--make-template :table nil ;; no tables for ephemeral snippets + :key (nth 0 parsed) + :content (nth 1 parsed) + :name (nth 2 parsed) + :expand-env (nth 5 parsed))))) + (cond (yas--current-template + (let ((buffer-name (format "*testing snippet: %s*" (yas--template-name yas--current-template)))) + (kill-buffer (get-buffer-create buffer-name)) + (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) + (point-max) + (yas--template-expand-env yas--current-template)) + (when (and debug + (require 'yasnippet-debug nil t)) + (yas-debug-snippets "*YASnippet trace*" 'snippet-navigation) + (display-buffer "*YASnippet trace*")))) + (t + (yas--message 1 "Cannot test snippet for unknown major mode"))))) + +(defun yas-active-keys () + "Return all active trigger keys for current buffer and point." + (cl-remove-duplicates + (cl-remove-if-not #'stringp (cl-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))))) + +(defun yas-describe-table-by-namehash () + "Display snippet tables by NAMEHASH." + (interactive) + (with-current-buffer (get-buffer-create "*YASnippet Tables by NAMEHASH*") + (let ((inhibit-read-only t)) + (erase-buffer) + (insert "YASnippet tables by NAMEHASH: \n") + (maphash + (lambda (_mode table) + (insert (format "\nSnippet table `%s':\n\n" (yas--table-name table))) + (maphash + (lambda (key _v) + (insert (format " key %s maps snippets: %s\n" key + (let ((names)) + (maphash #'(lambda (k _v) + (push k names)) + (gethash key (yas--table-hash table))) + names)))) + (yas--table-hash table))) + yas--tables)) + (view-mode +1) + (goto-char 1) + (display-buffer (current-buffer)))) + +(defun yas-describe-tables (&optional with-nonactive) + "Display snippets for each table." + (interactive "P") + (let ((original-buffer (current-buffer)) + (tables (yas--get-snippet-tables))) + (with-current-buffer (get-buffer-create "*YASnippet Tables*") + (let ((inhibit-read-only t)) + (when with-nonactive + (maphash #'(lambda (_k v) + (cl-pushnew v tables)) + yas--tables)) + (erase-buffer) + (insert "YASnippet tables:\n") + (dolist (table tables) + (yas--describe-pretty-table table original-buffer)) + (yas--create-snippet-xrefs)) + (help-mode) + (goto-char 1) + (display-buffer (current-buffer))))) + +(defun yas--describe-pretty-table (table &optional original-buffer) + (insert (format "\nSnippet table `%s'" + (yas--table-name table))) + (if (yas--table-parents table) + (insert (format " parents: %s\n" + (mapcar #'yas--table-name + (yas--table-parents table)))) + (insert "\n")) + (insert (make-string 100 ?-) "\n") + (insert "group state name key binding\n") + (let ((groups-hash (make-hash-table :test #'equal))) + (maphash #'(lambda (_k v) + (let ((group (or (yas--template-fine-group v) + "(top level)"))) + (when (yas--template-name v) + (puthash group + (cons v (gethash group groups-hash)) + groups-hash)))) + (yas--table-uuidhash table)) + (maphash + #'(lambda (group templates) + (setq group (truncate-string-to-width group 25 0 ? "...")) + (insert (make-string 100 ?-) "\n") + (dolist (p templates) + (let* ((name (truncate-string-to-width (propertize (format "\\\\snippet `%s'" (yas--template-name p)) + 'yasnippet p) + 50 0 ? "...")) + (group (prog1 group + (setq group (make-string (length group) ? )))) + (condition-string (let ((condition (yas--template-condition p))) + (if (and condition + original-buffer) + (with-current-buffer original-buffer + (if (yas--eval-condition condition) + "(y)" + "(s)")) + "(a)"))) + (key-description-string (key-description (yas--template-keybinding p))) + (template-key-padding (if (string= key-description-string "") nil ? ))) + (insert group " " + condition-string " " + name (if (string-match "\\.\\.\\.$" name) + "'" " ") + " " + (truncate-string-to-width (or (yas--template-key p) "") + 15 0 template-key-padding "...") + (or template-key-padding "") + (truncate-string-to-width key-description-string + 15 0 nil "...") + "\n")))) + groups-hash))) + + + +;;; User convenience functions, for using in `yas-key-syntaxes' + +(defun yas-try-key-from-whitespace (_start-point) + "As `yas-key-syntaxes' element, look for whitespace delimited key. + +A newline will be considered whitespace even if the mode syntax +marks it as something else (typically comment ender)." + (skip-chars-backward "^[:space:]\n")) + +(defun yas-shortest-key-until-whitespace (_start-point) + "Like `yas-longest-key-from-whitespace' but take the shortest key." + (when (/= (skip-chars-backward "^[:space:]\n" (1- (point))) 0) + 'again)) + +(defun yas-longest-key-from-whitespace (start-point) + "As `yas-key-syntaxes' element, look for longest key between point and whitespace. + +A newline will be considered whitespace even if the mode syntax +marks it as something else (typically comment ender)." + (if (= (point) start-point) + (yas-try-key-from-whitespace start-point) + (forward-char)) + (unless (<= start-point (1+ (point))) + 'again)) + + + +;;; User convenience functions, for using in snippet definitions + +(defvar yas-modified-p nil + "Non-nil if field has been modified by user or transformation.") + +(defvar yas-moving-away-p nil + "Non-nil if user is about to exit field.") + +(defvar yas-text nil + "Contains current field text.") + +(defun yas-substr (str pattern &optional subexp) + "Search PATTERN in STR and return SUBEXPth match. + +If found, the content of subexp group SUBEXP (default 0) is + returned, or else the original STR will be returned." + (let ((grp (or subexp 0))) + (save-match-data + (if (string-match pattern str) + (match-string-no-properties grp str) + str)))) + +(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) + (let* ((last-link (last possibilities)) + (last-elem (car last-link))) + (when (listp last-elem) + (setcar last-link (car last-elem)) + (setcdr last-link (cdr last-elem)))) + (cl-some (lambda (fn) + (funcall fn "Choose: " possibilities)) + yas-prompt-functions))) + +(defun yas-key-to-value (alist) + (unless (or yas-moving-away-p + yas-modified-p) + (let ((key (read-key-sequence ""))) + (when (stringp key) + (or (cdr (cl-find key alist :key #'car :test #'string=)) + key))))) + +(defun yas-throw (text) + "Signal `yas-exception' with TEXT as the reason." + (signal 'yas-exception (list text))) +(put 'yas-exception 'error-conditions '(error yas-exception)) +(put 'yas-exception 'error-message "[yas] Exception") + +(defun yas-verify-value (possibilities) + "Verify that the current field value is in POSSIBILITIES. +Otherwise signal `yas-exception'." + (when (and yas-moving-away-p (cl-notany (lambda (pos) (string= pos yas-text)) possibilities)) + (yas-throw (format "Field only allows %s" possibilities)))) + +(defun yas-field-value (number) + "Get the string for field with NUMBER. + +Use this in primary and mirror transformations to get the text of +other fields." + (let* ((snippet (car (yas-active-snippets))) + (field (and snippet + (yas--snippet-find-field snippet number)))) + (when field + (yas--field-text-for-display field)))) + +(defun yas-text () + "Return `yas-text' if that exists and is non-empty, else nil." + (if (and yas-text + (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--get-field-once (number &optional transform-fn) + (unless yas-modified-p + (if transform-fn + (funcall transform-fn (yas-field-value number)) + (yas-field-value number)))) + +(defun yas-default-from-field (number) + (unless yas-modified-p + (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) + (if yas--current-template + (if (y-or-n-p (format "This snippet is unimplemented (missing %s) Visit the snippet definition? " + (or missing-feature + "something"))) + (yas--visit-snippet-file-1 yas--current-template)) + (message "No implementation. Missing %s" (or missing-feature "something")))) + + +;;; Snippet expansion and field management + +(defvar yas--active-field-overlay nil + "Overlays the currently active field.") + +(defvar yas--field-protection-overlays nil + "Two overlays protect the current active field.") + +(defvar yas-selected-text nil + "The selected region deleted on the last snippet expansion.") + +(defvar yas--start-column nil + "The column where the snippet expansion started.") + +(make-variable-buffer-local 'yas--active-field-overlay) +(make-variable-buffer-local 'yas--field-protection-overlays) +(put 'yas--active-field-overlay 'permanent-local t) +(put 'yas--field-protection-overlays 'permanent-local t) + +(cl-defstruct (yas--snippet (:constructor yas--make-snippet (expand-env))) + "A snippet. + +..." + expand-env + (fields '()) + (exit nil) + (id (yas--snippet-next-id) :read-only t) + (control-overlay nil) + active-field + ;; stacked expansion: the `previous-active-field' slot saves the + ;; active field where the child expansion took place + previous-active-field + force-exit) + +(cl-defstruct (yas--field (:constructor yas--make-field (number start end parent-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 + (mirrors '()) + (transform nil) + (modified-p nil) + next) + + +(cl-defstruct (yas--mirror (:constructor yas--make-mirror (start end transform))) + "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 + depth) + +(cl-defstruct (yas--exit (:constructor yas--make-exit (marker))) + marker + next) + +(defmacro yas--letenv (env &rest body) + "Evaluate BODY with bindings from ENV. +ENV is a lisp expression that evaluates to list of elements with +the form (VAR FORM), where VAR is a symbol and FORM is a lisp +expression that evaluates to its value." + (declare (debug (form body)) (indent 1)) + (let ((envvar (make-symbol "envvar"))) + `(let ((,envvar ,env)) + (cl-progv + (mapcar #'car ,envvar) + (mapcar (lambda (v-f) (eval (cadr v-f))) ,envvar) + ,@body)))) + +(defun yas--snippet-map-markers (fun snippet) + "Apply FUN to all marker (sub)fields in SNIPPET. +Update each field with the result of calling FUN." + (dolist (field (yas--snippet-fields snippet)) + (setf (yas--field-start field) (funcall fun (yas--field-start field))) + (setf (yas--field-end field) (funcall fun (yas--field-end field))) + (dolist (mirror (yas--field-mirrors field)) + (setf (yas--mirror-start mirror) (funcall fun (yas--mirror-start mirror))) + (setf (yas--mirror-end mirror) (funcall fun (yas--mirror-end mirror))))) + (let ((snippet-exit (yas--snippet-exit snippet))) + (when snippet-exit + (setf (yas--exit-marker snippet-exit) + (funcall fun (yas--exit-marker snippet-exit)))))) + +(defun yas--snippet-live-p (snippet) + "Return non-nil if SNIPPET hasn't been committed." + (catch 'live + (yas--snippet-map-markers (lambda (m) + (if (markerp m) m + (throw 'live nil))) + snippet) + t)) + +(defun yas--apply-transform (field-or-mirror field &optional empty-on-nil-p) + "Calculate transformed string for FIELD-OR-MIRROR from FIELD. + +If there is no transform for ht field, return nil. + +If there is a transform but it returns nil, return the empty +string iff EMPTY-ON-NIL-P is true." + (let* ((yas-text (yas--field-text-for-display field)) + (yas-modified-p (yas--field-modified-p field)) + (transform (if (yas--mirror-p field-or-mirror) + (yas--mirror-transform field-or-mirror) + (yas--field-transform field-or-mirror))) + (start-point (if (yas--mirror-p field-or-mirror) + (yas--mirror-start field-or-mirror) + (yas--field-start field-or-mirror))) + (transformed (and transform + (save-excursion + (goto-char start-point) + (let ((ret (yas--eval-for-string transform))) + (or ret (and empty-on-nil-p ""))))))) + transformed)) + +(defsubst yas--replace-all (from to &optional text) + "Replace all occurrences from FROM to TO. + +With optional string TEXT do it in that string." + (if text + (replace-regexp-in-string (regexp-quote from) to text t t) + (goto-char (point-min)) + (while (search-forward from nil t) + (replace-match to t t text)))) + +(defun yas--snippet-find-field (snippet number) + (cl-find-if (lambda (field) + (eq number (yas--field-number field))) + (yas--snippet-fields snippet))) + +(defun yas--snippet-sort-fields (snippet) + "Sort the fields of SNIPPET in navigation order." + (setf (yas--snippet-fields snippet) + (sort (yas--snippet-fields snippet) + #'yas--snippet-field-compare))) + +(defun yas--snippet-field-compare (field1 field2) + "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 + (if n2 + (or (zerop n2) (and (not (zerop n1)) + (< n1 n2))) + (not (zerop n1))) + (if n2 + (zerop n2) + (< (yas--field-start field1) + (yas--field-start field2)))))) + +(defun yas--field-probably-deleted-p (snippet field) + "Guess if SNIPPET's FIELD should be skipped." + (and + ;; field must be zero length + ;; + (zerop (- (yas--field-start field) (yas--field-end field))) + ;; field must have been modified + ;; + (yas--field-modified-p field) + ;; either: + (or + ;; 1) it's a nested field + ;; + (yas--field-parent-field field) + ;; 2) ends just before the snippet end + ;; + (and (eq field (car (last (yas--snippet-fields snippet)))) + (= (yas--field-start field) (overlay-end (yas--snippet-control-overlay snippet))))) + ;; the field numbered 0, just before the exit marker, should + ;; never be skipped + ;; + (not (and (yas--field-number field) + (zerop (yas--field-number field)))))) + +(defun yas-active-snippets (&optional beg end) + "Return a sorted list of active snippets. +The most recently-inserted snippets are returned first. + +Only snippets overlapping the region BEG ... END are returned. +Overlapping has the same meaning as described in `overlays-in'. +If END is omitted, it defaults to (1+ BEG). If BEG is omitted, +it defaults to point. A non-nil, non-buffer position BEG is +equivalent to a range covering the whole buffer." + (unless beg + (setq beg (point))) + (cond ((not (or (integerp beg) (markerp beg))) + (setq beg (point-min) end (point-max))) + ((not end) + (setq end (1+ beg)))) + (cl-sort + (delete-dups ;; Snippets have multiple overlays. + (delq nil + (mapcar (lambda (ov) (overlay-get ov 'yas--snippet)) + (overlays-in beg end)))) + #'>= :key #'yas--snippet-id)) + +(define-obsolete-function-alias 'yas--snippets-at-point + 'yas-active-snippets "0.12") + +(defun yas-next-field-or-maybe-expand () + "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) + (active-field (overlay-get yas--active-field-overlay 'yas--field))) + (when active-field + (unless (yas-expand-from-trigger-key active-field) + (yas-next-field)))) + (yas-next-field))) + +(defun yas-next-field-will-exit-p (&optional arg) + "Return non-nil if (yas-next-field ARG) would exit the current snippet." + (let ((snippet (car (yas-active-snippets))) + (active (overlay-get yas--active-field-overlay 'yas--field))) + (when snippet + (not (yas--find-next-field arg snippet active))))) + +(defun yas--find-next-field (n snippet active) + "Return the Nth field after the ACTIVE one in SNIPPET." + (let ((live-fields (cl-remove-if + (lambda (field) + (and (not (eq field active)) + (yas--field-probably-deleted-p snippet field))) + (yas--snippet-fields snippet)))) + (nth (abs n) (memq active (if (>= n 0) live-fields (reverse live-fields)))))) + +(defun yas-next-field (&optional arg) + "Navigate to the ARGth next field. + +If there's none, exit the snippet." + (interactive) + (unless arg (setq arg 1)) + (let* ((snippet (car (yas-active-snippets))) + (active-field (overlay-get yas--active-field-overlay 'yas--field)) + (target-field (yas--find-next-field arg snippet active-field))) + (yas--letenv (yas--snippet-expand-env snippet) + ;; Apply transform to active field. + (when active-field + (let ((yas-moving-away-p t)) + (when (yas--field-update-display active-field) + (yas--update-mirrors snippet)))) + ;; Now actually move... + (if target-field + (yas--move-to-field snippet target-field) + (yas-exit-snippet snippet))))) + +(defun yas--place-overlays (snippet field) + "Correctly place overlays for SNIPPET's FIELD." + (yas--make-move-field-protection-overlays snippet field) + ;; Only move active field overlays if this is field is from the + ;; innermost snippet. + (when (eq snippet (car (yas-active-snippets (1- (yas--field-start field)) + (1+ (yas--field-end field))))) + (yas--make-move-active-field-overlay snippet field))) + +(defun yas--move-to-field (snippet field) + "Update SNIPPET to move to field FIELD. + +Also create some protection overlays" + (goto-char (yas--field-start field)) + (yas--place-overlays snippet field) + (overlay-put yas--active-field-overlay 'yas--snippet snippet) + (overlay-put yas--active-field-overlay 'yas--field field) + (let ((number (yas--field-number field))) + ;; check for the special ${0: ...} field + (if (and number (zerop number)) + (progn + (set-mark (yas--field-end field)) + (setf (yas--snippet-force-exit snippet) + (or (yas--field-transform field) + t))) + ;; make this field active + (setf (yas--snippet-active-field snippet) field) + ;; primary field transform: first call to snippet transform + (unless (yas--field-modified-p field) + (if (yas--field-update-display field) + (yas--update-mirrors snippet) + (setf (yas--field-modified-p field) nil)))))) + +(defun yas-prev-field () + "Navigate to prev field. If there's none, exit the snippet." + (interactive) + (yas-next-field -1)) + +(defun yas-abort-snippet (&optional snippet) + (interactive) + (let ((snippet (or snippet + (car (yas-active-snippets))))) + (when snippet + (setf (yas--snippet-force-exit snippet) t)))) + +(defun yas-exit-snippet (snippet) + "Goto exit-marker of SNIPPET." + (interactive (list (cl-first (yas-active-snippets)))) + (when snippet + (setf (yas--snippet-force-exit snippet) t) + (goto-char (if (yas--snippet-exit snippet) + (yas--exit-marker (yas--snippet-exit snippet)) + (overlay-end (yas--snippet-control-overlay snippet)))))) + +(defun yas-exit-all-snippets () + "Exit all snippets." + (interactive) + (mapc #'(lambda (snippet) + (yas-exit-snippet snippet) + (yas--check-commit-snippet)) + (yas-active-snippets 'all))) + + +;;; Some low level snippet-routines: + +(defvar yas--inhibit-overlay-hooks nil + "Bind this temporarily to non-nil to prevent running `yas--on-*-modification'.") + +(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." + + (let ((control-overlay (yas--snippet-control-overlay snippet))) + ;; + ;; Save the end of the moribund snippet in case we need to revive it + ;; its original expansion. + ;; + (when (and control-overlay + (overlay-buffer control-overlay)) + (setq yas-snippet-beg (overlay-start control-overlay)) + (setq yas-snippet-end (overlay-end control-overlay)) + (delete-overlay control-overlay) + (setf (yas--snippet-control-overlay snippet) nil)) + + (let ((yas--inhibit-overlay-hooks t)) + (when yas--active-field-overlay + (delete-overlay yas--active-field-overlay)) + (when yas--field-protection-overlays + (mapc #'delete-overlay yas--field-protection-overlays))) + + ;; stacked expansion: if the original expansion took place from a + ;; field, make sure we advance it here at least to + ;; `yas-snippet-end'... + ;; + (let ((previous-field (yas--snippet-previous-active-field snippet))) + (when (and yas-snippet-end previous-field) + (yas--advance-end-maybe previous-field yas-snippet-end))) + + ;; Convert all markers to points, + ;; + (yas--markers-to-points snippet) + + ;; Take care of snippet revival on undo. + (if (and yas-snippet-revival (listp buffer-undo-list)) + (push `(apply yas--snippet-revive ,yas-snippet-beg ,yas-snippet-end ,snippet) + buffer-undo-list) + ;; Dismember the snippet... this is useful if we get called + ;; again from `yas--take-care-of-redo'.... + (setf (yas--snippet-fields snippet) nil))) + + (yas--message 4 "Snippet %s exited." (yas--snippet-id snippet))) + +(defvar yas--snippets-to-move nil) +(make-variable-buffer-local 'yas--snippets-to-move) + +(defun yas--prepare-snippets-for-move (beg end buf pos) + "Gather snippets in BEG..END for moving to POS in BUF." + (let ((to-move nil) + (snippets (yas-active-snippets beg end)) + (dst-base-line (with-current-buffer buf + (count-lines (point-min) pos)))) + (when snippets + (dolist (snippet snippets) + (yas--snippet-map-markers + (lambda (m) + (goto-char m) + (beginning-of-line) + (prog1 (cons (count-lines (point-min) (point)) + (yas--snapshot-marker-location m)) + (set-marker m nil))) + snippet) + (let ((ctrl-ov (yas--snapshot-overlay-line-location + (yas--snippet-control-overlay snippet)))) + (push (list ctrl-ov dst-base-line snippet) to-move) + (delete-overlay (car ctrl-ov)))) + (with-current-buffer buf + (setq yas--snippets-to-move (nconc to-move yas--snippets-to-move)))))) + +(defun yas--on-buffer-kill () + ;; Org mode uses temp buffers for fontification and "native tab", + ;; move all the snippets to the original org-mode buffer when it's + ;; killed. + (let ((org-marker nil)) + (when (and yas-minor-mode + (or (bound-and-true-p org-edit-src-from-org-mode) + (bound-and-true-p org-src--from-org-mode)) + (markerp + (setq org-marker + (or (bound-and-true-p org-edit-src-beg-marker) + (bound-and-true-p org-src--beg-marker))))) + (yas--prepare-snippets-for-move + (point-min) (point-max) + (marker-buffer org-marker) org-marker)))) + +(add-hook 'kill-buffer-hook #'yas--on-buffer-kill) + +(defun yas--finish-moving-snippets () + "Finish job started in `yas--prepare-snippets-for-move'." + (cl-loop for (ctrl-ov base-line snippet) in yas--snippets-to-move + for base-pos = (progn (goto-char (point-min)) + (forward-line base-line) (point)) + do (yas--snippet-map-markers + (lambda (l-m-r-w) + (goto-char base-pos) + (forward-line (nth 0 l-m-r-w)) + (save-restriction + (narrow-to-region (line-beginning-position) + (line-end-position)) + (yas--restore-marker-location (cdr l-m-r-w))) + (nth 1 l-m-r-w)) + snippet) + (goto-char base-pos) + (yas--restore-overlay-location ctrl-ov) + (yas--maybe-move-to-active-field snippet)) + (setq yas--snippets-to-move nil)) + +(defun yas--safely-call-fun (fun) + "Call FUN and catch any errors." + (condition-case error + (funcall fun) + ((debug error) + (yas--message 2 "Error running %s: %s" fun + (error-message-string error))))) + +(defun yas--safely-run-hook (hook) + "Call HOOK's functions. +HOOK should be a symbol, a hook variable, as in `run-hooks'." + (let ((debug-on-error (and (not (memq yas-good-grace '(t hooks))) + debug-on-error))) + (yas--safely-call-fun (apply-partially #'run-hooks hook)))) + +(defun yas--check-commit-snippet () + "Check if point exited the currently active field of the snippet. + +If so cleans up the whole snippet up." + (let* ((snippets (yas-active-snippets 'all)) + (snippets-left snippets) + (snippet-exit-transform) + ;; Record the custom snippet `yas-after-exit-snippet-hook' + ;; set in the expand-env field. + (snippet-exit-hook yas-after-exit-snippet-hook)) + (dolist (snippet snippets) + (let ((active-field (yas--snippet-active-field snippet))) + (yas--letenv (yas--snippet-expand-env snippet) + ;; Note: the `force-exit' field could be a transform in case of + ;; ${0: ...}, see `yas--move-to-field'. + (setq snippet-exit-transform (yas--snippet-force-exit snippet)) + (cond ((or snippet-exit-transform + (not (and active-field (yas--field-contains-point-p active-field)))) + (setq snippets-left (delete snippet snippets-left)) + (setf (yas--snippet-force-exit snippet) nil) + (setq snippet-exit-hook yas-after-exit-snippet-hook) + (yas--commit-snippet snippet)) + ((and active-field + (or (not yas--active-field-overlay) + (not (overlay-buffer yas--active-field-overlay)))) + ;; + ;; stacked expansion: this case is mainly for recent + ;; snippet exits that place us back int the field of + ;; another snippet + ;; + (save-excursion + (yas--move-to-field snippet active-field) + (yas--update-mirrors snippet))) + (t + nil))))) + (unless (or (null snippets) snippets-left) + (when snippet-exit-transform + (yas--eval-for-effect snippet-exit-transform)) + (let ((yas-after-exit-snippet-hook snippet-exit-hook)) + (yas--safely-run-hook 'yas-after-exit-snippet-hook))))) + +;; Apropos markers-to-points: +;; +;; This was found useful for performance reasons, so that an excessive +;; number of live markers aren't kept around in the +;; `buffer-undo-list'. We don't reuse the original marker object +;; because that leaves an unreadable object in the history list and +;; undo-tree persistence has trouble with that. +;; +;; This shouldn't bring horrible problems with undo/redo, but you +;; never know. +;; +(defun yas--markers-to-points (snippet) + "Save all markers of SNIPPET as positions." + (yas--snippet-map-markers (lambda (m) + (prog1 (marker-position m) + (set-marker m nil))) + snippet)) + +(defun yas--points-to-markers (snippet) + "Restore SNIPPET's marker positions, saved by `yas--markers-to-points'." + (yas--snippet-map-markers #'copy-marker snippet)) + +(defun yas--maybe-move-to-active-field (snippet) + "Try to move to SNIPPET's active (or first) field and return it if found." + (let ((target-field (or (yas--snippet-active-field snippet) + (car (yas--snippet-fields snippet))))) + (when target-field + (yas--move-to-field snippet target-field) + target-field))) + +(defun yas--field-contains-point-p (field &optional point) + (let ((point (or point + (point)))) + (and (>= point (yas--field-start field)) + (<= point (yas--field-end field))))) + +(defun yas--field-text-for-display (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." + (or undo-in-progress + (eq this-command 'undo) + (eq this-command 'redo))) + +(defun yas--make-control-overlay (snippet start end) + "Create the control overlay that surrounds the snippet and +holds the keymap." + (let ((overlay (make-overlay start + end + nil + nil + t))) + (overlay-put overlay 'keymap yas-keymap) + (overlay-put overlay 'priority yas-overlay-priority) + (overlay-put overlay 'yas--snippet snippet) + overlay)) + +(defun yas-current-field () + "Return the currently active field." + (and yas--active-field-overlay + (overlay-buffer yas--active-field-overlay) + (overlay-get yas--active-field-overlay 'yas--field))) + +(defun yas--maybe-clear-field-filter (cmd) + "Return CMD if at start of unmodified snippet field. +Use as a `:filter' argument for a conditional keybinding." + (let ((field (yas-current-field))) + (when (and field + (not (yas--field-modified-p field)) + (eq (point) (marker-position (yas--field-start field)))) + cmd))) + +(defun yas-skip-and-clear-field (&optional field) + "Clears unmodified FIELD if at field start, skips to next tab." + (interactive) + (yas--skip-and-clear (or field (yas-current-field))) + (yas-next-field 1)) + +(defun yas-skip-and-clear-or-delete-char (&optional field) + "Clears unmodified field if at field start, skips to next tab. + +Otherwise deletes a character normally by calling `delete-char'." + (interactive) + (declare (obsolete "Bind to `yas-maybe-skip-and-clear-field' instead." "0.13")) + (cond ((yas--maybe-clear-field-filter t) + (yas--skip-and-clear (or field (yas-current-field))) + (yas-next-field 1)) + (t (call-interactively 'delete-char)))) + +(defun yas--skip-and-clear (field &optional from) + "Deletes the region of FIELD and sets it's modified state to t. +If given, FROM indicates position to start at instead of FIELD's beginning." + ;; Just before skipping-and-clearing the field, mark its children + ;; fields as modified, too. If the children have mirrors-in-fields + ;; this prevents them from updating erroneously (we're skipping and + ;; deleting!). + ;; + (yas--mark-this-and-children-modified field) + (unless (= (yas--field-start field) (yas--field-end field)) + (delete-region (or from (yas--field-start field)) (yas--field-end field)))) + +(defun yas--mark-this-and-children-modified (field) + (setf (yas--field-modified-p field) t) + (let ((fom (yas--field-next field))) + (while (and fom + (yas--fom-parent-field fom)) + (when (and (eq (yas--fom-parent-field fom) field) + (yas--field-p fom)) + (yas--mark-this-and-children-modified fom)) + (setq fom (yas--fom-next fom))))) + +(defun yas--make-move-active-field-overlay (snippet field) + "Place the active field overlay in SNIPPET's FIELD. + +Move the overlay, or create it if it does not exit." + (if (and yas--active-field-overlay + (overlay-buffer yas--active-field-overlay)) + (move-overlay yas--active-field-overlay + (yas--field-start field) + (yas--field-end field)) + (setq yas--active-field-overlay + (make-overlay (yas--field-start field) + (yas--field-end field) + nil nil t)) + (overlay-put yas--active-field-overlay 'priority yas-overlay-priority) + (overlay-put yas--active-field-overlay 'face 'yas-field-highlight-face) + (overlay-put yas--active-field-overlay 'yas--snippet snippet) + (overlay-put yas--active-field-overlay 'modification-hooks '(yas--on-field-overlay-modification)) + (overlay-put yas--active-field-overlay 'insert-in-front-hooks + '(yas--on-field-overlay-modification)) + (overlay-put yas--active-field-overlay 'insert-behind-hooks + '(yas--on-field-overlay-modification)))) + +(defun yas--skip-and-clear-field-p (field beg _end length) + "Tell if newly modified FIELD should be cleared and skipped. +BEG, END and LENGTH like overlay modification hooks." + (and (= length 0) ; A 0 pre-change length indicates insertion. + (= beg (yas--field-start field)) ; Insertion at field start? + (not (yas--field-modified-p field)))) + +(defun yas--on-field-overlay-modification (overlay after? beg end &optional length) + "Clears the field and updates mirrors, conditionally. + +Only clears the field if it hasn't been modified and point is at +field start. This hook does nothing if an undo is in progress." + (unless (or (not after?) + yas--inhibit-overlay-hooks + (not (overlayp yas--active-field-overlay)) ; Avoid Emacs bug #21824. + ;; If a single change hits multiple overlays of the same + ;; snippet, then we delete the snippet the first time, + ;; and then subsequent calls get a deleted overlay. + ;; Don't delete the snippet again! + (not (overlay-buffer overlay)) + (yas--undo-in-progress)) + (let* ((inhibit-modification-hooks nil) + (yas--inhibit-overlay-hooks t) + (field (overlay-get overlay 'yas--field)) + (snippet (overlay-get yas--active-field-overlay 'yas--snippet))) + (if (yas--snippet-live-p snippet) + (save-match-data + (yas--letenv (yas--snippet-expand-env snippet) + (when (yas--skip-and-clear-field-p field beg end length) + ;; We delete text starting from the END of insertion. + (yas--skip-and-clear field end)) + (setf (yas--field-modified-p field) t) + (yas--advance-end-maybe field (overlay-end overlay)) + (save-excursion + (yas--field-update-display field)) + (yas--update-mirrors snippet))) + (lwarn '(yasnippet zombie) :warning "Killing zombie snippet!") + (delete-overlay overlay))))) + +(defun yas--auto-fill () + (let* ((orig-point (point)) + (end (progn (forward-paragraph) (point))) + (beg (progn (backward-paragraph) (point))) + (snippets (yas-active-snippets beg end)) + (remarkers nil) + (reoverlays nil)) + (dolist (snippet snippets) + (dolist (m (yas--collect-snippet-markers snippet)) + (when (and (<= beg m) (<= m end)) + (push (yas--snapshot-marker-location m beg end) remarkers))) + (push (yas--snapshot-overlay-location + (yas--snippet-control-overlay snippet) beg end) + reoverlays)) + (goto-char orig-point) + (let ((yas--inhibit-overlay-hooks t)) + (if (null yas--original-auto-fill-function) + ;; Try to get more info on #873/919. + (let ((yas--fill-fun-values `((t ,(default-value 'yas--original-auto-fill-function)))) + (fill-fun-values `((t ,(default-value 'auto-fill-function)))) + ;; Listing 2 buffers with the same value is enough + (print-length 3)) + (save-current-buffer + (dolist (buf (let ((bufs (buffer-list))) + ;; List the current buffer first. + (setq bufs (cons (current-buffer) + (remq (current-buffer) bufs))))) + (set-buffer buf) + (let* ((yf-cell (assq yas--original-auto-fill-function + yas--fill-fun-values)) + (af-cell (assq auto-fill-function fill-fun-values))) + (when (local-variable-p 'yas--original-auto-fill-function) + (if yf-cell (setcdr yf-cell (cons buf (cdr yf-cell))) + (push (list yas--original-auto-fill-function buf) yas--fill-fun-values))) + (when (local-variable-p 'auto-fill-function) + (if af-cell (setcdr af-cell (cons buf (cdr af-cell))) + (push (list auto-fill-function buf) fill-fun-values)))))) + (lwarn '(yasnippet auto-fill bug) :error + "`yas--original-auto-fill-function' unexpectedly nil in %S! Disabling auto-fill. + %S + `auto-fill-function': %S\n%s" + (current-buffer) yas--fill-fun-values fill-fun-values + (if (fboundp 'backtrace--print-frame) + (with-output-to-string + (mapc (lambda (frame) + (apply #'backtrace--print-frame frame)) + yas--watch-auto-fill-backtrace)) + "")) + ;; Try to avoid repeated triggering of this bug. + (auto-fill-mode -1) + ;; Don't pop up more than once in a session (still log though). + (defvar warning-suppress-types) ; `warnings' is autoloaded by `lwarn'. + (add-to-list 'warning-suppress-types '(yasnippet auto-fill bug))) + (funcall yas--original-auto-fill-function))) + (save-excursion + (setq end (progn (forward-paragraph) (point))) + (setq beg (progn (backward-paragraph) (point)))) + (save-excursion + (save-restriction + (narrow-to-region beg end) + (mapc #'yas--restore-marker-location remarkers) + (mapc #'yas--restore-overlay-location reoverlays)) + (mapc (lambda (snippet) + (yas--letenv (yas--snippet-expand-env snippet) + (yas--update-mirrors snippet))) + snippets)))) + + +;;; Apropos protection overlays: +;; +;; These exist for nasty users who will try to delete parts of the +;; snippet outside the active field. Actual protection happens in +;; `yas--on-protection-overlay-modification'. +;; +;; As of github #537 this no longer inhibits the command by issuing an +;; error: all the snippets at point, including nested snippets, are +;; automatically commited and the current command can proceed. +;; +(defun yas--make-move-field-protection-overlays (snippet field) + "Place protection overlays surrounding SNIPPET's FIELD. + +Move the overlays, or create them if they do not exit." + (let ((start (yas--field-start field)) + (end (yas--field-end field))) + ;; First check if the (1+ end) is contained in the buffer, + ;; otherwise we'll have to do a bit of cheating and silently + ;; insert a newline. the `(1+ (buffer-size))' should prevent this + ;; when using stacked expansion + ;; + (when (< (buffer-size) end) + (save-excursion + (let ((yas--inhibit-overlay-hooks t)) + (goto-char (point-max)) + (newline)))) + ;; go on to normal overlay creation/moving + ;; + (cond ((and yas--field-protection-overlays + (cl-every #'overlay-buffer yas--field-protection-overlays)) + (move-overlay (nth 0 yas--field-protection-overlays) + (1- start) start) + (move-overlay (nth 1 yas--field-protection-overlays) end (1+ end))) + (t + (setq yas--field-protection-overlays + (list (make-overlay (1- start) start nil t nil) + (make-overlay end (1+ end) nil t nil))) + (dolist (ov yas--field-protection-overlays) + (overlay-put ov 'face 'yas--field-debug-face) + (overlay-put ov 'yas--snippet snippet) + ;; (overlay-put ov 'evaporate t) + (overlay-put ov 'modification-hooks '(yas--on-protection-overlay-modification))))))) + +(defun yas--on-protection-overlay-modification (_overlay after? beg end &optional length) + "Commit the snippet if the protection overlay is being killed." + (unless (or yas--inhibit-overlay-hooks + (not after?) + (= length (- end beg)) ; deletion or insertion + (yas--undo-in-progress)) + (let ((snippets (yas-active-snippets))) + (yas--message 2 "Committing snippets. Action would destroy a protection overlay.") + (cl-loop for snippet in snippets + do (yas--commit-snippet snippet))))) + +(add-to-list 'debug-ignored-errors "^Exit the snippet first!$") + + +;;; Snippet expansion and "stacked" expansion: +;; +;; Stacked expansion is when you try to expand a snippet when already +;; inside a snippet expansion. +;; +;; The parent snippet does not run its fields modification hooks +;; (`yas--on-field-overlay-modification' and +;; `yas--on-protection-overlay-modification') while the child snippet +;; is active. This means, among other things, that the mirrors of the +;; parent snippet are not updated, this only happening when one exits +;; the child snippet. +;; +;; Unfortunately, this also puts some ugly (and not fully-tested) +;; bits of code in `yas-expand-snippet' and +;; `yas--commit-snippet'. I've tried to mark them with "stacked +;; expansion:". +;; +;; 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 +;; running. This would mean a lot of overlay modification hooks +;; running, but if managed correctly (including overlay priorities) +;; they should account for all situations... + +(defun yas-expand-snippet (snippet &optional start end expand-env) + "Expand SNIPPET at current point. + +Text between START and END will be deleted before inserting +template. EXPAND-ENV is a list of (SYM VALUE) let-style dynamic +bindings considered when expanding the snippet. If omitted, use +SNIPPET's expand-env field. + +SNIPPET may be a snippet structure (e.g., as returned by +`yas-lookup-snippet'), or just a snippet body (which is a string +for normal snippets, and a list for command snippets)." + (cl-assert (and yas-minor-mode + (memq 'yas--post-command-handler post-command-hook)) + nil + "[yas] `yas-expand-snippet' needs properly setup `yas-minor-mode'") + (run-hooks 'yas-before-expand-snippet-hook) + + (let* ((clear-field + (let ((field (and yas--active-field-overlay + (overlay-buffer yas--active-field-overlay) + (overlay-get yas--active-field-overlay 'yas--field)))) + (and field (yas--skip-and-clear-field-p + field (point) (point) 0) + field))) + (start (cond (start) + ((region-active-p) + (region-beginning)) + (clear-field + (yas--field-start clear-field)) + (t (point)))) + (end (cond (end) + ((region-active-p) + (region-end)) + (clear-field + (yas--field-end clear-field)) + (t (point)))) + (to-delete (and (> end start) + (buffer-substring-no-properties start end))) + (yas-selected-text + (cond (yas-selected-text) + ((and (region-active-p) + (not clear-field)) + to-delete)))) + (goto-char start) + (setq yas--indent-original-column (current-column)) + ;; Delete the region to delete, this *does* get undo-recorded. + (when to-delete + (delete-region start end)) + + (let ((content (if (yas--template-p snippet) + (yas--template-content snippet) + snippet))) + (when (and (not expand-env) (yas--template-p snippet)) + (setq expand-env (yas--template-expand-env snippet))) + (cond ((listp content) + ;; x) This is a snippet-command. + (yas--eval-for-effect content)) + (t + ;; x) This is a snippet-snippet :-) + (setq yas--start-column (current-column)) + ;; Stacked expansion: also shoosh the overlay modification hooks. + (let ((yas--inhibit-overlay-hooks t)) + (setq snippet + (yas--snippet-create content expand-env start (point)))) + + ;; Stacked-expansion: This checks for stacked expansion, save the + ;; `yas--previous-active-field' and advance its boundary. + (let ((existing-field (and yas--active-field-overlay + (overlay-buffer yas--active-field-overlay) + (overlay-get yas--active-field-overlay 'yas--field)))) + (when existing-field + (setf (yas--snippet-previous-active-field snippet) existing-field) + (yas--advance-end-maybe existing-field (overlay-end yas--active-field-overlay)))) + + ;; Exit the snippet immediately if no fields. + (unless (yas--snippet-fields snippet) + (yas-exit-snippet snippet)) + + ;; Now, schedule a move to the first field. + (let ((first-field (car (yas--snippet-fields snippet)))) + (when first-field + (sit-for 0) ;; fix issue 125 + (yas--letenv (yas--snippet-expand-env snippet) + (yas--move-to-field snippet first-field)) + (when (and (eq (yas--field-number first-field) 0) + (> (length (yas--field-text-for-display + first-field)) + 0)) + ;; Keep region for ${0:exit text}. + (setq deactivate-mark nil)))) + (yas--message 4 "snippet %d expanded." (yas--snippet-id snippet)) + t))))) + +(defun yas--take-care-of-redo (snippet) + "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 + ;; at least one field + (when (yas--snippet-fields snippet) + (yas--commit-snippet snippet))) + +(defun yas--snippet-revive (beg end snippet) + "Revives SNIPPET and creates a control overlay from BEG to END. + +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*. + +After revival, push the `yas--take-care-of-redo' in the +`buffer-undo-list'" + ;; Reconvert all the points to markers + (yas--points-to-markers snippet) + ;; When at least one editable field existed in the zombie snippet, + ;; try to revive the whole thing... + (when (yas--maybe-move-to-active-field snippet) + (setf (yas--snippet-control-overlay snippet) (yas--make-control-overlay snippet beg end)) + (overlay-put (yas--snippet-control-overlay snippet) 'yas--snippet snippet) + (when (listp buffer-undo-list) + (push `(apply yas--take-care-of-redo ,snippet) + buffer-undo-list)))) + +(defun yas--snippet-create (content expand-env begin end) + "Create a snippet from a template inserted at BEGIN to END. + +Returns the newly created snippet." + (save-restriction + (let ((snippet (yas--make-snippet expand-env))) + (yas--letenv expand-env + ;; Put a single undo action for the expanded snippet's + ;; content. + (let ((buffer-undo-list t)) + ;; Some versions of cc-mode fail when inserting snippet + ;; content in a narrowed buffer. + (goto-char begin) + (insert content) + (setq end (+ end (length content))) + (narrow-to-region begin end) + (goto-char (point-min)) + (yas--snippet-parse-create snippet)) + (when (listp buffer-undo-list) + (push (cons (point-min) (point-max)) + buffer-undo-list)) + + ;; Indent, collecting undo information normally. + (yas--indent snippet) + + ;; Follow up with `yas--take-care-of-redo' on the newly + ;; inserted snippet boundaries. + (when (listp buffer-undo-list) + (push `(apply yas--take-care-of-redo ,snippet) + buffer-undo-list)) + + ;; Sort and link each field + (yas--snippet-sort-fields snippet) + + ;; Create keymap overlay for snippet + (setf (yas--snippet-control-overlay snippet) + (yas--make-control-overlay snippet (point-min) (point-max))) + + ;; Move to end + (goto-char (point-max)) + + snippet)))) + + +;;; Apropos adjacencies and "fom's": +;; +;; Once the $-constructs bits like "$n" and "${:n" are deleted in the +;; recently expanded snippet, we might actually have many fields, +;; mirrors (and the snippet exit) in the very same position in the +;; buffer. Therefore we need to single-link the +;; fields-or-mirrors-or-exit (which I have abbreviated to "fom") +;; according to their original positions in the buffer. +;; +;; Then we have operation `yas--advance-end-maybe' and +;; `yas--advance-start-maybe', which conditionally push the starts and +;; ends of these foms down the chain. +;; +;; This allows for like the printf with the magic ",": +;; +;; printf ("${1:%s}\\n"${1:$(if (string-match "%" text) "," "\);")} \ +;; $2${1:$(if (string-match "%" text) "\);" "")}$0 +;; +(defun yas--fom-start (fom) + (cond ((yas--field-p fom) + (yas--field-start fom)) + ((yas--mirror-p fom) + (yas--mirror-start fom)) + (t + (yas--exit-marker fom)))) + +(defun yas--fom-end (fom) + (cond ((yas--field-p fom) + (yas--field-end fom)) + ((yas--mirror-p fom) + (yas--mirror-end fom)) + (t + (yas--exit-marker fom)))) + +(defun yas--fom-next (fom) + (cond ((yas--field-p fom) + (yas--field-next fom)) + ((yas--mirror-p fom) + (yas--mirror-next fom)) + (t + (yas--exit-next fom)))) + +(defun yas--fom-parent-field (fom) + (cond ((yas--field-p fom) + (yas--field-parent-field fom)) + ((yas--mirror-p fom) + (yas--mirror-parent-field fom)) + (t + nil))) + +(defun yas--calculate-adjacencies (snippet) + "Calculate adjacencies for fields or mirrors of SNIPPET. + +This is according to their relative positions in the buffer, and +has to be called before the $-constructs are deleted." + (let* ((fom-set-next-fom + (lambda (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))))) + (compare-fom-begs + (lambda (fom1 fom2) + (if (= (yas--fom-start fom2) (yas--fom-start fom1)) + (yas--mirror-p fom2) + (>= (yas--fom-start fom2) (yas--fom-start fom1))))) + (link-foms fom-set-next-fom)) + ;; make some yas--field, yas--mirror and yas--exit soup + (let ((soup)) + (when (yas--snippet-exit snippet) + (push (yas--snippet-exit snippet) soup)) + (dolist (field (yas--snippet-fields snippet)) + (push field soup) + (dolist (mirror (yas--field-mirrors field)) + (push mirror soup))) + (setq soup + (sort soup compare-fom-begs)) + (when soup + (cl-reduce link-foms soup))))) + +(defun yas--calculate-simple-fom-parentage (snippet fom) + "Discover if FOM is parented by some field in SNIPPET. + +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))) + (dolist (field (remq fom (yas--snippet-fields snippet))) + (when (and (<= (yas--field-start field) (yas--fom-start fom)) + (<= (yas--fom-end fom) (yas--field-end field)) + (< min (yas--field-start field)) + (< (yas--field-end field) max)) + (setq min (yas--field-start field) + max (yas--field-end field)) + (cond ((yas--field-p fom) + (setf (yas--field-parent-field fom) field)) + ((yas--mirror-p fom) + (setf (yas--mirror-parent-field fom) field)) + (t ; it's an exit, so noop + nil )))))) + +(defun yas--advance-end-maybe (fom newend) + "Maybe advance FOM's end to NEWEND if it needs it. + +If it does, also: + +* call `yas--advance-start-maybe' on FOM's next fom. + +* in case FOM is field call `yas--advance-end-maybe' on its parent + field + +Also, if FOM is an exit-marker, always call +`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) + (yas--advance-end-of-parents-maybe (yas--fom-parent-field fom) newend)) + ((yas--exit-p fom) + (yas--advance-start-maybe (yas--fom-next fom) newend)))) + +(defun yas--advance-start-maybe (fom newstart) + "Maybe advance FOM's start to NEWSTART if it needs it. + +If it does, also call `yas--advance-end-maybe' on FOM." + (when (and fom (< (yas--fom-start fom) newstart)) + (set-marker (yas--fom-start fom) newstart) + (yas--advance-end-maybe fom newstart))) + +(defun yas--advance-end-of-parents-maybe (field newend) + "Like `yas--advance-end-maybe' but for parent fields. + +Only works for fields and doesn't care about the start of the +next FOM. Works its way up recursively for parents of parents." + (when (and field + (< (yas--field-end field) newend)) + (set-marker (yas--field-end field) newend) + (yas--advance-end-of-parents-maybe (yas--field-parent-field field) newend))) + +(defvar yas--dollar-regions nil + "When expanding the snippet the \"parse-create\" functions add +cons cells to this var.") + +(defvar yas--indent-markers nil + "List of markers for manual indentation.") + +(defun yas--snippet-parse-create (snippet) + "Parse a recently inserted snippet template, creating all +necessary fields, mirrors and exit points. + +Meant to be called in a narrowed buffer, does various passes" + (let ((saved-quotes nil) + (parse-start (point))) + ;; Avoid major-mode's syntax propertizing function, since we + ;; change the syntax-table while calling `scan-sexps'. + (let ((syntax-propertize-function nil)) + (setq yas--dollar-regions nil) ; Reset the yas--dollar-regions. + (yas--protect-escapes nil '(?`)) ; Protect just the backquotes. + (goto-char parse-start) + (setq saved-quotes (yas--save-backquotes)) ; `expressions`. + (yas--protect-escapes) ; Protect escaped characters. + (goto-char parse-start) + (yas--indent-parse-create) ; Parse indent markers: `$>'. + (goto-char parse-start) + (yas--field-parse-create snippet) ; Parse fields with {}. + (goto-char parse-start) + (yas--simple-fom-create snippet) ; Parse simple mirrors & fields. + (goto-char parse-start) + (yas--transform-mirror-parse-create snippet) ; Parse mirror transforms. + ;; Invalidate any syntax-propertizing done while + ;; `syntax-propertize-function' was nil. + (syntax-ppss-flush-cache parse-start)) + ;; Set "next" links of fields & mirrors. + (yas--calculate-adjacencies snippet) + (yas--save-restriction-and-widen ; Delete $-constructs. + (yas--delete-regions yas--dollar-regions)) + ;; Make sure to do this insertion *after* deleting the dollar + ;; regions, otherwise we invalidate the calculated positions of + ;; all the fields following $0. + (let ((exit (yas--snippet-exit snippet))) + (goto-char (if exit (yas--exit-marker exit) (point-max)))) + (when (eq yas-wrap-around-region 'cua) + (setq yas-wrap-around-region ?0)) + (cond ((and yas-wrap-around-region yas-selected-text) + (insert yas-selected-text)) + ((and (characterp yas-wrap-around-region) + (get-register yas-wrap-around-region)) + (insert (prog1 (get-register yas-wrap-around-region) + (set-register yas-wrap-around-region nil))))) + (yas--restore-backquotes saved-quotes) ; Restore `expression` values. + (goto-char parse-start) + (yas--restore-escapes) ; Restore escapes. + (yas--update-mirrors snippet) ; Update mirrors for the first time. + (goto-char parse-start))) + +;; HACK: Some implementations of `indent-line-function' (called via +;; `indent-according-to-mode') delete text before they insert (like +;; cc-mode), some make complicated regexp replacements (looking at +;; you, org-mode). To find place where the marker "should" go after +;; indentation, we create a regexp based on what the line looks like +;; before, putting a capture group where the marker is. The regexp +;; matches any whitespace with [[:space:]]* to allow for the +;; indentation changing whitespace. Additionally, we try to preserve +;; the amount of whitespace *following* the marker, because +;; indentation generally affects whitespace at the beginning, not the +;; end. +;; +;; Two other cases where we apply a similar strategy: +;; +;; 1. Handling `auto-fill-mode', in this case we need to use the +;; current paragraph instead of line. +;; +;; 2. Moving snippets from an `org-src' temp buffer into the main org +;; buffer, in this case we need to count the line offsets (because org +;; may add indentation on each line making character positions +;; unreliable). +;; +;; This is all best-effort heuristic stuff, but it should cover 99% of +;; use-cases. + +(defun yas--snapshot-marker-location (marker &optional beg end) + "Returns info for restoring MARKER's location after indent. +The returned value is a list of the form (MARKER REGEXP WS-COUNT)." + (unless beg (setq beg (line-beginning-position))) + (unless end (setq end (line-end-position))) + (let ((before (split-string (buffer-substring-no-properties beg marker) + "[[:space:]\n]+" t)) + (after (split-string (buffer-substring-no-properties marker end) + "[[:space:]\n]+" t))) + (list marker + (concat "[[:space:]\n]*" + (mapconcat (lambda (s) + (if (eq s marker) "\\(\\)" + (regexp-quote s))) + (nconc before (list marker) after) + "[[:space:]\n]*")) + (progn (goto-char marker) + (skip-chars-forward "[:space:]\n" end) + (- (point) marker))))) + +(defun yas--snapshot-overlay-location (overlay beg end) + "Like `yas--snapshot-marker-location' for overlays. +The returned format is (OVERLAY (RE WS) (RE WS)). Either of +the (RE WS) lists may be nil if the start or end, respectively, +of the overlay is outside the range BEG .. END." + (let ((obeg (overlay-start overlay)) + (oend (overlay-end overlay))) + (list overlay + (when (and (<= beg obeg) (< obeg end)) + (cdr (yas--snapshot-marker-location obeg beg end))) + (when (and (<= beg oend) (< oend end)) + (cdr (yas--snapshot-marker-location oend beg end)))))) + +(defun yas--snapshot-overlay-line-location (overlay) + "Return info for restoring OVERLAY's line based location. +The returned format is (OVERLAY (LINE RE WS) (LINE RE WS))." + (let ((loc-beg (progn (goto-char (overlay-start overlay)) + (yas--snapshot-marker-location (point)))) + (loc-end (progn (goto-char (overlay-end overlay)) + (yas--snapshot-marker-location (point))))) + (setcar loc-beg (count-lines (point-min) (progn (goto-char (car loc-beg)) + (line-beginning-position)))) + (setcar loc-end (count-lines (point-min) (progn (goto-char (car loc-end)) + (line-beginning-position)))) + (list overlay loc-beg loc-end))) + +(defun yas--goto-saved-location (regexp ws-count) + "Move point to location saved by `yas--snapshot-marker-location'. +Buffer must be narrowed to BEG..END used to create the snapshot info." + (goto-char (point-min)) + (if (not (looking-at regexp)) + (lwarn '(yasnippet re-marker) :warning + "Couldn't find: %S" regexp) + (goto-char (match-beginning 1)) + (skip-chars-forward "[:space:]\n") + (skip-chars-backward "[:space:]\n" (- (point) ws-count)))) + +(defun yas--restore-marker-location (re-marker) + "Restores marker based on info from `yas--snapshot-marker-location'. +Buffer must be narrowed to BEG..END used to create the snapshot info." + (apply #'yas--goto-saved-location (cdr re-marker)) + (set-marker (car re-marker) (point))) + +(defun yas--restore-overlay-location (ov-locations) + "Restores marker based on info from `yas--snapshot-marker-location'. +Buffer must be narrowed to BEG..END used to create the snapshot info." + (cl-destructuring-bind (overlay loc-beg loc-end) ov-locations + (move-overlay overlay + (if (not loc-beg) (overlay-start overlay) + (apply #'yas--goto-saved-location loc-beg) + (point)) + (if (not loc-end) (overlay-end overlay) + (apply #'yas--goto-saved-location loc-end) + (point))))) + + +(defun yas--restore-overlay-line-location (ov-locations) + "Restores overlay based on info from `yas--snapshot-overlay-line-location'." + (save-restriction + (move-overlay (car ov-locations) + (save-excursion + (forward-line (car (nth 1 ov-locations))) + (narrow-to-region (line-beginning-position) (line-end-position)) + (apply #'yas--goto-saved-location (cdr (nth 1 ov-locations))) + (point)) + (save-excursion + (forward-line (car (nth 2 ov-locations))) + (narrow-to-region (line-beginning-position) (line-end-position)) + (apply #'yas--goto-saved-location (cdr (nth 2 ov-locations))) + (point))))) + +(defun yas--indent-region (from to snippet) + "Indent the lines between FROM and TO with `indent-according-to-mode'. +The SNIPPET's markers are preserved." + (save-excursion + (yas--save-restriction-and-widen + (let* ((snippet-markers (yas--collect-snippet-markers snippet)) + (to (set-marker (make-marker) to))) + (goto-char from) + (cl-loop for bol = (line-beginning-position) + for eol = (line-end-position) + if (or yas-also-indent-empty-lines + (/= bol eol)) + do + ;; Indent each non-empty line. + (let ((remarkers nil)) + (dolist (m snippet-markers) + (when (and (<= bol m) (<= m eol)) + (push (yas--snapshot-marker-location m bol eol) + remarkers))) + (unwind-protect + (progn (back-to-indentation) + (indent-according-to-mode)) + (save-restriction + (narrow-to-region bol (line-end-position)) + (mapc #'yas--restore-marker-location remarkers)))) + while (and (zerop (forward-line 1)) + (< (point) to))))))) + +(defvar yas--indent-original-column nil) +(defun yas--indent (snippet) + ;; Indent lines that had indent markers (`$>') on them. + (save-excursion + (dolist (marker yas--indent-markers) + (unless (eq yas-indent-line 'auto) + (goto-char marker) + (yas--indent-region (line-beginning-position) + (line-end-position) + snippet)) + ;; Finished with this marker. + (set-marker marker nil)) + (setq yas--indent-markers nil)) + ;; Now do stuff for `fixed' and `auto'. + (save-excursion + ;; We need to be at end of line, so that `forward-line' will only + ;; report 0 if it actually moves over a newline. + (end-of-line) + (cond ((eq yas-indent-line 'fixed) + (when (= (forward-line 1) 0) + (let ((indent-line-function + (lambda () + ;; We need to be at beginning of line in order to + ;; indent existing whitespace correctly. + (beginning-of-line) + (indent-to-column yas--indent-original-column)))) + (yas--indent-region (line-beginning-position) + (point-max) + snippet)))) + ((eq yas-indent-line 'auto) + (when (or yas-also-auto-indent-first-line + (= (forward-line 1) 0)) + (yas--indent-region (line-beginning-position) + (point-max) + snippet)))))) + +(defun yas--collect-snippet-markers (snippet) + "Make a list of all the markers used by SNIPPET." + (let (markers) + (yas--snippet-map-markers (lambda (m) (push m markers) m) snippet) + markers)) + +(defun yas--escape-string (escaped) + (concat "YASESCAPE" (format "%d" escaped) "PROTECTGUARD")) + +(defun yas--protect-escapes (&optional text escaped) + "Protect all escaped characters with their numeric ASCII value. + +With optional string TEXT do it in string instead of buffer." + (let ((changed-text text) + (text-provided-p text)) + (mapc #'(lambda (escaped) + (setq changed-text + (yas--replace-all (concat "\\" (char-to-string escaped)) + (yas--escape-string escaped) + (when text-provided-p changed-text)))) + (or escaped yas--escaped-characters)) + changed-text)) + +(defun yas--restore-escapes (&optional text escaped) + "Restore all escaped characters from their numeric ASCII value. + +With optional string TEXT do it in string instead of the buffer." + (let ((changed-text text) + (text-provided-p text)) + (mapc #'(lambda (escaped) + (setq changed-text + (yas--replace-all (yas--escape-string escaped) + (char-to-string escaped) + (when text-provided-p changed-text)))) + (or escaped yas--escaped-characters)) + changed-text)) + +(defun yas--save-backquotes () + "Save all \"\\=`(lisp-expression)\\=`\"-style expressions. +Return a list of (MARKER . STRING) entires for each backquoted +Lisp expression." + (let* ((saved-quotes nil) + (yas--snippet-buffer (current-buffer)) + (yas--change-detected nil) + (detect-change (lambda (_beg _end) + (when (eq (current-buffer) yas--snippet-buffer) + (setq yas--change-detected t))))) + (while (re-search-forward yas--backquote-lisp-expression-regexp nil t) + (let ((current-string (match-string-no-properties 1)) transformed) + (yas--save-restriction-and-widen + (delete-region (match-beginning 0) (match-end 0))) + (let ((before-change-functions + (cons detect-change before-change-functions))) + (setq transformed (yas--eval-for-string (yas--read-lisp + (yas--restore-escapes + current-string '(?`)))))) + (goto-char (match-beginning 0)) + (when transformed + (let ((marker (make-marker)) + (before-change-functions (cdr before-change-functions))) + (yas--save-restriction-and-widen + (insert "Y") ;; quite horrendous, I love it :) + (set-marker marker (point)) + (insert "Y")) + (push (cons marker transformed) saved-quotes))))) + (when yas--change-detected + (lwarn '(yasnippet backquote-change) :warning + "`%s' modified buffer in a backquote expression. + To hide this warning, add (yasnippet backquote-change) to `warning-suppress-types'." + (if yas--current-template + (yas--template-name yas--current-template) + "Snippet"))) + saved-quotes)) + +(defun yas--restore-backquotes (saved-quotes) + "Replace markers in SAVED-QUOTES with their values. +SAVED-QUOTES is the in format returned by `yas--save-backquotes'." + (cl-loop for (marker . string) in saved-quotes do + (save-excursion + (goto-char marker) + (yas--save-restriction-and-widen + (delete-char -1) + (insert string) + (delete-char 1)) + (set-marker marker nil)))) + +(defun yas--scan-sexps (from count) + (ignore-errors + (save-match-data ; `scan-sexps' may modify match data. + (with-syntax-table (standard-syntax-table) + (let ((parse-sexp-lookup-properties nil)) + (scan-sexps from count)))))) + +(defun yas--make-marker (pos) + "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--indent-parse-create () + "Parse the \"$>\" indentation markers just inserted." + (setq yas--indent-markers ()) + (while (search-forward "$>" nil t) + (delete-region (match-beginning 0) (match-end 0)) + ;; Mark the beginning of the line. + (push (yas--make-marker (line-beginning-position)) + yas--indent-markers)) + (setq yas--indent-markers (nreverse yas--indent-markers))) + +(defun yas--field-parse-create (snippet &optional parent-field) + "Parse most field expressions in SNIPPET, except for the simple one \"$n\". + +The following count as a field: + +* \"${n: text}\", for a numbered field with default text, as long as N is not 0; + +* \"${n: text$(expression)}, the same with a Lisp expression; + this is caught with the curiously named `yas--multi-dollar-lisp-expression-regexp' + +* the same as above but unnumbered, (no N:) and number is calculated automatically. + +When multiple expressions are found, only the last one counts." + ;; + (save-excursion + (while (re-search-forward yas--field-regexp nil t) + (let* ((real-match-end-0 (yas--scan-sexps (1+ (match-beginning 0)) 1)) + (number (and (match-string-no-properties 1) + (string-to-number (match-string-no-properties 1)))) + (brand-new-field (and real-match-end-0 + ;; break if on "$(" immediately + ;; after the ":", this will be + ;; caught as a mirror with + ;; transform later. + (not (string-match-p "\\`\\$[ \t\n]*(" + (match-string-no-properties 2))) + ;; allow ${0: some exit text} + ;; (not (and number (zerop number))) + (yas--make-field number + (yas--make-marker (match-beginning 2)) + (yas--make-marker (1- real-match-end-0)) + parent-field)))) + (when brand-new-field + (goto-char real-match-end-0) + (push (cons (1- real-match-end-0) real-match-end-0) + yas--dollar-regions) + (push (cons (match-beginning 0) (match-beginning 2)) + yas--dollar-regions) + (push brand-new-field (yas--snippet-fields snippet)) + (save-excursion + (save-restriction + (narrow-to-region (yas--field-start brand-new-field) (yas--field-end brand-new-field)) + (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 + ;; primary field transformations + ;; + (when parent-field + (save-excursion + (while (re-search-forward yas--multi-dollar-lisp-expression-regexp nil t) + (let* ((real-match-end-1 (yas--scan-sexps (match-beginning 1) 1))) + ;; commit the primary field transformation if: + ;; + ;; 1. we don't find it in yas--dollar-regions (a subnested + ;; field) might have already caught it. + ;; + ;; 2. we really make sure we have either two '$' or some + ;; text and a '$' after the colon ':'. This is a FIXME: work + ;; my regular expressions and end these ugly hacks. + ;; + (when (and real-match-end-1 + (not (member (cons (match-beginning 0) + real-match-end-1) + yas--dollar-regions)) + (not (eq ?: + (char-before (1- (match-beginning 1)))))) + (let ((lisp-expression-string (buffer-substring-no-properties (match-beginning 1) + real-match-end-1))) + (setf (yas--field-transform parent-field) + (yas--read-lisp (yas--restore-escapes lisp-expression-string)))) + (push (cons (match-beginning 0) real-match-end-1) + yas--dollar-regions))))))) + +(defun yas--transform-mirror-parse-create (snippet) + "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))) + (field (and number + (not (zerop number)) + (yas--snippet-find-field snippet number))) + (brand-new-mirror + (and real-match-end-0 + field + (yas--make-mirror (yas--make-marker (match-beginning 0)) + (yas--make-marker (match-beginning 0)) + (yas--read-lisp + (yas--restore-escapes + (buffer-substring-no-properties (match-beginning 2) + (1- real-match-end-0)))))))) + (when brand-new-mirror + (push brand-new-mirror + (yas--field-mirrors field)) + (yas--calculate-simple-fom-parentage snippet brand-new-mirror) + (push (cons (match-beginning 0) real-match-end-0) yas--dollar-regions))))) + +(defun yas--simple-fom-create (snippet) + "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) + (setf (yas--snippet-exit snippet) + (yas--make-exit (yas--make-marker (match-end 0)))) + (push (cons (match-beginning 0) (yas--exit-marker (yas--snippet-exit snippet))) + yas--dollar-regions)) + (t + (let ((field (yas--snippet-find-field snippet number)) + (fom)) + (if field + (push + (setq fom (yas--make-mirror + (yas--make-marker (match-beginning 0)) + (yas--make-marker (match-beginning 0)) + nil)) + (yas--field-mirrors field)) + (push + (setq fom (yas--make-field number + (yas--make-marker (match-beginning 0)) + (yas--make-marker (match-beginning 0)) + nil)) + (yas--snippet-fields snippet))) + (yas--calculate-simple-fom-parentage snippet fom)) + (push (cons (match-beginning 0) (match-end 0)) + yas--dollar-regions)))))) + +(defun yas--delete-regions (regions) + "Sort disjuct REGIONS by start point, then delete from the back." + (mapc #'(lambda (reg) + (delete-region (car reg) (cdr reg))) + (sort regions + #'(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+ (cl-reduce + #'max parents-mirrors + :key (lambda (m) + (yas--calculate-mirror-depth + m (cons mirror traversed)))))) + (parent 1) + (t 0)))))) + +(defun yas--update-mirrors (snippet) + "Update all the mirrors of SNIPPET." + (yas--save-restriction-and-widen + (save-excursion + (cl-loop + for (field . mirror) + in (cl-sort + ;; Make a list of (FIELD . MIRROR). + (cl-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 also handles the case where a mirror in + ;; a field causes another mirror to need reupdating. + #'> :key (lambda (fm) (yas--calculate-mirror-depth (cdr fm)))) + ;; Before updating a mirror with a parent-field, maybe advance + ;; its start (#290). + do (let ((parent-field (yas--mirror-parent-field mirror))) + (when parent-field + (yas--advance-start-maybe mirror (yas--fom-start parent-field)))) + ;; Update this mirror. + do (yas--mirror-update-display mirror field) + ;; Delay indenting until we're done all mirrors. We must do + ;; this to avoid losing whitespace between fields that are + ;; still empty (i.e., they will be non-empty after updating). + when (eq yas-indent-line 'auto) + collect (cons (yas--mirror-start mirror) (yas--mirror-end mirror)) + into indent-regions + ;; `yas--place-overlays' is needed since the active field and + ;; protected overlays might have been changed because of insertions + ;; in `yas--mirror-update-display'. + do (let ((active-field (yas--snippet-active-field snippet))) + (when active-field (yas--place-overlays snippet active-field))) + finally do + (let ((yas--inhibit-overlay-hooks t)) + (cl-loop for (beg . end) in (cl-sort indent-regions #'< :key #'car) + do (yas--indent-region beg end snippet))))))) + +(defun yas--mirror-update-display (mirror field) + "Update MIRROR according to FIELD (and mirror transform)." + + (let* ((mirror-parent-field (yas--mirror-parent-field mirror)) + (reflection (and (not (and mirror-parent-field + (yas--field-modified-p mirror-parent-field))) + (or (yas--apply-transform mirror field 'empty-on-nil) + (yas--field-text-for-display field))))) + (when (and reflection + (not (string= reflection (buffer-substring-no-properties (yas--mirror-start mirror) + (yas--mirror-end mirror))))) + (goto-char (yas--mirror-start mirror)) + (let ((yas--inhibit-overlay-hooks t)) + (insert reflection)) + (if (> (yas--mirror-end mirror) (point)) + (delete-region (point) (yas--mirror-end mirror)) + (set-marker (yas--mirror-end mirror) (point)) + (yas--advance-start-maybe (yas--mirror-next mirror) (point)) + ;; super-special advance + (yas--advance-end-of-parents-maybe mirror-parent-field (point)))))) + +(defun yas--field-update-display (field) + "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)))) + (when (and transformed + (not (string= transformed (buffer-substring-no-properties (yas--field-start field) + (yas--field-end field))))) + (setf (yas--field-modified-p field) t) + (goto-char (yas--field-start field)) + (let ((yas--inhibit-overlay-hooks t)) + (insert transformed) + (if (> (yas--field-end field) (point)) + (delete-region (point) (yas--field-end field)) + (set-marker (yas--field-end field) (point)) + (yas--advance-start-maybe (yas--field-next field) (point))) + t))))) + + +;;; Post-command hook: +;; +(defun yas--post-command-handler () + "Handles various yasnippet conditions after each command." + (when (and yas--watch-auto-fill-backtrace + (fboundp 'backtrace--print-frame) + (null yas--original-auto-fill-function) + (eq auto-fill-function 'yas--auto-fill)) + (lwarn '(yasnippet auto-fill bug) :error + "`yas--original-auto-fill-function' unexpectedly nil! Please report this backtrace\n%S" + (with-output-to-string + (mapc #'backtrace--print-frame + yas--watch-auto-fill-backtrace))) + ;; Don't pop up more than once in a session (still log though). + (defvar warning-suppress-types) ; `warnings' is autoloaded by `lwarn'. + (add-to-list 'warning-suppress-types '(yasnippet auto-fill bug))) + (condition-case err + (progn (yas--finish-moving-snippets) + (cond ((eq 'undo this-command) + ;; + ;; After undo revival the correct field is sometimes not + ;; restored correctly, this condition handles that + ;; + (let* ((snippet (car (yas-active-snippets))) + (target-field + (and snippet + (cl-find-if-not + (lambda (field) + (yas--field-probably-deleted-p snippet field)) + (remq nil + (cons (yas--snippet-active-field snippet) + (yas--snippet-fields snippet))))))) + (when target-field + (yas--move-to-field snippet target-field)))) + ((not (yas--undo-in-progress)) + ;; When not in an undo, check if we must commit the snippet + ;; (user exited it). + (yas--check-commit-snippet)))) + ((debug error) (signal (car err) (cdr err))))) + +;;; Fancy docs: +;; +;; The docstrings for some functions are generated dynamically +;; depending on the context. +;; +(put 'yas-expand 'function-documentation + '(yas--expand-from-trigger-key-doc t)) +(defun yas--expand-from-trigger-key-doc (context) + "A doc synthesizer for `yas--expand-from-trigger-key-doc'." + (let* ((yas-fallback-behavior (and context yas-fallback-behavior)) + (fallback-description + (cond ((eq yas-fallback-behavior 'call-other-command) + (let* ((fallback (yas--keybinding-beyond-yasnippet))) + (or (and fallback + (format "call command `%s'." + (pp-to-string fallback))) + "do nothing (`yas-expand' doesn't override\nanything)."))) + ((eq yas-fallback-behavior 'return-nil) + "do nothing.") + (t "defer to `yas-fallback-behavior' (which see).")))) + (concat "Expand a snippet before point. If no snippet +expansion is possible, " + fallback-description + "\n\nOptional argument FIELD is for non-interactive use and is an +object satisfying `yas--field-p' to restrict the expansion to."))) + +(put 'yas-expand-from-keymap 'function-documentation + '(yas--expand-from-keymap-doc t)) +(defun yas--expand-from-keymap-doc (context) + "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 (and context (eq this-command 'describe-key)) + (let* ((vec (this-single-command-keys)) + (templates (cl-mapcan (lambda (table) + (yas--fetch table vec)) + (yas--get-snippet-tables))) + (yas--direct-keymaps nil) + (fallback (key-binding vec))) + (concat "In this case, " + (when templates + (concat "these snippets are bound to this key:\n" + (yas--template-pretty-list templates) + "\n\nIf none of these expands, ")) + (or (and fallback + (format "fallback `%s' will be called." (pp-to-string fallback))) + "no fallback keybinding is called.")))))) + +(defun yas--template-pretty-list (templates) + (let ((acc) + (yas-buffer-local-condition 'always)) + (dolist (plate templates) + (setq acc (concat acc "\n*) " + (propertize (concat "\\\\snippet `" (car plate) "'") + 'yasnippet (cdr plate))))) + acc)) + +(define-button-type 'help-snippet-def + :supertype 'help-xref + 'help-function (lambda (template) (yas--visit-snippet-file-1 template)) + 'help-echo (purecopy "mouse-2, RET: find snippets's definition")) + +(defun yas--snippet-description-finish-runonce () + "Final adjustments for the help buffer when snippets are concerned." + (yas--create-snippet-xrefs) + (remove-hook 'temp-buffer-show-hook + #'yas--snippet-description-finish-runonce)) + +(defun yas--create-snippet-xrefs () + (save-excursion + (goto-char (point-min)) + (while (search-forward-regexp "\\\\\\\\snippet[ \s\t]+`\\([^']+\\)'" nil t) + (let ((template (get-text-property (match-beginning 1) + 'yasnippet))) + (when template + (help-xref-button 1 'help-snippet-def template) + (delete-region (match-end 1) (match-end 0)) + (delete-region (match-beginning 0) (match-beginning 1))))))) + +;;; Utils + +(defvar yas-verbosity 3 + "Log level for `yas--message' 4 means trace most anything, 0 means nothing.") + +(defun yas--message (level message &rest args) + "When LEVEL is at or below `yas-verbosity', log MESSAGE and ARGS." + (when (>= yas-verbosity level) + (message "%s" (apply #'yas--format message args)))) + +(defun yas--warning (format-control &rest format-args) + (let ((msg (apply #'format format-control format-args))) + (display-warning 'yasnippet msg :warning) + (yas--message 1 msg))) + +(defun yas--format (format-control &rest format-args) + (apply #'format (concat "[yas] " format-control) format-args)) + + +;;; Some hacks: +;; +;; The functions +;; +;; `locate-dominating-file' +;; `region-active-p' +;; +;; added for compatibility in emacsen < 23 +(unless (>= emacs-major-version 23) + (unless (fboundp 'region-active-p) + (defun region-active-p () (and transient-mark-mode mark-active))) + + (unless (fboundp 'locate-dominating-file) + (defvar locate-dominating-stop-dir-regexp + "\\`\\(?:[\\/][\\/][^\\/]+[\\/]\\|/\\(?:net\\|afs\\|\\.\\.\\.\\)/\\)\\'" + "Regexp of directory names which stop the search in `locate-dominating-file'. +Any directory whose name matches this regexp will be treated like +a kind of root directory by `locate-dominating-file' which will stop its search +when it bumps into it. +The default regexp prevents fruitless and time-consuming attempts to find +special files in directories in which filenames are interpreted as hostnames, +or mount points potentially requiring authentication as a different user.") + + (defun locate-dominating-file (file name) + "Look up the directory hierarchy from FILE for a file named NAME. +Stop at the first parent directory containing a file NAME, +and return the directory. Return nil if not found." + ;; We used to use the above locate-dominating-files code, but the + ;; directory-files call is very costly, so we're much better off doing + ;; multiple calls using the code in here. + ;; + ;; Represent /home/luser/foo as ~/foo so that we don't try to look for + ;; `name' in /home or in /. + (setq file (abbreviate-file-name file)) + (let ((root nil) + try) + (while (not (or root + (null file) + ;; FIXME: Disabled this heuristic because it is sometimes + ;; inappropriate. + ;; As a heuristic, we stop looking up the hierarchy of + ;; directories as soon as we find a directory belonging + ;; to another user. This should save us from looking in + ;; things like /net and /afs. This assumes that all the + ;; files inside a project belong to the same user. + ;; (let ((prev-user user)) + ;; (setq user (nth 2 (file-attributes file))) + ;; (and prev-user (not (equal user prev-user)))) + (string-match locate-dominating-stop-dir-regexp file))) + (setq try (file-exists-p (expand-file-name name file))) + (cond (try (setq root file)) + ((equal file (setq file (file-name-directory + (directory-file-name file)))) + (setq file nil)))) + root)))) + +;;; Unloading + +(defvar unload-function-defs-list) ; loadhist.el + +(defun yasnippet-unload-function () + "Disable minor modes when calling `unload-feature'." + ;; Disable `yas-minor-mode' everywhere it's enabled. + (yas-global-mode -1) + (save-current-buffer + (dolist (buffer (buffer-list)) + (set-buffer buffer) + (when yas-minor-mode + (yas-minor-mode -1)))) + ;; Remove symbol properties of all our functions, this avoids + ;; Bug#25088 in Emacs 25.1, where the compiler macro on + ;; `cl-defstruct' created functions hang around in the symbol plist + ;; and cause errors when loading again (we don't *need* to clean + ;; *all* symbol plists, but it's easier than being precise). + (dolist (def unload-function-defs-list) + (when (eq (car-safe def) 'defun) + (setplist (cdr def) nil))) + ;; Return nil so that `unload-feature' will take of undefining + ;; functions, and changing any buffers using `snippet-mode'. + nil) + + +;;; Backward compatibility to yasnippet <= 0.7 + +(defun yas-initialize () + "For backward compatibility, enable `yas-minor-mode' globally." + (declare (obsolete "Use (yas-global-mode 1) instead." "0.8")) + (yas-global-mode 1)) + +(defvar yas--backported-syms '(;; `defcustom's + ;; + yas-snippet-dirs + yas-prompt-functions + yas-indent-line + yas-also-auto-indent-first-line + yas-snippet-revival + yas-triggers-in-field + yas-fallback-behavior + yas-choose-keys-first + yas-choose-tables-first + yas-use-menu + yas-trigger-symbol + yas-wrap-around-region + yas-good-grace + yas-visit-from-menu + yas-expand-only-for-last-commands + yas-field-highlight-face + + ;; these vars can be customized as well + ;; + yas-keymap + yas-verbosity + yas-extra-modes + yas-key-syntaxes + yas-after-exit-snippet-hook + yas-before-expand-snippet-hook + yas-buffer-local-condition + yas-dont-activate + + ;; prompting functions + ;; + yas-x-prompt + yas-ido-prompt + yas-no-prompt + yas-completing-prompt + yas-dropdown-prompt + + ;; interactive functions + ;; + yas-expand + yas-minor-mode + yas-global-mode + yas-direct-keymaps-reload + yas-minor-mode-on + yas-load-directory + yas-reload-all + yas-compile-directory + yas-recompile-all + yas-about + yas-expand-from-trigger-key + yas-expand-from-keymap + yas-insert-snippet + yas-visit-snippet-file + yas-new-snippet + yas-load-snippet-buffer + yas-tryout-snippet + yas-describe-tables + yas-next-field-or-maybe-expand + yas-next-field + yas-prev-field + yas-abort-snippet + yas-exit-snippet + yas-exit-all-snippets + yas-skip-and-clear-or-delete-char + yas-initialize + + ;; symbols that I "exported" for use + ;; in snippets and hookage + ;; + yas-expand-snippet + yas-define-snippets + yas-define-menu + yas-snippet-beg + yas-snippet-end + yas-modified-p + yas-moving-away-p + yas-substr + yas-choose-value + yas-key-to-value + yas-throw + yas-verify-value + yas-field-value + yas-text + yas-selected-text + yas-default-from-field + yas-inside-string + yas-unimplemented + yas-define-condition-cache + yas-hippie-try-expand + + ;; debug definitions + ;; yas-debug-snippet-vars + ;; yas-exterminate-package + ;; yas-debug-test + + ;; testing definitions + ;; yas-should-expand + ;; yas-should-not-expand + ;; yas-mock-insert + ;; yas-make-file-or-dirs + ;; yas-variables + ;; yas-saving-variables + ;; yas-call-with-snippet-dirs + ;; yas-with-snippet-dirs +) + "Backported yasnippet symbols. + +They are mapped to \"yas/*\" variants.") + +(when yas-alias-to-yas/prefix-p + (dolist (sym yas--backported-syms) + (let ((backported (intern (replace-regexp-in-string "\\`yas-" "yas/" (symbol-name sym))))) + (when (boundp sym) + (make-obsolete-variable backported sym "yasnippet 0.8") + (defvaralias backported sym)) + (when (fboundp sym) + (make-obsolete backported sym "yasnippet 0.8") + (defalias backported sym)))) + (make-obsolete 'yas/root-directory 'yas-snippet-dirs "yasnippet 0.8") + (defvaralias 'yas/root-directory 'yas-snippet-dirs)) + +(defvar yas--exported-syms + (let (exported) + (mapatoms (lambda (atom) + (if (and (or (and (boundp atom) + (not (get atom 'byte-obsolete-variable))) + (and (fboundp atom) + (not (get atom 'byte-obsolete-info)))) + (string-match-p "\\`yas-[^-]" (symbol-name atom))) + (push atom exported)))) + exported) + "Exported yasnippet symbols. + +i.e. the ones with \"yas-\" single dash prefix. I will try to +keep them in future yasnippet versions and other elisp libraries +can more or less safely rely upon them.") + + +(provide 'yasnippet) +;; Local Variables: +;; coding: utf-8 +;; indent-tabs-mode: nil +;; End: +;;; yasnippet.el ends here -- cgit v1.2.3 From daf3cfca2c4421577c89cd3ba3774f6e8d2ba395 Mon Sep 17 00:00:00 2001 From: Nicholas D Steeves Date: Wed, 1 Aug 2018 23:10:25 -0300 Subject: Import yasnippet_0.13.0-2.debian.tar.xz [dgit import tarball yasnippet 0.13.0-2 yasnippet_0.13.0-2.debian.tar.xz] --- changelog | 183 ++++++++++++++++++++++++++++++++++++++++++++++++ clean | 2 + compat | 1 + control | 54 ++++++++++++++ copyright | 33 +++++++++ elpa-test | 2 + elpa-yasnippet.doc-base | 11 +++ elpa-yasnippet.elpa | 1 + elpa-yasnippet.install | 1 + rules | 7 ++ source/format | 1 + watch | 2 + yasnippet.maintscript | 1 + 13 files changed, 299 insertions(+) create mode 100644 changelog create mode 100644 clean create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 elpa-test create mode 100644 elpa-yasnippet.doc-base create mode 100644 elpa-yasnippet.elpa create mode 100644 elpa-yasnippet.install create mode 100755 rules create mode 100644 source/format create mode 100644 watch create mode 100644 yasnippet.maintscript diff --git a/changelog b/changelog new file mode 100644 index 0000000..b686761 --- /dev/null +++ b/changelog @@ -0,0 +1,183 @@ +yasnippet (0.13.0-2) unstable; urgency=medium + + * Team upload. + * Depend on elpa-htmlize instead of emacs-goodies-el. (Closes: #898579) + * Lowercase short descriptions + * Declare Standards-Version: 4.1.5. (No additional changes needed) + * debian/rules: Load htmlize before attempting to build docs. + - Needed to fontify src blocks. + * Restore build-dep on yasnippet-snippets for self-tests. + - Solves "Prepared just-in-time loading of snippets + (but no snippets found)" + - While it is not needed for tests to pass, elpa-yasnippet-snippets + enhances the quality of those tests. + * debian/control: Add minor enhancements to the comments. + + -- Nicholas D Steeves Wed, 01 Aug 2018 22:10:25 -0400 + +yasnippet (0.13.0-1) unstable; urgency=medium + + * Team upload. + * New upstream release. + * debian/control: + - Recommend elpa-yasnippet-snippets rather than yasnippet-snippets. + - Drop yasnippet-snippets from Build-Depends; it does not seem to be + needed for self-tests to pass. + - Switch Vcs from alioth to salsa. + - Switch to debhelper 11. + - Update Maintainer team name and email address. + - Drop emacs24 from Enhances. + * Declare compat level 11. + * Drop 0003-Debian-yas-installed-snippets-dir.patch + - Uses obsolete yas-installed-snippets-dir. + - Now elpa-yasnippets-snippets defines yasnippet-snippets-dir. + * Declare Standards-Version: 4.1.4. + - debian/control: Drop Built-Using because §7.8 says it should be used + "exactly when there are license or DFSG requirements to retain full + source code", and such requirements are not present in yasnippet. + + -- Nicholas D Steeves Sun, 13 May 2018 15:22:01 -0400 + +yasnippet (0.12.2-2) unstable; urgency=medium + + * Remove obsolete conffile /etc/emacs/site-start.d/50yasnippet.el + (Closes: #884621). + Thanks H.-Dirk Schmitt for noting the problem. + * Priority extra->optional. + * Declare compliance with Debian Policy 4.1.2. + + -- Sean Whitton Fri, 22 Dec 2017 10:07:25 +0000 + +yasnippet (0.12.2-1) unstable; urgency=medium + + * New upstream release. + * Tidy Copyright: fields & bump copyright years. + * Add a build-conflict with elpa-org. + See https://github.com/joaotavora/yasnippet/issues/852 + * Bump std-ver to 4.1.0 (no changes required). + + -- Sean Whitton Sat, 02 Sep 2017 13:21:36 -0700 + +yasnippet (0.12.1-1) unstable; urgency=medium + + * New upstream release (Closes: #771159). + * Drop 0001-typos-and-grammar.patch + Merged upstream. + * Drop 0002-Avoiding-having-git-as-a-building-dependency.patch + Obsoleted by upstream's switch to use SOURCE_DATE_EPOCH. + * Refresh 0003-Debian-yas-installed-snippets-dir.patch + * Drop 0004-no-timestamp-in-html-head.patch + Merged upstream. + * Bump standards version to 4.0.0 (no changes required). + + -- Sean Whitton Mon, 24 Jul 2017 15:18:42 -0700 + +yasnippet (0.11.0-2) unstable; urgency=medium + + * Add missing Vcs-* fields. + * Add 0004-no-timestamp-in-html-head.patch for a reproducible build. + + -- Sean Whitton Fri, 09 Dec 2016 21:38:44 -0700 + +yasnippet (0.11.0-1) unstable; urgency=medium + + [ Sean Whitton ] + * New upstream release (Closes: #845061). + * Adopt package on behalf of pkg-emacsen team. + This has been approved by the de facto maintainer, Barak A. Pearlmutter. + - Replace Julián Hernández Gómez with pkg-emacsen team as Maintainer. + - Add myself as an uploader. + - Add myself to d/copyright file for debian/. + - Update copyright years for Barak A. Pearlmutter. + * Convert package to use dh_elpa (Closes: #671584, #818440). + - Build 'elpa-yasnippet' binary package. + - 'yasnippet' now a transition binary package. + - Rewrite d/rules. + - Add d/elpa-yasnippet.elpa & d/elpa-test control files. + - Replace build dependency on Emacs with build dependency on dh-elpa. + - Add build dependency on yasnippet-snippets for test suite. + - Drop debian/emacsen-*. + - Update doc-base registration. + * Add 0003-Debian-yas-installed-snippets-dir.patch (Closes: #818699). + * Add missing build dependency on emacs-goodies-el. + The documentation build process can optionally use htmlize.el. The + version of htmlize.el present in emacs-goodies-el is currently too old + for this to work, but adding the build dependency now will make this + work as soon as emacs-goodies-el is updated. + * Bump debhelper compat & dependency to 10. + * Update homepage field. + * Add Testsuite: field. + * Fix typo in package description. + * Add debian/clean. + * Bump standards version to 3.9.8 (no changes required). + * Run wrap-and-sort -abst. + + [ Barak A. Pearlmutter ] + * Drop 0001-Deal-with-the-invalid-function-quote-error-when-gene.patch + Merged upstream. + * Refresh 0002-Avoiding-having-git-as-a-building-dependency.patch + * Add 0001-typos-and-grammar.patch + * Swizzle debian/watch to github.com/joaotavora/yasnippet/tags + + -- Barak A. Pearlmutter Mon, 28 Nov 2016 14:19:31 +0000 + +yasnippet (0.9.0~beta1-5) unstable; urgency=medium + + * Fail to fail when files are overwritten during upgrade (closes: #799667) + + -- Barak A. Pearlmutter Sat, 03 Oct 2015 12:08:48 +0100 + +yasnippet (0.9.0~beta1-4) unstable; urgency=medium + + * Do not try to install with emacs23 (Closes: #787613) + + -- Barak A. Pearlmutter Sun, 14 Jun 2015 22:48:34 +0100 + +yasnippet (0.9.0~beta1-3) unstable; urgency=medium + + * Switch to architecture: all to avoid per-architecture binaries. + + -- Barak A. Pearlmutter Wed, 27 May 2015 09:23:15 +0100 + +yasnippet (0.9.0~beta1-2) unstable; urgency=medium + + [ Alberto Luaces ] + * Fix org-mode-based documentation generation when running with sbuild. + The system was trying to access unexistent $HOME directory. + + [ Barak A. Pearlmutter ] + * Merge additional clauses to prev stanza from Alberto. + * Close excremental bug fixed upstream, in prev stanza. + + -- Barak A. Pearlmutter Wed, 20 May 2015 11:34:25 +0100 + +yasnippet (0.9.0~beta1-1) unstable; urgency=medium + + * New upstream release, with shit missing (Closes: #592645) + * Modernize debian packaging scripts + * Updated watch file (Closes: #691385). + * Replaced load-path with debian-pkg-add-load-path-item (Closes: + #671561). + * This module is byte-compiled now (Closes: #672196). + + -- Barak A. Pearlmutter Thu, 14 May 2015 21:20:23 +0100 + +yasnippet (0.6.1c-1) unstable; urgency=low + + * New upstream release + * Bump Standards-Version from 3.8.0 to 3.8.4 (no changes needed). + * Switch to dpkg-source 3.0 (quilt) format + * Changed Section to lisp to match override + * Depend on emacs instead of emacs22 + * Remove python-docutils and python-pygments from build-deps + * Updated Copyright information + * Added doc-base control file + * Added watch file + + -- Julián Hernández Gómez Fri, 26 Feb 2010 10:34:33 +0800 + +yasnippet (0.5.10-1) unstable; urgency=low + + * Initial release. (Closes: #517948, #484465) + + -- Julián Hernández Gómez Fri, 27 Feb 2009 13:47:00 +0800 diff --git a/clean b/clean new file mode 100644 index 0000000..78e2dfd --- /dev/null +++ b/clean @@ -0,0 +1,2 @@ +.org-timestamps/ +doc/*.html diff --git a/compat b/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +11 diff --git a/control b/control new file mode 100644 index 0000000..c7dca53 --- /dev/null +++ b/control @@ -0,0 +1,54 @@ +Source: yasnippet +Section: lisp +Priority: optional +Maintainer: Debian Emacsen team +Uploaders: + Barak A. Pearlmutter , + Alberto Luaces Fernández , + Sean Whitton , +Build-Depends: + debhelper (>= 11~), + dh-elpa, + elpa-htmlize, + elpa-yasnippet-snippets +# See changelog entry 0.13.0-2 for why we build-depend on snippets. +# For an explanation of the build conflict on elpa-org, +# see https://github.com/joaotavora/yasnippet/issues/852 +Build-Conflicts: elpa-org +Standards-Version: 4.1.5 +Homepage: https://github.com/joaotavora/yasnippet +Testsuite: autopkgtest-pkg-elpa +Vcs-Browser: https://salsa.debian.org/emacsen-team/yasnippet +Vcs-Git: https://salsa.debian.org/emacsen-team/yasnippet.git + +Package: elpa-yasnippet +Architecture: all +Depends: + ${elpa:Depends}, + ${misc:Depends}, +Recommends: + elpa-yasnippet-snippets, +Enhances: + emacs, + emacs25, +Breaks: + yasnippet (<< 0.11.0), +Provides: + yasnippet, +Description: template system for Emacs + YASnippet (yet another snippet extension for Emacs) is a template + system for Emacs. It allows you to type an abbreviation and + automatically expand the abbreviation into function templates. + . + Bundled language templates includes: C, C++, C#, Perl, Python, Ruby, + SQL, LaTeX, HTML, CSS and more. + +Package: yasnippet +Architecture: all +Depends: + elpa-yasnippet, + ${misc:Depends}, +Description: transition Package, yasnippet to elpa-yasnippet + The yasnippet emacs addon has been elpafied. This dummy package + helps ease transition from yasnippet to elpa-yasnippet, and may + safely be removed. diff --git a/copyright b/copyright new file mode 100644 index 0000000..a91fb79 --- /dev/null +++ b/copyright @@ -0,0 +1,33 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: yasnippet +Source: https://github.com/capitaomorte/yasnippet + +Files: * +License: GPL-3+ +Copyright: (C) 2008-2017 Free Software Foundation, Inc. + +Files: debian/* +License: GPL-3+ +Copyright: 2009 Julián Hernández Gómez + 2015 Alberto Luaces , Barak A. Pearlmutter + 2016 Sean Whitton , Barak A. Pearlmutter + 2017 Sean Whitton + +License: GPL-3+ + . + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + . + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL-3'. diff --git a/elpa-test b/elpa-test new file mode 100644 index 0000000..78c204c --- /dev/null +++ b/elpa-test @@ -0,0 +1,2 @@ +# `yas--load-snippet-dirs' will try to create ~/.emacs.d/snippets +ert_eval = (setq user-emacs-directory (make-temp-file "yasnippet-test" 'dir-flag)) diff --git a/elpa-yasnippet.doc-base b/elpa-yasnippet.doc-base new file mode 100644 index 0000000..c35b315 --- /dev/null +++ b/elpa-yasnippet.doc-base @@ -0,0 +1,11 @@ +Document: yasnippet +Title: Yasnippet Tutorial and Howto +Author: pluskid +Abstract: This manual describes what yasnippet is + and how it can be used to automatically expand an + abbreviation into function templates on Emacs. +Section: Help + +Format: HTML +Index: /usr/share/doc/elpa-yasnippet/html/index.html +Files: /usr/share/doc/elpa-yasnippet/html/*.html diff --git a/elpa-yasnippet.elpa b/elpa-yasnippet.elpa new file mode 100644 index 0000000..7fac1a8 --- /dev/null +++ b/elpa-yasnippet.elpa @@ -0,0 +1 @@ +yasnippet.el diff --git a/elpa-yasnippet.install b/elpa-yasnippet.install new file mode 100644 index 0000000..c60eb56 --- /dev/null +++ b/elpa-yasnippet.install @@ -0,0 +1 @@ +doc/*.html doc/images doc/stylesheets usr/share/doc/elpa-yasnippet/html/ diff --git a/rules b/rules new file mode 100755 index 0000000..01eeea8 --- /dev/null +++ b/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f + +%: + dh $@ --with elpa + +override_dh_auto_build: + HOME=$$(pwd) emacs -Q -L /usr/share/emacs/site-lisp/elpa-src/htmlize-* -L . --batch -l htmlize -l doc/yas-doc-helper.el -f yas--generate-html-batch diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 0000000..c84fd92 --- /dev/null +++ b/watch @@ -0,0 +1,2 @@ +version=3 +https://github.com/joaotavora/yasnippet/tags (?:.*/)?v?(\d[\d\.]*)\.tar\.gz diff --git a/yasnippet.maintscript b/yasnippet.maintscript new file mode 100644 index 0000000..55a6bde --- /dev/null +++ b/yasnippet.maintscript @@ -0,0 +1 @@ +rm_conffile /etc/emacs/site-start.d/50yasnippet.el -- cgit v1.2.3