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 output-format=text
# Tells whether to display a full report or only the messages # Tells whether to display a full report or only the messages
reports=no reports=yes
[BASIC] [BASIC]

View File

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

View File

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

View File

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

View File

@ -1,36 +1,43 @@
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
"""Convenience methods that help with debugging. They should never be used in
production code."""
import sys import sys
from UltiSnips.compatibility import as_unicode 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" else "C:/windows/temp/ultisnips.txt"
with open(dump_filename, "w") as dump_file: with open(DUMP_FILENAME, "w"):
pass # clears the file pass # clears the file
def echo_to_hierarchy(to): def echo_to_hierarchy(text_object):
par = to """Outputs the given 'text_object' and its childs hierarchically."""
while par._parent: par = par._parent # pylint:disable=protected-access
parent = text_object
def _do_print(to, indent=""): while parent._parent:
debug(indent + as_unicode(to)) parent = parent._parent
def _do_print(text_object, indent=""):
"""prints recursively."""
debug(indent + as_unicode(text_object))
try: try:
for c in to._childs: for child in text_object._childs:
_do_print(c, indent=indent + " ") _do_print(child, indent=indent + " ")
except AttributeError: except AttributeError:
pass pass
_do_print(parent)
_do_print(par) def debug(msg):
"""Dumb 'msg' into the debug file."""
def debug(s): msg = as_unicode(msg)
s = as_unicode(s) with open(DUMP_FILENAME, "ab") as dump_file:
with open(dump_filename, "ab") as dump_file: dump_file.write((msg + '\n').encode("utf-8"))
dump_file.write((s + '\n').encode("utf-8"))
def print_stack(): def print_stack():
"""Dump a stack trace into the debug file."""
import traceback import traceback
with open(dump_filename, "ab") as dump_file: with open(DUMP_FILENAME, "ab") as dump_file:
traceback.print_stack(file=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 #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
"""Implements a container for parsed snippets."""
import hashlib import hashlib
import os import os
@ -13,67 +15,71 @@ def _hash_file(path):
class SnippetDictionary(object): class SnippetDictionary(object):
def __init__(self, *args, **kwargs): """See module docstring."""
self._added = []
self.reset()
def reset(self): def __init__(self):
self._snippets = [] self._added = []
self._extends = [] self._extends = []
self._files = {} self._files = {}
self._snippets = []
def add_snippet(self, s, fn=None): def add_snippet(self, snippet, filename):
if fn: """Add 'snippet' to this dictionary. If 'filename' is given, also watch
self._snippets.append(s) the original file for changes."""
if filename:
if fn not in self.files: self._snippets.append(snippet)
self.addfile(fn) if filename not in self.files:
self.addfile(filename)
else: else:
self._added.append(s) self._added.append(snippet)
def get_matching_snippets(self, trigger, potentially): 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: 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: 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 @property
def snippets(self): def snippets(self):
"""Returns all snippets in this dictionary."""
return self._added + self._snippets return self._added + self._snippets
def clear_snippets(self, triggers=[]): def clear_snippets(self, triggers=None):
"""Remove all snippets that match each trigger in triggers. """Remove all snippets that match each trigger in 'triggers'. When
When triggers is empty, removes all snippets. 'triggers' is None, empties this dictionary completely."""
""" if triggers is None:
triggers = []
if triggers: if triggers:
for t in triggers: for trigger in triggers:
for s in self.get_matching_snippets(t, potentially=False): for snippet in self.get_matching_snippets(trigger, False):
if s in self._snippets: if snippet in self._snippets:
self._snippets.remove(s) self._snippets.remove(snippet)
if s in self._added: if snippet in self._added:
self._added.remove(s) self._added.remove(snippet)
else: else:
self._snippets = [] self._snippets = []
self._added = [] self._added = []
@property
def files(self):
return self._files
def addfile(self, path): def addfile(self, path):
"""Add this file to the files we read triggers from."""
self.files[path] = _hash_file(path) 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(): for path, hash in self.files.items():
if not hash or hash != _hash_file(path): if not hash or hash != _hash_file(path):
return True return True
return False return False
def extends(): @property
def fget(self): 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 return self._extends
def fset(self, value):
self._extends = value
return locals()
extends = property(**extends())

View File

@ -2,15 +2,14 @@
# encoding: utf-8 # encoding: utf-8
import unittest 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): class _MPBase(object):
def runTest(self): def runTest(self):
obj = Position(*self.obj) obj = Position(*self.obj)
for pivot, diff, wanted in self.steps: for pivot, delta, wanted in self.steps:
obj.move(Position(*pivot), Position(*diff)) obj.move(Position(*pivot), Position(*delta))
self.assertEqual(Position(*wanted), obj) self.assertEqual(Position(*wanted), obj)
class MovePosition_DelSameLine(_MPBase, unittest.TestCase): 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__), "..")) import os.path as p, sys; sys.path.append(p.join(p.dirname(__file__), ".."))
from _diff import diff, guess_edit from _diff import diff, guess_edit
from geometry import Position from position import Position
def transform(a, cmds): def transform(a, cmds):
@ -184,7 +184,3 @@ if __name__ == '__main__':
unittest.main() unittest.main()
# k = TestEditScript() # k = TestEditScript()
# unittest.TextTestRunner().run(k) # unittest.TextTestRunner().run(k)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,109 +1,105 @@
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
"""Implements TabStop transformations."""
import re import re
import sys import sys
from UltiSnips.text_objects._mirror import Mirror from UltiSnips.text_objects._mirror import Mirror
from UltiSnips.escaping import unescape, fill_in_whitespace
# flag used to display only one time the lack of unidecode def _find_closing_brace(string, start_pos):
UNIDECODE_ALERT_RAISED = False """Finds the corresponding closing brace after start_pos."""
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 bracks_open = 1
for idx, c in enumerate(v[start_pos:]): for idx, char in enumerate(string[start_pos:]):
if c == '(': if char == '(':
if v[idx+start_pos-1] != '\\': if string[idx+start_pos-1] != '\\':
bracks_open += 1 bracks_open += 1
elif c == ')': elif char == ')':
if v[idx+start_pos-1] != '\\': if string[idx+start_pos-1] != '\\':
bracks_open -= 1 bracks_open -= 1
if not bracks_open: if not bracks_open:
return start_pos+idx+1 return start_pos+idx+1
m = self._CONDITIONAL.search(v)
def _part_conditional(v): def _split_conditional(string):
"""Split the given conditional 'string' into its arguments."""
bracks_open = 0 bracks_open = 0
args = [] args = []
carg = "" carg = ""
for idx, c in enumerate(v): for idx, char in enumerate(string):
if c == '(': if char == '(':
if v[idx-1] != '\\': if string[idx-1] != '\\':
bracks_open += 1 bracks_open += 1
elif c == ')': elif char == ')':
if v[idx-1] != '\\': if string[idx-1] != '\\':
bracks_open -= 1 bracks_open -= 1
elif c == ':' and not bracks_open and not v[idx-1] == '\\': elif char == ':' and not bracks_open and not string[idx-1] == '\\':
args.append(carg) args.append(carg)
carg = "" carg = ""
continue continue
carg += c carg += char
args.append(carg) args.append(carg)
return args return args
while m: def _replace_conditional(match, string):
start = m.start() """Replaces a conditional match in a transformation."""
end = _find_closingbrace(v,start+4) conditional_match = _CONDITIONAL.search(string)
args = _part_conditional(v[start+4:end-1]) while conditional_match:
start = conditional_match.start()
end = _find_closing_brace(string, start+4)
args = _split_conditional(string[start+4:end-1])
rv = "" rv = ""
if match.group(int(m.group(1))): if match.group(int(conditional_match.group(1))):
rv = self._unescape(self._replace_conditional(match,args[0])) rv = unescape(_replace_conditional(match, args[0]))
elif len(args) > 1: elif len(args) > 1:
rv = self._unescape(self._replace_conditional(match,args[1])) rv = unescape(_replace_conditional(match, args[1]))
string = string[:start] + rv + string[end:]
conditional_match = _CONDITIONAL.search(string)
return string
v = v[:start] + rv + v[end:] _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."""
m = self._CONDITIONAL.search(v) def __init__(self, expression):
return v self._expression = expression
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): def replace(self, match):
start, end = match.span() """Replaces 'match' through the correct replacement string."""
transformed = self._expression
tv = self._s
# Replace all $? with capture groups # Replace all $? with capture groups
tv = self._DOLLAR.subn(lambda m: match.group(int(m.group(1))), tv)[0] transformed = _DOLLAR.subn(
lambda m: match.group(int(m.group(1))), transformed)[0]
# Replace CaseFoldings # Replace Case switches
tv = self._SIMPLE_CASEFOLDINGS.subn(self._scase_folding, tv)[0] def _one_char_case_change(match):
tv = self._LONG_CASEFOLDINGS.subn(self._lcase_folding, tv)[0] """Replaces one character case changes."""
tv = self._replace_conditional(match, tv) 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]
return self._unescape(self._schar_escape(tv)) 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 TextObjectTransformation(object): class TextObjectTransformation(object):
"""Base class for Transformations and ${VISUAL}."""
def __init__(self, token): def __init__(self, token):
self._convert_to_ascii = False self._convert_to_ascii = False
@ -125,20 +121,26 @@ class TextObjectTransformation(object):
self._replace = _CleverReplace(token.replace) self._replace = _CleverReplace(token.replace)
def _transform(self, text): 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: if self._convert_to_ascii:
try: try:
import unidecode import unidecode
text = unidecode.unidecode(text) text = unidecode.unidecode(text)
except Exception as e: except Exception: # pylint:disable=broad-except
if UNIDECODE_ALERT_RAISED == False: if UNIDECODE_ALERT_RAISED == False:
UNIDECODE_ALERT_RAISED = True 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: if self._find is None:
return text 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): class Transformation(Mirror, TextObjectTransformation):
"""See module docstring."""
def __init__(self, parent, ts, token): def __init__(self, parent, ts, token):
Mirror.__init__(self, parent, ts, token) Mirror.__init__(self, parent, ts, token)
TextObjectTransformation.__init__(self, token) TextObjectTransformation.__init__(self, token)

View File

@ -6,7 +6,7 @@
from collections import deque from collections import deque
from UltiSnips.compatibility import as_unicode, byte2col from UltiSnips.compatibility import as_unicode, byte2col
from UltiSnips.geometry import Position from UltiSnips.position import Position
import UltiSnips._vim as _vim import UltiSnips._vim as _vim
class VimPosition(Position): 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 # 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) # <F13> when you send a | symbol while using german key mappings)
# pylint: skip-file
import os import os
import tempfile import tempfile
import unittest import unittest