Moving towards Snippet providers.

Removed parsing responsibilities from SnippetManager and instead put
them into the new module providers. Renamed private methods on
SnippetManager that are not meant to be called by external libraries to
start with _. Refactored tests so that expected failures can be tested
and therefore removed the testing flag from SnippetManager.
This commit is contained in:
Holger Rapp 2014-02-14 23:58:00 +01:00
parent a9d946135f
commit 2eb82d127b
11 changed files with 454 additions and 408 deletions

View File

@ -100,7 +100,7 @@ function! s:compensate_for_pum()
""" to explicitly check for the presence of the popup menu, and update """ to explicitly check for the presence of the popup menu, and update
""" the vim-state accordingly. """ the vim-state accordingly.
if pumvisible() if pumvisible()
exec g:_uspy "UltiSnips_Manager.cursor_moved()" exec g:_uspy "UltiSnips_Manager._cursor_moved()"
endif endif
endfunction endfunction
@ -108,9 +108,9 @@ function! UltiSnips#Edit(...)
if a:0 == 1 && a:1 != '' if a:0 == 1 && a:1 != ''
let type = a:1 let type = a:1
else else
exec g:_uspy "vim.command(\"let type = '%s'\" % UltiSnips_Manager.primary_filetype)" exec g:_uspy "vim.command(\"let type = '%s'\" % UltiSnips_Manager._primary_filetype)"
endif endif
exec g:_uspy "vim.command(\"let file = '%s'\" % UltiSnips_Manager.file_to_edit(vim.eval(\"type\")))" exec g:_uspy "vim.command(\"let file = '%s'\" % UltiSnips_Manager._file_to_edit(vim.eval(\"type\")))"
let mode = 'e' let mode = 'e'
if exists('g:UltiSnipsEditSplit') if exists('g:UltiSnipsEditSplit')
@ -212,7 +212,7 @@ function! UltiSnips#SnippetsInCurrentScope()
endfunction endfunction
function! UltiSnips#SaveLastVisualSelection() function! UltiSnips#SaveLastVisualSelection()
exec g:_uspy "UltiSnips_Manager.save_last_visual_selection()" exec g:_uspy "UltiSnips_Manager._save_last_visual_selection()"
return "" return ""
endfunction endfunction
@ -257,15 +257,15 @@ endfunction
function! UltiSnips#CursorMoved() function! UltiSnips#CursorMoved()
exec g:_uspy "UltiSnips_Manager.cursor_moved()" exec g:_uspy "UltiSnips_Manager._cursor_moved()"
endf endf
function! UltiSnips#LeavingBuffer() function! UltiSnips#LeavingBuffer()
exec g:_uspy "UltiSnips_Manager.leaving_buffer()" exec g:_uspy "UltiSnips_Manager._leaving_buffer()"
endf 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
" }}} " }}}

View File

@ -346,11 +346,6 @@ file: >
" Traverse in reverse order " Traverse in reverse order
let g:UltiSnipsDontReverseSearchPath="0" let g:UltiSnipsDontReverseSearchPath="0"
By default, whenever a snippet expand is triggered, UltiSnips will check for
modifications to the snippet file associated with the filetype and reload it
if necessary. This behavior can be disabled as follows: >
let g:UltiSnipsDoHash=0
|UltiSnips-adding-snippets| explains which files are parsed for a given filetype. |UltiSnips-adding-snippets| explains which files are parsed for a given filetype.

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
# encoding: utf-8
"""Sources of snippet definitions."""
# TODO(sirver): these should register themselves with the Manager, so that
# other plugins can extend them more easily.
from UltiSnips.providers.snippet_file import UltiSnipsFileProvider, \
base_snippet_files_for
from UltiSnips.providers.added_snippets_provider import AddedSnippetsProvider

View File

@ -0,0 +1,59 @@
#!/usr/bin/env python
# encoding: utf-8
"""Base class for snippet providers."""
from collections import defaultdict
from UltiSnips.providers._snippet_dictionary import SnippetDictionary
class SnippetProvider(object):
"""See module docstring."""
def __init__(self):
self._snippets = defaultdict(SnippetDictionary)
def get_snippets(self, filetypes, before, possible):
"""Returns the snippets for all 'filetypes' (in order) and their
parents matching the text 'before'. If 'possible' is true, a partial
match is enough."""
found_snippets = []
for ft in filetypes:
found_snippets += self._find_snippets(ft, before, possible)
# Search if any of the snippets overwrites the previous
# Dictionary allows O(1) access for easy overwrites
snippets = {}
for snip in found_snippets:
if (snip.trigger not in snippets) or snip.overwrites_previous:
snippets[snip.trigger] = []
snippets[snip.trigger].append(snip)
# Transform dictionary into flat list of snippets
selected_snippets = set(
[item for sublist in snippets.values() for item in sublist])
# Return snippets to their original order
snippets = [snip for snip in found_snippets if
snip in selected_snippets]
return snippets
def _find_snippets(self, ft, trigger, potentially=False, seen=None):
"""Find snippets matching 'trigger' for 'ft'. If 'potentially' is True,
partial matches are enough."""
snips = self._snippets.get(ft, None)
if not snips:
return []
if not seen:
seen = set()
seen.add(ft)
parent_results = []
# TODO(sirver): extends information is not bound to one
# provider. It should be tracked further up.
for parent_ft in snips.extends:
if parent_ft not in seen:
seen.add(parent_ft)
parent_results += self._find_snippets(parent_ft, trigger,
potentially, seen)
return parent_results + snips.get_matching_snippets(
trigger, potentially)

View File

@ -6,14 +6,13 @@
import hashlib import hashlib
import os import os
def _hash_file(path): def _hash_file(path):
"""Returns a hashdigest of 'path'""" """Returns a hashdigest of 'path'"""
if not os.path.isfile(path): if not os.path.isfile(path):
return False return False
return hashlib.sha1(open(path, "rb").read()).hexdigest() return hashlib.sha1(open(path, "rb").read()).hexdigest()
# TODO(sirver): This class should not hash any files nor keep track of extends.
class SnippetDictionary(object): class SnippetDictionary(object):
"""See module docstring.""" """See module docstring."""

View File

@ -0,0 +1,14 @@
#!/usr/bin/env python
# encoding: utf-8
"""Handles manually added snippets (i.e. not in a file)."""
from UltiSnips.providers._base import SnippetProvider
class AddedSnippetsProvider(SnippetProvider):
"""See module docstring."""
# TODO(sirver): filename makes no sense here. Is it even used?
def add_snippet(self, ft, snippet, filename):
"""Adds the given 'snippet' for 'ft'."""
self._snippets[ft].add_snippet(snippet, filename)

View File

