From 36a1bc1c17afd24480abfd282303ef2eb6f231a6 Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Fri, 20 Jan 2012 15:32:30 +0100 Subject: [PATCH] Be less clever again about Where to check for changes. - Remove duplicate lines from last and current text. Still, this needs to traverse the whole buffer. - Removed functionality from VimState that had nothing to do with 'State'. --- plugin/UltiSnips/__init__.py | 335 ++++++++++++++---------------- plugin/UltiSnips/edit_distance.py | 2 - test.py | 6 +- 3 files changed, 156 insertions(+), 187 deletions(-) diff --git a/plugin/UltiSnips/__init__.py b/plugin/UltiSnips/__init__.py index bfcdb1f..a3da9c3 100644 --- a/plugin/UltiSnips/__init__.py +++ b/plugin/UltiSnips/__init__.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # encoding: utf-8 +# TODO: Currently Caches whole buffer. Is this really needed? +# TODO: Currently searches whole buffer. Is this really needed? + import edit_distance from debug import debug @@ -62,6 +65,134 @@ def echom(mes, *args): mes = mes % args vim.command('echom %s' % vim_string(mes)) +# TODO: this function should be moved +def select_span(r): + _unmap_select_mode_mapping() + + delta = r.end - r.start + lineno, col = r.start.line, r.start.col + + set_vim_cursor(lineno + 1, col) + + # Case 1: Zero Length Tabstops + if delta.line == delta.col == 0: + if col == 0 or vim.eval("mode()") != 'i' and \ + col < len(as_unicode(vim.current.buffer[lineno])): + feedkeys(r"\i") + else: + feedkeys(r"\a") + else: + # Case 2a: Non zero length and inclusive selection + # TODO: check with exclusive selection + # If a tabstop immediately starts with a newline, the selection + # must start after the last character in the current line. But if + # we are in insert mode and out of it, we cannot go past the + # last character with move_one_right and therefore cannot + # visual-select this newline. We have to hack around this by adding + # an extra space which we can select. Note that this problem could + # be circumvent by selecting the tab backwards (that is starting + # at the end); one would not need to modify the line for this. This creates other + # trouble though + if col >= len(as_unicode(vim.current.buffer[lineno])): + vim.current.buffer[lineno] += " " + + if delta.line: + move_lines = "%ij" % delta.line + else: + move_lines = "" + # Depending on the current mode and position, we + # might need to move escape out of the mode and this + # will move our cursor one left + if col != 0 and vim.eval("mode()") == 'i': + move_one_right = "l" + else: + move_one_right = "" + + # After moving to the correct line, we go back to column 0 + # and select right from there. Note that the we have to select + # one column less since vim's visual selection is including the + # ending while Python slicing is excluding the ending. + if r.end.col == 0: + # Selecting should end at beginning of line -> Select the + # previous line till its end + do_select = "k$" + elif r.end.col > 1: + do_select = "0%il" % (r.end.col-1) + else: + do_select = "0" + + move_cmd = LangMapTranslator().translate( + r"\%sv%s%s\" % (move_one_right, move_lines, do_select) + ) + + feedkeys(move_cmd) + + +# TODO: this function should be moved +def _unmap_select_mode_mapping(): + """This function unmaps select mode mappings if so wished by the user. + Removes select mode mappings that can actually be typed by the user + (ie, ignores things like ). + """ + if int(vim.eval("g:UltiSnipsRemoveSelectModeMappings")): + ignores = vim.eval("g:UltiSnipsMappingsToIgnore") + ['UltiSnips'] + + for option in ("", ""): + # Put all smaps into a var, and then read the var + vim.command(r"redir => _tmp_smaps | silent smap %s " % option + + "| redir END") + + # Check if any mappings where found + all_maps = list(filter(len, vim.eval(r"_tmp_smaps").splitlines())) + if (len(all_maps) == 1 and all_maps[0][0] not in " sv"): + # "No maps found". String could be localized. Hopefully + # it doesn't start with any of these letters in any + # language + continue + + # Only keep mappings that should not be ignored + maps = [m for m in all_maps if + not any(i in m for i in ignores) and len(m.strip())] + + for m in maps: + # The first three chars are the modes, that might be listed. + # We are not interested in them here. + trig = m[3:].split()[0] + + # The bar separates commands + if trig[-1] == "|": + trig = trig[:-1] + "" + + # Special ones + if trig[0] == "<": + add = False + # Only allow these + for valid in ["Tab", "NL", "CR", "C-Tab", "BS"]: + if trig == "<%s>" % valid: + add = True + if not add: + continue + + # UltiSnips remaps . Keep this around. + if trig == "": + continue + + # Actually unmap it + try: + cmd = ("silent! sunmap %s %s") % (option, trig) + vim.command(cmd) + except: + # Bug 908139: ignore unmaps that fail because of + # unprintable characters. This is not ideal because we + # will not be able to unmap lhs with any unprintable + # character. If the lhs stats with a printable + # character this will leak to the user when he tries to + # type this character as a first in a selected tabstop. + # This case should be rare enough to not bother us + # though. + pass + + class _SnippetDictionary(object): def __init__(self, *args, **kwargs): self._added = [] @@ -499,68 +630,6 @@ class VimState(object): self._lline = self._cline self._cline = as_unicode(vim.current.buffer[line]) - def select_span(self, r): - self._unmap_select_mode_mapping() - - delta = r.end - r.start - lineno, col = r.start.line, r.start.col - - set_vim_cursor(lineno + 1, col) - - # Case 1: Zero Length Tabstops - if delta.line == delta.col == 0: - if col == 0 or vim.eval("mode()") != 'i' and \ - col < len(as_unicode(vim.current.buffer[lineno])): - feedkeys(r"\i") - else: - feedkeys(r"\a") - else: - # Case 2a: Non zero length and inclusive selection - # TODO: check with exclusive selection - # If a tabstop immediately starts with a newline, the selection - # must start after the last character in the current line. But if - # we are in insert mode and out of it, we cannot go past the - # last character with move_one_right and therefore cannot - # visual-select this newline. We have to hack around this by adding - # an extra space which we can select. Note that this problem could - # be circumvent by selecting the tab backwards (that is starting - # at the end); one would not need to modify the line for this. This creates other - # trouble though - if col >= len(as_unicode(vim.current.buffer[lineno])): - vim.current.buffer[lineno] += " " - - if delta.line: - move_lines = "%ij" % delta.line - else: - move_lines = "" - # Depending on the current mode and position, we - # might need to move escape out of the mode and this - # will move our cursor one left - if col != 0 and vim.eval("mode()") == 'i': - move_one_right = "l" - else: - move_one_right = "" - - # After moving to the correct line, we go back to column 0 - # and select right from there. Note that the we have to select - # one column less since vim's visual selection is including the - # ending while Python slicing is excluding the ending. - if r.end.col == 0: - # Selecting should end at beginning of line -> Select the - # previous line till its end - do_select = "k$" - elif r.end.col > 1: - do_select = "0%il" % (r.end.col-1) - else: - do_select = "0" - - move_cmd = LangMapTranslator().translate( - r"\%sv%s%s\" % (move_one_right, move_lines, do_select) - ) - - feedkeys(move_cmd) - - def buf_changed(self): return self._text_changed buf_changed = property(buf_changed) @@ -590,68 +659,6 @@ class VimState(object): ########################### # Private functions below # ########################### - def _unmap_select_mode_mapping(self): - """This function unmaps select mode mappings if so wished by the user. - Removes select mode mappings that can actually be typed by the user - (ie, ignores things like ). - """ - if int(vim.eval("g:UltiSnipsRemoveSelectModeMappings")): - ignores = vim.eval("g:UltiSnipsMappingsToIgnore") + ['UltiSnips'] - - for option in ("", ""): - # Put all smaps into a var, and then read the var - vim.command(r"redir => _tmp_smaps | silent smap %s " % option + - "| redir END") - - # Check if any mappings where found - all_maps = list(filter(len, vim.eval(r"_tmp_smaps").splitlines())) - if (len(all_maps) == 1 and all_maps[0][0] not in " sv"): - # "No maps found". String could be localized. Hopefully - # it doesn't start with any of these letters in any - # language - continue - - # Only keep mappings that should not be ignored - maps = [m for m in all_maps if - not any(i in m for i in ignores) and len(m.strip())] - - for m in maps: - # The first three chars are the modes, that might be listed. - # We are not interested in them here. - trig = m[3:].split()[0] - - # The bar separates commands - if trig[-1] == "|": - trig = trig[:-1] + "" - - # Special ones - if trig[0] == "<": - add = False - # Only allow these - for valid in ["Tab", "NL", "CR", "C-Tab", "BS"]: - if trig == "<%s>" % valid: - add = True - if not add: - continue - - # UltiSnips remaps . Keep this around. - if trig == "": - continue - - # Actually unmap it - try: - cmd = ("silent! sunmap %s %s") % (option, trig) - vim.command(cmd) - except: - # Bug 908139: ignore unmaps that fail because of - # unprintable characters. This is not ideal because we - # will not be able to unmap lhs with any unprintable - # character. If the lhs stats with a printable - # character this will leak to the user when he tries to - # type this character as a first in a selected tabstop. - # This case should be rare enough to not bother us - # though. - pass class SnippetManager(object): def __init__(self): @@ -797,64 +804,30 @@ class SnippetManager(object): ct = map(as_unicode, vim.current.buffer) lt = map(as_unicode, self._lvb[:]) - cache = 10 # TODO: increase - c0 = min(self._vstate.pos.line, self._vstate.ppos.line) - c1 = max(self._vstate.pos.line, self._vstate.ppos.line) + lt_span = [0, len(lt)] + ct_span = [0, len(ct)] + initial_line = 0 - def is_prefix(a, b): - """x: wieviele man vorne von a abtrennen muss, y: wieviele dnan gleich sind""" - for y in range(len(b)-1, 0, -1): - for x in range(0, len(a)-1): - if a[x:x+y] == b[:y]: - return x, y - return None, None + # Cut down on lines searched for changes. Start from behind and + # remove all equal lines. Then do the same from the front. + while (lt[lt_span[1]-1] == ct[ct_span[1]-1] and + (lt_span[0] < lt_span[1]) and + (ct_span[0] < ct_span[1])): + ct_span[1] -= 1 + lt_span[1] -= 1 + while (lt[lt_span[0]] == ct[ct_span[0]] and + (lt_span[0] < lt_span[1]) and + (ct_span[0] < ct_span[1])): + ct_span[0] += 1 + lt_span[0] += 1 + initial_line += 1 + ct_span[0] = max(0, ct_span[0] - 1) + lt_span[0] = max(0, lt_span[0] - 1) + initial_line = max(0, initial_line - 1) - def is_suffix(a, b): - return is_prefix(a[::-1], b[::-1]) - - sl = max(0, c0 - cache) - el = c1 + cache - ctb = ct[sl:el+1] - ltb = lt[sl:el+1] - - assert(sl == 0) # TODO - - xs, ys = is_prefix(ctb, ltb) # TODO: stupid name for function - debug("xs: %r, ys: %r" % (xs, ys)) - xe, ye = is_suffix(ctb, ltb) - debug("xe: %r, ye: %r" % (xe, ye)) - lt_span = (0, sys.maxint) - ct_span = (0, sys.maxint) - if xs is not None and xe is not None: - assert(xs == 0 and xe == 0) # TODO - fdl_front = ys - fdl_back = len(vim.current.buffer) - ye - 1 - if fdl_back < fdl_front: - overlap = fdl_front - fdl_back - debug("overlap: %r" % (overlap)) - ys -= overlap - lt_span = (ys + xs - 1, len(ltb) - ye - xe) - ct_span = (ys - 1, fdl_back + 1) - - - debug("all lt: %r" % (lt))#[sl:el+1])) - debug("all ct: %r" % (ct))#[sl:el+1])) - - # TODO - # start_line = min(self._vstate.pos.line, self._vstate.ppos.line) - # ct = as_unicode('\n').join(map(as_unicode, vim.current.buffer[start_line:])) - # lt = as_unicode('\n').join(self._lvb[start_line:]) - lt = lt[lt_span[0]:lt_span[1]] - ct = ct[ct_span[0]:ct_span[1]] - debug("lt: %r" % (lt)) - debug("ct: %r" % (ct)) - debug("lt_span: %r, ct_span: %r" % (lt_span, ct_span)) - - lt = '\n'.join(lt) - ct = '\n'.join(ct) - rv = edit_distance.edit_script(lt, ct, lt_span[0]) - debug("rv: %r" % (rv,)) - self._csnippets[0].edited(rv) + lt = '\n'.join(lt[lt_span[0]:lt_span[1]]) + ct = '\n'.join(ct[ct_span[0]:ct_span[1]]) + self._csnippets[0].edited(edit_distance.edit_script(lt, ct, initial_line)) self._check_if_still_inside_snippet() if self._csnippets: @@ -929,7 +902,7 @@ class SnippetManager(object): if self._cs: self._ctab = self._cs.select_next_tab(backwards) if self._ctab: - self._vstate.select_span(self._ctab.span) + 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 35db180..dfbfefc 100755 --- a/plugin/UltiSnips/edit_distance.py +++ b/plugin/UltiSnips/edit_distance.py @@ -6,8 +6,6 @@ import sys # TODO: check test cases here. They are not up to date -from .debug import debug - def edit_script(a, b, sline = 0): d = defaultdict(list) seen = defaultdict(lambda: sys.maxint) diff --git a/test.py b/test.py index 4df8797..6ce2600 100755 --- a/test.py +++ b/test.py @@ -24,6 +24,8 @@ # The testsuite will use ``screen`` to inject commands into the Vim under test, # and will compare the resulting output to expected results. # +# +# TODO: visual line selection -> replace with more, less and == amount of lines import os import tempfile @@ -819,7 +821,6 @@ class TabStop_Shell_TextInNextLine(_VimTest): wanted = "hi hallo\nWeiterand more" class TabStop_Shell_InDefValue_Leave(_VimTest): skip_on_windows = True - sleeptime = 0.09 # Do this very slowly snippets = ("test", "Hallo ${1:now `echo fromecho`} end") keys = "test" + EX + JF + "and more" wanted = "Hallo now fromecho endand more" @@ -841,7 +842,6 @@ class TabStop_Shell_TestEscapedCharsAndShellVars_Overwrite(_VimTest): class TabStop_Shell_ShebangPython(_VimTest): skip_on_windows = True - sleeptime = 0.09 # Do this very slowly snippets = ("test", """Hallo ${1:now `#!/usr/bin/env python print "Hallo Welt" `} end""") @@ -1432,7 +1432,6 @@ class Transformation_OptionReplaceGlobalMatchInReplace_ECR(_VimTest): keys = "test" + EX + "a, nice, building" wanted = "a, nice, building a, nice, building" class TransformationUsingBackspaceToDeleteDefaultValueInFirstTab_ECR(_VimTest): - sleeptime = 0.09 # Do this very slowly snippets = ("test", "snip ${1/.+/(?0:m1)/} ${2/.+/(?0:m2)/} " "${1:default} ${2:def}") keys = "test" + EX + BS + JF + "hi" @@ -1787,7 +1786,6 @@ class Multiple_ManySnippetsOneTrigger_ECR(_VimTest): ("test", "Case28", "This is Case 28"), ("test", "Case29", "This is Case 29"), ) #}}} - sleeptime = 0.09 # Do this very slowly keys = "test" + EX + " " + ESC + ESC + "ahi" wanted = "testhi" # End: Selecting Between Same Triggers #}}}