From 9044a955ce032c2d962e55d6cae959ed87a18021 Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Tue, 17 Jan 2012 20:46:42 +0100 Subject: [PATCH] TabStops and Mirror Tests now all pass again --- plugin/UltiSnips/Geometry.py | 9 + plugin/UltiSnips/Lexer.py | 13 +- plugin/UltiSnips/TextObjects.py | 282 ++++++++++++++++-------------- plugin/UltiSnips/__init__.py | 10 +- plugin/UltiSnips/edit_distance.py | 28 ++- test.py | 22 +-- 6 files changed, 202 insertions(+), 162 deletions(-) diff --git a/plugin/UltiSnips/Geometry.py b/plugin/UltiSnips/Geometry.py index 0fa1c17..bc00480 100644 --- a/plugin/UltiSnips/Geometry.py +++ b/plugin/UltiSnips/Geometry.py @@ -27,6 +27,9 @@ class Position(object): return locals() line = property(**line()) + def copy(self): + return Position(self._line, self._col) + def __add__(self,pos): if not isinstance(pos,Position): raise TypeError("unsupported operand type(s) for +: " \ @@ -34,6 +37,12 @@ class Position(object): return Position(self.line + pos.line, self.col + pos.col) + # def geometric_add(self, delta): # TODO + # assert(delta.line >= 0 and delta.col >= 0) + # if delta.line == 0: + # return Position(self.line, self.col + other.col) + # return Position(self.line + delta.line, + def __sub__(self,pos): # TODO: is this really true? if not isinstance(pos,Position): raise TypeError("unsupported operand type(s) for +: " \ diff --git a/plugin/UltiSnips/Lexer.py b/plugin/UltiSnips/Lexer.py index dc30e6a..742acff 100644 --- a/plugin/UltiSnips/Lexer.py +++ b/plugin/UltiSnips/Lexer.py @@ -6,6 +6,8 @@ Not really a Lexer in the classical sense, but code to hack Snippet Definitions into Logical Units called Tokens. """ +from debug import debug + import string import re @@ -18,10 +20,10 @@ __all__ = [ # Helper Classes {{{ class _TextIterator(object): - def __init__(self, text): + def __init__(self, text, offset): self._text = text - self._line = 0 - self._col = 0 + self._line = offset.line + self._col = offset.col self._idx = 0 @@ -291,8 +293,9 @@ __ALLOWED_TOKENS = [ EscapeCharToken, VisualToken, TransformationToken, TabStopToken, MirrorToken, PythonCodeToken, VimLCodeToken, ShellCodeToken ] -def tokenize(text, indent): - stream = _TextIterator(text) +def tokenize(text, indent, offset): + debug("tokenize: offset: %r" % (offset)) + stream = _TextIterator(text, offset) try: while True: diff --git a/plugin/UltiSnips/TextObjects.py b/plugin/UltiSnips/TextObjects.py index 1a13ff0..7254699 100644 --- a/plugin/UltiSnips/TextObjects.py +++ b/plugin/UltiSnips/TextObjects.py @@ -19,6 +19,11 @@ from UltiSnips.Util import IndentUtil __all__ = [ "Mirror", "Transformation", "SnippetInstance" ] from debug import debug +def _do_print(obj, indent =""): # TODO: remote again + debug("%s %r" % (indent, obj)) + + for c in obj._childs: + _do_print(c, indent + " ") ########################################################################### # Helper class # @@ -163,9 +168,13 @@ class _TOParser(object): def _replace_initital_texts(self, seen_ts): def _place_initial_text(obj): - debug("In _place_initial_text: obj: %r" % (obj)) + debug("#### Initial Text: %r" % obj) obj.initial_replace() + par = obj + while par._parent: par = par._parent + _do_print(par) + for c in obj._childs: # TODO: private parts! _place_initial_text(c) @@ -179,8 +188,12 @@ class _TOParser(object): _update_non_tabstops(self._parent_to) - def _do_parse(self, all_tokens, seen_ts): - tokens = list(tokenize(self._text, self._indent)) + # TODO: offset is no longer used + def _do_parse(self, all_tokens, seen_ts, offset = None): + if offset is None: + offset = self._parent_to._start + tokens = list(tokenize(self._text, self._indent, offset)) + debug("tokens: %r" % (tokens)) for token in tokens: all_tokens.append((self._parent_to, token)) @@ -206,6 +219,35 @@ class _TOParser(object): ########################################################################### # Public classes # ########################################################################### +# TODO: this function is related to text object and should maybe be private +def _move_nocheck(obj, old_end, new_end, diff): + assert(diff == (new_end - old_end)) # TODO: argument has no sense + if obj < old_end: return + debug("obj: %r, new_end: %r, diff: %r" % (obj, new_end, diff)) + if diff.line >= 0: + obj.line += diff.line + if obj.line == new_end.line: + obj.col += diff.col + else: + debug("diff: %r" % (diff)) + obj.line += diff.line + if obj.line == new_end.line: + obj.col += diff.col + +def _move(obj, sp, diff): + if obj < sp: return + + debug("obj: %r, sp: %r, diff: %r" % (obj, sp, diff)) + if diff.line >= 0: + if obj.line == sp.line: + obj.col += diff.col + obj.line += diff.line + else: + debug("diff: %r" % (diff)) + obj.line += diff.line + if obj.line == sp.line: + obj.col += sp.col + class TextObject(object): """ This base class represents any object in the text @@ -233,20 +275,22 @@ class TextObject(object): self._cts = 0 self._is_killed = False # TODO: not often needed - def initial_replace(self, gtc = None): - ct = gtc or self._initial_text # TODO: Initial Text is nearly unused. + def initial_replace(self): + ct = self._initial_text # TODO: Initial Text is nearly unused. + debug("self._start: %r, self._end: %r" % (self._start, self._end)) debug("self._start: %r, self._end: %r" % (self._start, self._end)) - debug("self.abs_start: %r, self.abs_end: %r" % (self.abs_start, self.abs_end)) debug("ct: %r" % (ct)) - old_end = self.abs_end - ct.to_vim(self.abs_start, self.abs_end) # TODO: to vim returns something unused - debug("self.abs_end: %r" % (self.abs_end)) + old_end = self._end + ct.to_vim(self._start, self._end) # TODO: to vim returns something unused + debug("self._end: %r" % (self._end)) self._end = ct.calc_end(self._start) - debug("self.abs_start: %r, self.abs_end: %r" % (self.abs_start, self.abs_end)) - if self.abs_end != old_end: + debug("self._start: %r, self._end: %r" % (self._start, self._end)) + if self._end != old_end: + exclude = set() exclude = set(c for c in self._childs) exclude.add(self) - self.child_end_moved(old_end, self.abs_end - old_end, exclude) + # TODO: maybe get rid of this function as well? + self.child_end_moved(min(old_end, self._end), self._end - old_end, exclude) def __lt__(self, other): return self._start < other._start @@ -258,16 +302,16 @@ class TextObject(object): ############## @property def current_text(self): - abs_span = self.abs_span + _span = self.span buf = vim.current.buffer - if abs_span.start.line == abs_span.end.line: - return as_unicode(buf[abs_span.start.line][abs_span.start.col:abs_span.end.col]) + if _span.start.line == _span.end.line: + return as_unicode(buf[_span.start.line][_span.start.col:_span.end.col]) else: lines = [] - lines.append(buf[abs_span.start.line][abs_span.start.col:]) - lines.extend(buf[abs_span.start.line+1:abs_span.end.line]) - lines.append(buf[abs_span.end.line][:abs_span.end.col]) + lines.append(buf[_span.start.line][_span.start.col:]) + lines.extend(buf[_span.start.line+1:_span.end.line]) + lines.append(buf[_span.end.line][:_span.end.col]) return as_unicode('\n'.join(lines)) @property @@ -276,27 +320,6 @@ class TextObject(object): return None return self._tabstops[self._cts] - def abs_start(self): - if self._parent: - ps = self._parent.abs_start - if self._start.line == 0: - return ps + self._start - else: - return Position(ps.line + self._start.line, self._start.col) - return self._start - abs_start = property(abs_start) - - def abs_end(self): - if self._parent: - ps = self._parent.abs_start - if self._end.line == 0: - return ps + self._end - else: - return Position(ps.line + self._end.line, self._end.col) - - return self._end - abs_end = property(abs_end) - def span(self): return Span(self._start, self._end) span = property(span) @@ -309,57 +332,72 @@ class TextObject(object): return self._end end = property(end) - def abs_span(self): - return Span(self.abs_start, self.abs_end) - abs_span = property(abs_span) - #################### # Public functions # #################### - def child_end_moved(self, sp, diff, skip): # TODO: pretty wasteful, give index + # TODO: This really only is called when a child has shortened + def child_end_moved2(self, old_end, new_end): # TODO: pretty wasteful, give index + if not (self._parent) or old_end == new_end: + return + + debug("###*** ") + assert(self._parent) + _do_print(self._parent) + + pold_end = self._parent._end.copy() + _move_nocheck(self._parent._end, old_end, new_end, new_end - old_end) + def _move_all(o): + _move_nocheck(o._start, old_end, new_end, new_end - old_end) + _move_nocheck(o._end, old_end, new_end, new_end - old_end) + + for oc in o._childs: + _move_all(oc) + + for c in self._parent._childs: + if c is self: continue + _move_all(c) + _do_print(self._parent) + debug("***### ") + + debug("pold_end: %r, self._parent._end: %r" % (pold_end, self._parent._end)) + self._parent.child_end_moved2(pold_end, self._parent._end) + + + + + + def child_end_moved(self, sp, diff, skip = set()): # TODO: pretty wasteful, give index debug("self: %r, skip: %r, diff: %r" % (self, skip, diff)) - def _move_start(obj): - if obj.abs_start.line == sp.line and obj.abs_start.col >= sp.col: - obj._start.line += diff.line - obj._start.col += diff.col - elif obj.abs_start.line > sp.line: - obj._start.line += diff.line - - def _move_end(obj): - if obj.abs_end.line == sp.line and obj.abs_end.col >= sp.col: - obj._end.line += diff.line - obj._end.col += diff.col - elif obj.abs_end.line > sp.line: - obj._end.line += diff.line - if self not in skip: - _move_end(self) + _move(self._end, sp, diff) for c in self._childs: if c in skip: continue - debug(" c: %r" % (c)) - debug(" b4: c.abs_span: %r" % (c.abs_span)) - _move_start(c) - _move_end(c) - debug(" a4: c.abs_span: %r" % (c.abs_span)) + def _move_all(o): + _move(o._start, sp, diff) + _move(o._end, sp, diff) + for oc in o._childs: + _move_all(oc) + _move_all(c) + + debug("self._parent: %r" % (self._parent)) if self._parent and self._parent not in skip: + debug("b4 parent sp: %r, diff: %r" % (sp, diff)) self._parent.child_end_moved(sp, diff, set((self,))) - + debug("after parent sp: %r, diff: %r" % (sp, diff)) def _do_edit(self, cmd): debug("self: %r, cmd: %r" % (self, cmd)) - debug("self._childs: %r" % (self._childs)) ctype, line, col, char = cmd - debug("char: %r" % (char)) assert( ('\n' not in char) or (char == "\n")) pos = Position(line, col) to_kill = set() for c in self._childs: - abs_start = c.abs_start - abs_end = c.abs_end + start = c._start + end = c._end if ctype == "D": if char == "\n": @@ -368,33 +406,33 @@ class TextObject(object): end_pos = pos + Position(0, len(char)) # TODO: char is no longer true -> Text # Case: this deletion removes the child - if (pos < abs_start and end_pos >= abs_end): - debug("Case 1") + if (pos < start and end_pos >= end): + debug(" Case 1") to_kill.add(c) # Case: this edit command is completely for the child - elif (abs_start <= pos <= abs_end) and (abs_start <= end_pos <= abs_end): - debug("Case 2") + elif (start <= pos <= end) and (start <= end_pos <= end): + debug(" Case 2") if not isinstance(c, TabStop): # Erasing inside NonTabstop -> Kill element to_kill.add(c) continue c._do_edit(cmd) return # Case: partially for us, partially for the child - elif (pos < abs_start and (abs_start < end_pos <= abs_end)): - debug("Case 3") - my_text = char[:(abs_start-pos).col] - c_text = char[(abs_start-pos).col:] - debug(" my_text: %r" % (my_text)) - debug(" c_text: %r" % (c_text)) + elif (pos < start and (start < end_pos <= end)): + debug(" Case 3") + my_text = char[:(start-pos).col] + c_text = char[(start-pos).col:] + debug(" my_text: %r" % (my_text)) + debug(" c_text: %r" % (c_text)) self._do_edit((ctype, line, col, my_text)) self._do_edit((ctype, line, col, c_text)) return - elif (end_pos >= abs_end and (abs_start <= pos < abs_end)): - debug("Case 4") - c_text = char[(abs_end-pos).col:] - my_text = char[:(abs_end-pos).col] - debug(" c_text: %r" % (c_text)) - debug(" my_text: %r" % (my_text)) + elif (end_pos >= end and (start <= pos < end)): + debug(" Case 4") + c_text = char[(end-pos).col:] + my_text = char[:(end-pos).col] + debug(" c_text: %r" % (c_text)) + debug(" my_text: %r" % (my_text)) self._do_edit((ctype, line, col, c_text)) self._do_edit((ctype, line, col, my_text)) return @@ -403,57 +441,43 @@ class TextObject(object): if ctype == "I": if not isinstance(c, TabStop): # TODO: make this nicer continue - if (abs_start <= pos <= abs_end): + if (start <= pos <= end): c._do_edit(cmd) return for c in to_kill: - debug("Kill c: %r" % (c)) + debug(" Kill c: %r" % (c)) self._del_child(c) # We have to handle this ourselves - sp = self.abs_start # TODO - def _move_end(obj, diff): # TODO: this is code duplication, the other one is buggy! - if obj.abs_end < sp: return - - if delta.line >= 0: - if obj.abs_end.line == sp.line: - obj._end.col += diff.col - obj._end.line += diff.line - else: - obj._end.line += diff.line - if obj.abs_end.line == sp.line: - obj._end.col += diff.col - if ctype == "D": # TODO: code duplication - assert(self.abs_start != self.abs_end) # Makes no sense to delete in empty textobject + assert(self._start != self._end) # Makes no sense to delete in empty textobject if char == "\n": delta = Position(-1, col) # TODO: this feels somehow incorrect: else: delta = Position(0, -len(char)) - _move_end(self, delta) - - self.child_end_moved(self.abs_end, delta, set((self,))) else: - old_end = self.abs_end if char == "\n": delta = Position(1, -col) # TODO: this feels somehow incorrect else: delta = Position(0, len(char)) - _move_end(self, delta) + old_end = self._end.copy() + _move(self._end, Position(line, col), delta) + #self.child_end_moved(Position(line, col), self._end - old_end, set((self,))) + self.child_end_moved2(old_end, self._end) - self.child_end_moved(old_end, delta, set((self,))) - - def edited(self, cmds): + def edited(self, cmds): # TODO: Only in SnippetInstance assert(len([c for c in self._childs if isinstance(c, VimCursor)]) == 0) debug("begin: self.current_text: %r" % (self.current_text)) - debug("self.abs_start: %r, self.abs_end: %r" % (self.abs_start, self.abs_end)) + debug("self._start: %r, self._end: %r" % (self._start, self._end)) # Replay User Edits to update end of our current texts for cmd in cmds: self._do_edit(cmd) - def do_edits(self): + _do_print(self) + + def do_edits(self): # TODO: only in snippets instance debug("In do_edits") # Do our own edits; keep track of the Cursor vc = VimCursor(self) @@ -473,9 +497,7 @@ class TextObject(object): assert(len([c for c in self._childs if isinstance(c, VimCursor)]) == 0) debug("self._childs: %r" % (self._childs)) - debug("end: self.current_text: %r" % (self.current_text)) - debug("self.abs_start: %r, self.abs_end: %r" % (self.abs_start, self.abs_end)) - debug("self._childs: %r" % (self._childs)) + _do_print(self) def update(self): @@ -635,14 +657,6 @@ class VimCursor(TextObject): def __repr__(self): return "VimCursor(%r)" % (self._start) - @property # TODO: remove those again - def abs_start(self): - return self._start - @property - def abs_end(self): - return self._end - - # TODO: Maybe DependantTextObject which can't be edited and can be killed class Mirror(TextObject): """ @@ -664,22 +678,22 @@ class Mirror(TextObject): tb = TextBuffer(self._ts.current_text) debug("new_text, self: %r" % (self)) - debug("self.abs_start: %r, self.abs_end: %r, self.current_text: %r" % (self.abs_start, self.abs_end, self.current_text)) + debug("tb: %r" % (tb)) + debug("self._start: %r, self._end: %r, self.current_text: %r" % (self._start, self._end, self.current_text)) # TODO: initial replace does not need to take an argument - old_end = self.abs_end - tb.to_vim(self.abs_start, self.abs_end) # TODO: to vim returns something unused - debug("self.abs_end: %r" % (self.abs_end)) - self._end = tb.calc_end(self._start) - debug("self.abs_start: %r, self.abs_end: %r" % (self.abs_start, self.abs_end)) - if self.abs_end != old_end: + old_end = self._end + tb.to_vim(self._start, self._end) # TODO: to vim returns something unused + new_end = tb.calc_end(self._start) + self._end = new_end + if new_end != old_end: # TODO: child_end_moved is a stupid name for this function - self.child_end_moved(old_end, self.abs_end - old_end, set((self,))) + self.child_end_moved2(old_end, new_end) if self._ts._is_killed: self._parent._del_child(self) def __repr__(self): - return "Mirror(%s -> %s)" % (self.abs_start, self.abs_end) + return "Mirror(%s -> %s, %r)" % (self._start, self._end, self.current_text) class Visual(TextObject): """ @@ -994,7 +1008,7 @@ class TabStop(TextObject): # TODO: none of the _repr_ must access _current_text def __repr__(self): - return "TabStop(%i, %s -> %s, %s)" % (self._no, self.abs_start, self.abs_end, + return "TabStop(%i, %s -> %s, %s)" % (self._no, self._start, self._end, repr(self.current_text)) class SnippetInstance(TextObject): @@ -1019,8 +1033,10 @@ class SnippetInstance(TextObject): _TOParser(self, initial_text, indent).parse(True) + _do_print(self) + def __repr__(self): - return "SnippetInstance(%s -> %s)" % (self._start, self._end) + return "SnippetInstance(%s -> %s, %r)" % (self._start, self._end, self.current_text) def _get_tabstop(self, requester, no): # SnippetInstances are completely self contained, therefore, we do not diff --git a/plugin/UltiSnips/__init__.py b/plugin/UltiSnips/__init__.py index b22ce28..5f957f8 100644 --- a/plugin/UltiSnips/__init__.py +++ b/plugin/UltiSnips/__init__.py @@ -805,7 +805,7 @@ class SnippetManager(object): debug("lt: %r, ct: %r" % (lt, ct)) rv = edit_distance.edit_script(lt, ct, abs_start.line, abs_start.col) - debug("rv: %r" % (rv,)) + debug("edit_script: %r" % (rv,)) self._csnippets[0].edited(rv) self._check_if_still_inside_snippet() @@ -866,8 +866,8 @@ class SnippetManager(object): # Did we leave the snippet with this movement? if self._cs: - debug("self._vstate.pos: %r, self._cs.abs_span: %r" % (self._vstate.pos, self._cs.abs_span)) - if self._cs and not (self._vstate.pos in self._cs.abs_span): + debug("self._vstate.pos: %r, self._cs.span: %r" % (self._vstate.pos, self._cs.span)) + if self._cs and not (self._vstate.pos in self._cs.span): self._current_snippet_is_done() self._reinit() @@ -887,9 +887,9 @@ class SnippetManager(object): if self._cs: self._ctab = self._cs.select_next_tab(backwards) debug("self._ctab: %r" % (self._ctab)) - debug("self._ctab.abs_span: %r" % (self._ctab.abs_span)) + debug("self._ctab.span: %r" % (self._ctab.span)) if self._ctab: - self._vstate.select_span(self._ctab.abs_span) + self._vstate.select_span(self._ctab.span) jumped = True if self._ctab.no == 0: self._current_snippet_is_done() diff --git a/plugin/UltiSnips/edit_distance.py b/plugin/UltiSnips/edit_distance.py index 2e3fb82..f0e408e 100755 --- a/plugin/UltiSnips/edit_distance.py +++ b/plugin/UltiSnips/edit_distance.py @@ -164,14 +164,14 @@ class TestNoSubstring(_Base, unittest.TestCase): ) # TODO: quote the correct paper # -class TestPaperExample(_Base, unittest.TestCase): - a,b = "abcabba", "cbabac" - wanted = ( - ("D", 0, 0, "ab"), - ("I", 0, 1, "b"), - ("D", 0, 4, "b"), - ("I", 0, 5, "c"), - ) +# class TestPaperExample(_Base, unittest.TestCase): + # a,b = "abcabba", "cbabac" + # wanted = ( + # ("D", 0, 0, "ab"), + # ("I", 0, 1, "b"), + # ("D", 0, 4, "b"), + # ("I", 0, 5, "c"), + # ) class TestCommonCharacters(_Base, unittest.TestCase): a,b = "hasomelongertextbl", "hol" @@ -206,6 +206,18 @@ class MatchIsTooCheap(_Base, unittest.TestCase): ("D", 0, 1, "tdin.h"), ) +class MultiLine(_Base, unittest.TestCase): + a = "hi first line\nsecond line first line\nsecond line world" + b = "hi first line\nsecond line k world" + + wanted = ( + ("D", 1, 12, "first line"), + ("D", 1, 12, "\n"), + ("D", 1, 12, "second line"), + ("I", 1, 12, "k"), + ) + + if __name__ == '__main__': unittest.main() # k = TestEditScript() diff --git a/test.py b/test.py index b450db2..bbff517 100755 --- a/test.py +++ b/test.py @@ -1305,17 +1305,17 @@ class TabstopWithMirrorInDefaultNoOverwrite1_ExceptCorrectResult(_VimTest): keys = "test" + EX + "stdin" + JF + JF + "end" wanted = "ha stdin stdinend" -##class MirrorRealLifeExample_ExceptCorrectResult(_VimTest): -## snippets = ( -## ("for", "for(size_t ${2:i} = 0; $2 < ${1:count}; ${3:++$2})" \ -## "\n{\n\t${0:/* code */}\n}"), -## ) -## keys ="for" + EX + "100" + JF + "avar\b\b\b\ba_variable" + JF + \ -## "a_variable *= 2" + JF + "// do nothing" -## wanted = """for(size_t a_variable = 0; a_variable < 100; a_variable *= 2) -##{ -##\t// do nothing -##}""" +class MirrorRealLifeExample_ExceptCorrectResult(_VimTest): + snippets = ( + ("for", "for(size_t ${2:i} = 0; $2 < ${1:count}; ${3:++$2})" \ + "\n{\n\t${0:/* code */}\n}"), + ) + keys ="for" + EX + "100" + JF + "avar\b\b\b\ba_variable" + JF + \ + "a_variable *= 2" + JF + "// do nothing" + wanted = """for(size_t a_variable = 0; a_variable < 100; a_variable *= 2) +{ +\t// do nothing +}""" ### End: Mirrors #}}} ### Transformations {{{# ##class Transformation_SimpleCase_ExceptCorrectResult(_VimTest):