9

Q: how do I get yasnippet to expand a text string correctly (recognizing snippet fields) from inside elisp code?

A very simple snippet is a just a string of characters with some control characters intermingled. We can also use elisp forms to create richer snippets, but it's not clear to me how to expand them appropriately.

Here's a simple example that captures the problem. I'm creating an org-mode snippet to insert a link skeleton, which looks like [[source][description]]. The following simple snippet produces the desired behavior of "start in the source field, end up in the description field":

[[$1][$0]]

We can intermingle elisp code in snippets (wrapped in `s). I would have thought that the following would produce exactly the same behavior as the simpler version, because the if will simply evaluate to [[$1][$0]] as a string:

`(if t "[[$1][$0]]")`

It does not do so, however. Instead, it acts as if the $0 and $1 fields are not fields, ends the snippet, and places point after the final ]. So: how does one get the desired behavior when an elisp form evaluates to a text string with yasnippet fields embedded in that string?

EDIT: as requested, here's the particular use-case that inspired the question. If the X clipboard contains a url link, I'd like it to populate the source field in the org link ([[source][description]]) and leave me in the destination field. It uses a helper function (dan-xclipboard-link-p) which returns the url if it exists, or else nil. It seems to me that I should be able to do something like this:

`(let ((link (dan-xclipboard-link-p)))
   (if link
       (concat "[[" link "][$0]]")
     "[[$1][$0]]"))`

In fact, with an earlier version of yasnippet, a version of this code worked as I intended it.

EDIT 2: Interestingly enough, this snippet works when there is a link on the X clipboard, but fails when not (ie, it doesn't recognize $1 as a field:

[[`(or (dan-xclipboard-link-p) "$1")`][$0]]
Dan
  • 32,584
  • 6
  • 98
  • 168
  • How about `(if t (yas-insert-snippet))`? Can you give a more realistic conditional so that we can understand the actual use case ? This could be a [XY problem](http://mywiki.wooledge.org/XyProblem) with more elegant solutions. – Vamsi Oct 03 '14 at 15:20
  • I'm not seeing how `yas-insert-snippet` applies here. At any rate, an actual use case (that used to work) is now in the question. – Dan Oct 04 '14 at 15:14
  • @Dan It does not work because the elisp form is evaluated when the is **being expanded**, at which point yasnippet has already read the snippet. I guess what you want to do is templatize the template itself, not sure that is possible with yasnippet at this point of time – Iqbal Ansari Oct 05 '14 at 03:26
  • @IqbalAnsari I have kind of crude way of templatizing the snippets themselves. See my longer solution. Thanks. – Vamsi Oct 05 '14 at 03:42

1 Answers1

12

SHORT SOLUTION

If you do not mind an additional TAB keystroke, definining your snippet as below should work.

 [[${1:`(dan-xclipboard-link-p)`}][$0]]

The embedded lisp returns the url from the clipboard if it exists and sets it to be the default text in the field $1. You can type over the link to change it or just TAB (yas-next-field) to move point to $0.

LONGER FULL FEATURED SOLUTION

I got the idea based on @IqbalAnsari's comment after writing the shorter solution. You can use the condition predicate for choosing which snippet to expand. For instance define two snippets with the same trigger

SNIPPET 1

  # -*- mode: snippet; require-final-newline: nil -*-
  # name: org-link
  # key: [[
  # binding: direct-keybinding
  # type: snippet
  # condition: (not (dan-xclipboard-link-p))
  # --
  [[$1][$0]]

SNIPPET 2

  # -*- mode: snippet; require-final-newline: nil -*-
  # name: org-link-with-default-paste
  # key: [[
  # binding: direct-keybinding
  # type: snippet
  # condition: (dan-xclipboard-link-p)
  # --
  [[`(dan-xclipboard-link-p)`][$0]]

Both the snippets have the same trigger, but have inverted predicates denoted by the condition:. As such only one of the snippets will be expanded depending upon the value returned by (dan-xclipboard-link-p). This way you can position the point at $0 directly.

To answer the general question, no it is not possible for yasnippet to recognize fields within elisp as it parses embedded elisp blocks separately without any concern for normal template syntax. I am not sure how the previous versions worked though.

Vamsi
  • 3,916
  • 22
  • 35
  • Great, I was thinking of something along the same lines. Though I would have preferred the extra 'tab' over writing two snippets. +1 – Iqbal Ansari Oct 05 '14 at 05:38
  • Thanks for the thoughts. By the way, I'd come up with something similar to the short solution and fixed a typo with the brackets for future reference. The conditional snippets are a nice touch. – Dan Oct 06 '14 at 00:32