@ -0,0 +1,182 @@
#!/usr/bin/env python
# encoding: utf-8
"""Code to provide access to UltiSnips files from disk."""
import glob
import os
from UltiSnips.providers._base import SnippetProvider
from UltiSnips.providers.ultisnips_file import parse_snippets_file
from UltiSnips.snippet_definition import SnippetDefinition
import UltiSnips._vim as _vim
def _plugin_dir():
"""Calculates the plugin directory for UltiSnips."""
directory = __file__
for _ in range(10):
directory = os.path.dirname(directory)
if (os.path.isdir(os.path.join(directory, "plugin")) and
os.path.isdir(os.path.join(directory, "doc"))):
return directory
raise Exception("Unable to find the plugin directory.")
def _snippets_dir_is_before_plugin_dir():
""" Returns True if the snippets directory comes before the plugin
directory in Vim's runtime path. False otherwise.
"""
paths = [os.path.realpath(os.path.expanduser(p)).rstrip(os.path.sep)
for p in _vim.eval("&runtimepath").split(',')]
home = _vim.eval("$HOME")
def vim_path_index(suffix):
"""Returns index of 'suffix' in 'paths' or -1 if it is not found."""
path = os.path.realpath(os.path.join(home, suffix)).rstrip(os.path.sep)
try:
return paths.index(path)
except ValueError:
return -1
try:
real_vim_path_index = max(
vim_path_index(".vim"), vim_path_index("vimfiles"))
plugin_path_index = paths.index(_plugin_dir())
return plugin_path_index < real_vim_path_index
except ValueError:
return False
def _should_reverse_search_path():
""" If the user defined g:UltiSnipsDontReverseSearchPath then return True
or False based on the value of that variable, else defer to
_snippets_dir_is_before_plugin_dir to determine whether this is True or
False.
"""
if _vim.eval("exists('g:UltiSnipsDontReverseSearchPath')") != "0":
return _vim.eval("g:UltiSnipsDontReverseSearchPath") != "0"
return not _snippets_dir_is_before_plugin_dir()
def base_snippet_files_for(ft, default=True):
"""Returns a list of snippet files matching the given filetype (ft).
If default is set to false, it doesn't include shipped files.
Searches through each path in 'runtimepath' in reverse order,
in each of these, it searches each directory name listed in
'g:UltiSnipsSnippetDirectories' in order, then looks for files in these
directories called 'ft.snippets' or '*_ft.snippets' replacing ft with
the filetype.
"""
if _vim.eval("exists('b:UltiSnipsSnippetDirectories')") == "1":
snippet_dirs = _vim.eval("b:UltiSnipsSnippetDirectories")
else:
snippet_dirs = _vim.eval("g:UltiSnipsSnippetDirectories")
paths = _vim.eval("&runtimepath").split(',')
if _should_reverse_search_path():
paths = paths[::-1]
base_snippets = os.path.realpath(os.path.join(_plugin_dir(), "UltiSnips"))
ret = []
for rtp in paths:
for snippet_dir in snippet_dirs:
pth = os.path.realpath(os.path.expanduser(
os.path.join(rtp, snippet_dir)))
patterns = ["%s.snippets", "%s_*.snippets", os.path.join("%s", "*")]
if not default and pth == base_snippets:
patterns.remove("%s.snippets")
for pattern in patterns:
for fn in glob.glob(os.path.join(pth, pattern % ft)):
if fn not in ret:
ret.append(fn)
return ret
class SnippetSyntaxError(RuntimeError):
"""Thrown when a syntax error is found in a file."""
def __init__(self, filename, line_index, msg):
RuntimeError.__init__(self, "%s in %s:%d" % (
msg, filename, line_index))
class UltiSnipsFileProvider(SnippetProvider):
"""Manages all snippets definitons found in rtp."""
def get_snippets(self, filetypes, before, possible):
for ft in filetypes:
self._ensure_loaded(ft)
return SnippetProvider.get_snippets(self, filetypes, before, possible)
def _ensure_loaded(self, ft, already_loaded=None):
"""Make sure that the snippets for 'ft' and everything it extends are
loaded."""
if not already_loaded:
already_loaded = set()
if ft in already_loaded:
return
already_loaded.add(ft)
if self._needs_update(ft):
self._load_snippets_for(ft)
for parent in self._snippets[ft].extends:
self._ensure_loaded(parent, already_loaded)
def _needs_update(self, ft):
"""Returns true if any files for 'ft' have changed and must be
reloaded."""
if ft not in self._snippets:
return True
elif self._snippets[ft].has_any_file_changed():
return True
else:
cur_snips = set(base_snippet_files_for(ft))
old_snips = set(self._snippets[ft].files)
if cur_snips - old_snips:
return True
return False
def _load_snippets_for(self, ft):
"""Load all snippets for the given 'ft'."""
if ft in self._snippets:
del self._snippets[ft]
for fn in base_snippet_files_for(ft):
self._parse_snippets(ft, fn)
# Now load for the parents
for parent_ft in self._snippets[ft].extends:
if parent_ft not in self._snippets:
self._load_snippets_for(parent_ft)
def _parse_snippets(self, ft, filename):
"""Parse the file 'filename' for the given 'ft' and watch it for
changes in the future. 'file_data' can be injected in tests."""
self._snippets[ft].addfile(filename)
file_data = open(filename, "r").read()
for event, data in parse_snippets_file(file_data):
if event == "error":
msg, line_index = data
filename = _vim.eval("""fnamemodify(%s, ":~:.")""" %
_vim.escape(filename))
raise SnippetSyntaxError(filename, line_index, msg)
elif event == "clearsnippets":
triggers, = data
self._snippets[ft].clear_snippets(triggers)
elif event == "extends":
# TODO(sirver): extends information is more global
# than one snippet provider.
filetypes, = data
self._add_extending_info(ft, filetypes)
elif event == "snippet":
trigger, value, description, options, globals = data
self._snippets[ft].add_snippet(
SnippetDefinition(trigger, value, description, options,
globals), filename
)
else:
assert False, "Unhandled %s: %r" % (event, data)
def _add_extending_info(self, ft, parents):
"""Add the list of 'parents' as being extended by the 'ft'."""
sd = self._snippets[ft]
for parent in parents:
if parent in sd.extends:
continue
sd.extends.append(parent)

View File

@ -33,7 +33,7 @@ def _words_for_line(trigger, before, num_words=None):
return before[len(before_words):].strip() return before[len(before_words):].strip()
class Snippet(object): class SnippetDefinition(object):
"""Represents a snippet as parsed from a file.""" """Represents a snippet as parsed from a file."""
_INDENT = re.compile(r"^[ \t]*") _INDENT = re.compile(r"^[ \t]*")
@ -49,7 +49,7 @@ class Snippet(object):
self._globals = globals self._globals = globals
def __repr__(self): def __repr__(self):
return "Snippet(%s,%s,%s)" % ( return "SnippetDefinition(%s,%s,%s)" % (
self._trigger, self._description, self._opts) self._trigger, self._description, self._opts)
def _re_match(self, trigger): def _re_match(self, trigger):

View File

