More linting and refactorings mainly in _transformation.py.

This commit is contained in:
Holger Rapp 2014-02-08 14:23:16 +01:00
parent 63021206cd
commit c67a59f579
18 changed files with 315 additions and 353 deletions

View File

@ -55,7 +55,7 @@ disable=
output-format=text
# Tells whether to display a full report or only the messages
reports=no
reports=yes
[BASIC]

View File

@ -12,7 +12,7 @@ import traceback
from UltiSnips._diff import diff, guess_edit
from UltiSnips.compatibility import as_unicode
from UltiSnips.geometry import Position
from UltiSnips.position import Position
from UltiSnips.snippet import Snippet
from UltiSnips.snippet_dictionary import SnippetDictionary
from UltiSnips.snippets_file_parser import SnippetsFileParser
@ -155,7 +155,6 @@ class SnippetManager(object):
self.backward_trigger = backward_trigger
self._supertab_keys = None
self._csnippets = []
self.reset()
@err_to_scratch_buffer
@ -163,7 +162,7 @@ class SnippetManager(object):
"""Reset the class to the state it had directly after creation."""
self._vstate = VimState()
self._test_error = test_error
self._snippets = {}
self._snippets = defaultdict(lambda: SnippetDictionary())
self._filetypes = defaultdict(lambda: ['all'])
self._visual_content = VisualContentPreserver()
@ -277,27 +276,14 @@ class SnippetManager(object):
"""
self._visual_content.conserve()
# TODO(sirver): replace through defaultdict
def snippet_dict(self, ft):
"""Makes sure that ft is in self._snippets."""
if ft not in self._snippets:
self._snippets[ft] = SnippetDictionary()
return self._snippets[ft]
@err_to_scratch_buffer
def add_snippet(self, trigger, value, description,
options, ft="all", globals=None, fn=None):
"""Add a snippet to the list of known snippets of the given 'ft'."""
self.snippet_dict(ft).add_snippet(
self._snippets[ft].add_snippet(
Snippet(trigger, value, description, options, globals or {}), fn
)
@err_to_scratch_buffer
def add_snippet_file(self, ft, path):
"""Add a file to be watched for changes to the 'ft' snippet dict."""
sd = self.snippet_dict(ft)
sd.addfile(path)
@err_to_scratch_buffer
def expand_anon(self, value, trigger="", description="",
options="", globals=None):
@ -326,7 +312,7 @@ class SnippetManager(object):
@err_to_scratch_buffer
def add_extending_info(self, ft, parents):
"""Add the list of 'parents' as being extended by the 'ft'."""
sd = self.snippet_dict(ft)
sd = self._snippets[ft]
for parent in parents:
if parent in sd.extends:
continue
@ -616,8 +602,9 @@ class SnippetManager(object):
return self._csnippets[-1]
def _parse_snippets(self, ft, filename, file_data=None):
"""Parse the file 'filename' for the given 'ft'."""
self.add_snippet_file(ft, filename)
"""Parse the file 'filename' for the given 'ft' and watch it for
changes in the future."""
self._snippets[ft].addfile(filename)
SnippetsFileParser(ft, filename, self, file_data).parse()
@property
@ -667,7 +654,7 @@ class SnippetManager(object):
def _load_snippets_for(self, ft):
"""Load all snippets for the given 'ft'."""
self.snippet_dict(ft).reset()
del self._snippets[ft]
for fn in _base_snippet_files_for(ft):
self._parse_snippets(ft, fn)
# Now load for the parents
@ -683,11 +670,11 @@ class SnippetManager(object):
if ft not in self._snippets:
return True
elif do_hash and self.snippet_dict(ft).needs_update():
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.snippet_dict(ft).files)
old_snips = set(self._snippets[ft].files)
if cur_snips - old_snips:
return True
return False
@ -705,7 +692,7 @@ class SnippetManager(object):
if self._needs_update(ft):
self._load_snippets_for(ft)
for parent in self.snippet_dict(ft).extends:
for parent in self._snippets[ft].extends:
self._ensure_loaded(parent, checked)
def _ensure_all_loaded(self):
@ -746,12 +733,12 @@ class SnippetManager(object):
if not snips:
return []
if not seen:
seen = []
seen.append(ft)
seen = set()
seen.add(ft)
parent_results = []
for parent_ft in snips.extends:
if parent_ft not in seen:
seen.append(parent_ft)
seen.add(parent_ft)
parent_results += self._find_snippets(parent_ft, trigger,
potentially, seen)
return parent_results + snips.get_matching_snippets(

View File

@ -5,7 +5,7 @@ from collections import defaultdict
import sys
from UltiSnips import _vim
from UltiSnips.geometry import Position
from UltiSnips.position import Position
def is_complete_edit(initial_line, a, b, cmds):
buf = a[:]
@ -171,4 +171,3 @@ def diff(a, b, sline = 0):
(("D",line, col, a[x]),) )
)
cost += 1

View File

@ -8,9 +8,9 @@ import re
import vim # pylint:disable=import-error
from vim import error # pylint:disable=import-error,unused-import
from UltiSnips.geometry import Position
from UltiSnips.compatibility import col2byte, byte2col, \
as_unicode, as_vimencoding
from UltiSnips.position import Position
class VimBuffer(object):
"""Wrapper around the current Vim buffer."""

View File

@ -1,36 +1,43 @@
#!/usr/bin/env python
# encoding: utf-8
"""Convenience methods that help with debugging. They should never be used in
production code."""
import sys
from UltiSnips.compatibility import as_unicode
dump_filename = "/tmp/file.txt" if not sys.platform.lower().startswith("win") \
DUMP_FILENAME = "/tmp/file.txt" if not sys.platform.lower().startswith("win") \
else "C:/windows/temp/ultisnips.txt"
with open(dump_filename, "w") as dump_file:
with open(DUMP_FILENAME, "w"):
pass # clears the file
def echo_to_hierarchy(to):
par = to
while par._parent: par = par._parent
def _do_print(to, indent=""):
debug(indent + as_unicode(to))
def echo_to_hierarchy(text_object):
"""Outputs the given 'text_object' and its childs hierarchically."""
# pylint:disable=protected-access
parent = text_object
while parent._parent:
parent = parent._parent
def _do_print(text_object, indent=""):
"""prints recursively."""
debug(indent + as_unicode(text_object))
try:
for c in to._childs:
_do_print(c, indent=indent + " ")
for child in text_object._childs:
_do_print(child, indent=indent + " ")
except AttributeError:
pass
_do_print(parent)
_do_print(par)
def debug(s):
s = as_unicode(s)
with open(dump_filename, "ab") as dump_file:
dump_file.write((s + '\n').encode("utf-8"))
def debug(msg):
"""Dumb 'msg' into the debug file."""
msg = as_unicode(msg)
with open(DUMP_FILENAME, "ab") as dump_file:
dump_file.write((msg + '\n').encode("utf-8"))
def print_stack():
"""Dump a stack trace into the debug file."""
import traceback
with open(dump_filename, "ab") as dump_file:
with open(DUMP_FILENAME, "ab") as dump_file:
traceback.print_stack(file=dump_file)

View File

@ -1,79 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
class Position(object):
def __init__(self, line, col):
self.line = line
self.col = col
def col():
def fget(self):
return self._col
def fset(self, value):
self._col = value
return locals()
col = property(**col())
def line():
doc = "Zero base line numbers"
def fget(self):
return self._line
def fset(self, value):
self._line = value
return locals()
line = property(**line())
def move(self, pivot, diff):
"""pivot is the position of the first changed
character, diff is how text after it moved"""
if self < pivot: return
if diff.line == 0:
if self.line == pivot.line:
self.col += diff.col
elif diff.line > 0:
if self.line == pivot.line:
self.col += diff.col - pivot.col
self.line += diff.line
else:
self.line += diff.line
if self.line == pivot.line:
self.col += - diff.col + pivot.col
def __add__(self,pos):
if not isinstance(pos,Position):
raise TypeError("unsupported operand type(s) for +: " \
"'Position' and %s" % type(pos))
return Position(self.line + pos.line, self.col + pos.col)
def __sub__(self,pos):
if not isinstance(pos,Position):
raise TypeError("unsupported operand type(s) for +: " \
"'Position' and %s" % type(pos))
return Position(self.line - pos.line, self.col - pos.col)
def diff(self,pos):
if not isinstance(pos,Position):
raise TypeError("unsupported operand type(s) for +: " \
"'Position' and %s" % type(pos))
if self.line == pos.line:
return Position(0, self.col - pos.col)
else:
if self > pos:
return Position(self.line - pos.line, self.col)
else:
return Position(self.line - pos.line, pos.col)
return Position(self.line - pos.line, self.col - pos.col)
def __eq__(self, other):
return (self._line, self._col) == (other._line, other._col)
def __ne__(self, other):
return (self._line, self._col) != (other._line, other._col)
def __lt__(self, other):
return (self._line, self._col) < (other._line, other._col)
def __le__(self, other):
return (self._line, self._col) <= (other._line, other._col)
def __repr__(self):
return "(%i,%i)" % (self._line, self._col)

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python
# encoding: utf-8
"""Represents a Position in a text file: (0 based line index, 0 based column
index) and provides methods for moving them around."""
class Position(object):
"""See module docstring."""
def __init__(self, line, col):
self.line = line
self.col = col
def move(self, pivot, delta):
"""'pivot' is the position of the first changed character, 'delta' is
how text after it moved"""
if self < pivot:
return
if delta.line == 0:
if self.line == pivot.line:
self.col += delta.col
elif delta.line > 0:
if self.line == pivot.line:
self.col += delta.col - pivot.col
self.line += delta.line
else:
self.line += delta.line
if self.line == pivot.line:
self.col += - delta.col + pivot.col
def delta(self, pos):
"""Returns the difference that the cursor must move to come from 'pos'
to us."""
assert isinstance(pos, Position)
if self.line == pos.line:
return Position(0, self.col - pos.col)
else:
if self > pos:
return Position(self.line - pos.line, self.col)
else:
return Position(self.line - pos.line, pos.col)
return Position(self.line - pos.line, self.col - pos.col)
def __add__(self, pos):
assert isinstance(pos, Position)
return Position(self.line + pos.line, self.col + pos.col)
def __sub__(self, pos):
assert isinstance(pos, Position)
return Position(self.line - pos.line, self.col - pos.col)
def __eq__(self, other):
return (self.line, self.col) == (other.line, other.col)
def __ne__(self, other):
return (self.line, self.col) != (other.line, other.col)
def __lt__(self, other):
return (self.line, self.col) < (other.line, other.col)
def __le__(self, other):
return (self.line, self.col) <= (other.line, other.col)
def __repr__(self):
return "(%i,%i)" % (self.line, self.col)

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python
# encoding: utf-8
"""Implements a container for parsed snippets."""
import hashlib
import os
@ -13,67 +15,71 @@ def _hash_file(path):
class SnippetDictionary(object):
def __init__(self, *args, **kwargs):
self._added = []
self.reset()
"""See module docstring."""
def reset(self):
self._snippets = []
def __init__(self):
self._added = []
self._extends = []
self._files = {}
self._snippets = []
def add_snippet(self, s, fn=None):
if fn:
self._snippets.append(s)
if fn not in self.files:
self.addfile(fn)
def add_snippet(self, snippet, filename):
"""Add 'snippet' to this dictionary. If 'filename' is given, also watch
the original file for changes."""
if filename:
self._snippets.append(snippet)
if filename not in self.files:
self.addfile(filename)
else:
self._added.append(s)
self._added.append(snippet)
def get_matching_snippets(self, trigger, potentially):
"""Returns all snippets matching the given trigger."""
"""Returns all snippets matching the given trigger. If 'potentially' is
true, returns all that could_match()."""
if not potentially:
return [ s for s in self.snippets if s.matches(trigger) ]
return [s for s in self.snippets if s.matches(trigger)]
else:
return [ s for s in self.snippets if s.could_match(trigger) ]
return [s for s in self.snippets if s.could_match(trigger)]
@property
def snippets(self):
"""Returns all snippets in this dictionary."""
return self._added + self._snippets
def clear_snippets(self, triggers=[]):
"""Remove all snippets that match each trigger in triggers.
When triggers is empty, removes all snippets.
"""
def clear_snippets(self, triggers=None):
"""Remove all snippets that match each trigger in 'triggers'. When
'triggers' is None, empties this dictionary completely."""
if triggers is None:
triggers = []
if triggers:
for t in triggers:
for s in self.get_matching_snippets(t, potentially=False):
if s in self._snippets:
self._snippets.remove(s)
if s in self._added:
self._added.remove(s)
for trigger in triggers:
for snippet in self.get_matching_snippets(trigger, False):
if snippet in self._snippets:
self._snippets.remove(snippet)
if snippet in self._added:
self._added.remove(snippet)
else:
self._snippets = []
self._added = []
@property
def files(self):
return self._files
def addfile(self, path):
"""Add this file to the files we read triggers from."""
self.files[path] = _hash_file(path)
def needs_update(self):
def has_any_file_changed(self):
"""Returns True if any of our watched files has changed since we read
it last."""
for path, hash in self.files.items():
if not hash or hash != _hash_file(path):
return True
return False
def extends():
def fget(self):
return self._extends
def fset(self, value):
self._extends = value
return locals()
extends = property(**extends())
@property
def files(self):
"""All files we have read snippets from."""
return self._files
@property
def extends(self):
"""The list of filetypes this filetype extends."""
return self._extends

View File

@ -2,15 +2,14 @@
# encoding: utf-8
import unittest
import os.path as p, sys; sys.path.append(p.join(p.dirname(__file__), ".."))
from geometry import Position
from position import Position
class _MPBase(object):
def runTest(self):
obj = Position(*self.obj)
for pivot, diff, wanted in self.steps:
obj.move(Position(*pivot), Position(*diff))
for pivot, delta, wanted in self.steps:
obj.move(Position(*pivot), Position(*delta))
self.assertEqual(Position(*wanted), obj)
class MovePosition_DelSameLine(_MPBase, unittest.TestCase):

View File

@ -6,7 +6,7 @@ import unittest
import os.path as p, sys; sys.path.append(p.join(p.dirname(__file__), ".."))
from _diff import diff, guess_edit
from geometry import Position
from position import Position
def transform(a, cmds):
@ -184,7 +184,3 @@ if __name__ == '__main__':
unittest.main()
# k = TestEditScript()
# unittest.TextTestRunner().run(k)

View File

@ -4,7 +4,7 @@
"""Base classes for all text objects."""
import UltiSnips._vim as _vim
from UltiSnips.geometry import Position
from UltiSnips.position import Position
def _calc_end(text, start):
"""Calculate the end position of the 'text' starting at 'start."""
@ -126,7 +126,7 @@ class TextObject(object):
if self._parent:
self._parent._child_has_moved(
self._parent._childs.index(self), min(old_end, self._end),
self._end.diff(old_end)
self._end.delta(old_end)
)
def _update(self, done):

