autotrigger: make snippets expand without <tab>
This commit is contained in:
parent
e1e005a810
commit
76ebfec3cf
@ -151,3 +151,8 @@ endf
|
|||||||
function! UltiSnips#LeavingInsertMode()
|
function! UltiSnips#LeavingInsertMode()
|
||||||
exec g:_uspy "UltiSnips_Manager._leaving_insert_mode()"
|
exec g:_uspy "UltiSnips_Manager._leaving_insert_mode()"
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! UltiSnips#TrackChange()
|
||||||
|
exec g:_uspy "UltiSnips_Manager._track_change()"
|
||||||
|
endfunction
|
||||||
|
" }}}
|
||||||
|
@ -42,6 +42,7 @@ UltiSnips *snippet* *snippets* *UltiSnips*
|
|||||||
4.10.1 Pre-expand actions |UltiSnips-pre-expand-actions|
|
4.10.1 Pre-expand actions |UltiSnips-pre-expand-actions|
|
||||||
4.10.2 Post-expand actions |UltiSnips-post-expand-actions|
|
4.10.2 Post-expand actions |UltiSnips-post-expand-actions|
|
||||||
4.10.3 Post-jump actions |UltiSnips-post-jump-actions|
|
4.10.3 Post-jump actions |UltiSnips-post-jump-actions|
|
||||||
|
4.11 Autotrigger |UltiSnips-autotrigger|
|
||||||
5. UltiSnips and Other Plugins |UltiSnips-other-plugins|
|
5. UltiSnips and Other Plugins |UltiSnips-other-plugins|
|
||||||
5.1 Existing Integrations |UltiSnips-integrations|
|
5.1 Existing Integrations |UltiSnips-integrations|
|
||||||
5.2 Extending UltiSnips |UltiSnips-extending|
|
5.2 Extending UltiSnips |UltiSnips-extending|
|
||||||
@ -690,6 +691,9 @@ The options currently supported are: >
|
|||||||
python expression. This option can be specified along with other
|
python expression. This option can be specified along with other
|
||||||
options, like 'b'. See |UltiSnips-context-snippets| for more info.
|
options, like 'b'. See |UltiSnips-context-snippets| for more info.
|
||||||
|
|
||||||
|
A Snippet will be triggered automatically, when condition matches.
|
||||||
|
See |UltiSnips-autotrigger| for more info.
|
||||||
|
|
||||||
The end line is the 'endsnippet' keyword on a line by itself. >
|
The end line is the 'endsnippet' keyword on a line by itself. >
|
||||||
|
|
||||||
endsnippet
|
endsnippet
|
||||||
@ -1599,6 +1603,41 @@ def $1():
|
|||||||
endsnippet
|
endsnippet
|
||||||
------------------- SNAP -------------------
|
------------------- SNAP -------------------
|
||||||
|
|
||||||
|
4.11 Autotrigger *UltiSnips-autotrigger*
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Note: vim should be newer than 7.4.214 to support this feature.
|
||||||
|
|
||||||
|
Many language constructs can occur only at specific places, so it's
|
||||||
|
possible to use snippets without manually triggering them.
|
||||||
|
|
||||||
|
Snippet can be marked as autotriggered by specifying 'A' option in the snippet
|
||||||
|
definition.
|
||||||
|
|
||||||
|
After snippet is defined as being autotriggered, snippet condition will be
|
||||||
|
checked on every typed character and if condition matches, then snippet will
|
||||||
|
be triggered.
|
||||||
|
|
||||||
|
*Warning:* using of this feature can lead to significant vim slowdown. If you
|
||||||
|
discovered that, report an issue to the github.com/SirVer/UltiSnips.
|
||||||
|
|
||||||
|
Consider following snippets, that can be usefull in Go programming:
|
||||||
|
------------------- SNIP -------------------
|
||||||
|
snippet "^p" "package" rbA
|
||||||
|
package ${1:main}
|
||||||
|
endsnippet
|
||||||
|
|
||||||
|
snippet "^m" "func main" rbA
|
||||||
|
func main() {
|
||||||
|
$1
|
||||||
|
}
|
||||||
|
endsnippet
|
||||||
|
------------------- SNAP -------------------
|
||||||
|
|
||||||
|
When "p" character will occur in the beginning of the line, it will be
|
||||||
|
automatically expanded into "package main". Same with "m" character. There is
|
||||||
|
no need to press trigger key after "m""
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
5. UltiSnips and Other Plugins *UltiSnips-other-plugins*
|
5. UltiSnips and Other Plugins *UltiSnips-other-plugins*
|
||||||
|
|
||||||
|
@ -46,6 +46,12 @@ command! -bang -nargs=? -complete=customlist,UltiSnips#FileTypeComplete UltiSnip
|
|||||||
|
|
||||||
command! -nargs=1 UltiSnipsAddFiletypes :call UltiSnips#AddFiletypes(<q-args>)
|
command! -nargs=1 UltiSnipsAddFiletypes :call UltiSnips#AddFiletypes(<q-args>)
|
||||||
|
|
||||||
|
augroup UltiSnips_AutoTrigger
|
||||||
|
au!
|
||||||
|
au InsertCharPre * call UltiSnips#TrackChange()
|
||||||
|
au TextChangedI * call UltiSnips#TrackChange()
|
||||||
|
augroup END
|
||||||
|
|
||||||
call UltiSnips#map_keys#MapKeys()
|
call UltiSnips#map_keys#MapKeys()
|
||||||
|
|
||||||
" vim: ts=8 sts=4 sw=4
|
" vim: ts=8 sts=4 sw=4
|
||||||
|
@ -23,12 +23,15 @@ class SnippetSource(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def loaded(self, filetypes):
|
||||||
|
return len(self._snippets) > 0
|
||||||
|
|
||||||
def _get_existing_deep_extends(self, base_filetypes):
|
def _get_existing_deep_extends(self, base_filetypes):
|
||||||
"""Helper for get all existing filetypes extended by base filetypes."""
|
"""Helper for get all existing filetypes extended by base filetypes."""
|
||||||
deep_extends = self.get_deep_extends(base_filetypes)
|
deep_extends = self.get_deep_extends(base_filetypes)
|
||||||
return [ft for ft in deep_extends if ft in self._snippets]
|
return [ft for ft in deep_extends if ft in self._snippets]
|
||||||
|
|
||||||
def get_snippets(self, filetypes, before, possible):
|
def get_snippets(self, filetypes, before, possible, autotrigger_only):
|
||||||
"""Returns the snippets for all 'filetypes' (in order) and their
|
"""Returns the snippets for all 'filetypes' (in order) and their
|
||||||
parents matching the text 'before'. If 'possible' is true, a partial
|
parents matching the text 'before'. If 'possible' is true, a partial
|
||||||
match is enough. Base classes can override this method to provide means
|
match is enough. Base classes can override this method to provide means
|
||||||
@ -40,7 +43,8 @@ class SnippetSource(object):
|
|||||||
result = []
|
result = []
|
||||||
for ft in self._get_existing_deep_extends(filetypes):
|
for ft in self._get_existing_deep_extends(filetypes):
|
||||||
snips = self._snippets[ft]
|
snips = self._snippets[ft]
|
||||||
result.extend(snips.get_matching_snippets(before, possible))
|
result.extend(snips.get_matching_snippets(before, possible,
|
||||||
|
autotrigger_only))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_clear_priority(self, filetypes):
|
def get_clear_priority(self, filetypes):
|
||||||
|
@ -16,13 +16,23 @@ class SnippetDictionary(object):
|
|||||||
"""Add 'snippet' to this dictionary."""
|
"""Add 'snippet' to this dictionary."""
|
||||||
self._snippets.append(snippet)
|
self._snippets.append(snippet)
|
||||||
|
|
||||||
def get_matching_snippets(self, trigger, potentially):
|
def get_matching_snippets(self, trigger, potentially, autotrigger_only):
|
||||||
"""Returns all snippets matching the given trigger.
|
"""Returns all snippets matching the given trigger.
|
||||||
|
|
||||||
If 'potentially' is true, returns all that could_match().
|
If 'potentially' is true, returns all that could_match().
|
||||||
|
|
||||||
|
If 'autotrigger_only' is true, function will return only snippets which
|
||||||
|
are marked with flag 'A' (should be automatically expanded without
|
||||||
|
trigger key press).
|
||||||
|
It's handled specially to avoid walking down the list of all snippets,
|
||||||
|
which can be very slow, because function will be called on each change
|
||||||
|
made in insert mode.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
all_snippets = self._snippets
|
all_snippets = self._snippets
|
||||||
|
if autotrigger_only:
|
||||||
|
all_snippets = [s for s in all_snippets if s.has_option('A')]
|
||||||
|
|
||||||
if not potentially:
|
if not potentially:
|
||||||
return [s for s in all_snippets if s.matches(trigger)]
|
return [s for s in all_snippets if s.matches(trigger)]
|
||||||
else:
|
else:
|
||||||
@ -43,3 +53,6 @@ class SnippetDictionary(object):
|
|||||||
if (trigger not in self._cleared or
|
if (trigger not in self._cleared or
|
||||||
priority > self._cleared[trigger]):
|
priority > self._cleared[trigger]):
|
||||||
self._cleared[trigger] = priority
|
self._cleared[trigger] = priority
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._snippets)
|
||||||
|
@ -100,6 +100,8 @@ class SnippetManager(object):
|
|||||||
self._snip_expanded_in_action = False
|
self._snip_expanded_in_action = False
|
||||||
self._inside_action = False
|
self._inside_action = False
|
||||||
|
|
||||||
|
self._last_inserted_char = ''
|
||||||
|
|
||||||
self._added_snippets_source = AddedSnippetsSource()
|
self._added_snippets_source = AddedSnippetsSource()
|
||||||
self.register_snippet_source('ultisnips_files', UltiSnipsFileSource())
|
self.register_snippet_source('ultisnips_files', UltiSnipsFileSource())
|
||||||
self.register_snippet_source('added', self._added_snippets_source)
|
self.register_snippet_source('added', self._added_snippets_source)
|
||||||
@ -541,7 +543,7 @@ class SnippetManager(object):
|
|||||||
elif feedkey:
|
elif feedkey:
|
||||||
_vim.command('return %s' % _vim.escape(feedkey))
|
_vim.command('return %s' % _vim.escape(feedkey))
|
||||||
|
|
||||||
def _snips(self, before, partial):
|
def _snips(self, before, partial, autotrigger_only=False):
|
||||||
"""Returns all the snippets for the given text before the cursor.
|
"""Returns all the snippets for the given text before the cursor.
|
||||||
|
|
||||||
If partial is True, then get also return partial matches.
|
If partial is True, then get also return partial matches.
|
||||||
@ -552,7 +554,8 @@ class SnippetManager(object):
|
|||||||
clear_priority = None
|
clear_priority = None
|
||||||
cleared = {}
|
cleared = {}
|
||||||
for _, source in self._snippet_sources:
|
for _, source in self._snippet_sources:
|
||||||
source.ensure(filetypes)
|
if not autotrigger_only or not source.loaded(filetypes):
|
||||||
|
source.ensure(filetypes)
|
||||||
|
|
||||||
# Collect cleared information from sources.
|
# Collect cleared information from sources.
|
||||||
for _, source in self._snippet_sources:
|
for _, source in self._snippet_sources:
|
||||||
@ -565,7 +568,14 @@ class SnippetManager(object):
|
|||||||
cleared[key] = value
|
cleared[key] = value
|
||||||
|
|
||||||
for _, source in self._snippet_sources:
|
for _, source in self._snippet_sources:
|
||||||
for snippet in source.get_snippets(filetypes, before, partial):
|
possible_snippets = source.get_snippets(
|
||||||
|
filetypes,
|
||||||
|
before,
|
||||||
|
partial,
|
||||||
|
autotrigger_only
|
||||||
|
)
|
||||||
|
|
||||||
|
for snippet in possible_snippets:
|
||||||
if ((clear_priority is None or snippet.priority > clear_priority)
|
if ((clear_priority is None or snippet.priority > clear_priority)
|
||||||
and (snippet.trigger not in cleared or
|
and (snippet.trigger not in cleared or
|
||||||
snippet.priority > cleared[snippet.trigger])):
|
snippet.priority > cleared[snippet.trigger])):
|
||||||
@ -667,10 +677,10 @@ class SnippetManager(object):
|
|||||||
self._snip_expanded_in_action = True
|
self._snip_expanded_in_action = True
|
||||||
|
|
||||||
|
|
||||||
def _try_expand(self):
|
def _try_expand(self, autotrigger_only=False):
|
||||||
"""Try to expand a snippet in the current place."""
|
"""Try to expand a snippet in the current place."""
|
||||||
before = _vim.buf.line_till_cursor
|
before = _vim.buf.line_till_cursor
|
||||||
snippets = self._snips(before, False)
|
snippets = self._snips(before, False, autotrigger_only)
|
||||||
if snippets:
|
if snippets:
|
||||||
# prefer snippets with context if any
|
# prefer snippets with context if any
|
||||||
snippets_with_context = [s for s in snippets if s.context]
|
snippets_with_context = [s for s in snippets if s.context]
|
||||||
@ -765,6 +775,18 @@ class SnippetManager(object):
|
|||||||
finally:
|
finally:
|
||||||
self._inside_action = old_flag
|
self._inside_action = old_flag
|
||||||
|
|
||||||
|
@err_to_scratch_buffer
|
||||||
|
def _track_change(self):
|
||||||
|
inserted_char = _vim.eval('v:char')
|
||||||
|
try:
|
||||||
|
if inserted_char == '':
|
||||||
|
before = _vim.buf.line_till_cursor
|
||||||
|
if before and before[-1] == self._last_inserted_char:
|
||||||
|
self._try_expand(autotrigger_only=True)
|
||||||
|
finally:
|
||||||
|
self._last_inserted_char = inserted_char
|
||||||
|
|
||||||
|
|
||||||
UltiSnips_Manager = SnippetManager( # pylint:disable=invalid-name
|
UltiSnips_Manager = SnippetManager( # pylint:disable=invalid-name
|
||||||
vim.eval('g:UltiSnipsExpandTrigger'),
|
vim.eval('g:UltiSnipsExpandTrigger'),
|
||||||
vim.eval('g:UltiSnipsJumpForwardTrigger'),
|
vim.eval('g:UltiSnipsJumpForwardTrigger'),
|
||||||
|
69
test/test_Autotrigger.py
Normal file
69
test/test_Autotrigger.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from test.vim_test_case import VimTestCase as _VimTest
|
||||||
|
from test.constant import *
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def has_patch(version, executable):
|
||||||
|
output = subprocess.check_output([executable, "--version"])
|
||||||
|
patch = 1
|
||||||
|
for line in output.decode('utf-8').split("\n"):
|
||||||
|
if line.startswith("Included patches:"):
|
||||||
|
patch = line.split('-')[1]
|
||||||
|
|
||||||
|
return int(patch) >= version
|
||||||
|
|
||||||
|
|
||||||
|
def check_required_vim_version(test):
|
||||||
|
if test.vim_flavor == 'neovim':
|
||||||
|
return None
|
||||||
|
if not has_patch(214, test.vim._vim_executable):
|
||||||
|
return 'Vim newer than 7.4.214 is required'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Autotrigger_CanMatchSimpleTrigger(_VimTest):
|
||||||
|
skip_if = check_required_vim_version
|
||||||
|
files = { 'us/all.snippets': r"""
|
||||||
|
snippet a "desc" A
|
||||||
|
autotriggered
|
||||||
|
endsnippet
|
||||||
|
"""}
|
||||||
|
keys = 'a'
|
||||||
|
wanted = 'autotriggered'
|
||||||
|
|
||||||
|
|
||||||
|
class Autotrigger_CanMatchContext(_VimTest):
|
||||||
|
skip_if = check_required_vim_version
|
||||||
|
files = { 'us/all.snippets': r"""
|
||||||
|
snippet a "desc" "snip.line == 2" Ae
|
||||||
|
autotriggered
|
||||||
|
endsnippet
|
||||||
|
"""}
|
||||||
|
keys = 'a\na'
|
||||||
|
wanted = 'autotriggered\na'
|
||||||
|
|
||||||
|
|
||||||
|
class Autotrigger_CanExpandOnTriggerWithLengthMoreThanOne(_VimTest):
|
||||||
|
skip_if = check_required_vim_version
|
||||||
|
files = { 'us/all.snippets': r"""
|
||||||
|
snippet abc "desc" A
|
||||||
|
autotriggered
|
||||||
|
endsnippet
|
||||||
|
"""}
|
||||||
|
keys = 'abc'
|
||||||
|
wanted = 'autotriggered'
|
||||||
|
|
||||||
|
|
||||||
|
class Autotrigger_WillProduceNoExceptionWithVimLowerThan214(_VimTest):
|
||||||
|
skip_if = lambda self: 'Vim older than 7.4.214 is required' \
|
||||||
|
if has_patch(214, self.vim._vim_executable) else None
|
||||||
|
|
||||||
|
files = { 'us/all.snippets': r"""
|
||||||
|
snippet abc "desc" A
|
||||||
|
autotriggered
|
||||||
|
endsnippet
|
||||||
|
"""}
|
||||||
|
keys = 'abc'
|
||||||
|
wanted = 'abc'
|
@ -154,8 +154,8 @@ from UltiSnips.snippet.source import SnippetSource
|
|||||||
from UltiSnips.snippet.definition import UltiSnipsSnippetDefinition
|
from UltiSnips.snippet.definition import UltiSnipsSnippetDefinition
|
||||||
|
|
||||||
class MySnippetSource(SnippetSource):
|
class MySnippetSource(SnippetSource):
|
||||||
def get_snippets(self, filetypes, before, possible):
|
def get_snippets(self, filetypes, before, possible, autotrigger_only):
|
||||||
if before.endswith('blumba'):
|
if before.endswith('blumba') and autotrigger_only == False:
|
||||||
return [
|
return [
|
||||||
UltiSnipsSnippetDefinition(
|
UltiSnipsSnippetDefinition(
|
||||||
-100, "blumba", "this is a dynamic snippet", "", "", {}, "blub",
|
-100, "blumba", "this is a dynamic snippet", "", "", {}, "blub",
|
||||||
|
Loading…
Reference in New Issue
Block a user