7

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]]
  • 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 with more elegant solutions. – Vamsi Oct 3 '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 4 '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 5 '14 at 3:26
  • @IqbalAnsari I have kind of crude way of templatizing the snippets themselves. See my longer solution. Thanks. – Vamsi Oct 5 '14 at 3:42
11

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.

  • 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 5 '14 at 5: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 6 '14 at 0:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.