@ -5,17 +5,15 @@
from collections import defaultdict from collections import defaultdict
from functools import wraps from functools import wraps
import glob
import os import os
import re
import traceback import traceback
from UltiSnips._diff import diff, guess_edit from UltiSnips._diff import diff, guess_edit
from UltiSnips.compatibility import as_unicode from UltiSnips.compatibility import as_unicode
from UltiSnips.position import Position from UltiSnips.position import Position
from UltiSnips.snippet import Snippet from UltiSnips.providers import UltiSnipsFileProvider, \
from UltiSnips.snippet_definitions import parse_snippets_file base_snippet_files_for, AddedSnippetsProvider
from UltiSnips.snippet_dictionary import SnippetDictionary from UltiSnips.snippet_definition import SnippetDefinition
from UltiSnips.vim_state import VimState, VisualContentPreserver from UltiSnips.vim_state import VimState, VisualContentPreserver
import UltiSnips._vim as _vim import UltiSnips._vim as _vim
@ -40,86 +38,6 @@ def _ask_snippets(snippets):
except KeyboardInterrupt: except KeyboardInterrupt:
return None return None
def _base_snippet_files_for(ft, default=True):
"""Returns a list of snippet files matching the given filetype (ft).
If default is set to false, it doesn't include shipped files.
Searches through each path in 'runtimepath' in reverse order,
in each of these, it searches each directory name listed in
'g:UltiSnipsSnippetDirectories' in order, then looks for files in these
directories called 'ft.snippets' or '*_ft.snippets' replacing ft with
the filetype.
"""
if _vim.eval("exists('b:UltiSnipsSnippetDirectories')") == "1":
snippet_dirs = _vim.eval("b:UltiSnipsSnippetDirectories")
else:
snippet_dirs = _vim.eval("g:UltiSnipsSnippetDirectories")
base_snippets = os.path.realpath(os.path.join(
__file__, "../../../UltiSnips"))
ret = []
paths = _vim.eval("&runtimepath").split(',')
if _should_reverse_search_path():
paths = paths[::-1]
for rtp in paths:
for snippet_dir in snippet_dirs:
pth = os.path.realpath(os.path.expanduser(
os.path.join(rtp, snippet_dir)))
patterns = ["%s.snippets", "%s_*.snippets", os.path.join("%s", "*")]
if not default and pth == base_snippets:
patterns.remove("%s.snippets")
for pattern in patterns:
for fn in glob.glob(os.path.join(pth, pattern % ft)):
if fn not in ret:
ret.append(fn)
return ret
def _plugin_dir():
"""Calculates the plugin directory for UltiSnips."""
directory = __file__
for _ in range(10):
directory = os.path.dirname(directory)
if (os.path.isdir(os.path.join(directory, "plugin")) and
os.path.isdir(os.path.join(directory, "doc"))):
return directory
raise Exception("Unable to find the plugin directory.")
def _snippets_dir_is_before_plugin_dir():
""" Returns True if the snippets directory comes before the plugin
directory in Vim's runtime path. False otherwise.
"""
paths = [os.path.realpath(os.path.expanduser(p)).rstrip(os.path.sep)
for p in _vim.eval("&runtimepath").split(',')]
home = _vim.eval("$HOME")
def vim_path_index(suffix):
"""Returns index of 'suffix' in 'paths' or -1 if it is not found."""
path = os.path.realpath(os.path.join(home, suffix)).rstrip(os.path.sep)
try:
return paths.index(path)
except ValueError:
return -1
try:
real_vim_path_index = max(
vim_path_index(".vim"), vim_path_index("vimfiles"))
plugin_path_index = paths.index(_plugin_dir())
return plugin_path_index < real_vim_path_index
except ValueError:
return False
def _should_reverse_search_path():
""" If the user defined g:UltiSnipsDontReverseSearchPath then return True
or False based on the value of that variable, else defer to
_snippets_dir_is_before_plugin_dir to determine whether this is True or
False.
"""
if _vim.eval("exists('g:UltiSnipsDontReverseSearchPath')") != "0":
return _vim.eval("g:UltiSnipsDontReverseSearchPath") != "0"
return not _snippets_dir_is_before_plugin_dir()
def err_to_scratch_buffer(func): def err_to_scratch_buffer(func):
"""Decorator that will catch any Exception that 'func' throws and displays """Decorator that will catch any Exception that 'func' throws and displays
@ -137,13 +55,14 @@ https://bugs.launchpad.net/ultisnips/+filebug.
Following is the full stack trace: Following is the full stack trace:
""" """
msg += traceback.format_exc() msg += traceback.format_exc()
self.leaving_buffer() # Vim sends no WinLeave msg here. # Vim sends no WinLeave msg here.
self._leaving_buffer() # pylint:disable=protected-access
_vim.new_scratch_buffer(msg) _vim.new_scratch_buffer(msg)
return wrapper return wrapper
# TODO(sirver): This class has too many responsibilities - it should not also # TODO(sirver): This class is still too long. It should only contain public
# care for the parsing and managing of parsed snippets. # facing methods, most of the private methods should be moved outside of it.
class SnippetManager(object): class SnippetManager(object):
"""The main entry point for all UltiSnips functionality. All Vim functions """The main entry point for all UltiSnips functionality. All Vim functions
call methods in this class.""" call methods in this class."""
@ -154,21 +73,7 @@ class SnippetManager(object):
self.backward_trigger = backward_trigger self.backward_trigger = backward_trigger
self._supertab_keys = None self._supertab_keys = None
self._csnippets = [] self._csnippets = []
self.reset() self._reset()
@err_to_scratch_buffer
def reset(self, test_error=False):
"""Reset the class to the state it had directly after creation."""
self._vstate = VimState()
self._test_error = test_error
self._snippets = defaultdict(SnippetDictionary)
self._filetypes = defaultdict(lambda: ['all'])
self._visual_content = VisualContentPreserver()
while len(self._csnippets):
self._current_snippet_is_done()
self._reinit()
@err_to_scratch_buffer @err_to_scratch_buffer
def jump_forwards(self): def jump_forwards(self):
@ -188,12 +93,28 @@ class SnippetManager(object):
@err_to_scratch_buffer @err_to_scratch_buffer
def expand(self): def expand(self):
"""Trie to expand a snippet at the current position.""" """Try to expand a snippet at the current position."""
_vim.command("let g:ulti_expand_res = 1") _vim.command("let g:ulti_expand_res = 1")
if not self._try_expand(): if not self._try_expand():
_vim.command("let g:ulti_expand_res = 0") _vim.command("let g:ulti_expand_res = 0")
self._handle_failure(self.expand_trigger) self._handle_failure(self.expand_trigger)
@err_to_scratch_buffer
def expand_or_jump(self):
"""
This function is used for people who wants to have the same trigger for
expansion and forward jumping. It first tries to expand a snippet, if
this fails, it tries to jump forward.
"""
_vim.command('let g:ulti_expand_or_jump_res = 1')
rv = self._try_expand()
if not rv:
_vim.command('let g:ulti_expand_or_jump_res = 2')
rv = self._jump()
if not rv:
_vim.command('let g:ulti_expand_or_jump_res = 0')
self._handle_failure(self.expand_trigger)
@err_to_scratch_buffer @err_to_scratch_buffer
def snippets_in_current_scope(self): def snippets_in_current_scope(self):
"""Returns the snippets that could be expanded to Vim as a global """Returns the snippets that could be expanded to Vim as a global
@ -246,37 +167,12 @@ class SnippetManager(object):
return True return True
@err_to_scratch_buffer
def expand_or_jump(self):
"""
This function is used for people who wants to have the same trigger for
expansion and forward jumping. It first tries to expand a snippet, if
this fails, it tries to jump forward.
"""
_vim.command('let g:ulti_expand_or_jump_res = 1')
rv = self._try_expand()
if not rv:
_vim.command('let g:ulti_expand_or_jump_res = 2')
rv = self._jump()
if not rv:
_vim.command('let g:ulti_expand_or_jump_res = 0')
self._handle_failure(self.expand_trigger)
@err_to_scratch_buffer
def save_last_visual_selection(self):
"""
This is called when the expand trigger is pressed in visual mode.
Our job is to remember everything between '< and '> and pass it on to
${VISUAL} in case it will be needed.
"""
self._visual_content.conserve()
@err_to_scratch_buffer @err_to_scratch_buffer
def add_snippet(self, trigger, value, description, def add_snippet(self, trigger, value, description,
options, ft="all", globals=None, fn=None): options, ft="all", globals=None, fn=None):
"""Add a snippet to the list of known snippets of the given 'ft'.""" """Add a snippet to the list of known snippets of the given 'ft'."""
self._snippets[ft].add_snippet( self._added_snippets_provider.add_snippet(ft, SnippetDefinition(
Snippet(trigger, value, description, options, globals or {}), fn trigger, value, description, options, globals or {}), fn
) )
@err_to_scratch_buffer @err_to_scratch_buffer
@ -287,7 +183,7 @@ class SnippetManager(object):
globals = {} globals = {}
before = _vim.buf.line_till_cursor before = _vim.buf.line_till_cursor
snip = Snippet(trigger, value, description, options, globals) snip = SnippetDefinition(trigger, value, description, options, globals)
if not trigger or snip.matches(before): if not trigger or snip.matches(before):
self._do_snippet(snip, before) self._do_snippet(snip, before)
@ -295,8 +191,28 @@ class SnippetManager(object):
else: else:
return False return False
def reset_buffer_filetypes(self):
"""Reset the filetypes for the current buffer."""
if _vim.buf.number in self._filetypes:
del self._filetypes[_vim.buf.number]
def add_buffer_filetypes(self, ft):
"""Checks for changes in the list of snippet files or the contents of
the snippet files and reloads them if necessary. """
buf_fts = self._filetypes[_vim.buf.number]
idx = -1
for ft in ft.split("."):
ft = ft.strip()
if not ft:
continue
try:
idx = buf_fts.index(ft)
except ValueError:
self._filetypes[_vim.buf.number].insert(idx + 1, ft)
idx += 1
@err_to_scratch_buffer @err_to_scratch_buffer
def cursor_moved(self): def _cursor_moved(self):
"""Called whenever the cursor moved.""" """Called whenever the cursor moved."""
self._vstate.remember_position() self._vstate.remember_position()
if _vim.eval("mode()") not in 'in': if _vim.eval("mode()") not in 'in':
@ -360,40 +276,40 @@ class SnippetManager(object):
self._csnippets[0].update_textobjects() self._csnippets[0].update_textobjects()
self._vstate.remember_buffer(self._csnippets[0]) self._vstate.remember_buffer(self._csnippets[0])
def leaving_buffer(self): @err_to_scratch_buffer
def _reset(self):
"""Reset the class to the state it had directly after creation."""
self._vstate = VimState()
self._filetypes = defaultdict(lambda: ['all'])
self._visual_content = VisualContentPreserver()
self._snippet_providers = [
AddedSnippetsProvider(),
UltiSnipsFileProvider()
]
self._added_snippets_provider = self._snippet_providers[0]
while len(self._csnippets):
self._current_snippet_is_done()
self._reinit()
@err_to_scratch_buffer
def _save_last_visual_selection(self):
"""
This is called when the expand trigger is pressed in visual mode.
Our job is to remember everything between '< and '> and pass it on to
${VISUAL} in case it will be needed.
"""
self._visual_content.conserve()
def _leaving_buffer(self):
"""Called when the user switches tabs/windows/buffers. It basically """Called when the user switches tabs/windows/buffers. It basically
means that all snippets must be properly terminated.""" means that all snippets must be properly terminated."""
while len(self._csnippets): while len(self._csnippets):
self._current_snippet_is_done() self._current_snippet_is_done()
self._reinit() self._reinit()
###################################
# Private/Protect Functions Below #
###################################
def _report_error(self, msg):
"""Shows 'msg' as error to the user."""
msg = _vim.escape("UltiSnips: " + msg)
if self._test_error:
msg = msg.replace('"', r'\"')
msg = msg.replace('|', r'\|')
_vim.command("let saved_pos=getpos('.')")
_vim.command("$:put =%s" % msg)
_vim.command("call setpos('.', saved_pos)")
elif False:
_vim.command("echohl WarningMsg")
_vim.command("echomsg %s" % msg)
_vim.command("echohl None")
else:
_vim.command("echoerr %s" % msg)
def _add_extending_info(self, ft, parents):
"""Add the list of 'parents' as being extended by the 'ft'."""
sd = self._snippets[ft]
for parent in parents:
if parent in sd.extends:
continue
sd.extends.append(parent)
def _reinit(self): def _reinit(self):
"""Resets transient state.""" """Resets transient state."""
self._ctab = None self._ctab = None
@ -440,7 +356,7 @@ class SnippetManager(object):
self._ignore_movements = True self._ignore_movements = True
return jumped return jumped
def leaving_insert_mode(self): def _leaving_insert_mode(self):
"""Called whenever we leave the insert mode.""" """Called whenever we leave the insert mode."""
self._vstate.restore_unnamed_register() self._vstate.restore_unnamed_register()
@ -484,28 +400,10 @@ class SnippetManager(object):
before the cursor. If possible is True, then get all before the cursor. If possible is True, then get all
possible matches. possible matches.
""" """
self._ensure_all_loaded()
filetypes = self._filetypes[_vim.buf.number][::-1] filetypes = self._filetypes[_vim.buf.number][::-1]
snippets = []
found_snippets = [] for provider in self._snippet_providers:
for ft in filetypes: snippets.extend(provider.get_snippets(filetypes, before, possible))
found_snippets += self._find_snippets(ft, before, possible)
# Search if any of the snippets overwrites the previous
# Dictionary allows O(1) access for easy overwrites
snippets = {}
for snip in found_snippets:
if (snip.trigger not in snippets) or snip.overwrites_previous:
snippets[snip.trigger] = []
snippets[snip.trigger].append(snip)
# Transform dictionary into flat list of snippets
selected_snippets = set(
[item for sublist in snippets.values() for item in sublist])
# Return snippets to their original order
snippets = [snip for snip in found_snippets if
snip in selected_snippets]
return snippets return snippets
def _do_snippet(self, snippet, before): def _do_snippet(self, snippet, before):
@ -574,39 +472,15 @@ class SnippetManager(object):
return None return None
return self._csnippets[-1] return self._csnippets[-1]
def _parse_snippets(self, ft, filename, file_data=None):
"""Parse the file 'filename' for the given 'ft' and watch it for
changes in the future. 'file_data' can be injected in tests."""
self._snippets[ft].addfile(filename)
if file_data is None:
file_data = open(filename, "r").read()
for event, data in parse_snippets_file(file_data):
if event == "error":
msg, line_index = data
filename = _vim.eval("""fnamemodify(%s, ":~:.")""" %
_vim.escape(filename))
self._report_error("%s in %s(%d)" % (msg, filename, line_index))
break
elif event == "clearsnippets":
triggers, = data
self._snippets[ft].clear_snippets(triggers)
elif event == "extends":
filetypes, = data
self._add_extending_info(ft, filetypes)
elif event == "snippet":
trigger, value, descr, opts, globals = data
self.add_snippet(trigger, value, descr,
opts, ft, globals, filename)
else:
assert False, "Unhandled %s: %r" % (event, data)
@property @property
def primary_filetype(self): def _primary_filetype(self):
"""This filetype will be edited when UltiSnipsEdit is called without """This filetype will be edited when UltiSnipsEdit is called without
any arguments.""" any arguments."""
return self._filetypes[_vim.buf.number][0] return self._filetypes[_vim.buf.number][0]
def file_to_edit(self, ft): # pylint: disable=no-self-use # TODO(sirver): this should talk directly to the UltiSnipsFileProvider.
def _file_to_edit(self, ft): # pylint: disable=no-self-use
""" Gets a file to edit based on the given filetype. """ Gets a file to edit based on the given filetype.
If no filetype is given, uses the current filetype from Vim. If no filetype is given, uses the current filetype from Vim.
@ -617,7 +491,7 @@ class SnippetManager(object):
# This method is not using self, but is called by UltiSnips.vim and is # This method is not using self, but is called by UltiSnips.vim and is
# therefore in this class because it is the facade to Vim. # therefore in this class because it is the facade to Vim.
edit = None edit = None
existing = _base_snippet_files_for(ft, False) existing = base_snippet_files_for(ft, False)
filename = ft + ".snippets" filename = ft + ".snippets"
if _vim.eval("exists('g:UltiSnipsSnippetsDir')") == "1": if _vim.eval("exists('g:UltiSnipsSnippetsDir')") == "1":
@ -644,95 +518,3 @@ class SnippetManager(object):
edit = os.path.join(path, filename) edit = os.path.join(path, filename)
return edit return edit
def _load_snippets_for(self, ft):
"""Load all snippets for the given 'ft'."""
if ft in self._snippets:
del self._snippets[ft]
for fn in _base_snippet_files_for(ft):
self._parse_snippets(ft, fn)
# Now load for the parents
for parent_ft in self._snippets[ft].extends:
if parent_ft not in self._snippets:
self._load_snippets_for(parent_ft)
def _needs_update(self, ft):
"""Returns true if any files for 'ft' have changed and must be
reloaded."""
do_hash = _vim.eval('exists("g:UltiSnipsDoHash")') == "0" \
or _vim.eval("g:UltiSnipsDoHash") != "0"
if ft not in self._snippets:
return True
elif do_hash and self._snippets[ft].has_any_file_changed():
return True
elif do_hash:
cur_snips = set(_base_snippet_files_for(ft))
old_snips = set(self._snippets[ft].files)
if cur_snips - old_snips:
return True
return False
def _ensure_loaded(self, ft, checked=None):
"""Make sure that the snippets for 'ft' and everything it extends are
loaded."""
if not checked:
checked = set([ft])
elif ft in checked:
return
else:
checked.add(ft)
if self._needs_update(ft):
self._load_snippets_for(ft)
for parent in self._snippets[ft].extends:
self._ensure_loaded(parent, checked)
def _ensure_all_loaded(self):
"""Make sure that all filetypes fur the current buffer are loaded."""
for ft in self._filetypes[_vim.buf.number]:
self._ensure_loaded(ft)
def reset_buffer_filetypes(self):
"""Reset the filetypes for the current buffer."""
if _vim.buf.number in self._filetypes:
del self._filetypes[_vim.buf.number]
def add_buffer_filetypes(self, ft):
"""Checks for changes in the list of snippet files or the contents of
the snippet files and reloads them if necessary. """
buf_fts = self._filetypes[_vim.buf.number]
idx = -1
for ft in ft.split("."):
ft = ft.strip()
if not ft:
continue
try:
idx = buf_fts.index(ft)
except ValueError:
self._filetypes[_vim.buf.number].insert(idx + 1, ft)
idx += 1
def _find_snippets(self, ft, trigger, potentially=False, seen=None):
"""Find snippets matching trigger
ft - file type to search
trigger - trigger to match against
potentially - also returns snippets that could potentially match; that
is which triggers start with the current trigger
"""
snips = self._snippets.get(ft, None)
if not snips:
return []
if not seen:
seen = set()
seen.add(ft)
parent_results = []
for parent_ft in snips.extends:
if parent_ft not in seen:
seen.add(parent_ft)
parent_results += self._find_snippets(parent_ft, trigger,
potentially, seen)
return parent_results + snips.get_matching_snippets(
trigger, potentially)