View File

@ -9,8 +9,9 @@ definitions into logical units called Tokens.
import string
import re
from UltiSnips.geometry import Position
from UltiSnips.compatibility import as_unicode
from UltiSnips.position import Position
from UltiSnips.escaping import unescape
class _TextIterator(object):
"""Helper class to make iterating over text easier."""
@ -55,19 +56,6 @@ class _TextIterator(object):
"""Current position in the text."""
return Position(self._line, self._col)
def _unescape(text):
"""Removes escaping from 'text'."""
rv = ""
i = 0
while i < len(text):
if i+1 < len(text) and text[i] == '\\':
rv += text[i+1]
i += 1
else:
rv += text[i]
i += 1
return rv
def _parse_number(stream):
"""
Expects the stream to contain a number next, returns the number
@ -178,7 +166,7 @@ class VisualToken(Token):
if stream.peek() == ":":
stream.next()
self.alternative_text, char = _parse_till_unescaped_char(stream, '/}')
self.alternative_text = _unescape(self.alternative_text)
self.alternative_text = unescape(self.alternative_text)
if char == '/': # Transformation going on
try:

View File

@ -1,10 +1,10 @@
#!/usr/bin/env python
# encoding: utf-8
from UltiSnips.geometry import Position
from UltiSnips.text_objects._lexer import tokenize, EscapeCharToken, VisualToken, \
TransformationToken, TabStopToken, MirrorToken, PythonCodeToken, \
VimLCodeToken, ShellCodeToken
from UltiSnips.position import Position
from UltiSnips.text_objects._escaped_char import EscapedChar
from UltiSnips.text_objects._mirror import Mirror
from UltiSnips.text_objects._python_code import PythonCode

View File

@ -1,54 +1,52 @@
#!/usr/bin/env python
# encoding: utf-8
"""Implements `!p ` interpolation."""
import os
from collections import namedtuple
import UltiSnips._vim as _vim
from UltiSnips.compatibility import as_unicode
from UltiSnips.indent_util import IndentUtil
from UltiSnips.text_objects._base import NoneditableTextObject
import UltiSnips._vim as _vim
class _Tabs(object):
"""Allows access to tabstop content via t[] inside of python code."""
def __init__(self, to):
self._to = to
def __getitem__(self, no):
ts = self._to._get_tabstop(self._to, int(no))
ts = self._to._get_tabstop(self._to, int(no)) # pylint:disable=protected-access
if ts is None:
return ""
return ts.current_text
_VisualContent = namedtuple('_VisualContent', ['mode', 'text'])
class SnippetUtil(object):
""" Provides easy access to indentation, etc.
"""
"""Provides easy access to indentation, etc. This is the 'snip' object in
python code."""
def __init__(self, initial_indent, vmode, vtext):
self._ind = IndentUtil()
self._visual = _VisualContent(vmode, vtext)
self._initial_indent = self._ind.indent_to_spaces(initial_indent)
self._reset("")
def _reset(self, cur):
""" Gets the snippet ready for another update.
"""Gets the snippet ready for another update.
:cur: the new value for c.
"""
self._ind.reset()
self._c = cur
self._cur = cur
self._rv = ""
self._changed = False
self.reset_indent()
def shift(self, amount=1):
""" Shifts the indentation level.
"""Shifts the indentation level.
Note that this uses the shiftwidth because thats what code
formatters use.
@ -57,7 +55,7 @@ class SnippetUtil(object):
self.indent += " " * self._ind.shiftwidth * amount
def unshift(self, amount=1):
""" Unshift the indentation level.
"""Unshift the indentation level.
Note that this uses the shiftwidth because thats what code
formatters use.
@ -70,7 +68,7 @@ class SnippetUtil(object):
self.indent = ""
def mkline(self, line="", indent=None):
""" Creates a properly set up line.
"""Creates a properly set up line.
:line: the text to add
:indent: the indentation to have at the beginning
@ -90,62 +88,54 @@ class SnippetUtil(object):
return indent + line
def reset_indent(self):
""" Clears the indentation. """
"""Clears the indentation."""
self.indent = self._initial_indent
# Utility methods
@property
def fn(self):
""" The filename. """
def fn(self): # pylint:disable=no-self-use,invalid-name
"""The filename."""
return _vim.eval('expand("%:t")') or ""
@property
def basename(self):
""" The filename without extension. """
def basename(self): # pylint:disable=no-self-use
"""The filename without extension."""
return _vim.eval('expand("%:t:r")') or ""
@property
def ft(self):
""" The filetype. """
def ft(self): # pylint:disable=invalid-name
"""The filetype."""
return self.opt("&filetype", "")
# Necessary stuff
def rv():
""" The return value.
This is a list of lines to insert at the
location of the placeholder.
@property
def rv(self): # pylint:disable=invalid-name
"""The return value. The text to insert at the location of the
placeholder."""
return self._rv
Deprecates res.
"""
def fget(self):
return self._rv
def fset(self, value):
self._changed = True
self._rv = value
return locals()
rv = property(**rv())
@rv.setter
def rv(self, value): # pylint:disable=invalid-name
"""See getter."""
self._changed = True
self._rv = value
@property
def _rv_changed(self):
""" True if rv has changed. """
"""True if rv has changed."""
return self._changed
@property
def c(self):
""" The current text of the placeholder.
Deprecates cur.
"""
return self._c
def c(self): # pylint:disable=invalid-name
"""The current text of the placeholder."""
return self._cur
@property
def v(self):
def v(self): # pylint:disable=invalid-name
"""Content of visual expansions"""
return self._visual
def opt(self, option, default=None):
""" Gets a Vim variable. """
def opt(self, option, default=None): # pylint:disable=no-self-use
"""Gets a Vim variable."""
if _vim.eval("exists('%s')" % option) == "1":
try:
return _vim.eval(option)
@ -153,23 +143,24 @@ class SnippetUtil(object):
pass
return default
# Syntatic sugar
def __add__(self, value):
""" Appends the given line to rv using mkline. """
self.rv += '\n' # handles the first line properly
"""Appends the given line to rv using mkline."""
self.rv += '\n' # pylint:disable=invalid-name
self.rv += self.mkline(value)
return self
def __lshift__(self, other):
""" Same as unshift. """
"""Same as unshift."""
self.unshift(other)
def __rshift__(self, other):
""" Same as shift. """
"""Same as shift."""
self.shift(other)
class PythonCode(NoneditableTextObject):
"""See module docstring."""
def __init__(self, parent, token):
code = token.code.replace("\\`", "`")
@ -178,16 +169,16 @@ class PythonCode(NoneditableTextObject):
while snippet:
try:
self._locals = snippet.locals
t = snippet.visual_content.text
m = snippet.visual_content.mode
text = snippet.visual_content.text
mode = snippet.visual_content.mode
break
except AttributeError:
snippet = snippet._parent
self._snip = SnippetUtil(token.indent, m, t)
snippet = snippet._parent # pylint:disable=protected-access
self._snip = SnippetUtil(token.indent, mode, text)
self._globals = {}
globals = snippet.globals.get("!p", [])
exec("\n".join(globals).replace("\r\n", "\n"), self._globals)
exec("\n".join(globals).replace("\r\n", "\n"), self._globals) # pylint:disable=exec-used
# Add Some convenience to the code
self._code = "import re, os, vim, string, random\n" + code
@ -201,7 +192,7 @@ class PythonCode(NoneditableTextObject):
fn = os.path.basename(path)
ct = self.current_text
self._snip._reset(ct)
self._snip._reset(ct) # pylint:disable=protected-access
local_d = self._locals
local_d.update({
@ -213,10 +204,10 @@ class PythonCode(NoneditableTextObject):
'snip': self._snip,
})
exec(self._code, self._globals, local_d)
exec(self._code, self._globals, local_d) # pylint:disable=exec-used
rv = as_unicode(
self._snip.rv if self._snip._rv_changed
self._snip.rv if self._snip._rv_changed # pylint:disable=protected-access
else as_unicode(local_d['res'])
)

