TabStops and Mirror Tests now all pass again

This commit is contained in:
Holger Rapp 2012-01-17 20:46:42 +01:00
parent f1f6e9abff
commit 9044a955ce
6 changed files with 202 additions and 162 deletions

View File

@ -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 +: " \

View File

@ -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:

View File

@ -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,11 +406,11 @@ 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):
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):
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)
@ -380,19 +418,19 @@ class TextObject(object):
c._do_edit(cmd)
return
# Case: partially for us, partially for the child
elif (pos < abs_start and (abs_start < end_pos <= abs_end)):
elif (pos < start and (start < end_pos <= end)):
debug(" Case 3")
my_text = char[:(abs_start-pos).col]
c_text = char[(abs_start-pos).col:]
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)):
elif (end_pos >= end and (start <= pos < end)):
debug(" Case 4")
c_text = char[(abs_end-pos).col:]
my_text = char[:(abs_end-pos).col]
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))
@ -403,7 +441,7 @@ 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:
@ -411,49 +449,35 @@ class TextObject(object):
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

View File

@ -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()

View File

@ -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()

22
test.py
View File

@ -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):