3

In this video (at 1:14) UltiSnips is used to insert a snippet with optional arguments. In this case if the default text of the placeholder is deleted not just the placeholder disappears, but the whole line. Afterwards it is possible to jump back to that placeholder and enter text such that also the rest of the line reappears. I cannot recreate this behavior. The closest I got was with

snippet t
`!p
if t[1] == '':
    snip.rv = ''
else:
    snip.rv = 'test: '
`${1:default}`!p
if t[1] == '':
    snip.rv = ''
else:
    snip.rv = '\n'
`foobar: $0
endsnippet

This basically puts a placeholder and depending on whether it is filled, it get surrounded by some text. It does seem quite clunky to me as I'm checking twice and it's a lot of code for a seemingly easy task so I feel like I'm missing an option of UltiSnips. Does anyone have the code used for the screencast or can otherwise shine some light on this feature?

1 Answer 1

9

I think you can get the same result as in the video with this code:

${1/(".*)|.*/(?1:comment => )/}${1:"some comment"}${1/(".*)|.*/(?1:,
)/}

It can be broken down into 3 parts:

      ┌ mirror of tabstop n°1
      │                          ┌ substitution
    ┌─┤┌─────────────────────────┤
(A) ${1/(".*)|.*/(?1:comment => )/}


      ┌ tabstop n° 1
      │              ┌ placeholder text,                                       
      │              │ which fills the tabstop as long as you don't replace it 
    ┌─┤┌─────────────┤
(B) ${1:"some comment"}


      ┌ mirror of tabstop n°1
      │              ┌ beginning of substitution
    ┌─┤┌─────────────┤
(C) ${1/(".*)|.*/(?1:,
    )/}
    └─┤
      └ end of substitution

(B) is the real tabstop. The one in which you can insert an arbitrary comment. (A) and (C) are just mirrors of (B). But, contrary to a simple mirror, which would simply re-insert the original tabstop at the desired position (and which here you would write as $1), these mirrors perform a substitution on the original tabstop.


To avoid any confusion later, here are 3 kinds of ${some_number} token which are going to be used later:

┌───────────────────────────┬──────────────────────────────────────────────┐
│ $1                        │ a tabstop, if it's the first in the snippet  │
│                           │ a mirror of the 1st tabstop otherwise        │
├───────────────────────────┼──────────────────────────────────────────────┤
│ ${1:hello}                │ a tabstop, whose placeholder text is `hello` │
├───────────────────────────┼──────────────────────────────────────────────┤
│ ${1/pattern/replacement/} │ a mirror of the 1st tabstop                  │
│                           │ in which you substitute the text             │
│                           │ matched by `pattern` with `replacement`      │
└───────────────────────────┴──────────────────────────────────────────────┘

Here's how (A) works.

Purpose:
if you insert some text beginning with a double quote in the tabstop, the mirror should prepend it with comment =>. Otherwise, it shouldn't insert anything.

Suppose that when you jump to the tabstop (B), you write "hello world", and suppose you wrote (A) as a simple mirror $1. You would get:

snippet test
    $1${1:"some comment"}
endsnippet

→

"some comment""some comment"
└────────────┤└────────────┤
             │             └ tabstop
             └ mirror

But that's not how (A) was written earlier:

${1/(".*)|.*/(?1:comment => )/}

Here, you perform a substitution which replaces the text matched by the pattern (".*)|.* with the replacement (?1:comment => ).

The pattern consists of 2 branches, the first one being captured by the parentheses so that you can check whether it was matched in the replacement part of the substitution via ?1.

The first branch describes some text beginning with a double quote: ".*, while the 2nd one describes any text: .*

The replacement part checks whether the 1st capturing group is not empty (i.e. “Does the text you wrote in the 1st tabstop begin with a double quote?”). If it's not, it's replaced with comment =>, otherwise nothing is inserted. Nothing is inserted because there's no 2nd :. IOW, these 2 replacements are equivalent:

(?1:comment => )

(?1:comment => :)
 └┤ └─────────┤└┤
  │           │ └ if no, don't insert anything
  │           └ if yes, replace it with `comment => `
  └ is the tabstop matched by the 1st capturing group?

Note that the 2nd branch in the pattern (|.*) is important. It asks UltiSnips to remove the whole mirror if the original tabstop doesn't begin with a double quote.

And note that the parentheses in the replacement are also important. They tell UltiSnips that it's not a literal string, but a conditional expression which must be evaluated. They are similar to the \= token which you may use in the replacement part of a substitution command in Vim (see :h :s\=).


Here's how (C) works.

Purpose:
if you insert some text beginning with a double quote in the tabstop, the mirror should append it with a comma, a newline, and some indentation on the next line. Otherwise, it shouldn't insert anything.

Functionally, (C) is identical to (A): a mirror whose replacement part checks the contents of the original tabstop to decide what it should insert. The only difference is in the contents of the replacement part. The latter contains here a comma, a newline, and some indentation. Since it contains a newline, the code of the mirror is written on 2 lines:

                  newline inside replacement
                  v
${1/(".*)|.*/(?1:, 
)/}

You may find the code a little unreadable, and be tempted to move the newline outside the replacement:

                     newline outside replacement
                     v
${1/(".*)|.*/(?1:,)/}
…

But if you do, when you'll remove the tabstop, the whole line won't be removed: the newline and the indentation will remain. You have to include it in the replacement, so that its presence depends on the first capturing group being non-empty, and so that it's removed when you decide that the tabstop is useless.


Here's a full snippet which should implement the behavior you observed in the video:

snippet user "demo" bm
group { "${1:username}":
    ensure => ${2:present},
    uid => ${3:123},
    gid => ${4:123},
    ${5/(".*)|.*/(?1:comment => )/}${5:"some comment"}${5/(".*)|.*/(?1:,
    )/}home => ${6:"/home/$1"},
    managehome => ${7:false},
    require => ${8/./\u$0/}["${8:group}"],
}
endsnippet

At least it does seem to behave like in the video on my machine:

enter image description here


For more information, see :h Ultisnips-replacement-string.

1
  • Thank you very much for the elaborate reply. The solution is not as simple as I though it might be, but it is certainly useful.
    – Octaviour
    Commented Oct 12, 2018 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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