2

VSCode have the following variables that are quite helpful when creating snippets:

  1. BLOCK_COMMENT_START Example output: in PHP /* or in HTML <!--
  2. BLOCK_COMMENT_END Example output: in PHP */ or in HTML -->
  3. LINE_COMMENT Example output: in PHP //

I searched the documentation of UltiSnips and I have not found an equivalent variables that can be used in code interpolation. Does UltiSnips

Concept:

snippet todo "creates a todo comment
`!p snip.rv = snip.LINE_COMMENT`TODO: $1
endsnippet

More background about this here.

4
  • 1
    Per the page you linked you can type todo as a snippet in various file types and you'll get language-appropriate block comments. Couldn't you just copy the associated snippet(s) and remove the "todo" part from the emitted text? Or perhaps I'm not understanding what you require...
    – B Layer
    May 26 at 15:35
  • 1
    You could use &l:commentstring, which is a vimscript variable for the option of the same name. Check how it's formatted though.
    – D. Ben Knoble
    May 26 at 16:04
  • @BLayer What do you mean? The page that I've linked is about VSCode not UltiSnips.
    – AvidSeeker
    May 27 at 6:00
  • But the functionality I'm referring to is implemented in Ultisnips, too (you might need the compatible snippet collection vim-snippets, actually).
    – B Layer
    May 27 at 10:31
4

Overview

Ultisnips supports vimscript interpolation. To illustrate, the todo snippet for a block comment will have following structure:

snippet todob "todo with block comment" b
`!v {BLOCK_COMMENT_START}`TODO: $1`!v {BLOCK_COMMENT_END}`
endsnippet

where {BLOCK_COMMENT_START} and {BLOCK_COMMENT_END} are vimscript expressions, that we have to construct. The usage of `!v ` tells Ultisnips that we are doing a vimscript interpolation. (This is akin to `!p ` for python interpolation.)

The variables in vim would be &l:commentstring and &l:comments. We will have to extract the relevant strings from them.

Extract Block Comment Markers

As Ben mentioned in the comments, we can access the comment variables with &l:commentstring. This vim variable is filetype dependent, from which we can extract the start and end of block comments. From :h 'commentstring', it is of the form

"{BLOCK_COMMENT_START}%s{BLOCK_COMMENT_END}"

Therefore, we can use vimscript to extract the start and end block comment markers:

function GetBlockCommentMarks()
  return split(&l:commentstring, '%s')
endfunction

This function returns a list of the form ["{BLOCK_COMMENT_START}","{BLOCK_COMMENT_END}"].

Snippet for Block Comments

Now, we replace BLOCK_COMMENT_* with a call to our newly written function, to get the following snippet for block comments:

snippet todob "todo with block comment" b
`!v GetBlockCommentMarks()[0]`TODO: $1`!v GetBlockCommentMarks()[1]`
endsnippet

Extract Line Comment Marker

Things are more involved for line comments. Depending on the language or filetype, it can be found in &l:commentstring or &l:comments, or both.

  • If the filetype only has line comments, the line comment marker may be found in &l:commentstring, in the form "{LINE_COMMENT} %s"
  • Otherwise, it can be found in &l:comments

Like &l:commentstring, &l:comments is also filetype dependent. However, unlike &l:commentstring, it is a comma-separated list of parts of the form {flag}:{string}. It is more involved (and one can refer to :h 'comments' and :h format-comments for details), but for our purposes, what is relevant here is the {string} without any {flag}s.

One can use a regex to extract this {string}, as the following function illustrates:

function GetCommentMarker()
  if len(split(&l:commentstring, '%s')) == 1
    " if 'commentstring' xx%sxx contains no end part
    return split(&l:commentstring, '%s')[0]
  elseif match(&l:comments, '\v(,|^):[^,:]*(,|$)')
    " if 'comments' contains ',:xxx,'
    return matchstr(&l:comments, '\v(,|^):\zs[^,:]*\ze(,|$)')
  else
    echoerr "unable to find line comment marker."
  endif
endfunction

The above function first checks if &l:commentstring is of the appropriate form, and if so, extracts the relevant comment string. Otherwise it extracts the string from &l:comments.

Snippet for Line Comments

Finally, we call this function from inside our snippet, using vim interpolation once again:

snippet todoc "todo with line comment" b
`!v GetCommentMarker()`TODO: $1
endsnippet