View File

@ -5,9 +5,8 @@
user expands a snippet, a SnippetInstance is created to keep track of the
corresponding TextObjects. The Snippet itself is also a TextObject. """
from UltiSnips.geometry import Position
from UltiSnips.position import Position
import UltiSnips._vim as _vim
from UltiSnips.text_objects._base import EditableTextObject, \
NoneditableTextObject
from UltiSnips.text_objects._parser import TOParser

View File

@ -1,109 +1,105 @@
#!/usr/bin/env python
# encoding: utf-8
"""Implements TabStop transformations."""
import re
import sys
from UltiSnips.text_objects._mirror import Mirror
from UltiSnips.escaping import unescape, fill_in_whitespace
def _find_closing_brace(string, start_pos):
"""Finds the corresponding closing brace after start_pos."""
bracks_open = 1
for idx, char in enumerate(string[start_pos:]):
if char == '(':
if string[idx+start_pos-1] != '\\':
bracks_open += 1
elif char == ')':
if string[idx+start_pos-1] != '\\':
bracks_open -= 1
if not bracks_open:
return start_pos+idx+1
def _split_conditional(string):
"""Split the given conditional 'string' into its arguments."""
bracks_open = 0
args = []
carg = ""
for idx, char in enumerate(string):
if char == '(':
if string[idx-1] != '\\':
bracks_open += 1
elif char == ')':
if string[idx-1] != '\\':
bracks_open -= 1
elif char == ':' and not bracks_open and not string[idx-1] == '\\':
args.append(carg)
carg = ""
continue
carg += char
args.append(carg)
return args
def _replace_conditional(match, string):
"""Replaces a conditional match in a transformation."""
conditional_match = _CONDITIONAL.search(string)
while conditional_match:
start = conditional_match.start()
end = _find_closing_brace(string, start+4)
args = _split_conditional(string[start+4:end-1])
rv = ""
if match.group(int(conditional_match.group(1))):
rv = unescape(_replace_conditional(match, args[0]))
elif len(args) > 1:
rv = unescape(_replace_conditional(match, args[1]))
string = string[:start] + rv + string[end:]
conditional_match = _CONDITIONAL.search(string)
return string
_ONE_CHAR_CASE_SWITCH = re.compile(r"\\([ul].)", re.DOTALL)
_LONG_CASEFOLDINGS = re.compile(r"\\([UL].*?)\\E", re.DOTALL)
_DOLLAR = re.compile(r"\$(\d+)", re.DOTALL)
_CONDITIONAL = re.compile(r"\(\?(\d+):", re.DOTALL)
class _CleverReplace(object):
"""Mimics TextMates replace syntax."""
def __init__(self, expression):
self._expression = expression
def replace(self, match):
"""Replaces 'match' through the correct replacement string."""
transformed = self._expression
# Replace all $? with capture groups
transformed = _DOLLAR.subn(
lambda m: match.group(int(m.group(1))), transformed)[0]
# Replace Case switches
def _one_char_case_change(match):
"""Replaces one character case changes."""
if match.group(1)[0] == 'u':
return match.group(1)[-1].upper()
else:
return match.group(1)[-1].lower()
transformed = _ONE_CHAR_CASE_SWITCH.subn(
_one_char_case_change, transformed)[0]
def _multi_char_case_change(match):
"""Replaces multi character case changes."""
if match.group(1)[0] == 'U':
return match.group(1)[1:].upper()
else:
return match.group(1)[1:].lower()
transformed = _LONG_CASEFOLDINGS.subn(
_multi_char_case_change, transformed)[0]
transformed = _replace_conditional(match, transformed)
return unescape(fill_in_whitespace(transformed))
# flag used to display only one time the lack of unidecode
UNIDECODE_ALERT_RAISED = False
class _CleverReplace(object):
"""
This class mimics TextMates replace syntax
"""
_DOLLAR = re.compile(r"\$(\d+)", re.DOTALL)
_SIMPLE_CASEFOLDINGS = re.compile(r"\\([ul].)", re.DOTALL)
_LONG_CASEFOLDINGS = re.compile(r"\\([UL].*?)\\E", re.DOTALL)
_CONDITIONAL = re.compile(r"\(\?(\d+):", re.DOTALL)
_UNESCAPE = re.compile(r'\\[^ntrab]')
_SCHARS_ESCPAE = re.compile(r'\\[ntrab]')
def __init__(self, s):
self._s = s
def _scase_folding(self, m):
if m.group(1)[0] == 'u':
return m.group(1)[-1].upper()
else:
return m.group(1)[-1].lower()
def _lcase_folding(self, m):
if m.group(1)[0] == 'U':
return m.group(1)[1:].upper()
else:
return m.group(1)[1:].lower()
def _replace_conditional(self, match, v):
def _find_closingbrace(v,start_pos):
bracks_open = 1
for idx, c in enumerate(v[start_pos:]):
if c == '(':
if v[idx+start_pos-1] != '\\':
bracks_open += 1
elif c == ')':
if v[idx+start_pos-1] != '\\':
bracks_open -= 1
if not bracks_open:
return start_pos+idx+1
m = self._CONDITIONAL.search(v)
def _part_conditional(v):
bracks_open = 0
args = []
carg = ""
for idx, c in enumerate(v):
if c == '(':
if v[idx-1] != '\\':
bracks_open += 1
elif c == ')':
if v[idx-1] != '\\':
bracks_open -= 1
elif c == ':' and not bracks_open and not v[idx-1] == '\\':
args.append(carg)
carg = ""
continue
carg += c
args.append(carg)
return args
while m:
start = m.start()
end = _find_closingbrace(v,start+4)
args = _part_conditional(v[start+4:end-1])
rv = ""
if match.group(int(m.group(1))):
rv = self._unescape(self._replace_conditional(match,args[0]))
elif len(args) > 1:
rv = self._unescape(self._replace_conditional(match,args[1]))
v = v[:start] + rv + v[end:]
m = self._CONDITIONAL.search(v)
return v
def _unescape(self, v):
return self._UNESCAPE.subn(lambda m: m.group(0)[-1], v)[0]
def _schar_escape(self, v):
return self._SCHARS_ESCPAE.subn(lambda m: eval(r"'\%s'" % m.group(0)[-1]), v)[0]
def replace(self, match):
start, end = match.span()
tv = self._s
# Replace all $? with capture groups
tv = self._DOLLAR.subn(lambda m: match.group(int(m.group(1))), tv)[0]
# Replace CaseFoldings
tv = self._SIMPLE_CASEFOLDINGS.subn(self._scase_folding, tv)[0]
tv = self._LONG_CASEFOLDINGS.subn(self._lcase_folding, tv)[0]
tv = self._replace_conditional(match, tv)
return self._unescape(self._schar_escape(tv))
class TextObjectTransformation(object):
"""Base class for Transformations and ${VISUAL}."""
def __init__(self, token):
self._convert_to_ascii = False
@ -125,20 +121,26 @@ class TextObjectTransformation(object):
self._replace = _CleverReplace(token.replace)
def _transform(self, text):
global UNIDECODE_ALERT_RAISED
"""Do the actual transform on the given text."""
global UNIDECODE_ALERT_RAISED # pylint:disable=global-statement
if self._convert_to_ascii:
try:
import unidecode
text = unidecode.unidecode(text)
except Exception as e:
except Exception: # pylint:disable=broad-except
if UNIDECODE_ALERT_RAISED == False:
UNIDECODE_ALERT_RAISED = True
sys.stderr.write("Please install unidecode python package in order to be able to make ascii conversions.\n")
sys.stderr.write(
"Please install unidecode python package in order to "
"be able to make ascii conversions.\n")
if self._find is None:
return text
return self._find.subn(self._replace.replace, text, self._match_this_many)[0]
return self._find.subn(
self._replace.replace, text, self._match_this_many)[0]
class Transformation(Mirror, TextObjectTransformation):
"""See module docstring."""
def __init__(self, parent, ts, token):
Mirror.__init__(self, parent, ts, token)
TextObjectTransformation.__init__(self, token)

View File

@ -6,7 +6,7 @@
from collections import deque
from UltiSnips.compatibility import as_unicode, byte2col
from UltiSnips.geometry import Position
from UltiSnips.position import Position
import UltiSnips._vim as _vim
class VimPosition(Position):

View File

@ -29,6 +29,8 @@
# for this to work properly as SendKeys is a piece of chunk. (i.e. it sends
# <F13> when you send a | symbol while using german key mappings)
# pylint: skip-file
import os
import tempfile
import unittest