I want to define a yasnippet for shell-script mode that would insert a chunk of text whose content depends on the setting of the sh-shell variable.

Since the possible different text insertions are several lines long, and are quite different from each other, so doing this with conditional fields of the form

${N:`(case sh-shell (zsh  "foo")
                    (bash "bar")
                    (sh   "baz")
                    (t    "frobozz"))`}

...would all but eliminate the convenient formatting one this sort of template for.

EDIT: To clarify: the code above is not useful to me, because I need to insert multi-line formatted text, not little, one-word strings like "foo", "bar", etc.

If I were to use an unholy mix of Elisp and bash to represent what I'm looking for, this is how I'd represent it:

`(case sh-shell (zsh <<EOZSH)
zsh version
                   (bash <<EOBASH)
bash version of snippet looks like this
                   (sh   <<EOSH)
                   (t    <<EODEFAULT)
default version
  of snippet

IOW, the snippet would consist of a single embedded Elisp case expression, where the key of each key-value pair is a possible value of sh-shell, and the value is a (formatted) snippet/template; I've represented the latter using (totally invalid) heredoc notation.

Is there a (syntactically sound) way to implement this idea?

  • I don't understand what's wrong with the first version. The multi-line formatting is only visible in the snippet definition, where arguably it's a good thing as it makes it easy to read your code. The expansion is still just one line long. I think case won't work though, if sh-shell takes string values. case compares via eql, which only returns true if the arguments are the same lisp object, not if they are different objects with the same value. – Tyler Jan 26 '17 at 23:33
  • @Tyler: sorry for the confusion; I've edited my post to clarify things. – kjo Jan 27 '17 at 0:35
  • You can still use the same code, replacing foo etc with a multi-line string. Unless you need to dynamically manipulate the indentation of your inserted text, I don't know what a better approach would be. – Tyler Jan 27 '17 at 1:04

You can call any elisp function from your snippet definition, so if you can use a helper function defined in your init file to keep the snippet file tidier. In your .emacs or .emacs.d/init.el:

(defun snippet-helper ()
  (cond ((string-equal sh-shell "zsh") "foo")
        ((string-equal sh-shell "bash") "bar")
        ((string-equal sh-shell "sh") "baz")
        (t "foo")))

(note that the case form doesn't work when comparing strings that aren't the same object, so I used cond instead).

Then your snippet becomes:

# -*- mode: snippet -*-
# name: hhh
# key: hhh
# --
  • Actually, this doesn't work if your replacement strings contain multiple spaces to align the different lines. Extra spaces get removed by a call to (yas--indent snippet). Not sure how to fix this yet. – Tyler Jan 27 '17 at 22:44

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.