I'd like an easy way to create aliases (i.e., multiple triggers) for snippets, especially ones that are defined in global snippet file (i.e., so I can't simply edit the snippet definition to add the alias with a regex).
Can you provide me a use case? I.e. why do you want this feature implemented right now. I feel alias is a bandaid, so I am resisting to implement it right now. If I get to know the problem people try to solve with aliases better, maybe I can come up with a better solution.
Sure. For instance there is a trigger infdef in the vim-snippets repo, for "c" filetype. I would like to use #ifndefas the trigger, because in all likelihood I will start typing "#ifndef" before I remember that there is an alias for that. So right now I have to duplicate the snippet with my preferred trigger, which means if I ever want to change the snippet definition, I have to do it in multiple places.
More generally, I think it is a pretty common use case that people want to use a system-wide snippet (for instance, one from a third-party repo like vim-snippets), but they prefer a different trigger than the one it is defined with.
I dislike the thought of people changing the shipped triggers using aliases. It makes communication difficult as people will refer to aliases that they forgot months ago. And I much rather change global triggers so that they make sense to everybody. People also do not remap dd or jhkl.
If an alias feature is absolutely necessary I'd much rather allow expanding arbitrary snippets inside other snippets basically allowing you to forward a snippet. That seems more generally useful than aliasing...
About your use case in question: I think it is addressed by using a completion tool like YouCompleteMe or Neocomplete that will display UlitSnips snippets even if you did type only part of the trigger (and not the start). Are you using that?
I agree, being able to expand snippets inside of other snippets would be a much more general and very powerful feature, and would also function as the aliasing feature I'm looking for.
No, I'm not currently using any complimentary tools. Choosing from a list of snippets isn't really ideal, it's a lot of extra typing for something that is supposed to be saving time and keystrokes.
I understand your point about not wanting to have multiple triggers for the same snippet, but realistically how often does that really matter? I mean how often do people talk about specific snippets? Maybe a lot, I honestly don't know, I've only been using snippets for a few days, so maybe something that seems important to me now turns out to not really be once I'm into the swing of it.
There is more to it though. Some snippets use the trigger for fancy calculations (i.e. regular expression triggers and python interpolation). If an author relies on the trigger being one thing and the user turns it into another, those snippets will stop working. Maybe I am overcautious - but if we add this feature, I'll have to support it forever. So I am very reluctant to do this, I still like the inlining better (you need to define which trigger will be used in the snippet then).
Ah, ok. I didn't realize the trigger could be used that way, but that makes sense. Yes, I still think the inlining idea is much better.
Relevant discussion: https://answers.launchpad.net/ultisnips/+question/215812
About use cases, I was looking for this feature with the intention of having several triggers incprintf, incfprintf, incperror that all expand to #include <stdio.h> /* perror, printf, fprintf */. I always forget which header file declares what so I like to have inc+nameoffunction as a trigger that generates the proper #include. Now I have to duplicate the snippets (stdio.h defines many, for example).
@ecerulm how about using a regular expression trigger for that?
Best. Feature. Ever. I didn't realize that existed! It works like a charm to create "aliases". Only problem is that YouCompleMe will not offer autocompletion for those triggers.....
I know that here you explained that you do not wish to deal with parsing code objects, but how about this:
Whenever there is an (unescaped) $n inside of snip.rv, replace this with a tabstop. ${n:Default} will be replaced with a tabstop with and text Default. All user-defined $1, $2, ... tabstops will be executed in their standard order, then all $n tabstops will be executed in the order of appearance in snip.rv, and then lastly execute $0 (since this is always last). I don't think this should be too hard, as it should be a simple parse after snip.rv is received and _I think_ we would just need to put in some tabspot objects in the current SnippetInstance?
I know it's not the full functionality that you would want from dynamically adding tabstops; in fact it's a rather simple implementation. But I think this kind of functionality could solve a lot of peoples problems. What do you think? Am I missing anything? I can _try_ to see if I can get that kind of functionality working.
Unfortunately it is not that simple to implement the way you describe. Right now, UltiSnips first inserts the unparsed snippet text into the vim buffer and then parses it one textitem at a time. Your suggestions would require a recursion during that parsing that will be challenging (but not impossible) to do. If you want to have a go at it!
+1 to some alias-like functionality. I see your point about some snippets being tied to their trigger via a regex, so agree that being able to inline another snippet would be the ideal solution. Then you could even do neat things like specialize a regex snippet for a special case.
The current alternative doesn't seem ideal. Having to copy and paste the snippet to change the trigger doesn't seem very DRY, and forcing everyone to use the same trigger isn't great either. For instance, I put #!/usr/bin/env bash at the top of every bash script I write, and would love to just type ! instead of !env, but other people might not want the env version to be default, so ! shouldn't be the default trigger for this for everyone.
In the meantime, I'm trying to create a hack to do this, but am running into an issue. Here's my snippets file:
global !p
from UltiSnips import UltiSnips_Manager
def expand_snip(snip, name):
line = snip.buffer[snip.line]
snip.buffer[snip.line] = line[:snip.column] + name + line[snip.column:]
snip.cursor.set(snip.line, snip.column + len(name)+1)
UltiSnips_Manager.expand()
endglobal
snippet dst
hello
endsnippet
post_expand "expand_snip(snip, 'dst')"
snippet src
endsnippet
When I run this, I get the following error:
An error occured. This is either a bug in UltiSnips or a bug in a
snippet definition. If you think this is a bug, please report it to
https://github.com/SirVer/ultisnips/issues/new.
Following is the full stack trace:
Traceback (most recent call last):
File "/Users/pokey/.vim/plugged/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 61, in wrapper
return func(self, *args, **kwds)
File "/Users/pokey/.vim/plugged/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 163, in expand
if not self._try_expand():
File "/Users/pokey/.vim/plugged/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 725, in _try_expand
self._do_snippet(snippet, before)
File "/Users/pokey/.vim/plugged/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 696, in _do_snippet
self._jump()
File "/Users/pokey/.vim/plugged/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 502, in _jump
_vim.select(ntab.start, ntab.end)
File "/Users/pokey/.vim/plugged/ultisnips/pythonx/UltiSnips/_vim.py", line 167, in select
vim.current.window.cursor = start.line + 1, col
error: cursor position outside buffer
Any idea what might be going on / whether this is a reasonable hack?
@pokey: Looks like bug for me. Can you create separate issue?
Regarding specializing regex snippets: it's already can be done using context-aware snippets.
Excellent, thanks!
👍 for dynamic tabstop generation. It's neat having snippets for LaTeX table rows, so that you can quickly fill out a row without having to type " & " between every column. If the snip.rv were parsed for tabstops, then you could do something like the following:
snippet "tr([1-9][0-9]*)" "LaTeX table row" r
`!p
a = "$1"
p = int(match.group(1))
for i in range(p - 1): a += ( " & $" + str(i+2) )
snip.rv = a`
endsnippet
Instead you currently have to define a snippet for _every_ table row length:
snippet tr9 "LaTeX table row of length nine"
$1 & $2 & $3 & $4 & $5 & $6 & $7 & $8 & $9
endsnippet
which is kind of ridiculous.
This is a use case where the ability to dynamically-generate tabstops would be very useful.
@alexcoplan: It's already possible. You can use (a) anonymous dynamic snippets; (b) snippet aliasing.
@seletskiy where can I find out more about those? Can't find anything in the docs...
@alexcoplan: it's not in the docs because it's complex feature, implementing by using another US features. See, I've wrote article just for you: https://github.com/SirVer/ultisnips/blob/master/doc/examples/tabstop-generation/README.md
@seletskiy thanks so much! Really helpful.
I've tried using the above example in my ultisnips file, but it gives me the following error, even when used verbatim with no changes:
An error occured. This is either a bug in UltiSnips or a bug in a
snippet definition. If you think this is a bug, please report it to
https://github.com/SirVer/ultisnips/issues/new.
Following is the full stack trace:
Traceback (most recent call last):
File "/Users/alexander.tsepkov/.vim/bundle/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 56, in wrapper
return func(self, *args, **kwds)
File "/Users/alexander.tsepkov/.vim/bundle/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 146, in expand_or_jump
rv = self._try_expand()
File "/Users/alexander.tsepkov/.vim/bundle/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 577, in _try_expand
snippets = self._snips(before, False)
File "/Users/alexander.tsepkov/.vim/bundle/ultisnips/pythonx/UltiSnips/snippet_manager.py", line 492, in _snips
source.ensure(filetypes)
File "/Users/alexander.tsepkov/.vim/bundle/ultisnips/pythonx/UltiSnips/snippet/source/file/_base.py", line 43, in ensure
self._load_snippets_for(ft)
File "/Users/alexander.tsepkov/.vim/bundle/ultisnips/pythonx/UltiSnips/snippet/source/file/_base.py", line 74, in _load_snippets_for
self._parse_snippets(ft, fn)
File "/Users/alexander.tsepkov/.vim/bundle/ultisnips/pythonx/UltiSnips/snippet/source/file/_base.py", line 93, in _parse_snippets
raise SnippetSyntaxError(filename, line_index, msg)
SnippetSyntaxError: Invalid line 'post_jump "create_row_placeholders(snip)"' in .vim/bundle/vim-snippets/UltiSnips/my.snippets:658
I have no idea why it doesn't like post_jump despite being in documentation.
My bad, looks like I was using an outdated version of the plugin. BundleUpdate took care of the issue.
Another use case I see for snippet aliases is having a plugin providing a — possibly very complex — snippet. The plugin should avoid imposing a short name on the user (since snippet names are mostly a personal matter). So the plugin may give the snippet a long, disambiguated name, and the user may bind it to the short name of its liking.
@seletskiy The article for table rows in latex in very handy.
Based on it I just wrote a snippet for matrices and determinants that seems fine when triggering the snippet by itself, but have some problems when trying to do the same in math mode, within the snippet for \[ \].
For illustration (not Tex-syntactically correct, though), I will use the same snippet in your article (for table rows) instead of mine (for matrices), since the problem is the same (as explained below).
Details:
I have the following snippet:
snippet ll "Math" b
\[
${1:${VISUAL}}
\]
$0
plus the snippet tr(\d+) that you have provided:
global !p
def create_row_placeholders(snip):
# retrieving singlee line from current string and treat it like tabstops
# count
placeholders_amount = int(snip.buffer[snip.line].strip())
# erase current line
snip.buffer[snip.line] = ''
# create anonymous snippet with expected content and number of tabstops
anon_snippet_body = ' & '.join(['$' + str(i+1)
for i in range(placeholders_amount)])
# expand anonymous snippet
snip.expand_anon(anon_snippet_body)
endglobal
post_jump "create_row_placeholders(snip)"
snippet "tr(\d+)" "latex table row variable" br
`!p snip.rv = match.group(1)`
endsnippet
Expected behavior:
ll<Tab>tr8<Tab>
\[
& & & & & & &
\]
Actual behavior:
\[
8& & & & & & &
\]
In your code:
snip.expand_anon the snippet with a string beginning with & is expanded.snip.rv the captured value of 8 is returned.8) overwrites the first character of the expanded string (a whitespace).I say that the problem is the same with my code, because:
snip.expand_anon a snippet with a string beginning with \begin{pmatrix}\n is expanded.snip.rv the captured value of, for example m33 for a 3-by-3 matrix, is returned.m33) overwrites the first 3 characters of the expanded string giving m33gin{pmatrix}\nI thought that the problem can be solved just by replacing the text in t[1] in the ll snippet with an empty string whenever the snippet for matrices goes in it, but due to my inexperience with vim/UltiSnips only get some errors.
@Joqsan:
Try to use this one:
global !p
def create_row_placeholders(snip):
placeholders_amount = int(snip.context['columns'])
# create anonymous snippet with expected content and number of tabstops
anon_snippet_body = ' & '.join(['$' + str(i+1)
for i in range(placeholders_amount)])
# expand anonymous snippet
snip.expand_anon(anon_snippet_body)
endglobal
context "{'columns': 0}"
post_jump "create_row_placeholders(snip)"
snippet "tr(\d+)" "latex table row variable" br
`!p snip.context['columns'] = match.group(1)`
endsnippet
It's using special snip.context attribute which is set to the value returned by context directive (it should be non-empty if you want snippet to expand, therefore empty object of 0 will not work).
@seletskiy :
That works nicely. Really thankful!
Most helpful comment
@alexcoplan: it's not in the docs because it's complex feature, implementing by using another US features. See, I've wrote article just for you: https://github.com/SirVer/ultisnips/blob/master/doc/examples/tabstop-generation/README.md