diff --git a/PySnipEmu.py b/PySnipEmu.py index 27ed9a2..797c509 100644 --- a/PySnipEmu.py +++ b/PySnipEmu.py @@ -11,6 +11,10 @@ def debug(s): f.close() class TextObject(object): + """ + This base class represents any object in the text + that has a span in any ways + """ def __init__(self, start, end): self._start = start self._end = end @@ -26,9 +30,7 @@ class TextObject(object): def end(self): return self._end - def delta_rows(): - doc = "The RW foo property." def fget(self): return self._delta_rows def fset(self, value): @@ -37,7 +39,6 @@ class TextObject(object): delta_rows = property(**delta_rows()) def delta_cols(): - doc = "The RW foo property." def fget(self): return self._delta_cols def fset(self, value): @@ -47,31 +48,26 @@ class TextObject(object): class Mirror(TextObject): + """ + A Mirror object mirrors a TabStop that is, text is repeated here + """ def __init__(self, ts, idx, start): - self._ts = ts TextObject.__init__(self, (idx,start), (idx,start)) + self._tabstop = ts + @property def tabstop(self): - return self._ts - - @property - def number(self): - return self._no - - def update_span(self): - start = self._start - lines = self._ts.current_text.splitlines() - if len(lines) == 1: - self._end = (start[0]+len(lines)-1,len(lines[-1])) - elif len(lines) > 1: - self._end = (start[0],start[1]+len(lines[0]) ) + return self._tabstop class TabStop(TextObject): + """ + This is the most important TextObject. A TabStop is were the cursor + comes to rest when the user taps through the Snippet. + """ def __init__(self, no, idx, span, default_text = ""): TextObject.__init__(self, (idx,span[0]), (idx,span[1])) - self._no = no self._ct = default_text def current_text(): @@ -86,40 +82,56 @@ class TabStop(TextObject): def span(self): return (self._start[1]+self._delta_cols,self._start[1]+self._delta_cols+len(self._ct)) - def number(self): - return self._no - number = property(number) - class SnippetInstance(TextObject): + """ + A Snippet instance is an instance of a Snippet Definition. That is, + when the user expands a snippet, a SnippetInstance is created to + keep track of the corresponding TextObjects. The Snippet itself is + also a TextObject because it has a start an end + """ + def __init__(self,start,end, ts, mirrors): TextObject.__init__(self, start, end) - self._ts = ts + self._tabstops = ts self._mirrors = mirrors self._text_objects = ts.values() + mirrors self._selected_tab = None - self._cts = 0 + self._cts = None - def select_next_tab(self): - self._cts += 1 + for ts in self._tabstops.values(): + self._update_mirrors(ts) - if self._cts not in self._ts: - if 0 in self._ts: - self._cts = 0 + def select_next_tab(self, backwards = False): + if self._cts == 0: + if not backwards: + return False + + if backwards: + cts_bf = self._cts + + if self._cts == 0: + self._cts = max(self._tabstops.keys()) else: + self._cts -= 1 + if self._cts <= 0: + self._cts = cts_bf + else: + # All tabs handled? + if self._cts is None: self._cts = 1 + else: + self._cts += 1 - debug("self._cts: %i" % self._cts) + if self._cts not in self._tabstops: + self._cts = 0 + if 0 not in self._tabstops: + return False - ts = self._ts[self._cts] + ts = self._tabstops[self._cts] lineno, col = self._start - debug("ts._start: %s" % (ts.start,)) - debug("ts._delta_cols: %s" % (ts._delta_cols,)) - debug("ts.span: %s" % (ts.span,)) - debug("ts._ct: '%s'" % (ts._ct,)) - newline = lineno + ts.start[0] if newline == lineno: newcol = col + ts.span[0] @@ -144,67 +156,64 @@ class SnippetInstance(TextObject): % (move_one_right, endcol-newcol-1)) self._selected_tab = ts + return True + def _update_mirrors(self,for_ts): for m in self._mirrors: if m.tabstop == for_ts: mirror_line = m.start[0] line = vim.current.buffer[mirror_line] - line = line[:m.start[1]+m.delta_cols] + for_ts.current_text # + line[m.end[1]+m.delta_cols:] + line = line[:m.start[1]+m.delta_cols] + \ + for_ts.current_text + \ + line[m.end[1]+m.delta_cols:] + + oldspan = m.end[1]-m.start[1] + # TODO: evil hack! + m._end = (m.start[0],m.start[1]+len(for_ts.current_text)) + newspan = m.end[1]-m.start[1] + vim.current.buffer[mirror_line] = line - # m.update_span() + self._move_to_on_line(newspan-oldspan, m.start[0], m.start[1]+m.delta_cols,cobj=m) - # Now update all mirrors and tabstops, that they have moved - # lines = for_ts.current_text.splitlines() - # if len(lines): - # for om in self._mirrors: - # om.update_span() - # - # # if om.start[0] > m.start[0]: - # # om.delta_rows += len(lines)-1 - # if om.start[0] == m.start[0]: - # if om.start[1] >= m.start[1]: - # om.delta_cols += len(lines[-1]) + def _move_to_on_line(self,amount, lineno = None, col = None, cobj = None): + if self._cts is None: + return + + if lineno is None and col is None: + lineno,col = vim.current.window.cursor + lineno -= 1 + cobj = self._tabstops[self._cts] - def _move_to_on_line(self,amount): - debug("Move on line: %i" % amount) - lineno,col = vim.current.window.cursor - lineno -= 1 for m in self._text_objects: - debug(" m.start, lineno, col: %s %s %s" % (m.start, lineno, col)) if m.start[0] != lineno: continue - if m.start[1]+m.delta_cols >= col: + if m.start[1]+m.delta_cols >= col and m != cobj: m.delta_cols += amount - debug(" m.delta_cols: %s" % (m.delta_cols)) def backspace(self,count): - cts = self._ts[self._cts] + cts = self._tabstops[self._cts] ll = len(cts.current_text) - debug("backspace(%i):" % count) cts.current_text = cts.current_text[:-count] - debug(" cts.current_text: %s" % cts.current_text) self._move_to_on_line(len(cts.current_text)-ll) self._update_mirrors(cts) def chars_entered(self, chars): - cts = self._ts[self._cts] + cts = self._tabstops[self._cts] if self._selected_tab is not None: - self.backspace(len(cts.current_text)) + self._move_to_on_line(len(chars)-len(cts.current_text)) + cts.current_text = "" self._selected_tab = None + else: + self._move_to_on_line(len(chars)) cts.current_text += chars - debug("chars_entered(%s):" % chars) - debug(" cts.current_text: %s" % cts.current_text) - - self._move_to_on_line(len(chars)) - self._update_mirrors(cts) @@ -226,6 +235,7 @@ class Snippet(object): no = int(m.group(1)) def_text = m.group(2) + start, end = m.span() val = val[:start] + def_text + val[end:] @@ -234,7 +244,7 @@ class Snippet(object): start_in_line = start - line_start ts = TabStop(no, line_idx, (start_in_line,start_in_line+len(def_text)), def_text) - tabstops[ts.number] = ts + tabstops[no] = ts return val @@ -252,8 +262,8 @@ class Snippet(object): m = Mirror(tabstops[no], line_idx, start_in_line) mirrors.append(m) else: - ts = TabStop(no, line_idx, (start_in_line,start_in_line), "") - tabstops[ts.number] = ts + ts = TabStop(no, line_idx, (start_in_line,start_in_line)) + tabstops[no] = ts return val @@ -323,9 +333,11 @@ class SnippetManager(object): def add_snippet(self,trigger,value): self._snippets[trigger] = Snippet(trigger,value) - def try_expand(self): + def try_expand(self, backwards = False): if len(self._current_snippets): - self._current_snippets[-1].select_next_tab() + cs = self._current_snippets[-1] + if not cs.select_next_tab(backwards): + self._current_snippets.pop() self._last_cursor_pos = vim.current.window.cursor return @@ -342,11 +354,11 @@ class SnippetManager(object): word = before.split()[-1] if word in self._snippets: s = self._snippets[word].launch(before.rstrip()[:-len(word)], after) + self._last_cursor_pos = vim.current.window.cursor if s is not None: self._current_snippets.append(s) def cursor_moved(self): - debug("CursorMoved: %s" % vim.eval("mode()")) cp = vim.current.window.cursor @@ -368,7 +380,6 @@ class SnippetManager(object): self._last_cursor_pos = cp def entered_insert_mode(self): - debug("EnteredInsertMode: %s" % vim.eval("mode()")) pass PySnipSnippets = SnippetManager() diff --git a/PySnipEmu.vim b/PySnipEmu.vim index 7bb1dc5..93612fd 100644 --- a/PySnipEmu.vim +++ b/PySnipEmu.vim @@ -10,6 +10,14 @@ EOF return "" endfunction +function! PyVimSnips_JumpBackwards() + py << EOF +from PySnipEmu import PySnipSnippets +PySnipSnippets.try_expand(True) +EOF + return "" +endfunction + function! PyVimSnips_SelectWord(len) return "\".'v'.a:len."l\" @@ -25,6 +33,8 @@ python from PySnipEmu import PySnipSnippets inoremap =PyVimSnips_ExpandSnippet() snoremap :call PyVimSnips_ExpandSnippet() +inoremap + =PyVimSnips_JumpBackwards() +snoremap + :call PyVimSnips_JumpBackwards() au CursorMovedI * py PySnipSnippets.cursor_moved() au InsertEnter * py PySnipSnippets.entered_insert_mode() diff --git a/test.py b/test.py index 64d32c0..3bb1947 100755 --- a/test.py +++ b/test.py @@ -19,12 +19,6 @@ class _VimTest(unittest.TestCase): for c in str: self.send(c) - # splits = str.split('\t') - # for w in splits[:-1]: - # _send(w + '\t') - # _send(splits[-1]) - - def escape(self): self.type("\x1b") @@ -33,6 +27,9 @@ class _VimTest(unittest.TestCase): self.send(":py PySnipSnippets.reset()\n") + if not isinstance(self.snippets[0],tuple): + self.snippets = ( self.snippets, ) + for sv,content in self.snippets: self.send(''':py << EOF PySnipSnippets.add_snippet("%s","""%s""") @@ -70,9 +67,7 @@ EOF # Simple Expands # ################## class _SimpleExpands(_VimTest): - snippets = ( - ("hallo", "Hallo Welt!"), - ) + snippets = ("hallo", "Hallo Welt!") class SimpleExpand_ExceptCorrectResult(_SimpleExpands): def cmd(self): @@ -141,61 +136,131 @@ class MultilineExpandTestTyping_ExceptCorrectResult(_VimTest): ############ # TabStops # ############ -class ExitTabStop_ExceptCorrectResult(_VimTest): - snippets = ( - ("echo", "$0 run"), - ) +class TabStopSimpleReplace_ExceptCorrectResult(_VimTest): + snippets = ("hallo", "hallo ${0:End} ${1:Beginning}") + def cmd(self): + self.type("hallo\tna\tDu Nase") + def runTest(self): + self.assertEqual(self.output,"hallo Du Nase na") +class TabStopSimpleReplaceSurrounded_ExceptCorrectResult(_VimTest): + snippets = ("hallo", "hallo ${0:End} a small feed") + def cmd(self): + self.type("hallo\tNase") + def runTest(self): + self.assertEqual(self.output,"hallo Nase a small feed") +class TabStopSimpleReplaceSurrounded1_ExceptCorrectResult(_VimTest): + snippets = ("hallo", "hallo $0 a small feed") + def cmd(self): + self.type("hallo\tNase") + def runTest(self): + self.assertEqual(self.output,"hallo Nase a small feed") + + +class ExitTabStop_ExceptCorrectResult(_VimTest): + snippets = ("echo", "$0 run") def cmd(self): self.type("echo\ttest") - def runTest(self): self.assertEqual(self.output,"test run") -class TextTabStopNoReplace_ExceptCorrectResult(_VimTest): - snippets = ( - ("echo", "echo ${1:Hallo}"), - ) - +class TabStopNoReplace_ExceptCorrectResult(_VimTest): + snippets = ("echo", "echo ${1:Hallo}") def cmd(self): self.type("echo\t") - def runTest(self): self.assertEqual(self.output,"echo Hallo") -class TextTabStopSimpleReplace_ExceptCorrectResult(_VimTest): - snippets = ( - ("hallo", "hallo ${0:End} ${1:Beginning}"), - ) - +class TabStopTestJumping_ExceptCorrectResult(_VimTest): + snippets = ("hallo", "hallo ${0:End} mitte ${1:Beginning}") def cmd(self): - self.type("hallo\tna\tDu Nase") - + self.type("hallo\t\tTest\tHi") def runTest(self): - self.assertEqual(self.output,"hallo Du Nase na") - -class TextTabStopSimpleReplace_ExceptCorrectResult(_VimTest): - snippets = ( - ("hallo", "hallo ${0:End} ${1:Beginning}"), - ) - + self.assertEqual(self.output,"hallo TestHi mitte Beginning") +class TabStopTestJumping2_ExceptCorrectResult(_VimTest): + snippets = ("hallo", "hallo $0 $1") def cmd(self): - self.type("hallo\tna\tDu Nase") - + self.type("hallo\t\tTest\tHi") def runTest(self): - self.assertEqual(self.output,"hallo Du Nase na") + self.assertEqual(self.output,"hallo TestHi ") -# TODO: multiline mirrors +class TabStopTestBackwardJumping_ExceptCorrectResult(_VimTest): + snippets = ("hallo", "hallo ${0:End} mitte${1:Beginning}") + def cmd(self): + self.type("hallo\tSomelengthy Text\tHi+Lets replace it again\tBlah\t++\t") + def runTest(self): + self.assertEqual(self.output,"hallo Blah mitteLets replace it again") +class TabStopTestBackwardJumping2_ExceptCorrectResult(_VimTest): + snippets = ("hallo", "hallo $0 $1") + def cmd(self): + self.type("hallo\tSomelengthy Text\tHi+Lets replace it again\tBlah\t++\t") + def runTest(self): + self.assertEqual(self.output,"hallo Blah Lets replace it again") -class TextTabStopSimpleMirror_ExceptCorrectResult(_VimTest): - snippets = ( - ("test", "$1\n$1"), - ) +# TODO: pasting with while mirroring +########### +# MIRRORS # +########### +class TextTabStopTextAfterTab_ExceptCorrectResult(_VimTest): + snippets = ("test", "$1 Hinten\n$1") def cmd(self): self.type("test\thallo") + def runTest(self): + self.assertEqual(self.output,"hallo Hinten\nhallo") +class TextTabStopTextBeforeTab_ExceptCorrectResult(_VimTest): + snippets = ("test", "Vorne $1\n$1") + def cmd(self): + self.type("test\thallo") + def runTest(self): + self.assertEqual(self.output,"Vorne hallo\nhallo") +class TextTabStopTextSurroundedTab_ExceptCorrectResult(_VimTest): + snippets = ("test", "Vorne $1 Hinten\n$1") + def cmd(self): + self.type("test\thallo test") + def runTest(self): + self.assertEqual(self.output,"Vorne hallo test Hinten\nhallo test") +class TextTabStopTextBeforeMirror_ExceptCorrectResult(_VimTest): + snippets = ("test", "$1\nVorne $1") + def cmd(self): + self.type("test\thallo") + def runTest(self): + self.assertEqual(self.output,"hallo\nVorne hallo") +class TextTabStopAfterMirror_ExceptCorrectResult(_VimTest): + snippets = ("test", "$1\n$1 Hinten") + def cmd(self): + self.type("test\thallo") + def runTest(self): + self.assertEqual(self.output,"hallo\nhallo Hinten") +class TextTabStopSurroundMirror_ExceptCorrectResult(_VimTest): + snippets = ("test", "$1\nVorne $1 Hinten") + def cmd(self): + self.type("test\thallo welt") + def runTest(self): + self.assertEqual(self.output,"hallo welt\nVorne hallo welt Hinten") +class TextTabStopAllSurrounded_ExceptCorrectResult(_VimTest): + snippets = ("test", "ObenVorne $1 ObenHinten\nVorne $1 Hinten") + def cmd(self): + self.type("test\thallo welt") + def runTest(self): + self.assertEqual(self.output,"ObenVorne hallo welt ObenHinten\nVorne hallo welt Hinten") + + +# TODO: mirror mit tabstop mit default variable +# TODO: Mehrer tabs und mehrere mirrors +class TextTabStopSimpleMirrorMultiline_ExceptCorrectResult(_VimTest): + snippets = ("test", "$1\n$1") + def cmd(self): + self.type("test\thallo") def runTest(self): self.assertEqual(self.output,"hallo\nhallo") +class TextTabStopSimpleMirrorMultilineMany_ExceptCorrectResult(_VimTest): + snippets = ("test", " $1\n$1\na$1b\n$1\ntest $1 mich") + def cmd(self): + self.type("test\thallo") + def runTest(self): + self.assertEqual(self.output," hallo\nhallo\nahallob\nhallo\ntest hallo mich") + class TextTabStopSimpleMirrorDelete_ExceptCorrectResult(_VimTest): snippets = ( @@ -210,15 +275,24 @@ class TextTabStopSimpleMirrorDelete_ExceptCorrectResult(_VimTest): class TextTabStopSimpleMirrorSameLine_ExceptCorrectResult(_VimTest): snippets = ( - ("test", "$1 $1"), + ("test", "$1 $1"), ) def cmd(self): self.type("test\thallo") def runTest(self): - self.assertEqual(self.output,"hallo hallo") + self.assertEqual(self.output,"hallo hallo") +class TextTabStopSimpleMirrorSameLineMany_ExceptCorrectResult(_VimTest): + snippets = ( + ("test", "$1 $1 $1 $1"), + ) + def cmd(self): + self.type("test\thallo du") + + def runTest(self): + self.assertEqual(self.output,"hallo du hallo du hallo du hallo du") class TextTabStopSimpleMirrorDeleteSomeEnterSome_ExceptCorrectResult(_VimTest): snippets = ( ("test", "$1\n$1"), @@ -229,7 +303,36 @@ class TextTabStopSimpleMirrorDeleteSomeEnterSome_ExceptCorrectResult(_VimTest): def runTest(self): self.assertEqual(self.output,"halhups\nhalhups") -# TODO: this is not yet finished + +class TextTabStopSimpleTabstopWithDefaultSimpelType_ExceptCorrectResult(_VimTest): + snippets = ("test", "ha ${1:defa}\n$1") + def cmd(self): + self.type("test\tworld") + def runTest(self): + self.assertEqual(self.output, "ha world\nworld") +class TextTabStopSimpleTabstopWithDefaultComplexType_ExceptCorrectResult(_VimTest): + snippets = ("test", "ha ${1:default value} $1\nanother: $1 mirror") + def cmd(self): + self.type("test\tworld") + def runTest(self): + self.assertEqual(self.output, + "ha world world\nanother: world mirror") +class TextTabStopSimpleTabstopWithDefaultSimpelKeep_ExceptCorrectResult(_VimTest): + snippets = ("test", "ha ${1:defa}\n$1") + def cmd(self): + self.type("test\t") + def runTest(self): + self.assertEqual(self.output, "ha defa\ndefa") +class TextTabStopSimpleTabstopWithDefaultComplexKeep_ExceptCorrectResult(_VimTest): + snippets = ("test", "ha ${1:default value} $1\nanother: $1 mirror") + def cmd(self): + self.type("test\t") + def runTest(self): + self.assertEqual(self.output, + "ha default value default value\nanother: default value mirror") + + + # class TextTabStopMirrorMoreInvolved_ExceptCorrectResult(_VimTest): # snippets = ( # ("for", "for(size_t ${2:i} = 0; $2 < ${1:count}; ${3:++$2})\n{\n\t${0:/* code */}\n}"), @@ -240,6 +343,9 @@ class TextTabStopSimpleMirrorDeleteSomeEnterSome_ExceptCorrectResult(_VimTest): # # def runTest(self): # self.assertEqual(self.output,"hallo Du Nase na") +# TODO: recursive expansion +# TODO: mirrors in default expansion +# TODO: $1 ${1:This is the tabstop} if __name__ == '__main__': import sys