diff --git a/Rakefile b/Rakefile index 54015a8..04cb216 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,7 @@ require 'fileutils' def find_version - File.read("yasnippet.el") =~ /;; Version: *([0-9.]+) *$/ + File.read("yasnippet.el") =~ /;; Package-version: *([0-9.]+[a-z]?) *$/ $version = $1 end find_version @@ -19,7 +19,7 @@ desc "create a release package" task :package do release_dir = "pkg/yasnippet-#{$version}" FileUtils.mkdir_p(release_dir) - files = ['snippets', 'yasnippet.el'] + files = ['snippets', 'yasnippet.el', 'dropdown-list.el'] FileUtils.cp_r files, release_dir FileUtils.rm_r Dir[release_dir + "/**/.svn"] FileUtils.cd 'pkg' diff --git a/doc/changelog.html b/doc/changelog.html index ab1b98b..8dec07f 100644 --- a/doc/changelog.html +++ b/doc/changelog.html @@ -40,7 +40,30 @@
-
+
+

0.6.0b / 2009-07-2x

+
    +
  • Nested placeholders of the type <div${1: id="${2:someid}"}> $0.
  • +
  • More robust undo/redo support.
  • +
  • Stacked snippet expansion (snippet in snippet).
  • +
  • Transformation on a primary field with syntax ${1:default$(transform)}
  • +
  • Validations on field exit through the yas/verify-value +primary field transformation.
  • +
  • Wrapping the region in the exit marker $0 of the snippet. Use +yas/wrap-around-region.
  • +
  • Auto-indentation. Use yas/indent-line set to 'auto
  • +
  • Easier definition of snippets. Use yas/find-snippets or +yas/visit-snippet-file. In the new snippet-mode use +yas/load-snippet-buffer and yas/tryout-snippet.
  • +
  • Customization group yasnippet.
  • +
  • Overriding customization variables in snippets. Use the env: +let-form template keyword.
  • +
  • Fixed Issue 60
  • +
  • Fixed Issue 65
  • +
  • Fixed Issue 56
  • +
+
+

