From c67a59f579e316e0a4b01f98083b5be7e3635fdb Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Sat, 8 Feb 2014 14:23:16 +0100 Subject: [PATCH] More linting and refactorings mainly in _transformation.py. --- pylintrc | 2 +- pythonx/UltiSnips/__init__.py | 41 ++-- pythonx/UltiSnips/_diff.py | 3 +- pythonx/UltiSnips/_vim.py | 2 +- pythonx/UltiSnips/debug.py | 41 ++-- pythonx/UltiSnips/geometry.py | 79 ------- pythonx/UltiSnips/position.py | 65 ++++++ pythonx/UltiSnips/snippet_dictionary.py | 80 +++---- .../test_geometry.py => test_position.py} | 7 +- pythonx/UltiSnips/tests/test_diff.py | 6 +- pythonx/UltiSnips/text_objects/_base.py | 4 +- pythonx/UltiSnips/text_objects/_lexer.py | 18 +- pythonx/UltiSnips/text_objects/_parser.py | 2 +- .../UltiSnips/text_objects/_python_code.py | 109 +++++----- .../text_objects/_snippet_instance.py | 3 +- .../UltiSnips/text_objects/_transformation.py | 202 +++++++++--------- pythonx/UltiSnips/vim_state.py | 2 +- test.py | 2 + 18 files changed, 315 insertions(+), 353 deletions(-) delete mode 100644 pythonx/UltiSnips/geometry.py create mode 100644 pythonx/UltiSnips/position.py rename pythonx/UltiSnips/{tests/test_geometry.py => test_position.py} (88%) diff --git a/pylintrc b/pylintrc index dedc542..e7e5c50 100644 --- a/pylintrc +++ b/pylintrc @@ -55,7 +55,7 @@ disable= output-format=text # Tells whether to display a full report or only the messages -reports=no +reports=yes [BASIC] diff --git a/pythonx/UltiSnips/__init__.py b/pythonx/UltiSnips/__init__.py index 5bf8fe9..cef6234 100755 --- a/pythonx/UltiSnips/__init__.py +++ b/pythonx/UltiSnips/__init__.py @@ -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( diff --git a/pythonx/UltiSnips/_diff.py b/pythonx/UltiSnips/_diff.py index 5376d76..6e1c5d0 100644 --- a/pythonx/UltiSnips/_diff.py +++ b/pythonx/UltiSnips/_diff.py @@ -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 - diff --git a/pythonx/UltiSnips/_vim.py b/pythonx/UltiSnips/_vim.py index 019313b..af91084 100755 --- a/pythonx/UltiSnips/_vim.py +++ b/pythonx/UltiSnips/_vim.py @@ -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.""" diff --git a/pythonx/UltiSnips/debug.py b/pythonx/UltiSnips/debug.py index b98a2b2..62555e4 100644 --- a/pythonx/UltiSnips/debug.py +++ b/pythonx/UltiSnips/debug.py @@ -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) diff --git a/pythonx/UltiSnips/geometry.py b/pythonx/UltiSnips/geometry.py deleted file mode 100644 index e069314..0000000 --- a/pythonx/UltiSnips/geometry.py +++ /dev/null @@ -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) diff --git a/pythonx/UltiSnips/position.py b/pythonx/UltiSnips/position.py new file mode 100644 index 0000000..d22d04f --- /dev/null +++ b/pythonx/UltiSnips/position.py @@ -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) diff --git a/pythonx/UltiSnips/snippet_dictionary.py b/pythonx/UltiSnips/snippet_dictionary.py index d8e74ba..eba8db5 100644 --- a/pythonx/UltiSnips/snippet_dictionary.py +++ b/pythonx/UltiSnips/snippet_dictionary.py @@ -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 diff --git a/pythonx/UltiSnips/tests/test_geometry.py b/pythonx/UltiSnips/test_position.py similarity index 88% rename from pythonx/UltiSnips/tests/test_geometry.py rename to pythonx/UltiSnips/test_position.py index 71d0249..9da0426 100644 --- a/pythonx/UltiSnips/tests/test_geometry.py +++ b/pythonx/UltiSnips/test_position.py @@ -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): diff --git a/pythonx/UltiSnips/tests/test_diff.py b/pythonx/UltiSnips/tests/test_diff.py index 719ab91..5ddf909 100644 --- a/pythonx/UltiSnips/tests/test_diff.py +++ b/pythonx/UltiSnips/tests/test_diff.py @@ -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) - - - - diff --git a/pythonx/UltiSnips/text_objects/_base.py b/pythonx/UltiSnips/text_objects/_base.py index 6cd66d7..8f9abbe 100755 --- a/pythonx/UltiSnips/text_objects/_base.py +++ b/pythonx/UltiSnips/text_objects/_base.py @@ -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): diff --git a/pythonx/UltiSnips/text_objects/_lexer.py b/pythonx/UltiSnips/text_objects/_lexer.py index 79d08ec..86bf73f 100644 --- a/pythonx/UltiSnips/text_objects/_lexer.py +++ b/pythonx/UltiSnips/text_objects/_lexer.py @@ -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: diff --git a/pythonx/UltiSnips/text_objects/_parser.py b/pythonx/UltiSnips/text_objects/_parser.py index 1353163..8feea8a 100755 --- a/pythonx/UltiSnips/text_objects/_parser.py +++ b/pythonx/UltiSnips/text_objects/_parser.py @@ -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 diff --git a/pythonx/UltiSnips/text_objects/_python_code.py b/pythonx/UltiSnips/text_objects/_python_code.py index bc5d6f6..537b222 100755 --- a/pythonx/UltiSnips/text_objects/_python_code.py +++ b/pythonx/UltiSnips/text_objects/_python_code.py @@ -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']) ) diff --git a/pythonx/UltiSnips/text_objects/_snippet_instance.py b/pythonx/UltiSnips/text_objects/_snippet_instance.py index f11ce55..69d7aa9 100755 --- a/pythonx/UltiSnips/text_objects/_snippet_instance.py +++ b/pythonx/UltiSnips/text_objects/_snippet_instance.py @@ -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 diff --git a/pythonx/UltiSnips/text_objects/_transformation.py b/pythonx/UltiSnips/text_objects/_transformation.py index 96d1b5d..bf8f495 100755 --- a/pythonx/UltiSnips/text_objects/_transformation.py +++ b/pythonx/UltiSnips/text_objects/_transformation.py @@ -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) diff --git a/pythonx/UltiSnips/vim_state.py b/pythonx/UltiSnips/vim_state.py index 5036622..0d305ae 100755 --- a/pythonx/UltiSnips/vim_state.py +++ b/pythonx/UltiSnips/vim_state.py @@ -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): diff --git a/test.py b/test.py index 27136c9..462f790 100755 --- a/test.py +++ b/test.py @@ -29,6 +29,8 @@ # for this to work properly as SendKeys is a piece of chunk. (i.e. it sends # when you send a | symbol while using german key mappings) +# pylint: skip-file + import os import tempfile import unittest