181
test.py
View File

@ -31,16 +31,18 @@
# pylint: skip-file # pylint: skip-file
import os
import tempfile
import unittest
import time
import re
import platform
import sys
import subprocess
from textwrap import dedent from textwrap import dedent
import os
import platform
import random
import re
import shutil
import string
import subprocess
import sys
import tempfile
import time
import unittest
try: try:
import unidecode import unidecode
@ -66,20 +68,21 @@ LS = "@" # List snippets
EX = "\t" # EXPAND EX = "\t" # EXPAND
EA = "#" # Expand anonymous EA = "#" # Expand anonymous
# Some VIM functions
COMPL_KW = chr(24)+chr(14) COMPL_KW = chr(24)+chr(14)
COMPL_ACCEPT = chr(25) COMPL_ACCEPT = chr(25)
NUMBER_OF_RETRIES_FOR_EACH_TEST = 4 NUMBER_OF_RETRIES_FOR_EACH_TEST = 4
def RunningOnWindows(): def running_on_windows():
if platform.system() == "Windows": if platform.system() == "Windows":
return "Does not work on Windows." return "Does not work on Windows."
def NoUnidecodeAvailable(): def no_unidecode_available():
if not UNIDECODE_IMPORTED: if not UNIDECODE_IMPORTED:
return "unidecode is not available." return "unidecode is not available."
def random_string(n):
return ''.join(random.choice(string.ascii_lowercase) for x in range(n))
class VimInterface: class VimInterface:
def focus(title=None): def focus(title=None):
@ -220,7 +223,7 @@ class VimInterfaceWindows(VimInterface):
class _VimTest(unittest.TestCase): class _VimTest(unittest.TestCase):
snippets = ("dummy", "donotdefine") snippets = ("dummy", "donotdefine")
snippets_test_file = ("", "", "") # file type, file name, file content snippets_test_file = ("", "") # filetype, file content
text_before = " --- some text before --- \n\n" text_before = " --- some text before --- \n\n"
text_after = "\n\n --- some text after --- " text_after = "\n\n --- some text after --- "
expected_error = "" expected_error = ""
@ -256,7 +259,8 @@ class _VimTest(unittest.TestCase):
def check_output(self): def check_output(self):
wanted = self.text_before + self.wanted + self.text_after wanted = self.text_before + self.wanted + self.text_after
if self.expected_error: if self.expected_error:
wanted = wanted + "\n" + self.expected_error self.assertRegexpMatches(self.output, self.expected_error)
return
for i in range(NUMBER_OF_RETRIES_FOR_EACH_TEST): for i in range(NUMBER_OF_RETRIES_FOR_EACH_TEST):
if self.output != wanted: if self.output != wanted:
# Redo this, but slower # Redo this, but slower
@ -273,6 +277,18 @@ class _VimTest(unittest.TestCase):
def _options_off(self): def _options_off(self):
pass pass
def _create_snippet_file(self, ft, content):
"""Create a snippet file and makes sure that it is found on the
runtimepath to be parsed."""
self._temporary_directory = tempfile.mkdtemp(prefix="UltiSnips_Test")
snippet_dir = random_string(20)
abs_snippet_dir = os.path.join(self._temporary_directory, snippet_dir)
os.mkdir(abs_snippet_dir)
with open(os.path.join(abs_snippet_dir, "%s.snippets" % ft), "w") as snippet_file:
snippet_file.write(dedent(content + "\n"))
self.vim.send(":let g:UltiSnipsSnippetDirectories=['%s']\n" % snippet_dir)
self.vim.send(""":set runtimepath=$VIMRUNTIME,%s,.\n""" % self._temporary_directory)
def setUp(self): def setUp(self):
reason_for_skipping = self.skip_if() reason_for_skipping = self.skip_if()
if reason_for_skipping is not None: if reason_for_skipping is not None:
@ -285,9 +301,9 @@ class _VimTest(unittest.TestCase):
self.send(":silent! close\n") self.send(":silent! close\n")
# Reset UltiSnips # Reset UltiSnips
self.send_py("UltiSnips_Manager.reset(test_error=True)") self.send_py("UltiSnips_Manager._reset()")
# Make it unlikely that we do not parse any shipped snippets # Make it unlikely that we do parse any shipped snippets.
self.send(":let g:UltiSnipsSnippetDirectories=['<un_def_ined>']\n") self.send(":let g:UltiSnipsSnippetDirectories=['<un_def_ined>']\n")
# Clear the buffer # Clear the buffer
@ -308,10 +324,10 @@ class _VimTest(unittest.TestCase):
self.send_py("UltiSnips_Manager.add_snippet(%r, %r, %r, %r)" % self.send_py("UltiSnips_Manager.add_snippet(%r, %r, %r, %r)" %
(sv, content, description, options)) (sv, content, description, options))
ft, fn, file_data = self.snippets_test_file ft, file_data = self.snippets_test_file
self._temporary_directory = ""
if ft: if ft:
self.send_py("UltiSnips_Manager._parse_snippets(%r, %r, %r)" % self._create_snippet_file(ft, file_data)
(ft, fn, dedent(file_data + '\n')))
if not self.interrupt: if not self.interrupt:
# Enter insert mode # Enter insert mode
@ -336,18 +352,17 @@ class _VimTest(unittest.TestCase):
self.output = self.vim.get_buffer_data() self.output = self.vim.get_buffer_data()
def tearDown(self):
if self._temporary_directory:
self.vim.send(""":set runtimepath=$VIMRUNTIME,.\n""")
shutil.rmtree(self._temporary_directory)
########################################################################### ###########################################################################
# BEGINNING OF TEST # # BEGINNING OF TEST #
########################################################################### ###########################################################################
# Snippet Definition Parsing {{{# # Snippet Definition Parsing {{{#
class _PS_Base(_VimTest): class ParseSnippets_SimpleSnippet(_VimTest):
def _options_on(self): snippets_test_file = ("all", r"""
self.send(":let UltiSnipsDoHash=0\n")
def _options_off(self):
self.send(":unlet UltiSnipsDoHash\n")
class ParseSnippets_SimpleSnippet(_PS_Base):
snippets_test_file = ("all", "test_file", r"""
snippet testsnip "Test Snippet" b! snippet testsnip "Test Snippet" b!
This is a test snippet! This is a test snippet!
endsnippet endsnippet
@ -355,39 +370,33 @@ class ParseSnippets_SimpleSnippet(_PS_Base):
keys = "testsnip" + EX keys = "testsnip" + EX
wanted = "This is a test snippet!" wanted = "This is a test snippet!"
class ParseSnippets_MissingEndSnippet(_PS_Base): class ParseSnippets_MissingEndSnippet(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet testsnip "Test Snippet" b! snippet testsnip "Test Snippet" b!
This is a test snippet! This is a test snippet!
""") """)
keys = "testsnip" + EX keys = "testsnip" + EX
wanted = "testsnip" + EX wanted = "testsnip" + EX
expected_error = dedent(""" expected_error = r"Missing 'endsnippet' for 'testsnip' in \S+:4"
UltiSnips: Missing 'endsnippet' for 'testsnip' in test_file(4)
""").strip()
class ParseSnippets_UnknownDirective(_PS_Base): class ParseSnippets_UnknownDirective(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
unknown directive unknown directive
""") """)
keys = "testsnip" + EX keys = "testsnip" + EX
wanted = "testsnip" + EX wanted = "testsnip" + EX
expected_error = dedent(""" expected_error = r"Invalid line 'unknown directive' in \S+:2"
UltiSnips: Invalid line 'unknown directive' in test_file(2)
""").strip()
class ParseSnippets_ExtendsWithoutFiletype(_PS_Base): class ParseSnippets_ExtendsWithoutFiletype(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
extends extends
""") """)
keys = "testsnip" + EX keys = "testsnip" + EX
wanted = "testsnip" + EX wanted = "testsnip" + EX
expected_error = dedent(""" expected_error = r"'extends' without file types in \S+:2"
UltiSnips: 'extends' without file types in test_file(2)
""").strip()
class ParseSnippets_ClearAll(_PS_Base): class ParseSnippets_ClearAll(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet testsnip "Test snippet" snippet testsnip "Test snippet"
This is a test. This is a test.
endsnippet endsnippet
@ -397,8 +406,8 @@ class ParseSnippets_ClearAll(_PS_Base):
keys = "testsnip" + EX keys = "testsnip" + EX
wanted = "testsnip" + EX wanted = "testsnip" + EX
class ParseSnippets_ClearOne(_PS_Base): class ParseSnippets_ClearOne(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet testsnip "Test snippet" snippet testsnip "Test snippet"
This is a test. This is a test.
endsnippet endsnippet
@ -412,8 +421,8 @@ class ParseSnippets_ClearOne(_PS_Base):
keys = "toclear" + EX + "\n" + "testsnip" + EX keys = "toclear" + EX + "\n" + "testsnip" + EX
wanted = "toclear" + EX + "\n" + "This is a test." wanted = "toclear" + EX + "\n" + "This is a test."
class ParseSnippets_ClearTwo(_PS_Base): class ParseSnippets_ClearTwo(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet testsnip "Test snippet" snippet testsnip "Test snippet"
This is a test. This is a test.
endsnippet endsnippet
@ -428,8 +437,8 @@ class ParseSnippets_ClearTwo(_PS_Base):
wanted = "toclear" + EX + "\n" + "testsnip" + EX wanted = "toclear" + EX + "\n" + "testsnip" + EX
class _ParseSnippets_MultiWord(_PS_Base): class _ParseSnippets_MultiWord(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet /test snip/ snippet /test snip/
This is a test. This is a test.
endsnippet endsnippet
@ -452,8 +461,8 @@ class ParseSnippets_MultiWord_Description_Option(_ParseSnippets_MultiWord):
keys = "snippet test" + EX keys = "snippet test" + EX
wanted = "This is yet another test." wanted = "This is yet another test."
class _ParseSnippets_MultiWord_RE(_PS_Base): class _ParseSnippets_MultiWord_RE(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet /[d-f]+/ "" r snippet /[d-f]+/ "" r
az test az test
endsnippet endsnippet
@ -476,16 +485,16 @@ class ParseSnippets_MultiWord_RE3(_ParseSnippets_MultiWord_RE):
keys = "test test test" + EX keys = "test test test" + EX
wanted = "re-test" wanted = "re-test"
class ParseSnippets_MultiWord_Quotes(_PS_Base): class ParseSnippets_MultiWord_Quotes(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet "test snip" snippet "test snip"
This is a test. This is a test.
endsnippet endsnippet
""") """)
keys = "test snip" + EX keys = "test snip" + EX
wanted = "This is a test." wanted = "This is a test."
class ParseSnippets_MultiWord_WithQuotes(_PS_Base): class ParseSnippets_MultiWord_WithQuotes(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet !"test snip"! snippet !"test snip"!
This is a test. This is a test.
endsnippet endsnippet
@ -493,32 +502,28 @@ class ParseSnippets_MultiWord_WithQuotes(_PS_Base):
keys = '"test snip"' + EX keys = '"test snip"' + EX
wanted = "This is a test." wanted = "This is a test."
class ParseSnippets_MultiWord_NoContainer(_PS_Base): class ParseSnippets_MultiWord_NoContainer(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet test snip snippet test snip
This is a test. This is a test.
endsnippet endsnippet
""") """)
keys = "test snip" + EX keys = "test snip" + EX
wanted = keys wanted = keys
expected_error = dedent(""" expected_error = "Invalid multiword trigger: 'test snip' in \S+:2"
UltiSnips: Invalid multiword trigger: 'test snip' in test_file(2)
""").strip()
class ParseSnippets_MultiWord_UnmatchedContainer(_PS_Base): class ParseSnippets_MultiWord_UnmatchedContainer(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
snippet !inv snip/ snippet !inv snip/
This is a test. This is a test.
endsnippet endsnippet
""") """)
keys = "inv snip" + EX keys = "inv snip" + EX
wanted = keys wanted = keys
expected_error = dedent(""" expected_error = "Invalid multiword trigger: '!inv snip/' in \S+:2"
UltiSnips: Invalid multiword trigger: '!inv snip/' in test_file(2)
""").strip()
class ParseSnippets_Global_Python(_PS_Base): class ParseSnippets_Global_Python(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
global !p global !p
def tex(ins): def tex(ins):
return "a " + ins + " b" return "a " + ins + " b"
@ -535,8 +540,8 @@ class ParseSnippets_Global_Python(_PS_Base):
keys = "ab" + EX + "\nac" + EX keys = "ab" + EX + "\nac" + EX
wanted = "x a bob b y\nx a jon b y" wanted = "x a bob b y\nx a jon b y"
class ParseSnippets_Global_Local_Python(_PS_Base): class ParseSnippets_Global_Local_Python(_VimTest):
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
global !p global !p
def tex(ins): def tex(ins):
return "a " + ins + " b" return "a " + ins + " b"
@ -855,43 +860,43 @@ class TabStopNavigatingInInsertModeSimple_ExceptCorrectResult(_VimTest):
# End: TabStop Tests #}}} # End: TabStop Tests #}}}
# ShellCode Interpolation {{{# # ShellCode Interpolation {{{#
class TabStop_Shell_SimpleExample(_VimTest): class TabStop_Shell_SimpleExample(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", "hi `echo hallo` you!") snippets = ("test", "hi `echo hallo` you!")
keys = "test" + EX + "and more" keys = "test" + EX + "and more"
wanted = "hi hallo you!and more" wanted = "hi hallo you!and more"
class TabStop_Shell_WithUmlauts(_VimTest): class TabStop_Shell_WithUmlauts(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", "hi `echo höüäh` you!") snippets = ("test", "hi `echo höüäh` you!")
keys = "test" + EX + "and more" keys = "test" + EX + "and more"
wanted = "hi höüäh you!and more" wanted = "hi höüäh you!and more"
class TabStop_Shell_TextInNextLine(_VimTest): class TabStop_Shell_TextInNextLine(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", "hi `echo hallo`\nWeiter") snippets = ("test", "hi `echo hallo`\nWeiter")
keys = "test" + EX + "and more" keys = "test" + EX + "and more"
wanted = "hi hallo\nWeiterand more" wanted = "hi hallo\nWeiterand more"
class TabStop_Shell_InDefValue_Leave(_VimTest): class TabStop_Shell_InDefValue_Leave(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", "Hallo ${1:now `echo fromecho`} end") snippets = ("test", "Hallo ${1:now `echo fromecho`} end")
keys = "test" + EX + JF + "and more" keys = "test" + EX + JF + "and more"
wanted = "Hallo now fromecho endand more" wanted = "Hallo now fromecho endand more"
class TabStop_Shell_InDefValue_Overwrite(_VimTest): class TabStop_Shell_InDefValue_Overwrite(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", "Hallo ${1:now `echo fromecho`} end") snippets = ("test", "Hallo ${1:now `echo fromecho`} end")
keys = "test" + EX + "overwrite" + JF + "and more" keys = "test" + EX + "overwrite" + JF + "and more"
wanted = "Hallo overwrite endand more" wanted = "Hallo overwrite endand more"
class TabStop_Shell_TestEscapedChars_Overwrite(_VimTest): class TabStop_Shell_TestEscapedChars_Overwrite(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", r"""`echo \`echo "\\$hi"\``""") snippets = ("test", r"""`echo \`echo "\\$hi"\``""")
keys = "test" + EX keys = "test" + EX
wanted = "$hi" wanted = "$hi"
class TabStop_Shell_TestEscapedCharsAndShellVars_Overwrite(_VimTest): class TabStop_Shell_TestEscapedCharsAndShellVars_Overwrite(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", r"""`hi="blah"; echo \`echo "$hi"\``""") snippets = ("test", r"""`hi="blah"; echo \`echo "$hi"\``""")
keys = "test" + EX keys = "test" + EX
wanted = "blah" wanted = "blah"
class TabStop_Shell_ShebangPython(_VimTest): class TabStop_Shell_ShebangPython(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ("test", """Hallo ${1:now `#!/usr/bin/env python snippets = ("test", """Hallo ${1:now `#!/usr/bin/env python
print "Hallo Welt" print "Hallo Welt"
`} end""") `} end""")
@ -1497,12 +1502,12 @@ class Transformation_CleverTransformLongLower_ExceptCorrectResult(_VimTest):
wanted = "HALLO hallo" wanted = "HALLO hallo"
class Transformation_SimpleCaseAsciiResult(_VimTest): class Transformation_SimpleCaseAsciiResult(_VimTest):
skip_if = lambda self: NoUnidecodeAvailable() skip_if = lambda self: no_unidecode_available()
snippets = ("ascii", "$1 ${1/(.*)/$1/a}") snippets = ("ascii", "$1 ${1/(.*)/$1/a}")
keys = "ascii" + EX + "éèàçôïÉÈÀÇÔÏ€" keys = "ascii" + EX + "éèàçôïÉÈÀÇÔÏ€"
wanted = "éèàçôïÉÈÀÇÔÏ€ eeacoiEEACOIEU" wanted = "éèàçôïÉÈÀÇÔÏ€ eeacoiEEACOIEU"
class Transformation_LowerCaseAsciiResult(_VimTest): class Transformation_LowerCaseAsciiResult(_VimTest):
skip_if = lambda self: NoUnidecodeAvailable() skip_if = lambda self: no_unidecode_available()
snippets = ("ascii", "$1 ${1/(.*)/\L$1\E/a}") snippets = ("ascii", "$1 ${1/(.*)/\L$1\E/a}")
keys = "ascii" + EX + "éèàçôïÉÈÀÇÔÏ€" keys = "ascii" + EX + "éèàçôïÉÈÀÇÔÏ€"
wanted = "éèàçôïÉÈÀÇÔÏ€ eeacoieeacoieu" wanted = "éèàçôïÉÈÀÇÔÏ€ eeacoieeacoieu"
@ -1909,9 +1914,9 @@ class RecTabStops_MirroredZeroTS_ECR(_VimTest):
keys = "m" + EX + "m1" + EX + "one" + JF + "two" + \ keys = "m" + EX + "m1" + EX + "one" + JF + "two" + \
JF + "three" + JF + "four" + JF + "end" JF + "three" + JF + "four" + JF + "end"
wanted = "[ [ one three three two ] four ]end" wanted = "[ [ one three three two ] four ]end"
class RecTabStops_ChildTriggerContainsParentTextObjects(_PS_Base): class RecTabStops_ChildTriggerContainsParentTextObjects(_VimTest):
# https://bugs.launchpad.net/ultisnips/+bug/1191617 # https://bugs.launchpad.net/ultisnips/+bug/1191617
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
global !p global !p
def complete(t, opts): def complete(t, opts):
if t: if t:
@ -2085,7 +2090,7 @@ class SnippetOptions_ExpandInwordSnippetsWithOtherChars_Expand2(_VimTest):
keys = "-test" + EX keys = "-test" + EX
wanted = "-Expand me!" wanted = "-Expand me!"
class SnippetOptions_ExpandInwordSnippetsWithOtherChars_Expand3(_VimTest): class SnippetOptions_ExpandInwordSnippetsWithOtherChars_Expand3(_VimTest):
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = (("test", "Expand me!", "", "i"), ) snippets = (("test", "Expand me!", "", "i"), )
keys = "ßßtest" + EX keys = "ßßtest" + EX
wanted = "ßßExpand me!" wanted = "ßßExpand me!"
@ -2409,7 +2414,7 @@ class RecTabStopsWithExpandtab_SpecialIndentProblem_ECR(_ExpandTabs):
# changes made 'manually', while the other vim version seem to do so. Since # changes made 'manually', while the other vim version seem to do so. Since
# the fault is not with UltiSnips, we simply skip this test on windows # the fault is not with UltiSnips, we simply skip this test on windows
# completely. # completely.
skip_if = lambda self: RunningOnWindows() skip_if = lambda self: running_on_windows()
snippets = ( snippets = (
("m1", "Something"), ("m1", "Something"),
("m", "\t$0"), ("m", "\t$0"),
@ -2443,10 +2448,10 @@ class ProperIndenting_AutoIndentAndNewline_ECR(_VimTest):
def _options_off(self): def _options_off(self):
self.send(":set noautoindent\n") self.send(":set noautoindent\n")
# Test for bug 1073816 # Test for bug 1073816
class ProperIndenting_FirstLineInFile_ECR(_PS_Base): class ProperIndenting_FirstLineInFile_ECR(_VimTest):
text_before = "" text_before = ""
text_after = "" text_after = ""
snippets_test_file = ("all", "test_file", r""" snippets_test_file = ("all", r"""
global !p global !p
def complete(t, opts): def complete(t, opts):
if t: if t:
@ -2565,7 +2570,7 @@ hi4Hello"""
# Test for bug 871357 # # Test for bug 871357 #
class TestLangmapWithUtf8_ExceptCorrectResult(_VimTest): class TestLangmapWithUtf8_ExceptCorrectResult(_VimTest):
skip_if = lambda self: RunningOnWindows() # SendKeys can't send UTF characters skip_if = lambda self: running_on_windows() # SendKeys can't send UTF characters
snippets = ("testme", snippets = ("testme",
"""my snipped ${1:some_default} """my snipped ${1:some_default}
and a mirror: $1 and a mirror: $1
@ -2971,7 +2976,7 @@ class Snippet_With_DoubleQuote_List(_VimTest):
# End: Quotes in Snippets #}}} # End: Quotes in Snippets #}}}
# Umlauts and Special Chars {{{# # Umlauts and Special Chars {{{#
class _UmlautsBase(_VimTest): class _UmlautsBase(_VimTest):
skip_if = lambda self: RunningOnWindows() # SendKeys can't send UTF characters skip_if = lambda self: running_on_windows() # SendKeys can't send UTF characters
class Snippet_With_Umlauts_List(_UmlautsBase): class Snippet_With_Umlauts_List(_UmlautsBase):
snippets = _snip_quote('ü') snippets = _snip_quote('ü')