0.5.10 / 2009-02-11

  • Added grouping support so that the snippets in the menu can be diff --git a/doc/changelog.rst b/doc/changelog.rst index 3e956c8..5a762a3 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,6 +6,42 @@ ChangeLog :Contact: pluskid@gmail.com :Date: 2008-03-22 + +0.6.0b / 2009-07-25 +=================== + +* Nested placeholders of the type `` $0``. + +* More robust undo/redo support. + +* Stacked snippet expansion (*snippet in snippet*). + +* Transformation on a primary field with syntax ``${1:default$(transform)}`` + +* Validations on field exit through the ``yas/verify-value`` + primary field transformation. + +* Wrapping the region in the exit marker ``$0`` of the snippet. Use + ``yas/wrap-around-region``. + +* Auto-indentation. Use ``yas/indent-line`` set to ``'auto`` + +* Easier definition of snippets. Use ``yas/find-snippets`` or + ``yas/visit-snippet-file``. In the new ``snippet-mode`` use + ``yas/load-snippet-buffer`` and ``yas/tryout-snippet``. + +* Customization group ``yasnippet``. + +* Overriding customization variables in snippets. Use the ``env: + let-form`` template keyword. + +* Fixed `Issue 60 + `_ +* Fixed `Issue 65 + `_ +* Fixed `Issue 56 + `_ + 0.5.10 / 2009-02-11 =================== diff --git a/doc/define_snippet.html b/doc/define_snippet.html index 64db46b..d4fa347 100644 --- a/doc/define_snippet.html +++ b/doc/define_snippet.html @@ -5,8 +5,8 @@ How to define a snippet ? - - + + @@ -43,47 +43,53 @@

    Contents

    +
    +

    Quickly finding/defining snippets

    +

    From version 0.6 upwards there are two ways you can quickly find a +snippet file. Once you find this file it will be set to +snippet-mode (see ahead)

    +
      +
    • M-x yas/find-snippets

      +

      Lets you find the snippet file in the directory the snippet was +loaded from (if it exists) like find-file-other-window.

      +
    • +
    • 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.

      +
    • +
    +
    +
    +

    Using the snippet-mode major mode

    +

    From version 0.6 upwards 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.

    +

    Two commands are defined in this mode:

    +
      +
    • M-x yas/load-snippet-buffer

      +
      +

      When editing a snippet, this loads the snippet into the correct +mode and menu. Bound to C-c C-c by default while in +snippet-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.

      +
      +
    • +
    +

    There are also snippets for making snippets: vars, field and +mirror.

    +
    -

    Define snippets using elisp code

    +

    Define snippets using elisp code

    As I mentioned above, you can define snippets directly by writing elisp code.

    -

    yas/define-snippets

    +

    yas/define-snippets

    The basic syntax of yas/define-snippets is

    (yas/define-snippets MODE SNIPPETS &optional PARENT)
     
    @@ -298,14 +347,14 @@ want to provide one. Here's an example:

    The example above is auto-generated code by yas/compile-bundle.

    -

    yas/compile-bundle

    +

    yas/compile-bundle

    yas/compile-bundle can be used to parse the snippets from a directory hierarchy and translate them into the elisp form. The translated code is faster to load. Further more, the generated bundle is a stand-alone file not depending on yasnippet.el. The released bundles of YASnippet are all generated this way.

    The basic syntax of yas/compile-bundle is

    -
    (yas/compile-bundle &optional yasnippet yasnippet-bundle snippet-roots code)
    +
    (yas/compile-bundle &optional yasnippet yasnippet-bundle snippet-roots code dropdown)
     

    As you can see, all the parameters are optional. The default values for those parameters are convenient for me to produce the default @@ -313,7 +362,8 @@ release bundle:

    (yas/compile-bundle "yasnippet.el"
                         "./yasnippet-bundle.el"
                         '("snippets")
    -                    "(yas/initialize)")
    +                    "(yas/initialize)"
    +                    "dropdown-list.el")
     

    The snippet-roots can be a list of root directories. This is useful when you have multiple snippet directories (maybe from other @@ -321,9 +371,11 @@ users). The code para customization code instead of the default (yas/initialize). For example, you can set yas/trigger-key to (kbd "SPC") here if you like.

    +

    From release 0.6 you have to specify the dropdown-list.el file if +you want it to be a part of the generated bundle.

    -

    yas/define

    +

    yas/define

    The basic syntax for yas/define is

    (yas/define mode key template &optional name condition group)
     
    @@ -335,12 +387,12 @@ you like.

    -

    The strategy to select a snippet

    +

    The strategy to select a snippet

    When user press the yas/trigger-key, YASnippet try to find a proper snippet to expand. The strategy to find such a snippet is explained here.

    -

    Finding the key

    +

    Finding the key

    YASnippet search from current point backward trying to find the snippet to be expanded. The default searching strategy is quite powerful. For example, in c-mode, "bar", "foo_bar", @@ -362,7 +414,7 @@ following thing until found one:

    Emacs's syntax rule mean.

    -

    The condition system

    +

    The condition system

    I write forked snippet.el to make the smart-snippet.el. I call it smart-snippet because a condition can be attached to a snippet. This is really a good idea. However, writing condition for a snippet @@ -426,7 +478,7 @@ can be expanded as you expected, while other snippets like -

    Multiple snippet with the same key

    +

    Multiple snippet with the same key

    There can be multiple snippet bind to the same key. If you define a snippet with a key that is already used, you'll overwrite the original snippet definition. However, you can add a different postfix to the @@ -437,20 +489,21 @@ valid candidates when the key is

    When there are multiple candidates, YASnippet will let you select one. The UI for selecting multiple candidate can be customized. There're two variable related:

    -
      -
    • yas/window-system-popup-function: the function used when you -have a window system.
    • -
    • yas/text-popup-function: the function used when you don't have a -window system, i.e. when you are working in a terminal.
    • -
    -
    -Currently there're three solution come with YASnippet.
    +

    From version 0.6 of YASnippet this has changed significantly. A +customization variable, called yas/prompt-functions defines your +preferred method 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:

    +
    (setq yas/prompt-functions '(yas/x-prompt yas/dropdown-prompt))
    +
    +

    Currently there are some alternatives solution with YASnippet.

    images/popup-menu.png -
  • dropdown-list.el. And upload dropdown-list.el to YASnippet hompage for an optional download (since Jaeyoun didn't provide a URL).

    Then finally, in 0.4.0, I included a copy of the content of -dropdown-list.el [1] in yasnippet.el and made it the default +dropdown-list.el [1] in yasnippet.el and made it the default way for selecting multiple candidates.

    However, the original functions are still there, you can still use this

    (setq yas/window-system-popup-function
    @@ -492,17 +548,18 @@ way for selecting multiple candidates.

-

The Trigger Key

+

The Trigger Key

YASnippet is implemented as a minor-mode (yas/minor-mode). The trigger key yas/trigger-key is defined in yas/minor-mode-map to call yas/expand to try to expand a snippet.

-

The Minor Mode

+

The Minor Mode

images/minor-mode-indicator.png

When yas/minor-mode is enabled, the trigger key will take effect. The default key is (kbd "TAB"), however, you can freely -set it to some other key. By default, YASnippet add a hook to -after-change-major-mode-hook to enable yas/minor-mode [2] in +set it to some other key.

+

In version 0.5, YASnippet add a hook to +after-change-major-mode-hook to enable yas/minor-mode [2] in every buffer. This works fine for most modes, however, some mode doesn't follow the Emacs convention and doens't call this hook. You can either explicitly hook for those mode or just add it to @@ -515,9 +572,12 @@ can either explicitly hook for those mode or just add it to

Note that should be put after (require 'yasnippet) and before (yas/initialize). Further more, you may report it to me, I'll add that to the default value.

+

In version 0.6, just use yas/global-mode to enable YASnippet in +all major modes. Or put yas/minor-mode-on in that modes hook. See +the FAQ.

-

The Fallback

+

The Fallback

If yas/expand failed to find any suitable snippet to expand, it will disable the minor mode temporarily and find if there's any other command bind the yas/trigger-key. If found, the command will be @@ -533,7 +593,7 @@ e.g. hippie-expand. I with hippie-expand is already included in YASnippet.

-

Integration with hippie-expand

+

Integration with hippie-expand

To integrate with hippie-expand, just put yas/hippie-try-expand in hippie-expand-try-functions-list. Personally I would like to put @@ -541,13 +601,26 @@ in front of the list, but it can be put anywhere you prefer.

-

Other way to select a snippet

+

Other way to select a snippet

When you use the trigger key (so yas/expand) to expand a snippet, the key for the snippet is deleted before the template for the snippet is inserted.

However, there're other ways to insert a snippet.

+
+

yas/insert-snippet

+

The command M-x yas/insert-snippet lets you insert snippets at +point for you 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 +yas/prompt-functions.

+
-

The Menu

+

The Menu

YASnippet will setup a menu just after the Buffers Menu in the menubar. The snippets for all real modes are listed there under the menu. You can select a snippet from the menu to expand it. Since you @@ -579,7 +652,7 @@ maintain a list of known modes (y to that list if you need.

-

Expanding From Elisp Code

+

Expanding From Elisp Code

Sometimes you might want to expand a snippet directly by calling a functin from elisp code. You should call yas/expand-snippet instead of yas/expand in this case.

@@ -600,18 +673,18 @@ also indicate where to insert and expand the
-

The Syntax of the Template

+

The Syntax of the Template

The syntax of the snippet template is simple but powerful, very similar to TextMate's.

-

Plain Text

+

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 elisp code

+

Embedded elisp code

Elisp code can be embedded inside the template. They are written inside back-quotes (`):

They are evaluated when the snippet is being expanded. The evaluation @@ -624,11 +697,21 @@ $0 #endif /* $1 */

+

From version 0.6.0, snippets expansions are run with some special +emacs-lisp variables bound. One of this is yas/selected-text. You +can therefore define a snippet like:

+
for ($1;$2;$3) {
+  `yas/selected-text`$0
+}
+
+

to "wrap" the selected region inside your recently inserted +snippet. Alternatively, you can also customize the variable +yas/wrap-around-region to t which will do this automatically.

-
-

Tab Stops

+
+

Tab stop fields

Tab stops are fields that you can navigate back and forth by TAB -and S-TAB [3]. They are written by $ followed with a +and S-TAB [3]. 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:

@@ -637,8 +720,8 @@ fields. Here's a typical example:

</div>
-
-

Placeholders

+
+

Placeholder fields

Tab stops can have default values -- a.k.a placeholders. The syntax is like this:

${N:default value}
@@ -649,7 +732,7 @@ typing. The number can be omitted if you don't want to create
 mirrors or transformations for this field.

-

Mirrors

+

Mirrors

We refer the tab stops with placeholders as a field. A field can have mirrors. Its mirrors will get updated when you change the text of a field. Here's an example:

@@ -664,8 +747,8 @@ explanation is to see the screencast( -

Transformations

+
+

Mirrors with transformations

If the default value of a field starts with $, then it is interpreted as the transformation code instead of default value. A transformation is some arbitrary elisp code that will get evaluated in an environment @@ -709,27 +792,81 @@ ${1:$(make-string (string-width text) ?\=)} $0

- +
[1]With some minor change, mainly for fixing some trivial bugs.
- +
[2]This is done when you call yas/initialize.
- +
[3]Of course, this can be customized.
+
+

Fields with transformations

+

From version 0.6 on, you can also have lisp transformation inside +fields. These work mostly mirror transformations but 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 this is differentiated from a mirror with a transformation +by the existance of 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

+

As mentioned, the field transformation is invoked just after you enter +the field, and with some useful variables bound, notably +yas/field-modified-p and 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 yas/choose-value does this work for you. For example:

+
<div align="${2:$$(yas/choose-value '("right" "center" "left"))}">
+  $0
+</div>
+
+

See the definition of yas/choose-value to see how it was written +using the two variables. Also check out yas/verify-value for +another neat trick.

+
+
+

Nested placeholder fields

+

From version 0.6 on, you can also have nested placeholders of the type:

+
<div${1: id="${2:some_id}"}>$0</div>
+
+

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 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.

+
-

Indenting

+

Indenting

Many people miss the indenting feature of smart-snippet: when you place a $> in your snippet, an (indent-according-to-mode) will be executed there to indent the line. So you'll not need to hard-code @@ -747,6 +884,9 @@ this to YASnippet. Here's an example of the usage:

$0$> }$>
+

In 0.6.0 You should not need to use this feature although it's +supported for backward compatibility. Just set yas/indent-line to +'auto.

diff --git a/doc/define_snippet.rst b/doc/define_snippet.rst index dbd9baa..049995a 100644 --- a/doc/define_snippet.rst +++ b/doc/define_snippet.rst @@ -2,9 +2,9 @@ How to define a snippet ? ========================= -:Author: pluskid +:Author: pluskid, joaotavora :Contact: pluskid@gmail.com -:Date: 2008-03-20 +:Date: 2009-07-24 .. contents:: @@ -202,7 +202,49 @@ ignored. Here's a list of currently supported meta data: under the ``loops`` group which is under the ``control structure`` group. +Quickly finding/defining snippets +--------------------------------- +From version 0.6 upwards there are two ways you can quickly find a +snippet file. Once you find this file it will be set to +``snippet-mode`` (see ahead) + +* ``M-x yas/find-snippets`` + + Lets you find the snippet file in the directory the snippet was + loaded from (if it exists) like ``find-file-other-window``. + +* ``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. + + +Using the ``snippet-mode`` major mode +------------------------------------- + +From version 0.6 upwards 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. + +Two commands are defined in this mode: + +* ``M-x yas/load-snippet-buffer`` + + When editing a snippet, this loads the snippet into the correct + mode and menu. Bound to ``C-c C-c`` by default while in + ``snippet-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``. + +There are also snippets for making snippets: ``vars``, ``field`` and +``mirror``. Define snippets using elisp code -------------------------------- @@ -266,7 +308,7 @@ The basic syntax of ``yas/compile-bundle`` is .. sourcecode:: common-lisp - (yas/compile-bundle &optional yasnippet yasnippet-bundle snippet-roots code) + (yas/compile-bundle &optional yasnippet yasnippet-bundle snippet-roots code dropdown) As you can see, all the parameters are optional. The default values for those parameters are convenient for me to produce the default @@ -277,7 +319,8 @@ release bundle: (yas/compile-bundle "yasnippet.el" "./yasnippet-bundle.el" '("snippets") - "(yas/initialize)") + "(yas/initialize)" + "dropdown-list.el") The ``snippet-roots`` can be a list of root directories. This is useful when you have multiple snippet directories (maybe from other @@ -286,6 +329,9 @@ customization code instead of the default ``(yas/initialize)``. For example, you can set ``yas/trigger-key`` to ``(kbd "SPC")`` here if you like. +From release 0.6 you have to specify the ``dropdown-list.el`` file if +you want it to be a part of the generated bundle. + yas/define ~~~~~~~~~~ @@ -419,22 +465,29 @@ When there are multiple candidates, YASnippet will let you select one. The UI for selecting multiple candidate can be customized. There're two variable related: -* ``yas/window-system-popup-function``: the function used when you - have a window system. -* ``yas/text-popup-function``: the function used when you don't have a - window system, i.e. when you are working in a terminal. +From version 0.6 of YASnippet this has changed significantly. A new +customization variable, called ``yas/prompt-functions`` defines your +preferred method of being prompted for snippets. - Currently there're three solution come with YASnippet. +You can customize it with ``M-x customize-variable RET +yas/prompt-functions RET``. Alternatively you can put in your +emacs-file: + +.. sourcecode:: common-lisp + + (setq yas/prompt-functions '(yas/x-prompt yas/dropdown-prompt)) + +Currently there are some alternatives solution with YASnippet. .. image:: images/popup-menu.png :align: right -Popup Menu -~~~~~~~~~~ +Use the X window system +~~~~~~~~~~~~~~~~~~~~~~~ -The function ``yas/x-popup-menu-for-template`` 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: +The function ``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. @@ -442,15 +495,21 @@ window system widget, which means: ``C-p`` to navigate. * This function can't be used when in a terminal. -Just select the first one -~~~~~~~~~~~~~~~~~~~~~~~~~ +Use built-in Emacs selection methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This one is originally used in terminal mode. It doesn't let you to -choose anything, it just select the first one on behalf of you. So I -bet you never want to use this. :p +You can use functions ``yas/completing-prompt`` for the classic emacs +completion method or ``yas/ido-prompt`` for a much nicer looking +method. The best way is to try it. This works in a terminal. -Use a dropdown-menu.el -~~~~~~~~~~~~~~~~~~~~~~ +.. image:: images/idrop-menu.png + :align: center + +Use ``dropdown-menu.el`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +The function ``yas/dropdown-prompt`` can also be placed in the +``yas/prompt-functions`` list. .. image:: images/dropdown-menu.png :align: right @@ -497,7 +556,9 @@ The Minor Mode When ``yas/minor-mode`` is enabled, the trigger key will take effect. The default key is ``(kbd "TAB")``, however, you can freely -set it to some other key. By default, YASnippet add a hook to +set it to some other key. + +In version 0.5, YASnippet add a hook to ``after-change-major-mode-hook`` to enable ``yas/minor-mode`` [2]_ in every buffer. This works fine for most modes, however, some mode doesn't follow the Emacs convention and doens't call this hook. You @@ -515,6 +576,10 @@ Note that **should** be put after ``(require 'yasnippet)`` and before ``(yas/initialize)``. Further more, you may report it to me, I'll add that to the default value. +In version 0.6, just use ``yas/global-mode`` to enable YASnippet in +all major modes. Or put ``yas/minor-mode-on`` in that modes hook. See +the `FAQ `_. + The Fallback ~~~~~~~~~~~~ @@ -550,6 +615,22 @@ is inserted. However, there're other ways to insert a snippet. +``yas/insert-snippet`` +~~~~~~~~~~~~~~~~~~~~~~ + +The command ``M-x yas/insert-snippet`` lets you insert snippets at +point *for you 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 +``yas/prompt-functions``. + The Menu ~~~~~~~~ @@ -644,8 +725,22 @@ example for ``c-mode`` to calculate the header file guard dynamically: #endif /* $1 */ -Tab Stops ---------- +From version 0.6.0, snippets expansions are run with some special +emacs-lisp variables bound. One of this is ``yas/selected-text``. You +can therefore define a snippet like: + +.. sourcecode:: text + + for ($1;$2;$3) { + `yas/selected-text`$0 + } + +to "wrap" the selected region inside your recently inserted +snippet. Alternatively, you can also customize the variable +``yas/wrap-around-region`` to ``t`` which will do this automatically. + +Tab stop fields +--------------- Tab stops are fields that you can navigate back and forth by ``TAB`` and ``S-TAB`` [3]_. They are written by ``$`` followed with a @@ -659,8 +754,8 @@ fields. Here's a typical example: $0
-Placeholders ------------- +Placeholder fields +------------------ Tab stops can have default values -- a.k.a placeholders. The syntax is like this: @@ -701,8 +796,8 @@ as the field and others mirrors. .. _transformations: -Transformations ---------------- +Mirrors with transformations +---------------------------- If the default value of a field starts with ``$``, then it is interpreted as the transformation code instead of default value. A transformation @@ -764,6 +859,79 @@ is not. Here's an snippet for rst title: .. [2] This is done when you call ``yas/initialize``. .. [3] Of course, this can be customized. +Fields with transformations +--------------------------- + +From version 0.6 on, you can also have lisp transformation inside +fields. These work mostly mirror transformations but 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 + +.. sourcecode:: text + + #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 this is differentiated from a mirror with a transformation +by the existance of extra text between the ``:`` and the +transformation's ``$``. If you don't want this extra-text, you can use +two ``$``'s instead. + +.. sourcecode:: text + + #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 +--------------------------------- + +As mentioned, the field transformation is invoked just after you enter +the field, and with some useful variables bound, notably +``yas/field-modified-p`` and ``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 ``yas/choose-value`` does this work for you. For example: + +.. sourcecode:: text + +
+ $0 +
+ +See the definition of ``yas/choose-value`` to see how it was written +using the two variables. Also check out ``yas/verify-value`` for +another neat trick. + +Nested placeholder fields +------------------------- + +From version 0.6 on, you can also have nested placeholders of the type: + +.. sourcecode:: text + + $0
+ +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 ``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. + Indenting --------- @@ -789,3 +957,7 @@ this to YASnippet. Here's an example of the usage: $0$> }$> +In 0.6.0 You should **not** need to use this feature although it's +supported for backward compatibility. Just set ``yas/indent-line`` to +``'auto``. + diff --git a/doc/faq.html b/doc/faq.html index f9a40e0..2a40684 100644 --- a/doc/faq.html +++ b/doc/faq.html @@ -56,10 +56,13 @@ newline for you automatically.

M-x yas/minor-mode-on to manually turn on yas/minor-mode and try to expand the snippet again. If it works, then, you can add the following code to your .emacs before loading YASnippet:

-
(setq yas/extra-mode-hooks '(the-major-mode))
+
(add-hook 'the-major-mode-hook 'yas/minor-mode-on)
 

where the-major-mode is the major mode in which yas/minor-mode isn't enabled by default.

+

From YASnippet 0.6 you can also use the command M-x +yas/global-mode to turn on YASnippet automatically for all major +modes.

If yas/minor-mode is on but the snippet still not expanded. Then try to see what command is bound to the TAB key: press C-h k and then press TAB. Emacs will show you the result.

diff --git a/doc/faq.rst b/doc/faq.rst index abc7b58..aa8baf8 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -28,11 +28,15 @@ following code to your ``.emacs`` *before* loading YASnippet: .. sourcecode:: lisp - (setq yas/extra-mode-hooks '(the-major-mode)) + (add-hook 'the-major-mode-hook 'yas/minor-mode-on) where ``the-major-mode`` is the major mode in which ``yas/minor-mode`` isn't enabled by default. +From YASnippet 0.6 you can also use the command ``M-x +yas/global-mode`` to turn on YASnippet automatically for *all* major +modes. + If ``yas/minor-mode`` is on but the snippet still not expanded. Then try to see what command is bound to the ``TAB`` key: press ``C-h k`` and then press ``TAB``. Emacs will show you the result. diff --git a/doc/images/customization-group.png b/doc/images/customization-group.png new file mode 100644 index 0000000..d98f7a0 Binary files /dev/null and b/doc/images/customization-group.png differ diff --git a/doc/images/idrop-menu.png b/doc/images/idrop-menu.png new file mode 100644 index 0000000..f9a0217 Binary files /dev/null and b/doc/images/idrop-menu.png differ diff --git a/doc/index.html b/doc/index.html index 192028e..69e6a33 100644 --- a/doc/index.html +++ b/doc/index.html @@ -49,7 +49,8 @@
  • Normal Install
  • -
  • Bugs, Contribution and Support
  • +
  • Customization group
  • +
  • Bugs, Contribution and Support
  • Yasnippet is a template system for emacs. It allows you to type a @@ -107,11 +108,24 @@ following in your .emacsyas/load-directory "~/.emacs.d/plugins/yasnippet/snippets")

    -

    Please refer to the documentation for full customization.

    +

    Please refer to the documentation for full customization, or use the +customization group.

    +images/customization-group.png
    +
    +

    Customization group

    +

    From version 0.6 onwards, there is a customization group that you can +access with:

    +

    M-x customize-group RET yasnippet RET

    +

    Each customization variable affects how some part of YASnippet works, +for example automatic snippet indentation, what prompting method to +use, whether to expand snippets inside snippets, etc...

    +

    Inside the customization group, each variable is reasonably documented +to explain what it does.

    +
    -

    Bugs, Contribution and Support

    +

    Bugs, Contribution and Support

    • If you find a bug, please report it at Issue List.
    • If you have problem using YASnippet, or have some new ideas, please diff --git a/doc/index.rst b/doc/index.rst index fa01873..3eb8391 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -78,7 +78,26 @@ following in your ``.emacs`` file: (yas/initialize) (yas/load-directory "~/.emacs.d/plugins/yasnippet/snippets") -Please refer to the documentation for full customization. +Please refer to the documentation for full customization, or use the +customization group. + +.. image:: images/customization-group.png + :align: right + +Customization group +=================== + +From version 0.6 onwards, there is a customization group that you can +access with: + +``M-x customize-group RET yasnippet RET`` + +Each customization variable affects how some part of YASnippet works, +for example automatic snippet indentation, what prompting method to +use, whether to expand snippets inside snippets, etc... + +Inside the customization group, each variable is reasonably documented +to explain what it does. Bugs, Contribution and Support ============================== diff --git a/dropdown-list.el b/dropdown-list.el new file mode 100644 index 0000000..7b451d5 --- /dev/null +++ b/dropdown-list.el @@ -0,0 +1,251 @@ +;;; dropdown-list.el --- Drop-down menu interface +;; +;; Filename: dropdown-list.el +;; Description: Drop-down menu interface +;; Author: Jaeyoun Chung [jay.chung@gmail.com] +;; Maintainer: +;; Copyright (C) 2008 Jaeyoun Chung +;; Created: Sun Mar 16 11:20:45 2008 (Pacific Daylight Time) +;; Version: +;; Last-Updated: Sun Mar 16 12:19:49 2008 (Pacific Daylight Time) +;; By: dradams +;; Update #: 43 +;; URL: http://www.emacswiki.org/cgi-bin/wiki/dropdown-list.el +;; Keywords: convenience menu +;; Compatibility: GNU Emacs 21.x, GNU Emacs 22.x +;; +;; Features that might be required by this library: +;; +;; `cl'. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Commentary: +;; +;; According to Jaeyoun Chung, "overlay code stolen from company-mode.el." +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Change log: +;; +;; 2008/03/16 dadams +;; Clean-up - e.g. use char-to-string for control chars removed by email posting. +;; Moved example usage code (define-key*, command-selector) inside the library. +;; Require cl.el at byte-compile time. +;; Added GPL statement. +;; 2008/01/06 Jaeyoun Chung +;; Posted to gnu-emacs-sources@gnu.org at 9:10 p.m. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 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, 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; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth +;; Floor, Boston, MA 02110-1301, USA. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Code: + +(eval-when-compile (require 'cl)) ;; decf, fourth, incf, loop, mapcar* + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defface dropdown-list-face + '((t :inherit default :background "lightyellow" :foreground "black")) + "*Bla." :group 'dropdown-list) + +(defface dropdown-list-selection-face + '((t :inherit dropdown-list-face :background "purple")) + "*Bla." :group 'dropdown-list) + +(defvar dropdown-list-overlays nil) + +(defun dropdown-list-hide () + (while dropdown-list-overlays + (delete-overlay (pop dropdown-list-overlays)))) + +(defun dropdown-list-put-overlay (beg end &optional prop value prop2 value2) + (let ((ov (make-overlay beg end))) + (overlay-put ov 'window t) + (when prop + (overlay-put ov prop value) + (when prop2 (overlay-put ov prop2 value2))) + ov)) + +(defun dropdown-list-line (start replacement &optional no-insert) + ;; start might be in the middle of a tab, which means we need to hide the + ;; tab and add spaces + (let ((end (+ start (length replacement))) + beg-point end-point + before-string after-string) + (goto-char (point-at-eol)) + (if (< (current-column) start) + (progn (setq before-string (make-string (- start (current-column)) ? )) + (setq beg-point (point))) + (goto-char (point-at-bol)) ;; Emacs bug, move-to-column is wrong otherwise + (move-to-column start) + (setq beg-point (point)) + (when (> (current-column) start) + (goto-char (1- (point))) + (setq beg-point (point)) + (setq before-string (make-string (- start (current-column)) ? )))) + (move-to-column end) + (setq end-point (point)) + (let ((end-offset (- (current-column) end))) + (when (> end-offset 0) (setq after-string (make-string end-offset ?b)))) + (when no-insert + ;; prevent inheriting of faces + (setq before-string (when before-string (propertize before-string 'face 'default))) + (setq after-string (when after-string (propertize after-string 'face 'default)))) + (let ((string (concat before-string replacement after-string))) + (if no-insert + string + (push (dropdown-list-put-overlay beg-point end-point 'invisible t + 'after-string string) + dropdown-list-overlays))))) + +(defun dropdown-list-start-column (display-width) + (let ((column (mod (current-column) (window-width))) + (width (window-width))) + (cond ((<= (+ column display-width) width) column) + ((> column display-width) (- column display-width)) + ((>= width display-width) (- width display-width)) + (t nil)))) + +(defun dropdown-list-move-to-start-line (candidate-count) + (decf candidate-count) + (let ((above-line-count (save-excursion (- (vertical-motion (- candidate-count))))) + (below-line-count (save-excursion (vertical-motion candidate-count)))) + (cond ((= below-line-count candidate-count) + t) + ((= above-line-count candidate-count) + (vertical-motion (- candidate-count)) + t) + ((>= (+ below-line-count above-line-count) candidate-count) + (vertical-motion (- (- candidate-count below-line-count))) + t) + (t nil)))) + +(defun dropdown-list-at-point (candidates &optional selidx) + (dropdown-list-hide) + (let* ((lengths (mapcar #'length candidates)) + (max-length (apply #'max lengths)) + (start (dropdown-list-start-column (+ max-length 3))) + (i -1) + (candidates (mapcar* (lambda (candidate length) + (let ((diff (- max-length length))) + (propertize + (concat (if (> diff 0) + (concat candidate (make-string diff ? )) + (substring candidate 0 max-length)) + (format "%3d" (+ 2 i))) + 'face (if (eql (incf i) selidx) + 'dropdown-list-selection-face + 'dropdown-list-face)))) + candidates + lengths))) + (save-excursion + (and start + (dropdown-list-move-to-start-line (length candidates)) + (loop initially (vertical-motion 0) + for candidate in candidates + do (dropdown-list-line (+ (current-column) start) candidate) + while (/= (vertical-motion 1) 0) + finally return t))))) + +(defun dropdown-list (candidates) + (let ((selection) + (temp-buffer)) + (save-window-excursion + (unwind-protect + (let ((candidate-count (length candidates)) + done key (selidx 0)) + (while (not done) + (unless (dropdown-list-at-point candidates selidx) + (switch-to-buffer (setq temp-buffer (get-buffer-create "*selection*")) + 'norecord) + (delete-other-windows) + (delete-region (point-min) (point-max)) + (insert (make-string (length candidates) ?\n)) + (goto-char (point-min)) + (dropdown-list-at-point candidates selidx)) + (setq key (read-key-sequence "")) + (cond ((and (stringp key) + (>= (aref key 0) ?1) + (<= (aref key 0) (+ ?0 (min 9 candidate-count)))) + (setq selection (- (aref key 0) ?1) + done t)) + ((member key `(,(char-to-string ?\C-p) [up] "p")) + (setq selidx (mod (+ candidate-count (1- (or selidx 0))) + candidate-count))) + ((member key `(,(char-to-string ?\C-n) [down] "n")) + (setq selidx (mod (1+ (or selidx -1)) candidate-count))) + ((member key `(,(char-to-string ?\f)))) + ((member key `(,(char-to-string ?\r) [return])) + (setq selection selidx + done t)) + (t (setq done t))))) + (dropdown-list-hide) + (and temp-buffer (kill-buffer temp-buffer))) + ;; (when selection + ;; (message "your selection => %d: %s" selection (nth selection candidates)) + ;; (sit-for 1)) + selection))) + +(defun define-key* (keymap key command) + "Add COMMAND to the multiple-command binding of KEY in KEYMAP. +Use multiple times to bind different COMMANDs to the same KEY." + (define-key keymap key (combine-command command (lookup-key keymap key)))) + +(defun combine-command (command defs) + "$$$$$ FIXME - no doc string" + (cond ((null defs) command) + ((and (listp defs) + (eq 'lambda (car defs)) + (= (length defs) 4) + (listp (fourth defs)) + (eq 'command-selector (car (fourth defs)))) + (unless (member `',command (cdr (fourth defs))) + (setcdr (fourth defs) (nconc (cdr (fourth defs)) `(',command)))) + defs) + (t + `(lambda () (interactive) (command-selector ',defs ',command))))) + +(defvar command-selector-last-command nil "$$$$$ FIXME - no doc string") + +(defun command-selector (&rest candidates) + "$$$$$ FIXME - no doc string" + (if (and (eq last-command this-command) command-selector-last-command) + (call-interactively command-selector-last-command) + (let* ((candidate-strings + (mapcar (lambda (candidate) + (format "%s" (if (symbolp candidate) + candidate + (let ((s (format "%s" candidate))) + (if (>= (length s) 7) + (concat (substring s 0 7) "...") + s))))) + candidates)) + (selection (dropdown-list candidate-strings))) + (when selection + (let ((cmd (nth selection candidates))) + (call-interactively cmd) + (setq command-selector-last-command cmd)))))) + +;;;;;;;;;;;;;;;;;;;; + +(provide 'dropdown-list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; dropdown-list.el ends here \ No newline at end of file diff --git a/snippets/text-mode/email b/snippets/text-mode/email index b953111..1ac7f94 100644 --- a/snippets/text-mode/email +++ b/snippets/text-mode/email @@ -1,3 +1,3 @@ #name : (user's email) # -- -`user-mail-address` \ No newline at end of file +`(replace-regexp-in-string "@" "@NOSPAM." user-mail-address)` \ No newline at end of file diff --git a/snippets/text-mode/html-mode/div b/snippets/text-mode/html-mode/div index 0ecfde3..1c9c0c8 100644 --- a/snippets/text-mode/html-mode/div +++ b/snippets/text-mode/html-mode/div @@ -1,3 +1,3 @@ #name : ...
    # -- -$0 \ No newline at end of file +$0 \ No newline at end of file diff --git a/snippets/text-mode/html-mode/dov b/snippets/text-mode/html-mode/dov new file mode 100644 index 0000000..e8341ea --- /dev/null +++ b/snippets/text-mode/html-mode/dov @@ -0,0 +1,11 @@ +#name : ... +# -- +a mirror up here $3 + + + + $0 + + + actually some other shit and $3 + diff --git a/snippets/text-mode/html-mode/label b/snippets/text-mode/html-mode/label deleted file mode 100644 index a90d679..0000000 --- a/snippets/text-mode/html-mode/label +++ /dev/null @@ -1,3 +0,0 @@ -#name : -# -- - \ No newline at end of file diff --git a/snippets/text-mode/python-mode/class b/snippets/text-mode/python-mode/class index 9617c07..72f339b 100644 --- a/snippets/text-mode/python-mode/class +++ b/snippets/text-mode/python-mode/class @@ -36,23 +36,19 @@ class ${1:ClassName}(${2:object}): indent))) } ${4:$ - (let* ((indent (concat "\n" (make-string (current-column) 32))) - (self-vars (mapconcat - '(lambda (x) - (if (not (string= (nth 0 x) "")) - (concat "self._" (nth 0 x) " = " (nth 0 x)))) - (mapcar - '(lambda (x) - (mapcar - '(lambda (x) - (replace-regexp-in-string "[[:blank:]]*$" "" - (replace-regexp-in-string "^[[:blank:]]*" "" x))) - x)) - (mapcar '(lambda (x) (split-string x "=")) - (split-string text ","))) - (concat indent)))) - (if (string= self-vars "") - indent - self-vars)) + (mapconcat + '(lambda (x) + (if (not (string= (nth 0 x) "")) + (concat "self._" (nth 0 x) " = " (nth 0 x)))) + (mapcar + '(lambda (x) + (mapcar + '(lambda (x) + (replace-regexp-in-string "[[:blank:]]*$" "" + (replace-regexp-in-string "^[[:blank:]]*" "" x))) + x)) + (mapcar '(lambda (x) (split-string x "=")) + (split-string text ","))) + (concat "\n" (make-string (current-column) 32))) } $0 diff --git a/snippets/text-mode/ruby-mode/cls b/snippets/text-mode/ruby-mode/cls index 4ce8a0b..da28fb7 100644 --- a/snippets/text-mode/ruby-mode/cls +++ b/snippets/text-mode/ruby-mode/cls @@ -2,12 +2,12 @@ #contributor : hitesh #group : definitions # -- -class ${1:$ - (let ((fn (capitalize (file-name-nondirectory +class ${1:`(let ((fn (capitalize (file-name-nondirectory (file-name-sans-extension - (buffer-file-name)))))) + (or (buffer-file-name) + (buffer-name (current-buffer)))))))) (cond ((string-match "_" fn) (replace-match "" nil nil fn)) - (t fn)))} + (t fn)))`} $0 end diff --git a/snippets/text-mode/snippet-mode/field b/snippets/text-mode/snippet-mode/field new file mode 100644 index 0000000..bdaf0d4 --- /dev/null +++ b/snippets/text-mode/snippet-mode/field @@ -0,0 +1,5 @@ +# name : ${ ... } field +# contributor : joaotavora +# key : $f +# -- +\${${1:${2:n}:}$3${4:\$(${5:lisp-fn})}\}$0 \ No newline at end of file diff --git a/snippets/text-mode/snippet-mode/mirror b/snippets/text-mode/snippet-mode/mirror new file mode 100644 index 0000000..94c8e56 --- /dev/null +++ b/snippets/text-mode/snippet-mode/mirror @@ -0,0 +1,6 @@ +# name : ${n:$(...)} mirror +# key : $m +# contributor : joaotavora +# -- +\${${2:n}:${4:\$(${5:reflection-fn})}\}$0 + diff --git a/snippets/text-mode/snippet-mode/vars b/snippets/text-mode/snippet-mode/vars new file mode 100644 index 0000000..f271767 --- /dev/null +++ b/snippets/text-mode/snippet-mode/vars @@ -0,0 +1,9 @@ +# name : Snippet header +# contributor : joaotavora +# -- +# name : $1${2: +# key : ${3:expand-key}}${4: +# key : ${5:group}} +# contributor : $6 +# -- +$0 \ No newline at end of file diff --git a/yasnippet.el b/yasnippet.el index 6af0296..084d2c1 100644 --- a/yasnippet.el +++ b/yasnippet.el @@ -1,10 +1,11 @@ -;;; yasnippet.el --- Yet another snippet extension for Emacs. +;;; Yasnippet.el --- Yet another snippet extension for Emacs. ;; Copyright 2008 pluskid -;; Author: pluskid -;; Created: 02 Mar 2008 -;; Version: 0.5.10 +;; Authors: pluskid , joaotavora +;; Version: 0.6.0 +;; Package-version: 0.6.0b +;; X-URL: http://code.google.com/p/yasnippet/ ;; Keywords: snippet, textmate ;; URL: http://code.google.com/p/yasnippet/ ;; EmacsWiki: YaSnippetMode @@ -27,29 +28,274 @@ ;;; Commentary: ;; Basic steps to setup: -;; 1. Place `yasnippet.el' in your `load-path'. -;; 2. In your .emacs file: +;; +;; 1. In your .emacs file: +;; (add-to-list 'load-path "/dir/to/yasnippet.el") ;; (require 'yasnippet) -;; 3. Place the `snippets' directory somewhere. E.g: ~/.emacs.d/snippets -;; 4. In your .emacs file -;; (yas/initialize) -;; (yas/load-directory "~/.emacs.d/snippets") +;; 2. Place the `snippets' directory somewhere. E.g: ~/.emacs.d/snippets +;; 3. In your .emacs file +;; (setq yas/root-directory "~/.emacs/snippets") +;; (yas/load-directory yas/root-directory) +;; 4. To enable the YASnippet menu and tab-trigger expansion +;; M-x yas/minor-mode +;; 5. To globally enable the minor mode in *all* buffers +;; M-x yas/global-mode ;; -;; For more information and detailed usage, refer to the project page: +;; Steps 4. and 5. are optional, you don't have to use the minor +;; mode to use YASnippet. +;; +;; +;; Major commands are: +;; +;; M-x yas/expand +;; +;; Try to expand snippets before point. In `yas/minor-mode', +;; this is bound to `yas/trigger-key' which you can customize. +;; +;; M-x yas/load-directory +;; +;; Prompts you for a directory hierarchy of snippets to load. +;; +;; 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/find-snippets +;; +;; Lets you find the snippet file in the directory the +;; snippet was loaded from (if it exists) like +;; `find-file-other-window'. +;; +;; 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/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'. +;; +;; The `dropdown-list.el' extension is bundled with YASnippet, you +;; can optionally use it the preferred "prompting method", puting in +;; your .emacs file, for example: +;; +;; (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 +;; +;; For more information and detailed usage, refer to the project page: ;; http://code.google.com/p/yasnippet/ ;;; Code: -(eval-when-compile (require 'cl)) +(require 'cl) +(require 'easymenu) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; User customizable variables -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar yas/dont-activate nil - "If set to t, don't activate yas/minor-mode automatically.") -(make-variable-buffer-local 'yas/dont-activate) +;; -(defvar yas/key-syntaxes (list "w" "w_" "w_." "w_.\\" "^ ") +(defgroup yasnippet nil + "Yet Another Snippet extension" + :group 'editing) + +(defcustom yas/root-directory nil + "Root directory that stores the snippets for each major mode. + +Can also be a list of strings, for multiple root directories." + :type '(string) + :group 'yasnippet) + +(defcustom yas/prompt-functions '(yas/x-prompt + yas/dropdown-prompt + yas/completing-prompt + yas/ido-prompt + yas/no-prompt) + "Functions to prompt for keys, templates, etc interactively." + :type 'list + :group 'yasnippet) + +(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 indendation +after expansion (the manual per-line \"$>\" indentation still +applies)." + :type '(choice (const :tag "Nothing" nothing) + (const :tag "Fixed" fixed) + (const :tag "Auto" auto)) + :group 'yasnippet) + +(defcustom yas/snippet-revival t + "Non-nil means re-activate snippet fields after undo/redo." + :type 'boolean + :group 'yasnippet) + +(defcustom yas/trigger-key "TAB" + "The key bound to `yas/expand' when function `yas/minor-mode' is active. + +Value is a string that is converted to the internal Emacs key +representation using `read-kbd-macro'." + :type 'string + :group 'yasnippet) + +(defcustom yas/next-field-key "TAB" + "The key to navigate to next field when a snippet is active. + +Value is a string that is converted to the internal Emacs key +representation using `read-kbd-macro'." + :type 'string + :group 'yasnippet) + +(defcustom yas/prev-field-key '("" "") + "The key to navigate to previous field when a snippet is active. + +Can also be a list of keys. + +Value is a string that is converted to the internal Emacs key +representation using `read-kbd-macro'." + :type 'string + :group 'yasnippet) + +(defcustom yas/skip-and-clear-key "C-d" + "The key to clear the currently active field. + +Value is a string that is converted to the internal Emacs key +representation using `read-kbd-macro'." + :type 'string + :group 'yasnippet) + +(defcustom yas/triggers-in-field nil + "If non-nil, `yas/next-field-key' can trigger stacked expansions. + +Otherwise, `yas/next-field-key' just tries to move on to the next +field" + :type 'boolean + :group 'yasnippet) + +(defcustom yas/fallback-behavior 'call-other-command + "How to act when `yas/trigger-key' does *not* expand a snippet. + +The fall back behavior of YASnippet when it can't find a snippet +to expand. + +`call-other-command' means try to temporarily disable + YASnippet and call other command bound to `yas/trigger-key'. + +`return-nil' means return do nothing." + :type '(choice (const :tag "Call previous command" 'call-other-command) + (const :tag "Do nothing" 'return-nil)) + :group 'yasnippet) + +(defcustom yas/choose-keys-first t + "If non-nil, `yas/insert-snippet' prompts for key, then for template. + +Otherwise `yas/insert-snippet' prompts for all possible +templates and inserts the selected one." + :type 'boolean + :group 'yasnippet) + +(defcustom yas/use-menu t + "Display a YASnippet menu in the menu bar. + +If this is set to t, all snippet template of the current +mode will be listed under the menu \"yasnippet\"." + :type 'boolean + :group 'yasnippet) + +(defcustom yas/trigger-symbol " =>" + "The text that will be used in menu to represent the trigger." + :type 'string + :group 'yasnippet) + +(defcustom yas/show-all-modes-in-menu nil + "Display \"artificial\" major modes in menu bar as well. + +Currently, YASnippet only all \"real modes\" to menubar. For +example, you define snippets for \"cc-mode\" and make it the +parent of `c-mode', `c++-mode' and `java-mode'. There's really +no such mode like \"cc-mode\". So we don't show it in the yasnippet +menu to avoid the menu becoming too big with strange modes. The +snippets defined for \"cc-mode\" can still be accessed from +menu-bar->c-mode->parent (or c++-mode, java-mode, all are ok). +However, if you really like to show all modes in the menu, set +this variable to t." + :type 'boolean + :group 'yasnippet) + +(defcustom yas/wrap-around-region nil + "If non-nil, snippet expansion wraps around selected region. + +The wrapping occurs just before the snippet's exit marker. This +can be overriden on a per-snippet basis." + :type 'boolean + :group 'yasnippet) + +(defcustom yas/good-grace t + "If non-nil, don't raise errors in inline elisp evaluation. + +An error string \"[yas] error\" is returned instead." + + + :type 'boolean + :group 'yasnippet) + +(defface yas/field-highlight-face + '((((class color) (background light)) (:background "DarkSeaGreen1")) + (t (:background "DimGrey"))) + "The face used to highlight the currently active field of a snippet" + :group 'yasnippet) + +(defface yas/field-debug-face + '() + "The face used for debugging some overlays normally hidden" + :group 'yasnippet) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; User semi-customizable variables +;; + +(defvar yas/keymap (make-sparse-keymap) + "The keymap active while a snippet expansion is in progress.") + +(defun yas/define-some-keys (keys keymap definition) + "Bind KEYS to DEFINITION in KEYMAP, read with `read-kbd-macro'." + (let ((keys (or (and (listp keys) keys) + (list keys)))) + (dolist (key keys) + (define-key keymap (read-kbd-macro key) definition)))) + +(eval-when-compile + (yas/define-some-keys yas/next-field-key yas/keymap 'yas/next-field-or-maybe-expand) + (yas/define-some-keys yas/prev-field-key yas/keymap 'yas/prev-field) + (yas/define-some-keys yas/skip-and-clear-key yas/keymap 'yas/skip-and-clear-or-delete-char)) + +(defvar yas/key-syntaxes (list "w" "w_" "w_." "^ ") "A list of syntax of a key. This list is tried in the order to try to find a key. For example, if the list is '(\"w\" \"w_\"). And in emacs-lisp-mode, where \"-\" has the syntax of \"_\": @@ -58,84 +304,22 @@ foo-bar will first try \"bar\", if not found, then \"foo-bar\" is tried.") -(defvar yas/root-directory nil - "The (list of) root directory that stores the snippets for each -major modes.") - -(defvar yas/indent-line t - "Each (except the 1st) line of the snippet template is indented to -current column if this variable is non-`nil'.") -(make-variable-buffer-local 'yas/indent-line) - -(defvar yas/trigger-key (kbd "TAB") - "The key to bind as a trigger of snippet.") -(defvar yas/next-field-key (kbd "TAB") - "The key to navigate to next field.") - -(defvar yas/keymap (make-sparse-keymap) - "The keymap of snippet.") -(define-key yas/keymap yas/next-field-key 'yas/next-field-group) -(define-key yas/keymap (kbd "S-TAB") 'yas/prev-field-group) -(define-key yas/keymap (kbd "") 'yas/prev-field-group) -(define-key yas/keymap (kbd "") 'yas/prev-field-group) -(define-key yas/keymap (kbd "") 'yas/prev-field-group) - -(defvar yas/show-all-modes-in-menu nil - "Currently yasnippet only all \"real modes\" to menubar. For -example, you define snippets for \"cc-mode\" and make it the -parent of `c-mode', `c++-mode' and `java-mode'. There's really -no such mode like \"cc-mode\". So we don't show it in the yasnippet -menu to avoid the menu becoming too big with strange modes. The -snippets defined for \"cc-mode\" can still be accessed from -menu-bar->c-mode->parent (or c++-mode, java-mode, all are ok). -However, if you really like to show all modes in the menu, set -this variable to t.") -(defvar yas/use-menu t - "If this is set to `t', all snippet template of the current -mode will be listed under the menu \"yasnippet\".") -(defvar yas/trigger-symbol " =>" - "The text that will be used in menu to represent the trigger.") - -(defface yas/field-highlight-face - '((((class color) (background light)) (:background "DarkSeaGreen2")) - (t (:background "DimGrey"))) - "The face used to highlight a field of snippet.") -(defface yas/mirror-highlight-face - '((((class color) (background light)) (:background "LightYellow2")) - (t (:background "gray22"))) - "The face used to highlight mirror fields of a snippet.") - -(defvar yas/window-system-popup-function #'yas/dropdown-list-popup-for-template - "When there's multiple candidate for a snippet key. This function -is called to let user select one of them. `yas/text-popup-function' -is used instead when not in a window system.") -(defvar yas/text-popup-function #'yas/dropdown-list-popup-for-template - "When there's multiple candidate for a snippet key. If not in a -window system, this function is called to let user select one of -them. `yas/window-system-popup-function' is used instead when in -a window system.") - -(defvar yas/extra-mode-hooks - '() - "A list of mode-hook that should be hooked to enable yas/minor-mode. -Most modes need no special consideration. Some mode (like `ruby-mode') -doesn't call `after-change-major-mode-hook' need to be hooked explicitly.") -(mapc '(lambda (x) - (add-to-list 'yas/extra-mode-hooks - x)) - '(ruby-mode-hook actionscript-mode-hook ox-mode-hook python-mode-hook)) - (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.") + +`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/stackd snippet expansion!") (defvar yas/before-expand-snippet-hook '() - "Hooks to run after a before expanding a snippet.") + "Hooks to run just before expanding a snippet.") (defvar yas/buffer-local-condition '(if (and (not (bobp)) @@ -149,24 +333,33 @@ proper values: t) "Condition to yasnippet local to each buffer. +The default value helps filtering out potential snippet +expansions inside comments and string literals, unless the +snippet itself contains a condition that returns the symbol +`force-in-comment'. + * If yas/buffer-local-condition evaluate to nil, snippet won't be expanded. * If it evaluate to the a cons cell where the car is the - symbol require-snippet-condition and the cdr is a - symbol (let's call it requirement): + symbol `require-snippet-condition' and the cdr is a + symbol (let's call it \"requirement\"): * If the snippet has no condition, then it won't be expanded. - * If the snippet has a condition but evaluate to nil or + * If the snippet has a condition but it evaluates to nil or error occured during evaluation, it won't be expanded. * If the snippet has a condition that evaluate to - non-nil (let's call it result): - * If requirement is t, the snippet is ready to be + non-nil (let's call it \"result\"): + * If \"requirement\" is t, the snippet is ready to be expanded. - * If requirement is eq to result, the snippet is ready + * If \"requirement\" is eq to \"result\", the snippet is ready to be expanded. * Otherwise the snippet won't be expanded. - * If it evaluate to other non-nil value: + + * If it evaluates to `always', snippet is unconditionally + expanded. + + * If it evaluates to other non-nil value: * If the snippet has no condition, or has a condition that evaluate to non-nil, it is ready to be expanded. * Otherwise, it won't be expanded. @@ -179,230 +372,176 @@ Here's an example: '(if (python-in-string/comment) '(require-snippet-condition . force-in-comment) t))))") -(eval-when-compile - (make-variable-buffer-local 'yas/buffer-local-condition)) - -(defvar yas/fallback-behavior 'call-other-command - "The fall back behavior of YASnippet when it can't find a snippet -to expand. - - * 'call-other-command means try to temporarily disable - YASnippet and call other command bound to `yas/trigger-key'. - * 'return-nil means return nil.") +(make-variable-buffer-local 'yas/buffer-local-condition) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Utility functions for transformations ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defun yas/substr (str pattern &optional group) - "Search PATTERN in STR. If found, the content of group - GROUP (default 0) is returned, or else the original STR will be - returned." - (let ((grp (or group 0))) - (save-match-data - (if (string-match pattern str) - (match-string-no-properties grp str) - str)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Internal variables -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar yas/version "0.5.10") +;; + +(defvar yas/version "0.6.0b") (defvar yas/snippet-tables (make-hash-table) - "A hash table of snippet tables corresponding to each major-mode.") + "A hash table of snippet tables corresponding to each major mode.") + (defvar yas/menu-table (make-hash-table) - "A hash table of menus of corresponding major-mode.") -(defvar yas/menu-keymap (make-sparse-keymap "YASnippet")) -;; empty menu will cause problems, so we insert some items -(define-key yas/menu-keymap [yas/about] - '(menu-item "About" yas/about)) -(define-key yas/menu-keymap [yas/reload] - '(menu-item "Reload all snippets" yas/reload-all)) -(define-key yas/menu-keymap [yas/load] - '(menu-item "Load snippets..." yas/load-directory)) -(define-key yas/menu-keymap [yas/separator] - '(menu-item "--")) + "A hash table of menus of corresponding major mode.") (defvar yas/known-modes '(ruby-mode rst-mode markdown-mode) "A list of mode which is well known but not part of emacs.") -(defconst yas/escape-backslash - (concat "YASESCAPE" "BACKSLASH" "PROTECTGUARD")) -(defconst yas/escape-dollar - (concat "YASESCAPE" "DOLLAR" "PROTECTGUARD")) -(defconst yas/escape-backquote - (concat "YASESCAPE" "BACKQUOTE" "PROTECTGUARD")) + +(defvar yas/escaped-characters + '(?\\ ?` ?' ?$ ?} ) + "List of characters which *might* need to be escaped.") (defconst yas/field-regexp - (concat "$\\([0-9]+\\)" "\\|" - "${\\(?:\\([0-9]+\\):\\)?\\([^}]*\\)}")) + "${\\([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]+\\):\\)?$\\([^}]*\\)" + "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.") + (defun yas/snippet-next-id () (let ((id yas/snippet-id-seed)) (incf yas/snippet-id-seed) id)) -(defvar yas/overlay-modification-hooks - (list 'yas/overlay-modification-hook) - "The list of hooks to the overlay modification event.") -(defvar yas/overlay-insert-in-front-hooks - (list 'yas/overlay-insert-in-front-hook) - "The list of hooks of the overlay inserted in front event.") -(defvar yas/keymap-overlay-modification-hooks - (list 'yas/overlay-maybe-insert-behind-hook) - "The list of hooks of the big keymap overlay modification event.") +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Minor mode stuff +;; +;; TODO: XXX: This is somehow needed in Carbon Emacs for MacOSX +(defvar last-buffer-undo-list nil) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; YASnippet minor mode -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar yas/minor-mode-map (make-sparse-keymap) - "The keymap of yas/minor-mode") -(defvar yas/minor-mode-on-hook nil - "Hook to call when yas/minor-mode is on.") -(defvar yas/minor-mode-off-hook nil - "Hook to call when yas/minor-mode is off.") + "The keymap used when function `yas/minor-mode' is active.") + +(defun yas/init-keymap-and-menu () + (easy-menu-define yas/minor-mode-menu + yas/minor-mode-map + "Menu used when YAS/minor-mode is active." + (cons "YASnippet" + (mapcar #'(lambda (ent) + (when (third ent) + (define-key yas/minor-mode-map (third ent) (second ent))) + (vector (first ent) (second ent) t)) + (list (list "--") + (list "Expand trigger" 'yas/expand (read-kbd-macro yas/trigger-key)) + (list "Insert at point..." 'yas/insert-snippet "\C-c&\C-s") + (list "Visit snippet file..." 'yas/visit-snippet-file "\C-c&\C-v") + (list "Find snippets..." 'yas/find-snippets "\C-c&\C-f") + (list "About" 'yas/about) + (list "Reload-all-snippets" 'yas/reload-all) + (list "Load snippets..." 'yas/load-directory)))))) + +(eval-when-compile + (yas/init-keymap-and-menu)) + (define-minor-mode yas/minor-mode "Toggle YASnippet mode. + +When YASnippet mode is enabled, the `tas/trigger-key' key expands +snippets of code depending on the mode. + With no argument, this command toggles the mode. positive prefix argument turns on the mode. Negative prefix argument turns off the mode. -When YASnippet mode is enabled, the TAB key -expands snippets of code depending on the mode. +You can customize the key through `yas/trigger-key'. -You can customize the key through `yas/trigger-key'." - ;; The initial value. +Key bindings: +\\{yas/minor-mode-map}" nil ;; The indicator for the mode line. " yas" - :group 'editing - (define-key yas/minor-mode-map yas/trigger-key 'yas/expand)) + :group 'yasnippet) - -(defun yas/minor-mode-auto-on () - "Turn on YASnippet minor mode unless `yas/dont-activate' is -set to t." - (unless yas/dont-activate - (yas/minor-mode-on))) (defun yas/minor-mode-on () "Turn on YASnippet minor mode." (interactive) (yas/minor-mode 1)) + (defun yas/minor-mode-off () "Turn off YASnippet minor mode." (interactive) (yas/minor-mode -1)) +(define-globalized-minor-mode yas/global-mode yas/minor-mode yas/minor-mode-on + :group 'yasnippet) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Internal Structs +;; Major mode stuff +;; +(defvar yas/font-lock-keywords + (append '(("^#.*$" . font-lock-comment-face)) + lisp-font-lock-keywords + lisp-font-lock-keywords-1 + lisp-font-lock-keywords-2 + '(("$\\([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)) + ("${" font-lock-keyword-face) + ("$[0-9]+?" font-lock-preprocessor-face) + ("\\(\\$(\\)" 1 font-lock-preprocessor-face) + ("}" + (0 font-lock-keyword-face))))) + +(defvar snippet-mode-map (make-sparse-keymap)) +(define-key snippet-mode-map "\C-c\C-c" 'yas/load-snippet-buffer) +(define-key snippet-mode-map "\C-c\C-t" 'yas/tryout-snippet) + + +(define-derived-mode snippet-mode text-mode "YASnippet" + "A mode for editing yasnippets" + (setq font-lock-defaults '(yas/font-lock-keywords)) + (set (make-local-variable 'require-final-newline) nil) + (use-local-map snippet-mode-map)) + + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Internal structs for template management +;; + (defstruct (yas/template (:constructor yas/make-template - (content name condition))) + (content name condition env file))) "A template for a snippet." content name - condition) -(defstruct (yas/snippet (:constructor yas/make-snippet ())) - "A snippet. + condition + env + file) -Description of some fields: - -`yas/snippet-saved-buffer-undo-list' saves the value of -`buffer-undo-list' just after the snippet has been expanded. This -is to be restored when the snippet is cleaned up. Thus the -snippet expansion can still be undone after -`yas/cleanup-snippet', even if field-level undo steps were -recorded. - -`yas/snippet-end-marker' saves the actual end position of the -snippets main overlay, at the time the snippet was cleaned -up. Thus `yas/undo-expand-snippet' can clean it up properly. - -TODO: describe the rest of the fields" - (groups nil) - (exit-marker nil) - (id (yas/snippet-next-id) :read-only t) - (overlay nil) - (saved-buffer-undo-list nil) - (end-marker nil)) - -(defstruct (yas/group (:constructor yas/make-group (primary-field snippet))) - "A group contains a list of field with the same number." - primary-field - (fields (list primary-field)) - (next nil) - (prev nil) - snippet) -(defstruct (yas/field - (:constructor yas/make-field (overlay number value transform))) - "A field in a snippet." - overlay - number - transform - value) (defstruct (yas/snippet-table (:constructor yas/make-snippet-table ())) "A table to store snippets for a perticular mode." (hash (make-hash-table :test 'equal)) + (default-directory nil) (parent nil)) -(defun yas/snippet-valid? (snippet) - "See if snippet is valid (ie. still alive)." - (and (not (null snippet)) - (not (null (yas/snippet-overlay snippet))) - (not (null (overlay-start (yas/snippet-overlay snippet)))))) - -(defun yas/snippet-add-field (snippet field) - "Add FIELD to SNIPPET." - (let ((group (find field - (yas/snippet-groups snippet) - :test - '(lambda (field group) - (and (not (null (yas/field-number field))) - (not (null (yas/group-number group))) - (= (yas/field-number field) - (yas/group-number group))))))) - (if group - (yas/group-add-field group field) - (push (yas/make-group field snippet) - (yas/snippet-groups snippet))))) - -(defun yas/group-value (group) - "Get the default value of the field group." - (or (yas/field-value - (yas/group-primary-field group)) - "")) -(defun yas/group-number (group) - "Get the number of the field GROUP." - (yas/field-number - (yas/group-primary-field group))) -(defun yas/group-add-field (group field) - "Add a FIELD to the field GROUP. If the value of the primary -field is nil and that of the field is not nil, the field is set -as the primary field of the group." - (push field (yas/group-fields group)) - (when (and (null (yas/field-value (yas/group-primary-field group))) - (yas/field-value field)) - (setf (yas/group-primary-field group) field))) - -(defun yas/snippet-field-compare (field1 field2) - "Compare two fields. The field with a number is sorted first. -If they both have a number, compare through the number. If neither -have, compare through the start point of the overlay." - (let ((n1 (yas/field-number field1)) - (n2 (yas/field-number field2))) - (if n1 - (if n2 - (< n1 n2) - t) - (if n2 - nil - (< (overlay-start (yas/field-overlay field1)) - (overlay-start (yas/field-overlay field2))))))) - (defun yas/template-condition-predicate (condition) (condition-case err (save-excursion @@ -414,40 +553,62 @@ have, compare through the start point of the overlay." (error-message-string err))) nil)))) -(defun yas/filter-templates-by-condition (templates) - "Filter the templates using the condition. The rules are: - * If the template has no condition, it is kept. - * If the template's condition eval to non-nil, it is kept. - * Otherwise (eval error or eval to nil) it is filtered." - (remove-if-not '(lambda (pair) - (let ((condition (yas/template-condition (cdr pair)))) - (if (null condition) - (if yas/require-template-condition - nil - t) - (let ((result - (yas/template-condition-predicate condition))) - (if yas/require-template-condition - (if (eq yas/require-template-condition t) - result - (eq result yas/require-template-condition)) - result))))) - templates)) +(defun yas/filter-templates-by-condition (templates) + "Filter the templates using the applicable condition. + +TEMPLATES is a list of cons (KEY . TEMPLATE) where KEY 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 + (remove-if-not #'(lambda (pair) + (let* ((condition (yas/template-condition (cdr pair))) + (result (and condition + (yas/template-condition-predicate condition)))) + (cond ((eq requirement t) + result) + (t + (eq requirement result))))) + templates)))) (defun yas/snippet-table-fetch (table key) "Fetch a snippet binding to KEY from TABLE. If not found, fetch from parent if any." - (let ((templates (yas/filter-templates-by-condition - (gethash key (yas/snippet-table-hash table))))) - (when (and (null templates) - (not (null (yas/snippet-table-parent table)))) - (setq templates (yas/snippet-table-fetch - (yas/snippet-table-parent table) - key))) - templates)) + (when table + (let* ((unfiltered (gethash key (yas/snippet-table-hash table))) + (templates (yas/filter-templates-by-condition unfiltered))) + (when (and (null templates) + (not (null (yas/snippet-table-parent table)))) + (setq templates (yas/snippet-table-fetch + (yas/snippet-table-parent table) + key))) + templates))) + +(defun yas/snippet-table-all-templates (table) + (when table + (let ((acc)) + (maphash #'(lambda (key templates) + (setq acc (append acc templates))) + (yas/snippet-table-hash table)) + (append (yas/filter-templates-by-condition acc) + (yas/snippet-table-all-templates (yas/snippet-table-parent table)))))) + +(defun yas/snippet-table-all-keys (table) + (when table + (let ((acc)) + (maphash #'(lambda (key templates) + (when (yas/filter-templates-by-condition templates) + (push key acc))) + (yas/snippet-table-hash table)) + (append acc + (yas/snippet-table-all-keys (yas/snippet-table-parent table)))))) + (defun yas/snippet-table-store (table full-key key template) - "Store a snippet template in the table." + "Store a snippet template in the TABLE." (puthash key (yas/modify-alist (gethash key (yas/snippet-table-hash table)) @@ -457,7 +618,8 @@ fetch from parent if any." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Internal functions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; + (defun yas/ensure-minor-mode-priority () "Ensure that the key binding of yas/minor-mode takes priority." (unless (eq 'yas/minor-mode @@ -478,47 +640,60 @@ a list of modes like this to help the judgement." (find mode yas/known-modes))) (defun yas/eval-string (string) + ;; TODO: This is a possible optimization point, the expression could + ;; be stored in cons format instead of string, "Evaluate STRING and convert the result to string." - (condition-case err - (save-excursion - (save-restriction - (save-match-data - (widen) - (format "%s" (eval (read string)))))) - (error (format "(error in elisp evaluation: %s)" - (error-message-string err))))) -(defun yas/calculate-field-value (field value) - "Calculate the value of the field. If there's a transform -for this field, apply it. Otherwise, the value is returned -unmodified." - (let ((text value) - (transform (yas/field-transform field))) - (if transform - (yas/eval-string transform) - text))) -(defsubst yas/replace-all (from to) - "Replace all occurance from FROM to TO." - (goto-char (point-min)) - (while (search-forward from nil t) - (replace-match to t t))) + (let ((retval (catch 'yas/exception + (condition-case err + (save-excursion + (save-restriction + (save-match-data + (widen) + (let ((result (eval (read string)))) + (when result + (format "%s" result)))))) + (error (if yas/good-grace + "[yas] elisp error!" + (error (format "[yas] elisp error: %s" + (error-message-string err))))))))) + (when (and (consp retval) + (eq 'yas/exception (car retval))) + (error (cdr retval))) + retval)) -(defun yas/snippet-table (mode) - "Get the snippet table corresponding to MODE." - (let ((table (gethash mode yas/snippet-tables))) +(defun yas/snippet-table-get-create (mode &optional directory) + "Get the snippet table corresponding to MODE. + +Optional DIRECTORY gets recorded as the default directory to +search for snippet files if the retrieved/created table didn't +already have such a property." + (let ((table (gethash mode + yas/snippet-tables))) (unless table (setq table (yas/make-snippet-table)) (puthash mode table yas/snippet-tables)) + (unless (or (not directory) (yas/snippet-table-default-directory table)) + (setf (yas/snippet-table-default-directory table) + directory)) table)) -(defsubst yas/current-snippet-table () + +(defun yas/current-snippet-table (&optional mode-symbol dont-search-parents) "Get the snippet table for current major-mode." - (yas/snippet-table major-mode)) + (let ((mode (or mode-symbol + major-mode))) + (or (gethash mode + yas/snippet-tables) + (and (not dont-search-parents) + (get mode 'derived-mode-parent) + (yas/current-snippet-table (get mode 'derived-mode-parent)))))) (defun yas/menu-keymap-for-mode (mode) "Get the menu keymap correspondong to MODE." (let ((keymap (gethash mode yas/menu-table))) (unless keymap (setq keymap (make-sparse-keymap)) - (puthash mode keymap yas/menu-table)) + (puthash mode + keymap yas/menu-table)) keymap)) (defun yas/current-key () @@ -545,355 +720,19 @@ the template of a snippet in the current snippet-table." start end))) -(defun yas/synchronize-fields (field-group) - "Update all fields' text according to the primary field." - (when (yas/snippet-valid? (yas/group-snippet field-group)) - (save-excursion - (let* ((inhibit-modification-hooks t) - (primary (yas/group-primary-field field-group)) - (text (yas/current-field-text primary))) - ;; For all fields except the primary, replace their text - (yas/replace-fields-with-value (remove-if #'(lambda (field) - (equal field primary)) - (yas/group-fields field-group)) - text))))) -(defun yas/current-field-text (field) - (let ((primary-overlay (yas/field-overlay field))) - (when primary-overlay - (buffer-substring-no-properties (overlay-start primary-overlay) - (overlay-end primary-overlay))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Template-related and snippet loading functions - -(defun yas/overlay-modification-hook (overlay after? beg end &optional length) - "Modification hook for snippet field overlay." - (when (and after? (not undo-in-progress)) - (yas/synchronize-fields (overlay-get overlay 'yas/group)))) - -(defun yas/overlay-insert-in-front-hook (overlay after? beg end &optional length) - "Hook for snippet overlay when text is inserted in front of a snippet field." - (when after? - (let ((field-group (overlay-get overlay 'yas/group)) - (inhibit-modification-hooks t)) - (when (not (overlay-get overlay 'yas/modified?)) - (overlay-put overlay 'yas/modified? t) - (when (> (overlay-end overlay) end) - (save-excursion - (goto-char end) - (delete-char (- (overlay-end overlay) end))))) - (yas/synchronize-fields field-group)))) - -(defun yas/overlay-maybe-insert-behind-hook (overlay after? beg end &optional length) - "Insert behind hook sometimes doesn't get called. I don't know why. -So I add modification hook in the big overlay and try to detect `insert-behind' -event manually." - (when after? - (cond ((and (= beg end) - (> length 0) - (= (overlay-start overlay) - (overlay-end overlay))) - (yas/exit-snippet (overlay-get overlay 'yas/snippet-reference))) - ((and (= length 0) - (> end beg) - (null (yas/current-snippet-overlay beg)) - (not (bobp))) - (let ((field-overlay (yas/current-snippet-overlay (1- beg)))) - (if field-overlay - (when (= beg (overlay-end field-overlay)) - (move-overlay field-overlay - (overlay-start field-overlay) - end) - (yas/synchronize-fields (overlay-get field-overlay 'yas/group))) - (let ((snippet (yas/snippet-of-current-keymap)) - (done nil)) - (if snippet - (do* ((groups (yas/snippet-groups snippet) (cdr groups)) - (group (car groups) (car groups))) - ((or (null groups) - done)) - (setq field-overlay (yas/field-overlay - (yas/group-primary-field group))) - (when (and (= (overlay-start field-overlay) - (overlay-end field-overlay)) - (= beg - (overlay-start field-overlay))) - (move-overlay field-overlay beg end) - (yas/synchronize-fields group) - (setq done t))))))))))) - -(defun yas/remove-recent-undo-from-history () - (let ((undo (car buffer-undo-list))) - (while (null undo) - (setq buffer-undo-list (cdr buffer-undo-list)) - (setq undo (car buffer-undo-list))) - ;; Remove this undo operation record - (setq buffer-undo-list (cdr buffer-undo-list)))) - -(defun yas/undo-expand-snippet (start key snippet) - "Undo a snippet expansion. Delete the overlays. This undo can't be -redo-ed." - (yas/remove-recent-undo-from-history) - (let ((inhibit-modification-hooks t) - (buffer-undo-list t)) - (yas/exit-snippet snippet) - (goto-char start) - (delete-char (- (yas/snippet-end-marker snippet) - start)) - (insert key))) - -(defun yas/replace-fields-with-value (fields text) - "In all of the fields of the snippet group GROUP fields, delete -whatever value (string) existed and insert TEXT instead. - -The string to insert is calculated according to -`yas/calculate-field-value', which might insert different text -for each field." - (dolist (field fields) - (let* ((overlay (yas/field-overlay field)) - (start (overlay-start overlay)) - (end (overlay-end overlay)) - (length (- end start))) - (goto-char start) - (insert (yas/calculate-field-value field text)) - (if (eq length 0) - (move-overlay overlay start (point))) - (delete-char length)))) - -(defun yas/expand-snippet (start end template) - "Expand snippet at current point. Text between START and END -will be deleted before inserting template." - (run-hooks 'yas/before-expand-snippet-hook) - - (goto-char start) - - (let ((key (buffer-substring-no-properties start end)) - (original-undo-list buffer-undo-list) ;; save previous undo information - (inhibit-modification-hooks t) - (length (- end start)) - (column (current-column))) - (save-restriction - (narrow-to-region start start) - - (setq buffer-undo-list t) ;; disable undo for a short while - (insert template) - - ;; Step 1: do necessary indent - (when yas/indent-line - (let* ((indent (if indent-tabs-mode - (concat (make-string (/ column tab-width) ?\t) - (make-string (% column tab-width) ?\ )) - (make-string column ?\ )))) - (goto-char (point-min)) - (while (and (zerop (forward-line)) - (= (current-column) 0)) - (insert indent)))) - - ;; Step 2: protect backslash and backquote - (yas/replace-all "\\\\" yas/escape-backslash) - (yas/replace-all "\\`" yas/escape-backquote) - - ;; Step 3: evaluate all backquotes - (goto-char (point-min)) - (while (re-search-forward "`\\([^`]*\\)`" nil t) - ;; go back so that (current-column) in elisp code evaluation - ;; will calculate to a meaningful value - (goto-char (match-beginning 0)) - (replace-match (yas/eval-string (match-string-no-properties 1)) - t t)) - - ;; Step 4: protect all escapes, including backslash and backquot - ;; which may be produced in Step 3 - (yas/replace-all "\\\\" yas/escape-backslash) - (yas/replace-all "\\`" yas/escape-backquote) - (yas/replace-all "\\$" yas/escape-dollar) - - ;; Step 5: Create and register a brand new snippet in the local - ;; `yas/registered-snippets' var. Create fields. - (let ((snippet (yas/register-snippet (yas/make-snippet)))) - (goto-char (point-min)) - (while (re-search-forward yas/field-regexp nil t) - (let ((number (or (match-string-no-properties 1) - (match-string-no-properties 2))) - (transform nil) - (value (match-string-no-properties 3))) - (when (eq (elt value 0) ?\$) - (setq transform (substring value 1)) - (setq value nil)) - (if (and number - (string= "0" number)) - (progn - (replace-match "") - (setf (yas/snippet-exit-marker snippet) - (copy-marker (point) t))) - (yas/snippet-add-field - snippet - (yas/make-field - (make-overlay (match-beginning 0) (match-end 0)) - (and number (string-to-number number)) - value - transform))))) - - ;; Step 6: Sort and link each field group - (setf (yas/snippet-groups snippet) - (sort (yas/snippet-groups snippet) - '(lambda (group1 group2) - (yas/snippet-field-compare - (yas/group-primary-field group1) - (yas/group-primary-field group2))))) - (let ((prev nil)) - (dolist (group (yas/snippet-groups snippet)) - (setf (yas/group-prev group) prev) - (when prev - (setf (yas/group-next prev) group)) - (setq prev group))) - - ;; Step 7: Create keymap overlay for snippet - (let ((overlay (make-overlay (point-min) - (point-max) - nil - nil - t))) - (overlay-put overlay - 'modification-hooks - yas/keymap-overlay-modification-hooks) - (overlay-put overlay - 'insert-behind-hooks - yas/keymap-overlay-modification-hooks) - (overlay-put overlay 'keymap yas/keymap) - (overlay-put overlay 'yas/snippet-reference snippet) - (setf (yas/snippet-overlay snippet) overlay) - (setf (yas/snippet-end-marker snippet) (overlay-end overlay))) - - ;; Step 8: Replace fields with default values - (dolist (group (yas/snippet-groups snippet)) - (yas/replace-fields-with-value (yas/group-fields group) - (yas/group-value group))) - - ;; Step 9: restore all escape characters - (yas/replace-all yas/escape-dollar "$") - (yas/replace-all yas/escape-backquote "`") - (yas/replace-all yas/escape-backslash "\\") - - ;; Step 10: Set up properties of overlays - (dolist (group (yas/snippet-groups snippet)) - (let ((overlay (yas/field-overlay - (yas/group-primary-field group)))) - (overlay-put overlay 'yas/snippet snippet) - (overlay-put overlay 'yas/group group) - (overlay-put overlay 'yas/modified? nil) - (overlay-put overlay 'modification-hooks yas/overlay-modification-hooks) - (overlay-put overlay 'insert-in-front-hooks yas/overlay-insert-in-front-hooks) - (overlay-put overlay 'face 'yas/field-highlight-face) - (dolist (field (yas/group-fields group)) - (unless (equal overlay (yas/field-overlay field)) - (overlay-put (yas/field-overlay field) - 'face - 'yas/mirror-highlight-face))))) - - ;; Step 11: move to end and make sure exit-marker exist - (goto-char (point-max)) - (unless (yas/snippet-exit-marker snippet) - (setf (yas/snippet-exit-marker snippet) (copy-marker (point) t))) - - ;; Step 12: Construct undo information - (unless (eq original-undo-list t) - (add-to-list 'original-undo-list - `(apply yas/undo-expand-snippet - ,(point-min) - ,key - ,snippet))) - - ;; Step 13: remove the trigger key - (widen) - (delete-char length) - - ;; Step 14: Do necessary indenting - (save-excursion - (let ((ovst (overlay-start (yas/snippet-overlay snippet))) - (oven (copy-marker - (1+ (overlay-end (yas/snippet-overlay snippet)))))) - (when (and ovst oven) - (goto-char ovst) - (while (re-search-forward "$>" oven t) - (replace-match "") - (indent-according-to-mode))))) - - ;; Step 15: Restore undo information, and also save it for future use. - (setf (yas/snippet-saved-buffer-undo-list snippet) original-undo-list) - (setq buffer-undo-list original-undo-list) - - ;; Step 16: place the cursor at a proper place - (let ((groups (yas/snippet-groups snippet)) - (exit-marker (yas/snippet-exit-marker snippet))) - (if groups - (goto-char (overlay-start - (yas/field-overlay - (yas/group-primary-field - (car groups))))) - ;; no need to call exit-snippet, since no overlay created. - (yas/exit-snippet snippet))) - - )))) - -(defun yas/current-snippet-overlay (&optional point) - "Get the most proper overlay which is belongs to a snippet." - (let ((point (or point (point))) - (snippet-overlay nil)) - (dolist (overlay (overlays-at point)) - ;; appending and removing-duplicates fixes a bug when overlays - ;; are not recognized because point is really at the end - (when (overlay-get overlay 'yas/snippet) - (if (null snippet-overlay) - (setq snippet-overlay overlay) - (when (> (yas/snippet-id (overlay-get overlay 'yas/snippet)) - (yas/snippet-id (overlay-get snippet-overlay 'yas/snippet))) - (setq snippet-overlay overlay))))) - snippet-overlay)) - -(defun yas/snippet-of-current-keymap (&optional point) - "Get the snippet holding the snippet keymap under POINT." - (let ((point (or point (point))) - (keymap-snippet nil) - (snippet nil)) - (dolist (overlay (overlays-at point)) - (setq snippet (overlay-get overlay 'yas/snippet-reference)) - (when snippet - (if (null keymap-snippet) - (setq keymap-snippet snippet) - (when (> (yas/snippet-id snippet) - (yas/snippet-id keymap-snippet)) - (setq keymap-snippet snippet))))) - keymap-snippet)) - -(defun yas/current-overlay-for-navigation () - "Get current overlay for navigation. Might be overlay at current or previous point." - (let ((overlay1 (yas/current-snippet-overlay)) - (overlay2 (if (bobp) - nil - (yas/current-snippet-overlay (- (point) 1))))) - (if (null overlay1) - overlay2 - (if (or (null overlay2) - (eq (overlay-get overlay1 'yas/snippet) - (overlay-get overlay2 'yas/snippet))) - overlay1 - (if (> (yas/snippet-id (overlay-get overlay2 'yas/snippet)) - (yas/snippet-id (overlay-get overlay1 'yas/snippet))) - overlay2 - overlay1))))) - -(defun yas/navigate-group (group next?) - "Go to next of previous field group. Exit snippet if none." - (let ((target (if next? - (yas/group-next group) - (yas/group-prev group)))) - (if target - (goto-char (overlay-start - (yas/field-overlay - (yas/group-primary-field target)))) - (yas/exit-snippet (yas/group-snippet group))))) - -(defun yas/parse-template (&optional file-name) +(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. + +Return a snippet-definition, i.e. a list + + (KEY TEMPLATE NAME CONDITION GROUP ENV) + If the buffer contains a line of \"# --\" then the contents above this line are ignored. Variables can be set above this line through the syntax: @@ -907,48 +746,62 @@ Here's a list of currently recognized variables: * condition * key * group + * env #name: #include \"...\" # -- #include \"$1\"" (goto-char (point-min)) - (let ((name file-name) template bound condition key group) + (let* ((name (and file (file-name-nondirectory file))) + (key name) + template + bound + condition + group + env) (if (re-search-forward "^# --\n" nil t) - (progn (setq template - (buffer-substring-no-properties (point) - (point-max))) - (setq bound (point)) - (goto-char (point-min)) - (while (re-search-forward "^#\\([^ ]+?\\) *: *\\(.*\\)$" bound t) - (when (string= "name" (match-string-no-properties 1)) - (setq name (match-string-no-properties 2))) - (when (string= "condition" (match-string-no-properties 1)) - (setq condition (read (match-string-no-properties 2)))) - (when (string= "group" (match-string-no-properties 1)) - (setq group (match-string-no-properties 2))) - (when (string= "key" (match-string-no-properties 1)) - (setq key (match-string-no-properties 2))))) + (progn (setq template + (buffer-substring-no-properties (point) + (point-max))) + (setq bound (point)) + (goto-char (point-min)) + (while (re-search-forward "^# *\\([^ ]+?\\) *: *\\(.*\\)$" bound t) + (when (string= "name" (match-string-no-properties 1)) + (setq name (match-string-no-properties 2))) + (when (string= "condition" (match-string-no-properties 1)) + (setq condition (read (match-string-no-properties 2)))) + (when (string= "group" (match-string-no-properties 1)) + (setq group (match-string-no-properties 2))) + (when (string= "env" (match-string-no-properties 1)) + (setq env (match-string-no-properties 2))) + (when (string= "key" (match-string-no-properties 1)) + (setq key (match-string-no-properties 2))))) (setq template - (buffer-substring-no-properties (point-min) (point-max)))) - (list key template name condition group))) + (buffer-substring-no-properties (point-min) (point-max)))) + (list key template name condition group env file))) -(defun yas/directory-files (directory file?) - "Return directory files or subdirectories in full path." +(defun yas/subdirs (directory &optional file?) + "Return subdirs or files of DIRECTORY according to FILE?." (remove-if (lambda (file) (or (string-match "^\\." (file-name-nondirectory file)) + (string-match "~$" + (file-name-nondirectory file)) (if file? (file-directory-p file) (not (file-directory-p file))))) (directory-files directory t))) (defun yas/make-menu-binding (template) - (lexical-let ((template template)) - (lambda () - (interactive) - (yas/expand-snippet (point) - (point) - template)))) + `(lambda () (interactive) (yas/expand-from-menu ,template))) + +(defun yas/expand-from-menu (template) + (let ((where (if mark-active + (cons (region-beginning) (region-end)) + (cons (point) (point))))) + (yas/expand-snippet (car where) + (cdr where) + (yas/template-content template)))) (defun yas/modify-alist (alist key value) "Modify ALIST to map KEY to VALUE. return the new alist." @@ -959,77 +812,166 @@ Here's a list of currently recognized variables: (setcdr pair value) alist))) -(defun yas/fake-keymap-for-popup (templates) - "Create a fake keymap for popup menu usage." - (cons 'keymap - (mapcar (lambda (pair) - (let* ((template (cdr pair)) - (name (yas/template-name template)) - (content (yas/template-content template))) - (list content 'menu-item name t))) - templates))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Popping up for keys and templates +;; +(defun yas/prompt-for-template (templates &optional prompt) + "Interactively choose a template from the list TEMPLATES. -(defun yas/point-to-coord (&optional point) - "Get the xoffset/yoffset information of POINT. -If POINT is not given, default is to current point. -If `posn-at-point' is not available (like in Emacs 21.3), -t is returned simply." - (if (fboundp 'posn-at-point) - (let ((x-y (posn-x-y (posn-at-point (or point (point)))))) - (list (list (+ (car x-y) 10) - (+ (cdr x-y) 20)) - (selected-window))) - t)) +TEMPLATES is a list of `yas/template'." + (let ((template (some #'(lambda (fn) + (funcall fn (or prompt "Choose a snippet: ") + templates #'(lambda (template) + (yas/template-name template)))) + yas/prompt-functions))) + template)) -(defun yas/x-popup-menu-for-template (templates) - "Show a popup menu listing templates to let the user select one." - (car (x-popup-menu (yas/point-to-coord) - (yas/fake-keymap-for-popup templates)))) -(defun yas/text-popup-for-template (templates) - "Can't display popup menu in text mode. Just select the first one." - (yas/template-content (cdar templates))) -(defun yas/dropdown-list-popup-for-template (templates) - "Use dropdown-list.el to popup for templates. Better than the -default \"select first\" behavior of `yas/text-popup-for-template'. -You can also use this in window-system. +(defun yas/prompt-for-keys (keys &optional prompt) + "Interactively choose a template key from the list KEYS." + (if keys + (some #'(lambda (fn) + (funcall fn (or prompt "Choose a snippet key: ") keys)) + yas/prompt-functions))) -NOTE: You need to download and install dropdown-list.el to use this." - (if (fboundp 'dropdown-list) - (let ((n (dropdown-list (mapcar (lambda (i) - (yas/template-name - (cdr i))) - templates)))) - (if n - (yas/template-content - (cdr (nth n templates))) - nil)) - (error "Please download and install dropdown-list.el to use this"))) +(defun yas/x-prompt (prompt choices &optional display-fn) + (when (and window-system choices) + (let ((keymap (cons 'keymap + (cons + prompt + (mapcar (lambda (choice) + (list choice + 'menu-item + (if display-fn + (funcall display-fn choice) + choice) + t)) + choices))))) + (when (cdr keymap) + (car (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) + keymap)))))) -(defun yas/popup-for-template (templates) - (if window-system - (funcall yas/window-system-popup-function templates) - (funcall yas/text-popup-function templates))) +(defun yas/ido-prompt (prompt choices &optional display-fn) + (when (and (featurep 'ido) + ido-mode) + (let* ((formatted-choices (or (and display-fn + (mapcar display-fn choices)) + choices)) + (chosen (and formatted-choices + (ido-completing-read prompt + formatted-choices + nil + 'require-match + nil + nil)))) + (when chosen + (nth (position chosen formatted-choices) choices))))) +(defun yas/dropdown-prompt (prompt choices &optional display-fn) + (when (featurep 'dropdown-list) + (let* ((formatted-choices (or (and display-fn + (mapcar display-fn choices)) + choices)) + (chosen (and formatted-choices + (nth (dropdown-list formatted-choices) + choices)))) + chosen))) + +(defun yas/completing-prompt (prompt choices &optional display-fn) + (let* ((formatted-choices (or (and display-fn + (mapcar display-fn choices)) + choices)) + (chosen (and formatted-choices + (completing-read prompt + formatted-choices + nil + 'require-match + nil + nil)))) + (when chosen + (nth (position chosen formatted-choices) choices)))) + +(defun yas/no-prompt (prompt choices &optional display-fn) + (first choices)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Loading snippets from files +;; (defun yas/load-directory-1 (directory &optional parent) - "Really do the job of loading snippets from a directory -hierarchy." + + "Recursively load snippet templates from DIRECTORY." + (let ((mode-sym (intern (file-name-nondirectory directory))) - (snippets nil)) + (snippet-defs nil)) (with-temp-buffer - (dolist (file (yas/directory-files directory t)) + (dolist (file (yas/subdirs directory 'no-subdirs-just-files)) (when (file-readable-p file) (insert-file-contents file nil nil nil t) - (let* ((snip (yas/parse-template)) - (key (or (car snip) - (file-name-nondirectory file))) - (snip (cdr snip))) - (push (cons key snip) snippets))))) + (push (yas/parse-template file) + snippet-defs)))) (yas/define-snippets mode-sym - snippets - parent) - (dolist (subdir (yas/directory-files directory nil)) + snippet-defs + parent + directory) + (dolist (subdir (yas/subdirs directory)) (yas/load-directory-1 subdir mode-sym)))) +(defun yas/load-directory (directory) + "Load snippet definition from a directory hierarchy. + +Below the top-level directory, each directory is a mode +name. And under each subdirectory, each file is a definition +of a snippet. The file name is the trigger key and the +content of the file is the template." + (interactive "DSelect the root directory: ") + (unless (file-directory-p directory) + (error "Error %s not a directory" directory)) + (add-to-list 'yas/root-directory directory) + (dolist (dir (yas/subdirs directory)) + (yas/load-directory-1 dir)) + (when (interactive-p) + (message "done."))) + +(defun yas/reload-all () + "Reload all snippets and rebuild the YASnippet menu. " + + (interactive) + (let ((restore-global-mode nil) + (restore-minor-mode nil)) + (setq yas/snippet-tables (make-hash-table)) + (setq yas/menu-table (make-hash-table)) + (setf (cdr yas/minor-mode-menu) nil) + (setf (cdr yas/minor-mode-map) nil) + (when yas/global-mode + (yas/global-mode -1) + (setq restore-global-mode t)) + + (when yas/minor-mode + (yas/minor-mode -1) + (setq restore-minor-mode t)) + + (yas/init-keymap-and-menu) + + (if yas/root-directory + (if (listp yas/root-directory) + (dolist (directory yas/root-directory) + (yas/load-directory directory)) + (yas/load-directory yas/root-directory)) + (call-interactively 'yas/load-directory)) + + + (when restore-minor-mode + (yas/minor-mode 1)) + + (when restore-global-mode + (yas/global-mode 1)) + + (message "done."))) + (defun yas/quote-string (string) "Escape and quote STRING. foo\"bar\\! -> \"foo\\\"bar\\\\!\"" @@ -1039,30 +981,45 @@ foo\"bar\\! -> \"foo\\\"bar\\\\!\"" string t) "\"")) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Yasnipept Bundle + +(defun yas/initialize () + "For backward compatibility, enable `yas/minor-mode' globally" + (yas/global-mode 1)) (defun yas/compile-bundle - (&optional yasnippet yasnippet-bundle snippet-roots code) + (&optional yasnippet yasnippet-bundle snippet-roots code dropdown) "Compile snippets in SNIPPET-ROOTS to a single bundle file. -SNIPPET-ROOTS is a list of root directories that contains the snippets -definition. YASNIPPET is the yasnippet.el file path. YASNIPPET-BUNDLE -is the output file of the compile result. CODE is the code you would -like to used to initialize yasnippet. Here's the default value for -all the parameters: +SNIPPET-ROOTS is a list of root directories that contains the +snippets definition. YASNIPPET is the yasnippet.el file +path. YASNIPPET-BUNDLE is the output file of the compile +result. CODE is the code you would like to used to initialize +yasnippet. Last optional argument DROPDOWN is the filename of the +dropdown-list.el library. + +Here's the default value for all the parameters: (yas/compile-bundle \"yasnippet.el\" \"./yasnippet-bundle.el\" '(\"snippets\") - \"(yas/initialize)\")" + \"(yas/initialize)\") + +.. + +" (when (null yasnippet) (setq yasnippet "yasnippet.el")) (when (null yasnippet-bundle) (setq yasnippet-bundle "./yasnippet-bundle.el")) (when (null snippet-roots) (setq snippet-roots '("snippets"))) + (when (null dropdown) + (setq dropdown "dropdown-list.el")) (when (null code) (setq code (concat "(yas/initialize-bundle)" - "\n;;;###autoload" ; break through so that won't - "(require 'yasnippet-bundle)"))) ; be treated as magic comment + "\n;;;###autoload" ; break through so that won't + "(require 'yasnippet-bundle)"))) ; be treated as magic comment (let ((dirs (or (and (listp snippet-roots) snippet-roots) (list snippet-roots))) @@ -1073,14 +1030,18 @@ all the parameters: "Yet another snippet extension (Auto compiled bundle)\n") (insert-file-contents yasnippet) (goto-char (point-max)) + (insert "\n") + (when dropdown + (insert-file-contents dropdown)) + (goto-char (point-max)) (insert ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n") (insert ";;;; Auto-generated code ;;;;\n") (insert ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n") (insert "(defun yas/initialize-bundle ()\n" " \"Initialize YASnippet and load snippets in the bundle.\"" - " (yas/initialize)\n") + " (yas/global-mode 1)\n") (flet ((yas/define-snippets - (mode snippets &optional parent) + (mode snippets &optional parent directory) (with-current-buffer bundle-buffer (insert ";;; snippets for " (symbol-name mode) "\n") (insert "(yas/define-snippets '" (symbol-name mode) "\n") @@ -1107,9 +1068,12 @@ all the parameters: (insert (if parent (concat "'" (symbol-name parent)) "nil") + ;; (if directory + ;; (concat "\"" directory "\"") + ;; "nil") ")\n\n")))) (dolist (dir dirs) - (dolist (subdir (yas/directory-files dir nil)) + (dolist (subdir (yas/subdirs dir)) (yas/load-directory-1 subdir nil)))) (insert ")\n\n" code "\n") @@ -1125,68 +1089,35 @@ all the parameters: (save-buffer)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; User level functions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; User level functions +;;; + (defun yas/about () (interactive) (message (concat "yasnippet (version " yas/version - ") -- pluskid "))) -(defun yas/reload-all () - "Reload all snippets." - (interactive) - (if yas/root-directory - (if (listp yas/root-directory) - (dolist (directory yas/root-directory) - (yas/load-directory directory)) - (yas/load-directory yas/root-directory)) - (call-interactively 'yas/load-directory)) - (message "done.")) + ") -- pluskid /joaotavora "))) -(defun yas/load-directory (directory) - "Load snippet definition from a directory hierarchy. -Below the top-level directory, each directory is a mode -name. And under each subdirectory, each file is a definition -of a snippet. The file name is the trigger key and the -content of the file is the template." - (interactive "DSelect the root directory: ") - (unless (file-directory-p directory) - (error "Error %s not a directory" directory)) - (add-to-list 'yas/root-directory directory) - (dolist (dir (yas/directory-files directory nil)) - (yas/load-directory-1 dir)) - (when (interactive-p) - (message "done."))) - -(defun yas/initialize () - "Do necessary initialization." - (add-hook 'after-change-major-mode-hook - 'yas/minor-mode-auto-on) - (dolist (hook yas/extra-mode-hooks) - (add-hook hook - 'yas/minor-mode-auto-on)) - (add-hook 'yas/minor-mode-on-hook - 'yas/ensure-minor-mode-priority) - (when yas/use-menu - (define-key-after - (lookup-key global-map [menu-bar]) - [yasnippet] - (cons "YASnippet" yas/menu-keymap) - 'buffer))) - -(defun yas/define-snippets (mode snippets &optional parent-mode) +(defun yas/define-snippets (mode snippets &optional parent-mode directory) "Define snippets for MODE. SNIPPETS is a list of -snippet definition, of the following form: +snippet definitions, each taking the following form: (KEY TEMPLATE NAME CONDITION GROUP) -or the NAME, CONDITION or GROUP may be omitted. The optional 3rd -parameter can be used to specify the parent mode of MODE. That -is, when looking a snippet in MODE failed, it can refer to its -parent mode. The PARENT-MODE may not need to be a real mode." - (let ((snippet-table (yas/snippet-table mode)) +NAME, CONDITION or GROUP may be omitted. Optional PARENT-MODE +can be used to specify the parent mode of MODE. That is, when +looking a snippet in MODE failed, it can refer to its parent +mode. The PARENT-MODE does not need to be a real mode. + +Optional DIRECTORY is recorded in the `yas/snippet-table' if it +is created for the first time. Then, it becomes the default +directory to find snippet files. + + +" + (let ((snippet-table (yas/snippet-table-get-create mode directory)) (parent-table (if parent-mode - (yas/snippet-table parent-mode) + (yas/snippet-table-get-create parent-mode) nil)) (keymap (if yas/use-menu (yas/menu-keymap-for-mode mode) @@ -1200,7 +1131,7 @@ parent mode. The PARENT-MODE may not need to be a real mode." ,(yas/menu-keymap-for-mode parent-mode))))) (when (and yas/use-menu (yas/real-mode? mode)) - (define-key yas/menu-keymap (vector mode) + (define-key yas/minor-mode-menu (vector mode) `(menu-item ,(symbol-name mode) ,keymap))) (dolist (snippet snippets) (let* ((full-key (car snippet)) @@ -1208,20 +1139,28 @@ parent mode. The PARENT-MODE may not need to be a real mode." (name (or (nth 2 snippet) (file-name-extension full-key))) (condition (nth 3 snippet)) (group (nth 4 snippet)) - (template (yas/make-template (nth 1 snippet) + (template (yas/make-template (nth 1 snippet) (or name key) - condition))) + condition + (nth 5 snippet) + (nth 6 snippet)))) (yas/snippet-table-store snippet-table full-key key template) (when yas/use-menu (let ((group-keymap keymap)) + ;; delete this entry from another group if already exists + ;; in some other group. An entry is considered as existing + ;; in another group if its name string-matches. + (yas/delete-from-keymap group-keymap name) + + ;; ... then add this entry to the correct group (when (and (not (null group)) (not (string= "" group))) (dolist (subgroup (mapcar #'make-symbol (split-string group "\\."))) - (let ((subgroup-keymap (lookup-key group-keymap + (let ((subgroup-keymap (lookup-key group-keymap (vector subgroup)))) (when (null subgroup-keymap) (setq subgroup-keymap (make-sparse-keymap)) @@ -1231,15 +1170,46 @@ parent mode. The PARENT-MODE may not need to be a real mode." (setq group-keymap subgroup-keymap)))) (define-key group-keymap (vector (make-symbol full-key)) `(menu-item ,(yas/template-name template) - ,(yas/make-menu-binding (yas/template-content - template)) + ,(yas/make-menu-binding template) :keys ,(concat key yas/trigger-symbol))))))))) +(defun yas/delete-from-keymap (keymap name) + "Recursively delete items name NAME from KEYMAP and its submenus. + +Skip any submenus named \"parent mode\"" + ;; First of all, r ecursively 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 (keymapp (fourth item)) + (stringp (third item)) + (not (string= (third item) + "parent mode"))) + (yas/delete-from-keymap (fourth item) name))) + (rest keymap)) + ;; + (when (keymapp keymap) + (let ((pos-in-keymap)) + (while (setq pos-in-keymap (position-if #'(lambda (item) + (and (listp item) + (or + ;; the menu item we want to delete + (and (eq 'menu-item (second item)) + (third item) + (and (string= (third item) name))) + ;; a stale subgroup + (and (keymapp (fourth item)) + (null (rest (fourth item))))))) + keymap)) + (setf (nthcdr pos-in-keymap keymap) + (nthcdr (+ 1 pos-in-keymap) keymap)))))) + (defun yas/set-mode-parent (mode parent) "Set parent mode of MODE to PARENT." (setf (yas/snippet-table-parent - (yas/snippet-table mode)) - (yas/snippet-table parent)) + (yas/snippet-table-get-create mode)) + (yas/snippet-table-get-create parent)) (when yas/use-menu (define-key (yas/menu-keymap-for-mode mode) (vector 'parent-mode) `(menu-item "parent mode" @@ -1255,366 +1225,1382 @@ 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'." (if (not first-time?) (let ((yas/fallback-behavior 'return-nil)) (yas/expand)) - (when (and (null (car buffer-undo-list)) - (eq 'apply - (car (cadr buffer-undo-list))) - (eq 'yas/undo-expand-snippet - (cadr (cadr buffer-undo-list)))) - (undo 1)) + (undo 1) nil)) -(defun yas/expand () +(defun yas/require-template-specific-condition-p () + "Decides if this buffer requests/requires snippet-specific +conditions to filter out potential expansions." + (if (eq 'always yas/buffer-local-condition) + 'always + (let ((local-condition (yas/template-condition-predicate + yas/buffer-local-condition))) + (and local-condition + (consp local-condition) + (eq 'require-snippet-condition (car local-condition)) + (symbolp (cdr local-condition)) + (cdr local-condition))))) + +(defun yas/expand (&optional field) "Expand a snippet." (interactive) - (let ((local-condition (yas/template-condition-predicate - yas/buffer-local-condition))) - (if local-condition - (let ((yas/require-template-condition - (if (and (consp local-condition) - (eq 'require-snippet-condition (car local-condition)) - (symbolp (cdr local-condition))) - (cdr local-condition) - nil))) - (multiple-value-bind (templates start end) (yas/current-key) - (if templates - (let ((template (if (null (cdr templates)) ; only 1 template - (yas/template-content (cdar templates)) - (yas/popup-for-template templates)))) - (if template - (progn (yas/expand-snippet start end template) - 'expanded) ; expanded successfully - 'interrupted)) ; interrupted by user - (if (eq yas/fallback-behavior 'return-nil) - nil ; return nil - (let* ((yas/minor-mode nil) - (command (key-binding yas/trigger-key))) - (when (commandp command) - (call-interactively command)))))))))) + (multiple-value-bind (templates start end) (if field + (save-restriction + (narrow-to-region (yas/field-start field) (yas/field-end field)) + (yas/current-key)) + (yas/current-key)) + (if templates + (let ((template (or (and (rest templates) ;; more than one + (yas/prompt-for-template (mapcar #'cdr templates))) + (cdar templates)))) + (when template + (yas/expand-snippet start + end + (yas/template-content template) + (yas/template-env template)))) + (if (eq yas/fallback-behavior 'return-nil) + nil ; return nil + (let* ((yas/minor-mode nil) + (command (key-binding (read-kbd-macro yas/trigger-key)))) + (when (commandp command) + (call-interactively command))))))) -(defun yas/next-field-group () - "Navigate to next field group. If there's none, exit the snippet." +(defun yas/insert-snippet (&optional no-condition) + "Choose a snippet to expand, pop-up a list of choices according +to `yas/prompt-function'. + +With prefix argument NO-CONDITION, bypass filtering of snippets +by condition." + (interactive "P") + (let* ((yas/buffer-local-condition (or (and no-condition + 'always) + yas/buffer-local-condition)) + (templates (mapcar #'cdr + (if yas/choose-keys-first + (let ((key (yas/prompt-for-keys (yas/snippet-table-all-keys (yas/current-snippet-table))))) + (when key + (yas/snippet-table-fetch (yas/current-snippet-table) key))) + (yas/snippet-table-all-templates (yas/current-snippet-table))))) + (template (and templates + (or (and (rest templates) ;; more than one template for same key + (yas/prompt-for-template templates)) + (car templates)))) + (where (if mark-active + (cons (region-beginning) (region-end)) + (cons (point) (point))))) + (if template + (yas/expand-snippet (car where) + (cdr where) + (yas/template-content template) + (yas/template-env template)) + (message "[yas] 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 ((overlay (yas/current-overlay-for-navigation))) - (if overlay - (yas/navigate-group (overlay-get overlay 'yas/group) t) - (let ((snippet (yas/snippet-of-current-keymap)) - (done nil)) - (if snippet - (do* ((groups (yas/snippet-groups snippet) (cdr groups)) - (group (car groups) (car groups))) - ((or (null groups) - done) - (unless done - (let* ((overlay (yas/snippet-overlay snippet)) - (keymap (overlay-get overlay 'keymap)) - (command nil)) - (overlay-put overlay 'keymap nil) - (overlay-put overlay 'yas/snippet-reference nil) - (setq command (key-binding yas/next-field-key)) - (when (commandp command) - (call-interactively command)) - (overlay-put overlay 'keymap keymap) - (overlay-put overlay 'yas/snippet-reference snippet)))) - (when (= (point) - (overlay-start - (yas/field-overlay - (yas/group-primary-field group)))) - (setq done t) - (yas/navigate-group group t)))))))) + (let* ((yas/buffer-local-condition 'always) + (templates (mapcar #'cdr + (if yas/choose-keys-first + (let ((key (yas/prompt-for-keys (yas/snippet-table-all-keys (yas/current-snippet-table)) + "Choose a snippet key to edit: "))) + (when key + (yas/snippet-table-fetch (yas/current-snippet-table) key))) + (yas/snippet-table-all-templates (yas/current-snippet-table))))) + (template (and templates + (or (and (rest templates) ;; more than one template for same key + (yas/prompt-for-template templates + "Choose a snippet template to edit: ")) + (car templates))))) -(defun yas/prev-field-group () - "Navigate to prev field group. If there's none, exit the snippet." + (when template + (let ((file (yas/template-file template))) + (cond ((and file (file-exists-p file)) + (find-file-other-window file) + (snippet-mode)) + (file + (message "Original file %s no longer exists!" file)) + (t + (message "This snippet was not loaded from a file!"))))))) + +(defun yas/guess-snippet-directory () + "Try to guess the suitable yassnippet based on `major-mode'" + (let ((loaded-root (or (and (listp yas/root-directory) + (first yas/root-directory)) + yas/root-directory)) + (mode major-mode) + (path)) + (when loaded-root + (while mode + (setq path (format "%s/%s" + mode + (or path + ""))) + (setq mode (get mode 'derived-mode-parent))) + (concat loaded-root + (unless (string-match "/$" loaded-root) "/") + path)))) + + +(defun yas/find-snippets (&optional same-window) + "Looks for snippets file in the current mode's directory. + +This can be used to create new snippets for the currently active +major mode." + (interactive "P") + (let* ((current-table (yas/current-snippet-table major-mode 'dont-search-parents)) + (parents-table (yas/current-snippet-table major-mode)) + (parents-directory (and parents-table + (yas/snippet-table-default-directory parents-table))) + (guessed-directory (or (and current-table + (yas/snippet-table-default-directory current-table)) + (yas/guess-snippet-directory) + default-directory)) + (buffer)) + (unless (file-exists-p guessed-directory) + (if (y-or-n-p (format "Guessed directory (%s) does not exist! Create? " guessed-directory)) + (make-directory guessed-directory 'also-make-parents) + (if parents-directory + (setq guessed-directory parents-directory) + (setq guessed-directory default-directory)))) + (let ((default-directory guessed-directory)) + (setq buffer (call-interactively (if same-window + 'find-file + 'find-file-other-window))) + (when buffer + (save-excursion + (set-buffer buffer) + (when (eq major-mode 'fundamental-mode) + (snippet-mode))))))) + + +(defun yas/compute-major-mode-and-parent (file &optional prompt-if-failed) + (let* ((file-dir (and file + (directory-file-name (file-name-directory file)))) + (major-mode-name (and file-dir + (file-name-nondirectory file-dir))) + (parent-file-dir (and file-dir + (directory-file-name (file-name-directory file-dir)))) + (parent-mode-name (and parent-file-dir + (file-name-nondirectory parent-file-dir))) + (major-mode-sym (or (and major-mode-name + (intern major-mode-name)) + (when prompt-if-failed + (read-from-minibuffer "[yas] Cannot auto-detect major mode! Enter a major mode: ")))) + (parent-mode-sym (and parent-mode-name + (intern parent-mode-name)))) + (if (fboundp major-mode-sym) + (cons major-mode-sym + (when (fboundp parent-mode-sym) + parent-mode-sym))))) + +(defun yas/load-snippet-buffer (&optional kill) + "Parse and load current buffer's snippet definition. + +With optional prefix argument KILL quit the window and buffer." + (interactive "P") + (if buffer-file-name + (let ((major-mode-and-parent (yas/compute-major-mode-and-parent buffer-file-name))) + (if major-mode-and-parent + (let* ((parsed (yas/parse-template buffer-file-name)) + (name (and parsed + (third parsed)))) + (when name + (yas/define-snippets (car major-mode-and-parent) + (list parsed) + (cdr major-mode-and-parent)) + (when (and (buffer-modified-p) + (y-or-n-p "Save snippet? ")) + (save-buffer)) + (if kill + (quit-window kill) + (message "[yas] Snippet \"%s\" loaded for %s." name (car major-mode-and-parent))))) + (message "[yas] Cannot load snippet for unknown major mode"))) + (message "Save the buffer as a file first!"))) + +(defun yas/tryout-snippet (&optional debug) + "Test current buffers's snippet template in other buffer." + (interactive "P") + (let* ((major-mode-and-parent (yas/compute-major-mode-and-parent buffer-file-name)) + (parsed (and major-mode-and-parent + (fboundp (car major-mode-and-parent)) + (yas/parse-template (symbol-name (car major-mode-and-parent))))) + (template (and parsed + (yas/make-template (second parsed) (third parsed) nil (sixth parsed) nil)))) + (cond (template + (let ((buffer-name (format "*YAS TEST: %s*" (yas/template-name template)))) + (set-buffer (switch-to-buffer buffer-name)) + (erase-buffer) + (setq buffer-undo-list nil) + (funcall (car major-mode-and-parent)) + (yas/expand-snippet (point-min) (point-max) (yas/template-content template) (yas/template-env template)) + (when debug + (add-hook 'post-command-hook 'yas/debug-some-vars 't 'local)))) + (t + (message "[yas] Coulnd not parse template!"))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; User convenience functions, for using in snippet definitions +;;; + +(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 (possibilities) + "Prompt for a string in the list POSSIBILITIES and return it." + (unless (or yas/moving-away-p + yas/modified-p) + (some #'(lambda (fn) + (funcall fn "Choose: " possibilities)) + yas/prompt-functions))) + +(defun yas/key-to-value (alist) + "Prompt for a string in the list POSSIBILITIES and return it." + (unless (or yas/moving-away-p + yas/modified-p) + (let ((key (read-key-sequence ""))) + (when (stringp key) + (or (cdr (find key alist :key #'car :test #'string=)) + key))))) + +(defun yas/throw (text) + "Throw a yas/exception with TEXT as the reason." + (throw 'yas/exception (cons 'yas/exception text))) + +(defun yas/verify-value (possibilities) + "Verify that the current field value is in POSSIBILITIES + +Otherwise throw exception." + (when (and yas/moving-away-p (notany #'(lambda (pos) (string= pos yas/text)) possibilities)) + (yas/throw (format "[yas] field only allows %s" possibilities)))) + +(defun yas/field-value (number) + (let* ((snippet (car (yas/snippets-at-point))) + (field (and snippet + (yas/snippet-find-field snippet number)))) + (when field + (yas/field-text-for-display field)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; 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/deleted-text nil + "The text deleted in the last snippet expansion.") + +(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) +(make-variable-buffer-local 'yas/deleted-text) + +(defstruct (yas/snippet (:constructor yas/make-snippet ())) + "A snippet. + +..." + (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) + +(defstruct (yas/field (:constructor yas/make-field (number start end parent-field))) + "A field." + number + start end + parent-field + (mirrors '()) + (transform nil) + (modified-p nil) + (back-adjacent-fields nil) + (back-adjacent-mirrors nil)) + +(defstruct (yas/mirror (:constructor yas/make-mirror (start end transform))) + "A mirror." + start end + (transform nil)) + +(defun yas/apply-transform (field-or-mirror field) + "Calculate the value of the field/mirror. If there's a transform +for this field, apply it. Otherwise, returned nil." + (let* ((yas/text (yas/field-text-for-display field)) + (text yas/text) + (yas/modified-p (yas/field-modified-p field)) + (yas/moving-away-p nil) + (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) + (yas/eval-string transform))))) + transformed)) + +(defsubst yas/replace-all (from to &optional text) + "Replace all occurance from FROM to TO. + +With optional string TEXT do it in that string." + (goto-char (point-min)) + (if text + (replace-regexp-in-string from to text t t) + (while (search-forward from nil t) + (replace-match to t t text)))) + +(defun yas/snippet-find-field (snippet number) + (find-if #'(lambda (field) + (eq number (yas/field-number field))) + (yas/snippet-fields snippet))) + +(defun yas/snippet-field-compare (field1 field2) + "Compare two fields. The field with a number is sorted first. +If they both have a number, compare through the number. If neither +have, compare through the field's start point" + (let ((n1 (yas/field-number field1)) + (n2 (yas/field-number field2))) + (if n1 + (if n2 + (< n1 n2) + t) + (if n2 + nil + (< (yas/field-start field1) + (yas/field-start field2)))))) + +(defun yas/field-probably-deleted-p (field) + "Guess if FIELD was deleted because of his parent-field" + (and (zerop (- (yas/field-start field) (yas/field-end field))) + (yas/field-parent-field field))) + +(defun yas/snippets-at-point (&optional all-snippets) + "Return a sorted list of snippets at point, most recently +inserted first." + (sort + (remove nil (remove-duplicates (mapcar #'(lambda (ov) + (overlay-get ov 'yas/snippet)) + (if all-snippets + (overlays-in (point-min) (point-max)) + (overlays-at (point)))))) + #'(lambda (s1 s2) + (<= (yas/snippet-id s2) (yas/snippet-id s1))))) + +(defun yas/next-field-or-maybe-expand () + "Try to expand a snippet at a key before point, otherwise +delegate to `yas/next-field'." (interactive) - (let ((overlay (yas/current-overlay-for-navigation))) - (if overlay - (yas/navigate-group (overlay-get overlay 'yas/group) nil) - (let ((snippet (yas/snippet-of-current-keymap)) - (done nil)) - (if snippet - (do* ((groups (yas/snippet-groups snippet) (cdr groups)) - (group (car groups) (car groups))) - ((or (null groups) - done) - (unless done (message "Not in a snippet field."))) - (when (= (point) - (overlay-start - (yas/field-overlay - (yas/group-primary-field group)))) - (setq done t) - (yas/navigate-group group nil))) - (message "Not in a snippet field.")))))) + (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 active-field) + (yas/next-field)))) + (yas/next-field))) -(defun yas/exit-snippet (snippet) - "Goto exit-marker of SNIPPET and cleanup the snippe. Cleaning -up the snippet does not delete it!" +(defun yas/next-field (&optional arg) + "Navigate to next field. If there's none, exit the snippet." (interactive) - (goto-char (yas/snippet-exit-marker snippet)) - (yas/cleanup-snippet snippet)) - -;; Snippet register and unregister routines. -;; -;; XXX: Commentary on this section by joaot. -;; -;; These routines, along with minor modifications upwards, allow some -;; management of currently active snippets. -;; -;; The idea is to temporarily set `post-command-hook' while locally -;; "registered" snippets last. After each command, -;; `yas/check-cleanup-snippet' is run, checking for some condition and -;; possibly unregistering the snippet. When no more snippets are -;; registered, the `post-command-hook' is cleared up. -;; -;; They were introduced to fix bug 28 -;; "http://code.google.com/p/yasnippet/issues/detail?id=28". Whenever -;; point exits a snippet or a snippet field, *all* snippets are -;; destroyed. -;; -;; Also, this scheme have been reused to fix bug 33 -;; "http://code.google.com/p/yasnippet/issues/detail?id=33", which -;; deals with undoing changes when part of the snippet's field have -;; been filled out already. See commentary on "Field-level undo" below -;; - -(defvar yas/registered-snippets nil - "A hash table holding all active snippets") -(eval-when-compile - (make-variable-buffer-local 'yas/registered-snippets)) -(defun yas/get-registered-snippets () - (when (null yas/registered-snippets) - (setq yas/registered-snippets - (make-hash-table :test 'eq))) - yas/registered-snippets) - -(defun yas/register-snippet (snippet) - "Register SNIPPET in the `yas/registered-snippets' table. Add a -`yas/check-cleanup-snippet' function to the buffer-local -`post-command-hook' that should exist while at least one -registered snippet exists in the current buffer. Return snippet" - (puthash (yas/snippet-id snippet) snippet (yas/get-registered-snippets)) - (add-hook 'pre-command-hook 'yas/field-undo-before-hook 'append 'local) - (add-hook 'post-command-hook 'yas/check-cleanup-snippet 'append 'local) - (add-hook 'post-command-hook 'yas/field-undo-after-hook 'append 'local) - snippet) - -(defun yas/unregister-snippet (snippet) - "Unregister snippet from the `yas/registered-snippets' -table. Remove `yas/check-cleanup-snippet' from the buffer-local -`post-command-hook' if no more snippets registered in the -current buffer." - (remhash (yas/snippet-id snippet) (yas/get-registered-snippets)) - (when (eq 0 - (hash-table-count (yas/get-registered-snippets))) - (remove-hook 'pre-command-hook 'yas/field-undo-before-hook 'local) - (remove-hook 'post-command-hook 'yas/field-undo-after-hook 'local) - (remove-hook 'post-command-hook 'yas/check-cleanup-snippet 'local))) - -(defun yas/exterminate-snippets () - "Remove all locally registered snippets and remove - `yas/check-cleanup-snippet' from the `post-command-hook'" - (interactive) - (maphash #'(lambda (key snippet) (yas/cleanup-snippet snippet)) - (yas/get-registered-snippets))) - -(defun yas/cleanup-snippet (snippet) - "Cleanup SNIPPET, but leave point as it is. This renders the -snippet as ordinary text" - (let* ((overlay (yas/snippet-overlay snippet)) - (yas/snippet-beg (overlay-start overlay)) - (yas/snippet-end (overlay-end overlay))) - ;; save the end of the moribund snippet in case we need to undo - ;; its original expansion. This is used by `yas/undo-expand-snippet' - (when (and overlay - (overlay-buffer overlay)) - (setf (yas/snippet-end-marker snippet) yas/snippet-end) - (delete-overlay overlay)) - (dolist (group (yas/snippet-groups snippet)) - (dolist (field (yas/group-fields group)) - (delete-overlay (yas/field-overlay field)))) - (run-hooks 'yas/after-exit-snippet-hook)) - (yas/unregister-snippet snippet) - (setq buffer-undo-list (yas/snippet-saved-buffer-undo-list snippet))) - -(defun yas/check-cleanup-snippet () - "Checks if point exited any of the fields of the snippet, if so -clean it up. - -This function is part of `post-command-hook' while -registered snippets last." - (let ((snippet (yas/snippet-of-current-keymap))) - (cond ( ;; - ;; No snippet at point, cleanup *all* snippets - ;; - (null snippet) - (yas/exterminate-snippets)) - ( ;; - ;; A snippet exits at point, but point is out of any - ;; primary snippet field. - (and snippet - (notany #'(lambda (group) - (let ((primary-overlay (yas/field-overlay (yas/group-primary-field group)))) - (and (>= (point) (overlay-start primary-overlay)) - (<= (point) (overlay-end primary-overlay))))) - (yas/snippet-groups snippet))) - (yas/cleanup-snippet snippet)) - ( ;; - ;; Snippet at point, and point inside a snippet field, - ;; everything is normal - ;; - t + (let* ((arg (or arg + 1)) + (snippet (first (yas/snippets-at-point))) + (active-field (overlay-get yas/active-field-overlay 'yas/field)) + (live-fields (remove-if #'(lambda (field) + (and (not (eq field active-field)) + (yas/field-probably-deleted-p field))) + (yas/snippet-fields snippet))) + (active-field-pos (position active-field live-fields)) + (target-pos (and active-field-pos (+ arg active-field-pos))) + (target-field (nth target-pos live-fields))) + ;; First check if we're moving out of a field with a transform + ;; + (when (and active-field + (yas/field-transform active-field)) + (let* ((yas/moving-away-p t) + (yas/text (yas/field-text-for-display active-field)) + (text yas/text) + (yas/modified-p (yas/field-modified-p active-field))) + ;;; primary field transform: exit call to field-transform + (yas/eval-string (yas/field-transform active-field)))) + ;; Now actually move... + (cond ((>= target-pos (length live-fields)) + (yas/exit-snippet snippet)) + (target-field + (yas/move-to-field snippet target-field)) + (t nil)))) -;; Field-level undo functionality -;; -;; XXX: Commentary on this section by joaot. -;; -;; "Field-level undo" means undoing for bits of snippet fields that have -;; already been filled out. Because this is kind of experimental, I -;; have called it "field-undo", to distinguish it from regular undo -;; like the one used by `yas/undo-expand-snippet' to undo the original -;; snippet expansion. -;; -;; Field level undo allows no redos. Also, field level undo undoes any -;; change, even if it is only one character long. This might be -;; implemented in the future. -;; -;; Field level undo cooperates with normal undo and seems transparet -;; to the `undo' command. The basic idea is the same as with snippet -;; registration/unregistration. The undo history is saved in -;; `yas/field-undo-original-history' before each command and rewritten -;; if appropriate at the end. -;; -;; This is done by registering `yas/field-undo-before-hook' and -;; `yas/field-undo-after-hook' in the `pre-command-hook' and -;; `post-command-hook', respectively. -;; -;; Also, the `value' slot of the primary field of each group is used -;; to keep track of the most recently inserted text of that snippet -;; field. This could be seen as a hack, but that slot wasn't being -;; used anyway and its new meaning is actually quite reasonable. -;; -;; Another detail is that undo informatino shoulnd't be recorded for -;; some commands, most notably `undo' itself. Therefore, a variable -;; `yas/field-undo-forbidden-commands' has been introduced, to be -;; tested agains `this-command'. -;; +(defun yas/place-overlays (snippet field) + "Correctly place overlays for SNIPPET's FIELD" + (yas/make-move-field-protection-overlays snippet field) + (yas/make-move-active-field-overlay snippet field)) -(defvar yas/field-undo-history nil - "Saves the value of `buffer-undo-list' when undo information is -to be recorded by `yas/field-undo-after-hook'. A new piece of undo -is pushed into this variable and it then replaces -`buffer-undo-list' if appropriate.") +(defun yas/move-to-field (snippet field) + "Update SNIPPET to move to field FIELD. -(defvar yas/field-undo-forbidden-commands '(undo aquamacs-undo redo aquamacs-redo) - "A list of commands executed while a snippet is active that -should not trigger any undo-recording action") +Also create some protection overlays" + (goto-char (yas/field-start field)) + (setf (yas/snippet-active-field snippet) field) + (yas/place-overlays snippet field) + (overlay-put yas/active-field-overlay 'yas/field field) + ;;; primary field transform: first call to snippet transform + (unless (yas/field-modified-p field) + (if (yas/field-update-display field snippet) + (let ((inhibit-modification-hooks t)) + (yas/update-mirrors snippet)) + (setf (yas/field-modified-p field) nil)))) -(defun yas/field-undo-before-hook () - "Saves the field-level undo history, `buffer-undo-list' into a global -`yas/field-undo-history' variable just before a command is -performed. It will come in handy in case the command is to be undone" - (setq yas/field-undo-history buffer-undo-list)) +(defun yas/prev-field () + "Navigate to prev field. If there's none, exit the snippet." + (interactive) + (yas/next-field -1)) -(defun yas/field-undo-after-hook () - "Compares the value (a string) of the currently active snippet -group with a previously saved one. If these are different, undo -information is added to `buffer-undo-list' +(defun yas/exit-snippet (snippet) + "Goto exit-marker of SNIPPET." + (interactive) + (setf (yas/snippet-force-exit snippet) t) + (goto-char (if (yas/snippet-exit snippet) + (yas/snippet-exit snippet) + (overlay-end (yas/snippet-control-overlay snippet))))) -This function is added to the `post-command-hook' and should -be a part of that list while registered snippets last." - (let* ((overlay (or (yas/current-snippet-overlay) - (yas/current-snippet-overlay (1- (point))))) - (group (when overlay - (overlay-get overlay 'yas/group)))) - (when group - (let ((new-text (yas/current-field-text (yas/group-primary-field group))) - (old-text (yas/field-value (yas/group-primary-field group)))) - ;; - ;; Unless extended undo forbids `this-command', or the old and - ;; new field strings are the same, rewrite the undo history - ;; with a call to `yas/field-undo-group-text-change' - ;; instead of whatever was placed there by the currently - ;; finishing `this-command' command. This call receives the id - ;; of the currently active snippet, the group to be undone and - ;; the old text. - ;; - (unless (or (memq this-command yas/field-undo-forbidden-commands) - (string= new-text - old-text)) - ;; - ;; Push a separator onto the history list, if one wasn't - ;; there first. Have no clue why sometimes one is and one - ;; isn't. - ;; - (unless (null (car yas/field-undo-history)) - (push nil yas/field-undo-history)) - (push `(apply yas/field-undo-group-text-change - ,group - ,old-text) - yas/field-undo-history) - (setq buffer-undo-list yas/field-undo-history)) - ;; - ;; Then, in any case, save the new text into the value slot of - ;; the primary this is because some "forbidden" commands might - ;; really have changed the field value, most notably `undo' - ;; itself! This was a hard bug to track down! - ;; - (setf (yas/field-value (yas/group-primary-field group)) new-text))))) +;;; Apropos markers-to-points: +;;; +;;; This was ground useful for performance +;;; reasons, so that an excessive number of live markers arent kept +;;; aroung in the `buffer-undo-list'. However, in `markers-to-points', +;;; the set-to-nil markers can't simply be discarded and replaced with +;;; fresh ones in `points-to-markers'. The original marker that was +;;; just set to nilhas to be reused. +;;; +;;; This shouldn't bring horrible problems with undo/redo, but it +;;; would be one of the the first thing I'd remove if I was debugging that... +;;; -(defun yas/field-undo-group-text-change (group old-text) - "Undoes one step of field-level undo history, in the snippet - field group GROUP, replacing its text with OLD-TEXT, but - respecting any transforms." - (yas/remove-recent-undo-from-history) - (let ((inhibit-modification-hooks t) ; otherwise an additional - ; `yas/replace-fields-with-value' - ; is called - (buffer-undo-list t)) - (yas/replace-fields-with-value - (yas/group-fields group) - old-text))) +(defun yas/markers-to-points (snippet) + "Convert all markers in SNIPPET to a cons (POINT . MARKER) +where POINT is the original position of the marker and MARKER is +the original marker object with the position set to nil." + (dolist (field (yas/snippet-fields snippet)) + (let ((start (marker-position (yas/field-start field))) + (end (marker-position (yas/field-end field)))) + (set-marker (yas/field-start field) nil) + (set-marker (yas/field-end field) nil) + (setf (yas/field-start field) (cons start (yas/field-start field))) + (setf (yas/field-end field) (cons end (yas/field-end field)))) + (dolist (mirror (yas/field-mirrors field)) + (let ((start (marker-position (yas/mirror-start mirror))) + (end (marker-position (yas/mirror-end mirror)))) + (set-marker (yas/mirror-start mirror) nil) + (set-marker (yas/mirror-end mirror) nil) + (setf (yas/mirror-start mirror) (cons start (yas/mirror-start mirror))) + (setf (yas/mirror-end mirror) (cons end (yas/mirror-end mirror)))))) + (when (yas/snippet-exit snippet) + (let ((exit (marker-position (yas/snippet-exit snippet)))) + (set-marker (yas/snippet-exit snippet) nil) + (setf (yas/snippet-exit snippet) (cons exit (yas/snippet-exit snippet)))))) +(defun yas/points-to-markers (snippet) + "Convert all cons (POINT . MARKER) in SNIPPET to markers. This +is done by setting MARKER to POINT with `set-marker'." + (dolist (field (yas/snippet-fields snippet)) + (setf (yas/field-start field) (set-marker (cdr (yas/field-start field)) (car (yas/field-start field)))) + (setf (yas/field-end field) (set-marker (cdr (yas/field-end field)) (car (yas/field-end field)))) + (dolist (mirror (yas/field-mirrors field)) + (setf (yas/mirror-start mirror) (set-marker (cdr (yas/mirror-start mirror)) (car (yas/mirror-start mirror)))) + (setf (yas/mirror-end mirror) (set-marker (cdr (yas/mirror-end mirror)) (car (yas/mirror-end mirror)))))) + (when (yas/snippet-exit snippet) + (setf (yas/snippet-exit snippet) (set-marker (cdr (yas/snippet-exit snippet)) (car (yas/snippet-exit snippet)))))) + +(defun yas/commit-snippet (snippet &optional no-hooks) + "Commit SNIPPET, but leave point as it is. This renders the +snippet as ordinary text. + +Return a buffer position where the point should be placed if +exiting the snippet. + +NO-HOOKS means don't run the `yas/after-exit-snippet-hook' hooks." + + (let ((control-overlay (yas/snippet-control-overlay snippet)) + yas/snippet-beg + yas/snippet-end) + ;; + ;; 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)) + + (let ((inhibit-modification-hooks t)) + (when yas/active-field-overlay + (delete-overlay yas/active-field-overlay)) + (when yas/field-protection-overlays + (mapcar #'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-field-end-marker previous-field yas/snippet-end))) + + ;; Convert all markers to points, + ;; + (yas/markers-to-points snippet) + + ;; Take care of snippet revival + ;; + (if yas/snippet-revival + (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)) + + ;; XXX: `yas/after-exit-snippet-hook' should be run with + ;; `yas/snippet-beg' and `yas/snippet-end' bound. That might not + ;; be the case if the main overlay had somehow already + ;; disappeared, which sometimes happens when the snippet's messed + ;; up... + ;; + (unless no-hooks (run-hooks 'yas/after-exit-snippet-hook))) + + (message "[yas] snippet exited.")) + +(defun yas/check-commit-snippet () + "Checks if point exited the currently active field of the +snippet, if so cleans up the whole snippet up." + (let* ((snippets (yas/snippets-at-point 'all-snippets)) + (snippets-left snippets)) + (dolist (snippet snippets) + (let ((active-field (yas/snippet-active-field snippet))) + (cond ((or (prog1 (yas/snippet-force-exit snippet) + (setf (yas/snippet-force-exit snippet) nil)) + (not (and active-field (yas/field-contains-point-p active-field)))) + (setq snippets-left (delete snippet snippets-left)) + (yas/commit-snippet snippet snippets-left)) + ((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 snippets-left + (remove-hook 'post-command-hook 'yas/post-command-handler 'local) + (remove-hook 'pre-command-hook 'yas/pre-command-handler 'local)))) + +(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/pre-command-handler () ) + +(defun yas/post-command-handler () + "Handles various yasnippet conditions after each command." + (cond (yas/protection-violation + (goto-char yas/protection-violation) + (setq yas/protection-violation nil)) + ((eq 'undo this-command) + ;; + ;; After undo revival the correct field is sometimes not + ;; restored correctly, this condition handles that + ;; + (let* ((snippet (car (yas/snippets-at-point))) + (target-field (and snippet + (find-if-not #'yas/field-probably-deleted-p + (remove 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 (use exited it). + (yas/check-commit-snippet)))) + +(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) + "Creates 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 'yas/snippet snippet) + (overlay-put overlay 'evaporate t) + overlay)) + +(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) + (let ((field (or field + (and yas/active-field-overlay + (overlay-buffer yas/active-field-overlay) + (overlay-get yas/active-field-overlay 'yas/field))))) + (cond ((and field + (not (yas/field-modified-p field)) + (eq (point) (marker-position (yas/field-start field)))) + (yas/skip-and-clear field) + (yas/next-field 1)) + (t + (call-interactively 'delete-char))))) + +(defun yas/skip-and-clear (field) + "Deletes the region of FIELD and sets it modified state to t" + (setf (yas/field-modified-p field) t) + (delete-region (yas/field-start field) (yas/field-end field))) + +(defun yas/advance-field-end-marker (field newend) + "Advance FIELDs end-marker to NEWEND and recurse for parent fields" + (when (< (yas/field-end field) newend) + (set-marker (yas/field-end field) newend) + (when (yas/field-parent-field field) + (yas/advance-field-end-marker (yas/field-parent-field field) newend))) + ;; take care of adjacent fields + (let ((adjacents (yas/field-back-adjacent-fields field))) + (when adjacents + (dolist (adjacent adjacents) + (when (< (yas/field-start adjacent) newend) + (set-marker (yas/field-start adjacent) newend)) + (yas/advance-field-end-marker adjacent newend)))) + ;; take care of adjacent mirrors + (let ((adjacents (yas/field-back-adjacent-mirrors field))) + (when adjacents + (dolist (adjacent adjacents) + (when (< (yas/mirror-start adjacent) newend) + (set-marker (yas/mirror-start adjacent) newend)))))) + +(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 '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/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 it point it +at field start. This hook doesn't do anything if an undo is in +progress." + (unless (yas/undo-in-progress) + (let ((field (overlay-get yas/active-field-overlay 'yas/field))) + (cond (after? + (yas/advance-field-end-marker field (overlay-end overlay)) + ;;; primary field transform: normal calls to expression + (let ((saved-point (point))) + (yas/field-update-display field (car (yas/snippets-at-point))) + (goto-char saved-point)) + (yas/update-mirrors (car (yas/snippets-at-point)))) + (field + (when (and (not after?) + (not (yas/field-modified-p field)) + (eq (point) (if (markerp (yas/field-start field)) + (marker-position (yas/field-start field)) + (yas/field-start field)))) + (yas/skip-and-clear field)) + (setf (yas/field-modified-p field) t)))))) + +;;; 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'. +;;; +;;; Currently this signals an error which inhibits the command. For +;;; commands that move point (like `kill-line'), point is restored in +;;; the `yas/post-command-handler' using a global +;;; `yas/protection-violation' variable. +;;; +;;; Alternatively, I've experimented with an implementation that +;;; commits the snippet before actually calling `this-command' +;;; interactively, and then signals an eror, which is ignored. but +;;; blocks all other million modification hooks. This presented some +;;; problems with stacked expansion. +;;; + +(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 ((inhibit-modification-hooks t)) + (goto-char (point-max)) + (newline)))) + ;; go on to normal overlay creation/moving + ;; + (cond ((and yas/field-protection-overlays + (every #'overlay-buffer yas/field-protection-overlays)) + (move-overlay (first yas/field-protection-overlays) (1- start) start) + (move-overlay (second 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))))))) + +(defvar yas/protection-violation nil + "When non-nil, signals attempts to erronesly exit or modify the snippet. + +Functions in the `post-command-hook', for example +`yas/post-command-handler' can check it and reset its value to nil. The variables value is the point where the violation originated") + +(defun yas/on-protection-overlay-modification (overlay after? beg end &optional length) + "Signals a snippet violation, then issues error. + +The error should be ignored in `debug-ignored-errors'" + (cond ((not (or after? + (yas/undo-in-progress))) + (setq yas/protection-violation (point)) + (error "Exit the snippet first!")))) + +(add-to-list 'debug-ignored-errors "^Exit the snippet first!$") + +;;; Apropos stacked 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 in an undo/redo perpective, 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 (start end template &optional snippet-vars) + "Expand snippet at current point. Text between START and END +will be deleted before inserting template." + (run-hooks 'yas/before-expand-snippet-hook) + (goto-char start) + + ;; stacked expansion: shoosh the overlay modification hooks + ;; + (let ((key (buffer-substring-no-properties start end)) + (inhibit-modification-hooks t) + (column (current-column)) + snippet) + + ;; Delete the trigger key, this *does* get undo-recorded. + ;; + (delete-region start end) + + ;; Narrow the region down to the template, shoosh the + ;; `buffer-undo-list', and create the snippet, the new snippet + ;; updates its mirrors once, so we are left with some plain text. + ;; The undo action for deleting this plain text will get recorded + ;; at the end of this function. + (save-restriction + (narrow-to-region start start) + (condition-case err + (let ((buffer-undo-list t)) + ;; snippet creation might evaluate users elisp, which + ;; might generate errors, so we have to be ready to catch + ;; them mostly to make the undo information + ;; + (setq yas/start-column (save-restriction (widen) (current-column))) + (insert template) + (setq yas/deleted-text key) + (setq yas/selected-text (when mark-active key)) + (setq snippet + (if snippet-vars + (eval `(let ,(read snippet-vars) + (yas/snippet-create (point-min) (point-max)))) + (yas/snippet-create (point-min) (point-max))))) + (error + (push (cons (point-min) (point-max)) buffer-undo-list) + (error (format "[yas] parse error: %s" (cadr err)))))) + + ;; stacked-expansion: This checks for stacked expansion, save the + ;; `yas/previous-active-field' and advance its boudary. + ;; + (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-field-end-marker existing-field (overlay-end yas/active-field-overlay)))) + + ;; Exit the snippet immediately if no fields + ;; + (unless (yas/snippet-fields snippet) + (yas/exit-snippet snippet)) + + ;; Push two undo actions: the deletion of the inserted contents of + ;; the new snippet (whitout the "key") followed by an apply of + ;; `yas/take-care-of-redo' on the newly inserted snippet boundaries + ;; + (let ((start (overlay-start (yas/snippet-control-overlay snippet))) + (end (overlay-end (yas/snippet-control-overlay snippet)))) + (push (cons start end) buffer-undo-list) + (push `(apply yas/take-care-of-redo ,start ,end ,snippet) + buffer-undo-list)) + ;; Now, move to the first field + ;; + (let ((first-field (car (yas/snippet-fields snippet)))) + (when first-field + (yas/move-to-field snippet first-field)))) + (message "[yas] snippet expanded.")) + +(defun yas/take-care-of-redo (beg end 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 'no-hooks))) + +(defun yas/snippet-revive (beg end snippet) + "Revives the SNIPPET and creates a control overlay from BEG to +END. + +BEG and END are, we hope, the original snippets boudaries. 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... + ;; + (let ((target-field (or (yas/snippet-active-field snippet) + (car (yas/snippet-fields snippet))))) + (when target-field + (setf (yas/snippet-control-overlay snippet) (yas/make-control-overlay snippet beg end)) + (overlay-put (yas/snippet-control-overlay snippet) 'yas/snippet snippet) + + (yas/move-to-field snippet target-field) + + (add-hook 'post-command-hook 'yas/post-command-handler nil t) + (add-hook 'pre-command-hook 'yas/pre-command-handler t t) + + (push `(apply yas/take-care-of-redo ,beg ,end ,snippet) + buffer-undo-list)))) + +(defun yas/snippet-create (begin end) + "Creates a snippet from an template inserted between BEGIN and END. + +Returns the newly created snippet." + (let ((snippet (yas/make-snippet))) + (goto-char begin) + (yas/snippet-parse-create snippet) + + ;; Sort and link each field + (yas/snippet-sort-link-fields snippet) + + ;; Calculate field and mirror adjacencies + (yas/calculate-adjacencies snippet) + + ;; Update the mirrors for the first time + (yas/update-mirrors 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)) + + ;; Setup hooks + (add-hook 'post-command-hook 'yas/post-command-handler nil t) + (add-hook 'pre-command-hook 'yas/pre-command-handler t t) + + snippet)) + +(defun yas/snippet-sort-link-fields (snippet) + (setf (yas/snippet-fields snippet) + (sort (yas/snippet-fields snippet) + '(lambda (field1 field2) + (yas/snippet-field-compare field1 field2))))) + +(defun yas/calculate-adjacencies (snippet) + ;; For each field in the snippet + ;; + (dolist (field (yas/snippet-fields snippet)) + ;; Calculate its adjacencies to other mirrors and fields + ;; + (dolist (otherfield (yas/snippet-fields snippet)) + (dolist (mirror (yas/field-mirrors otherfield)) + (when (= (yas/field-end field) (yas/mirror-start mirror)) + (push mirror (yas/field-back-adjacent-mirrors field)))) + (when (and (not (eq otherfield field)) + (= (yas/field-end field) (yas/field-start otherfield))) + (when (not (find field (yas/field-back-adjacent-fields otherfield))) + (push otherfield (yas/field-back-adjacent-fields field))))) + ;; Calculate the adjacencies of each one of its mirrors + ;; + ;; TODO: Known bug. + )) + +(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 ((parse-start (point))) + ;; protect quote and backquote escapes + ;; + (yas/protect-escapes '(?` ?')) + ;; replace all backquoted expressions + ;; + (goto-char parse-start) + (yas/replace-backquotes) + ;; protect escapes again since previous stepds might have + ;; generated more characters needing escapinge + ;; + (goto-char parse-start) + (yas/protect-escapes) + ;; parse fields with {} + ;; + (goto-char parse-start) + (yas/field-parse-create snippet) + ;; parse simple mirrors and fields + ;; + (goto-char parse-start) + (yas/simple-mirror-parse-create snippet) + ;; parse mirror transforms + ;; + (goto-char parse-start) + (yas/transform-mirror-parse-create snippet) + ;; restore escapes + ;; + (goto-char parse-start) + (yas/restore-escapes) + ;; update mirrors for the first time + ;; + (yas/update-mirrors snippet) + ;; indent the best we can + ;; + (goto-char parse-start) + (yas/indent snippet))) + +(defun yas/indent (snippet) + (save-excursion + (while (re-search-forward "$>" nil t) + (delete-region (match-beginning 0) (match-end 0)) + (when (not (eq yas/indent-line 'auto)) + (indent-according-to-mode)))) + (save-excursion + (cond ((eq yas/indent-line 'fixed) + (let* ((indent (if indent-tabs-mode + (concat (make-string (/ column tab-width) ?\t) + (make-string (% column tab-width) ?\ )) + (make-string (current-colum) ?\ )))) + (goto-char (point-min)) + (while (and (zerop (forward-line)) + (= (current-column) 0)) + (insert indent)))) + ((eq yas/indent-line 'auto) + (let ((end (set-marker (make-marker) (point-max))) + (snippet-markers (yas/collect-snippet-markers snippet))) + (save-restriction + (widen) + ;; XXX: Here seems to be the indent problem: + ;; + ;; `indent-according-to-mode' uses whatever + ;; `indent-line-function' is available. Some + ;; implementations of these functions delete text + ;; before they insert. If there happens to be a marker + ;; just after the text being deleted, the insertion + ;; actually happens after the marker, which misplaces + ;; it. + ;; + ;; This would also happen if we had used overlays with + ;; the `front-advance' property set to nil. + ;; + (while (and (zerop (forward-line 1)) + (not (eobp)) + (<= (point) end)) + (goto-char (yas/real-line-beginning)) + (let ((trouble-markers (remove-if-not #'(lambda (marker) + (= marker (point))) + snippet-markers))) + (indent-according-to-mode) + (mapc #'(lambda (marker) + (set-marker marker (point))) + trouble-markers) + (indent-according-to-mode))) + (set-marker end nil)))) + (t + nil)))) + +(defun yas/collect-snippet-markers (snippet) + "Make a list of all the markers used by SNIPPET." + (let (markers) + (dolist (field (yas/snippet-fields snippet)) + (push (yas/field-start field) markers) + (push (yas/field-end field) markers) + (dolist (mirror (yas/field-mirrors field)) + (push (yas/mirror-start mirror) markers) + (push (yas/mirror-end mirror) markers))) + (when (and (yas/snippet-exit snippet) + (marker-buffer (yas/snippet-exit snippet))) + (push (yas/snippet-exit snippet) markers)) + markers)) + +(defun yas/real-line-beginning () + (let ((c (char-after (line-beginning-position))) + (n (line-beginning-position))) + (while (or (eql c ?\ ) + (eql c ?\t)) + (incf n) + (setq c (char-after n))) + n)) + + +(defun yas/escape-string (escaped) + (concat "YASESCAPE" (format "%d" escaped) "PROTECTGUARD")) + +(defun yas/protect-escapes (&optional escaped) + "Protect all escaped characters with their numeric ASCII value." + (mapc #'(lambda (escaped) + (yas/replace-all (concat "\\" (char-to-string escaped)) + (yas/escape-string escaped))) + (or escaped yas/escaped-characters))) + +(defun yas/restore-escapes (&optional text) + "Restore all escaped characters from their numeric ASCII value. + +With optional string TEXT do it in string instead" + (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)))) + yas/escaped-characters) + changed-text)) + +(defun yas/replace-backquotes () + "Replace all the \"`(lisp-expression)`\"-style expression + with their evaluated value" + (while (re-search-forward yas/backquote-lisp-expression-regexp nil t) + (let ((transformed (yas/eval-string (yas/restore-escapes (match-string 1))))) + (goto-char (match-end 0)) + (when transformed (insert transformed)) + (delete-region (match-beginning 0) (match-end 0))))) + +(defun yas/scan-sexps (from count) + (condition-case err + (with-syntax-table (standard-syntax-table) + (scan-sexps from count)) + (error + nil))) + +(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/field-parse-create (snippet &optional parent-field) + "Parse most field expression, 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; +* 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 + (not (save-match-data + (eq (string-match "$[ \t\n]*(" (match-string-no-properties 2)) 0))) + (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 + (delete-region (1- real-match-end-0) real-match-end-0) + (delete-region (match-beginning 0) (match-beginning 2)) + (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))))))) + (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))) + (when real-match-end-1 + (let ((lisp-expression-string (buffer-substring-no-properties (match-beginning 1) real-match-end-1))) + (setf (yas/field-transform parent-field) (yas/restore-escapes lisp-expression-string))) + (delete-region (match-beginning 0) real-match-end-1))))))) + +(defun yas/transform-mirror-parse-create (snippet) + "Parse the \"${n:$(lisp-expression)}\" mirror transformations." + (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)))) + (when (and real-match-end-0 + field) + (push (yas/make-mirror (yas/make-marker (match-beginning 0)) + (yas/make-marker (match-beginning 0)) + (yas/restore-escapes (buffer-substring-no-properties (match-beginning 2) + (1- real-match-end-0)))) + (yas/field-mirrors field)) + (delete-region (match-beginning 0) real-match-end-0))))) + +(defun yas/simple-mirror-parse-create (snippet) + "Parse the simple \"$n\" mirrors and the exit-marker." + (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-marker (match-end 0))) + (save-excursion + (goto-char (match-beginning 0)) + (when (and yas/wrap-around-region yas/selected-text) + (insert yas/selected-text)) + (delete-region (point) (yas/snippet-exit snippet)))) + (t + (let ((field (yas/snippet-find-field snippet number))) + (if field + (push (yas/make-mirror (yas/make-marker (match-beginning 0)) + (yas/make-marker (match-beginning 0)) + nil) + (yas/field-mirrors field)) + (push (yas/make-field number + (yas/make-marker (match-beginning 0)) + (yas/make-marker (match-beginning 0)) + nil) + (yas/snippet-fields snippet)))) + (delete-region (match-beginning 0) (match-end 0))))))) + +(defun yas/update-mirrors (snippet) + "Updates all the mirrors of SNIPPET." + (save-excursion + (dolist (field (yas/snippet-fields snippet)) + (dolist (mirror (yas/field-mirrors field)) + ;; stacked expansion: I added an `inhibit-modification-hooks' + ;; here, for safety, may need to remove if we the mechanism is + ;; altered. + ;; + (let ((inhibit-modification-hooks t)) + (yas/mirror-update-display mirror field) + ;; Take care of the fields adjacent to this mirror's back + ;; TODO: Known bug + + ;; `yas/place-overlays' is needed if the active field and + ;; protected overlays have been changed because of insertions + ;; in `yas/mirror-update-display' + ;; + (when (eq field (yas/snippet-active-field snippet)) + (yas/place-overlays snippet field))))))) + +(defun yas/mirror-update-display (mirror field) + "Update MIRROR according to FIELD (and mirror transform)." + (let ((reflection (or (yas/apply-transform mirror field) + (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)) + (insert reflection) + (if (> (yas/mirror-end mirror) (point)) + (delete-region (point) (yas/mirror-end mirror)) + (set-marker (yas/mirror-end mirror) (point)))))) + +(defun yas/field-update-display (field snippet) + "Much like `yas/mirror-update-display', but for fields" + (when (yas/field-transform field) + (let ((inhibit-modification-hooks t) + (transformed (yas/apply-transform field field)) + (point (point))) + (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)) + (insert transformed) + (if (> (yas/field-end field) (point)) + (delete-region (point) (yas/field-end field)) + (set-marker (yas/field-end field) (point))) + t)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Debug functions. Use (or change) at will whenever needed. +;; (defun yas/debug-some-vars () + "Debug snippets, fields, mirrors and the `buffer-undo-list'." (interactive) (with-output-to-temp-buffer "*YASnippet trace*" - (princ "Interesting YASnippet vars: \n\n") - (princ (format "Register hash-table: %s\n\n" (yas/get-registered-snippets))) - (cond ((eq (hash-table-count (yas/get-registered-snippets)) 0) - (princ " No registered snippets\n")) - (t - (maphash #'(lambda (key snippet) - (princ (format "\t key %s for snippet %s with %s groups\n" - key - (yas/snippet-id snippet) - (length (yas/snippet-groups snippet)))) - (dolist (group (yas/snippet-groups snippet)) - (princ (format "\t group with %s fields. Primary field is value is \"%s\"\n" - (length (yas/group-fields group)) - (yas/field-value (yas/group-primary-field group)))))) - (yas/get-registered-snippets)))) + (princ "Interesting YASnippet vars: \n\n") - (princ (format "\nPost command hook: %s\n" post-command-hook)) - (princ (format "\nPre command hook: %s\n" pre-command-hook)) + (princ (format "\nPost command hook: %s\n" post-command-hook)) + (princ (format "\nPre command hook: %s\n" pre-command-hook)) - (princ (format "\nUndo is %s. Undolist has %s elements. First 10 elements follow:\n" - (if (eq buffer-undo-list t) - "DISABLED" - "ENABLED") - (length buffer-undo-list))) - (let ((undo-list buffer-undo-list)) - (dotimes (i 10) - (when undo-list - (princ (format "%s: %s\n" i (car undo-list))) - (setq undo-list (cdr undo-list))))))) + (princ (format "%s live snippets in total\n" (length (yas/snippets-at-point (quote all-snippets))))) + (princ (format "%s live snippets at point:\n\n" (length (yas/snippets-at-point)))) + + (dolist (snippet (yas/snippets-at-point)) + (princ (format "\tsid: %s active field %d from %s to %s covering \"%s\"\n" + (yas/snippet-id snippet) + (yas/field-number (yas/snippet-active-field snippet)) + (marker-position (yas/field-start (yas/snippet-active-field snippet))) + (marker-position (yas/field-end (yas/snippet-active-field snippet))) + (buffer-substring-no-properties (yas/field-start (yas/snippet-active-field snippet)) (yas/field-end (yas/snippet-active-field snippet))))) + (dolist (field (yas/snippet-fields snippet)) + (princ (format "\tfield %d from %s to %s covering \"%s\" adj-fields %s adj-mirrors %s\n" + (yas/field-number field) + (marker-position (yas/field-start field)) + (marker-position (yas/field-end field)) + (buffer-substring-no-properties (yas/field-start field) (yas/field-end field)) + (length (yas/field-back-adjacent-fields field)) + (length (yas/field-back-adjacent-mirrors field)))) + (dolist (mirror (yas/field-mirrors field)) + (princ (format "\t\tmirror from %s to %s covering \"%s\"\n" + (marker-position (yas/mirror-start mirror)) + (marker-position (yas/mirror-end mirror)) + (buffer-substring-no-properties (yas/mirror-start mirror) (yas/mirror-end mirror))))))) + + + (princ (format "\nUndo is %s and point-max is %s.\n" + (if (eq buffer-undo-list t) + "DISABLED" + "ENABLED") + (point-max))) + (unless (eq buffer-undo-list t) + (princ (format "Undpolist has %s elements. First 10 elements follow:\n" (length buffer-undo-list))) + (let ((first-ten (subseq buffer-undo-list 0 19))) + (dolist (undo-elem first-ten) + (princ (format "%2s: %s\n" (position undo-elem first-ten) (truncate-string-to-width (format "%s" undo-elem) 70)))))))) + + +(defun yas/exterminate-package () + (interactive) + (yas/global-mode -1) + (yas/minor-mode -1) + (mapatoms #'(lambda (atom) + (when (string-match "yas/" (symbol-name atom)) + (unintern atom))))) + +(defun yas/debug-test (&optional quiet) + (interactive "P") + (yas/load-directory (or (and (listp yas/root-directory) + (first yas/root-directory)) + yas/root-directory + "~/Source/yasnippet/snippets/")) + ;;(kill-buffer (get-buffer "*YAS TEST*")) + (set-buffer (switch-to-buffer "*YAS TEST*")) + (mapcar #'yas/commit-snippet (yas/snippets-at-point 'all-snippets)) + (erase-buffer) + (setq buffer-undo-list nil) + (setq undo-in-progress nil) + (snippet-mode) + (yas/minor-mode 1) + (let ((abbrev)) + ;; (if (require 'ido nil t) + ;; (setq abbrev (ido-completing-read "Snippet abbrev: " '("crazy" "prip" "prop"))) + ;; (setq abbrev "prop")) + (setq abbrev "$f") + (insert abbrev)) + (unless quiet + (add-hook 'post-command-hook 'yas/debug-some-vars 't 'local))) (provide 'yasnippet) @@ -1630,274 +2616,16 @@ be a part of that list while registered snippets last." handle the end-of-buffer error fired in it by calling `forward-char' at the end of buffer." (condition-case err - ad-do-it - (error (message (error-message-string err))))) + ad-do-it + (error (message (error-message-string err))))) ;; disable c-electric-* serial command in YAS fields (add-hook 'c-mode-common-hook '(lambda () - (make-variable-buffer-local 'yas/keymap) - (dolist (k '(":" ">" ";" "<" "{" "}")) - (define-key yas/keymap - k 'self-insert-command)))) + (make-variable-buffer-local 'yas/keymap) + (dolist (k '(":" ">" ";" "<" "{" "}")) + (define-key yas/keymap + k 'self-insert-command)))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Contents of dropdown-list.el -;; -;; dropdown-list.el is used by yasnippet to select multiple -;; candidate snippets. -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; dropdown-list.el --- Drop-down menu interface -;; -;; Filename: dropdown-list.el -;; Description: Drop-down menu interface -;; Author: Jaeyoun Chung [jay.chung@gmail.com] -;; Maintainer: -;; Copyright (C) 2008 Jaeyoun Chung -;; Created: Sun Mar 16 11:20:45 2008 (Pacific Daylight Time) -;; Version: -;; Last-Updated: Sun Mar 16 12:19:49 2008 (Pacific Daylight Time) -;; By: dradams -;; Update #: 43 -;; URL: http://www.emacswiki.org/cgi-bin/wiki/dropdown-list.el -;; Keywords: convenience menu -;; Compatibility: GNU Emacs 21.x, GNU Emacs 22.x -;; -;; Features that might be required by this library: -;; -;; `cl'. -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;;; Commentary: -;; -;; According to Jaeyoun Chung, "overlay code stolen from company-mode.el." -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;;; Change log: -;; -;; 2008/03/16 dadams -;; Clean-up - e.g. use char-to-string for control chars removed by email posting. -;; Moved example usage code (define-key*, command-selector) inside the library. -;; Require cl.el at byte-compile time. -;; Added GPL statement. -;; 2008/01/06 Jaeyoun Chung -;; Posted to gnu-emacs-sources@gnu.org at 9:10 p.m. -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 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, 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; see the file COPYING. If not, write to -;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth -;; Floor, Boston, MA 02110-1301, USA. -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;;; Code: - -(eval-when-compile (require 'cl)) ;; decf, fourth, incf, loop, mapcar* - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defface dropdown-list-face - '((t :inherit default :background "lightyellow" :foreground "black")) - "*Bla." :group 'dropdown-list) - -(defface dropdown-list-selection-face - '((t :inherit dropdown-list-face :background "purple")) - "*Bla." :group 'dropdown-list) - -(defvar dropdown-list-overlays nil) - -(defun dropdown-list-hide () - (while dropdown-list-overlays - (delete-overlay (pop dropdown-list-overlays)))) - -(defun dropdown-list-put-overlay (beg end &optional prop value prop2 value2) - (let ((ov (make-overlay beg end))) - (overlay-put ov 'window t) - (when prop - (overlay-put ov prop value) - (when prop2 (overlay-put ov prop2 value2))) - ov)) - -(defun dropdown-list-line (start replacement &optional no-insert) - ;; start might be in the middle of a tab, which means we need to hide the - ;; tab and add spaces - (let ((end (+ start (length replacement))) - beg-point end-point - before-string after-string) - (goto-char (point-at-eol)) - (if (< (current-column) start) - (progn (setq before-string (make-string (- start (current-column)) ? )) - (setq beg-point (point))) - (goto-char (point-at-bol)) ;; Emacs bug, move-to-column is wrong otherwise - (move-to-column start) - (setq beg-point (point)) - (when (> (current-column) start) - (goto-char (1- (point))) - (setq beg-point (point)) - (setq before-string (make-string (- start (current-column)) ? )))) - (move-to-column end) - (setq end-point (point)) - (let ((end-offset (- (current-column) end))) - (when (> end-offset 0) (setq after-string (make-string end-offset ?b)))) - (when no-insert - ;; prevent inheriting of faces - (setq before-string (when before-string (propertize before-string 'face 'default))) - (setq after-string (when after-string (propertize after-string 'face 'default)))) - (let ((string (concat before-string replacement after-string))) - (if no-insert - string - (push (dropdown-list-put-overlay beg-point end-point 'invisible t - 'after-string string) - dropdown-list-overlays))))) - -(defun dropdown-list-start-column (display-width) - (let ((column (mod (current-column) (window-width))) - (width (window-width))) - (cond ((<= (+ column display-width) width) column) - ((> column display-width) (- column display-width)) - ((>= width display-width) (- width display-width)) - (t nil)))) - -(defun dropdown-list-move-to-start-line (candidate-count) - (decf candidate-count) - (let ((above-line-count (save-excursion (- (vertical-motion (- candidate-count))))) - (below-line-count (save-excursion (vertical-motion candidate-count)))) - (cond ((= below-line-count candidate-count) - t) - ((= above-line-count candidate-count) - (vertical-motion (- candidate-count)) - t) - ((>= (+ below-line-count above-line-count) candidate-count) - (vertical-motion (- (- candidate-count below-line-count))) - t) - (t nil)))) - -(defun dropdown-list-at-point (candidates &optional selidx) - (dropdown-list-hide) - (let* ((lengths (mapcar #'length candidates)) - (max-length (apply #'max lengths)) - (start (dropdown-list-start-column (+ max-length 3))) - (i -1) - (candidates (mapcar* (lambda (candidate length) - (let ((diff (- max-length length))) - (propertize - (concat (if (> diff 0) - (concat candidate (make-string diff ? )) - (substring candidate 0 max-length)) - (format "%3d" (+ 2 i))) - 'face (if (eql (incf i) selidx) - 'dropdown-list-selection-face - 'dropdown-list-face)))) - candidates - lengths))) - (save-excursion - (and start - (dropdown-list-move-to-start-line (length candidates)) - (loop initially (vertical-motion 0) - for candidate in candidates - do (dropdown-list-line (+ (current-column) start) candidate) - while (/= (vertical-motion 1) 0) - finally return t))))) - -(defun dropdown-list (candidates) - (let ((selection) - (temp-buffer)) - (save-window-excursion - (unwind-protect - (let ((candidate-count (length candidates)) - done key (selidx 0)) - (while (not done) - (unless (dropdown-list-at-point candidates selidx) - (switch-to-buffer (setq temp-buffer (get-buffer-create "*selection*")) - 'norecord) - (delete-other-windows) - (delete-region (point-min) (point-max)) - (insert (make-string (length candidates) ?\n)) - (goto-char (point-min)) - (dropdown-list-at-point candidates selidx)) - (setq key (read-key-sequence "")) - (cond ((and (stringp key) - (>= (aref key 0) ?1) - (<= (aref key 0) (+ ?0 (min 9 candidate-count)))) - (setq selection (- (aref key 0) ?1) - done t)) - ((member key `(,(char-to-string ?\C-p) [up] "p")) - (setq selidx (mod (+ candidate-count (1- (or selidx 0))) - candidate-count))) - ((member key `(,(char-to-string ?\C-n) [down] "n")) - (setq selidx (mod (1+ (or selidx -1)) candidate-count))) - ((member key `(,(char-to-string ?\f)))) - ((member key `(,(char-to-string ?\r) [return])) - (setq selection selidx - done t)) - (t (setq done t))))) - (dropdown-list-hide) - (and temp-buffer (kill-buffer temp-buffer))) - ;; (when selection - ;; (message "your selection => %d: %s" selection (nth selection candidates)) - ;; (sit-for 1)) - selection))) - -(defun define-key* (keymap key command) - "Add COMMAND to the multiple-command binding of KEY in KEYMAP. -Use multiple times to bind different COMMANDs to the same KEY." - (define-key keymap key (combine-command command (lookup-key keymap key)))) - -(defun combine-command (command defs) - "$$$$$ FIXME - no doc string" - (cond ((null defs) command) - ((and (listp defs) - (eq 'lambda (car defs)) - (= (length defs) 4) - (listp (fourth defs)) - (eq 'command-selector (car (fourth defs)))) - (unless (member `',command (cdr (fourth defs))) - (setcdr (fourth defs) (nconc (cdr (fourth defs)) `(',command)))) - defs) - (t - `(lambda () (interactive) (command-selector ',defs ',command))))) - -(defvar command-selector-last-command nil "$$$$$ FIXME - no doc string") - -(defun command-selector (&rest candidates) - "$$$$$ FIXME - no doc string" - (if (and (eq last-command this-command) command-selector-last-command) - (call-interactively command-selector-last-command) - (let* ((candidate-strings - (mapcar (lambda (candidate) - (format "%s" (if (symbolp candidate) - candidate - (let ((s (format "%s" candidate))) - (if (>= (length s) 7) - (concat (substring s 0 7) "...") - s))))) - candidates)) - (selection (dropdown-list candidate-strings))) - (when selection - (let ((cmd (nth selection candidates))) - (call-interactively cmd) - (setq command-selector-last-command cmd)))))) - -;;;;;;;;;;;;;;;;;;;; - -(provide 'dropdown-list) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; dropdown-list.el ends here ;;; yasnippet.el ends here