Edit: Block Comment Markers Revisited

There are some filetypes (such as java) where &l:commentstring stores the marker for a line comment, instead of block comments. In that case, one has to access the block comment markers via &l:comments.

Of relevance are the strings associated with the s and e flags, which specify the start and end of a block comment (Technically, the s,m,e flags come together, but the middle part can be ignored here).

As before, one can use a regex to extract the relevant strings, which we will add to the previously written function GetBlockCommentMarks. While we're at it, lets add an additional check to &l:commentstring.

function GetBlockCommentMarks()
  if len(split(&l:commentstring, '%s')) == 2
    " if 'commentstring' xx%sxx contains start and end part
    return split(&l:commentstring, '%s')
  endif

  let three_comment = matchstr(&l:comments, '\v(,|^)\zss[^O]{-}:.{-},m.{-}:.{-},e.{-}:.{-}\ze(,|$)')
  if three_comment != ""
    " if 'comments' contains s:xx,m:xx,e:xx
    let start_comment = matchstr(three_comment, '\v^s.{-}:\zs.{-}\ze,')
    let end_comment   = matchstr(three_comment, '\v,e.{-}:\zs.{-}\ze$')
    return [start_comment, end_comment]
  endif
  echoerr "unable to find block comment markers."
endfunction

The regex in the first matchstr() tries to pull s:xx,m:xx,e:xx from l&:comments; the next two matchstr()s extract the start and end comment markers. (The use of [^O] is an ad-hoc addition, so that it works with the java filetype)

Wrapping up

Here's the full code

" vim
function GetCommentMarker()
  if len(split(&l:commentstring, '%s')) == 1
    " if 'commentstring' xx%sxx contains no end part
    return split(&l:commentstring, '%s')[0]
  elseif match(&l:comments, '\v(,|^):[^,:]*(,|$)')
    " if 'comments' contains ',:xxx,'
    return matchstr(&l:comments, '\v(,|^):\zs[^,:]*\ze(,|$)')
  else
    echoerr "unable to find line comment marker."
  endif
endfunction

function GetBlockCommentMarks()
  if len(split(&l:commentstring, '%s')) == 2
    " if 'commentstring' xx%sxx contains start and end part
    return split(&l:commentstring, '%s')
  endif

  let three_comment = matchstr(&l:comments, '\v(,|^)\zss[^O]{-}:.{-},m.{-}:.{-},e.{-}:.{-}\ze(,|$)')
  if three_comment != ""
    " if 'comments' contains s:xx,m:xx,e:xx
    let start_comment = matchstr(three_comment, '\v^s.{-}:\zs.{-}\ze,')
    let end_comment   = matchstr(three_comment, '\v,e.{-}:\zs.{-}\ze$')
    return [start_comment, end_comment]
  endif
  echoerr "unable to find block comment markers."
endfunction
# snippets
snippet todoc "todo with line comment" b
`!v GetCommentMarker()`TODO: $1
endsnippet

snippet todob "todo with block comment" b
`!v GetBlockCommentMarks()[0]`TODO: $1`!v GetBlockCommentMarks()[1]`
endsnippet

Happy vimming!

9
  • Thank you! Great answer. It worked for C++, but not in Java and python: vim.error: Vim:E684: list index out of range: . So, I suppose vim returns an array of 1 element since python does not have a BLOCK_END. But Java should return a 2-element array.
    – AvidSeeker
    May 29 at 10:22
  • Hi, sorry for my late reply. I believe you are referring to block comments... Yes, python gives an error since it has no BLOCK_COMMENT_END. java is an interesting case, in that &l:commentstring contains LINE_COMMENT, instead of BLOCK_COMMENT_*. So, one has to extract BLOCK_COMMENT_* from &l:comments. See my edit
    – husB
    May 31 at 7:38
  • Nice. It works for Java, but unfortunately not python nor LaTeX. I still don't know much about vimscript to make a matchstr for python.
    – AvidSeeker
    Jun 3 at 11:09
  • @AvidSeeker Does it work if you replace the two lines containing echoerr "unable to find..." with return ""?
    – husB
    Jun 3 at 17:07
  • Not exactly. This just suppresses the error, and instead of inserting the comment character it leaves it empty. I've just tried it on Python and LaTeX file types.
    – AvidSeeker
    Jun 8 at 15:07

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.