def __init__(self, expand_trigger, forward_trigger, backward_trigger): self.expand_trigger = expand_trigger self.forward_trigger = forward_trigger self.backward_trigger = backward_trigger self._inner_state_up = False self._supertab_keys = None self._active_snippets = [] self._added_buffer_filetypes = defaultdict(lambda: []) self._vstate = VimState() self._visual_content = VisualContentPreserver() self._snippet_sources = [] self._snip_expanded_in_action = False self._inside_action = False self._last_change = ("", 0) self._added_snippets_source = AddedSnippetsSource() self.register_snippet_source("ultisnips_files", UltiSnipsFileSource()) self.register_snippet_source("added", self._added_snippets_source) enable_snipmate = "1" if vim_helper.eval("exists('g:UltiSnipsEnableSnipMate')") == "1": enable_snipmate = vim_helper.eval("g:UltiSnipsEnableSnipMate") if enable_snipmate == "1": self.register_snippet_source("snipmate_files", SnipMateFileSource()) self._should_update_textobjects = False self._should_reset_visual = False self._reinit()
def _handle_failure(self, trigger): """Mainly make sure that we play well with SuperTab.""" if trigger.lower() == "<tab>": feedkey = "\\" + trigger elif trigger.lower() == "<s-tab>": feedkey = "\\" + trigger else: feedkey = None mode = "n" if not self._supertab_keys: if vim_helper.eval("exists('g:SuperTabMappingForward')") != "0": self._supertab_keys = ( vim_helper.eval("g:SuperTabMappingForward"), vim_helper.eval("g:SuperTabMappingBackward"), ) else: self._supertab_keys = ["", ""] for idx, sttrig in enumerate(self._supertab_keys): if trigger.lower() == sttrig.lower(): if idx == 0: feedkey = r"\<Plug>SuperTabForward" mode = "n" elif idx == 1: feedkey = r"\<Plug>SuperTabBackward" mode = "p" # Use remap mode so SuperTab mappings will be invoked. break if feedkey in (r"\<Plug>SuperTabForward", r"\<Plug>SuperTabBackward"): vim_helper.command("return SuperTab(%s)" % vim_helper.escape(mode)) elif feedkey: vim_helper.command("return %s" % vim_helper.escape(feedkey))
def find_all_snippet_directories() -> List[str]: """Returns a list of the absolute path of all potential snippet directories, no matter if they exist or not.""" if vim_helper.eval("exists('b:UltiSnipsSnippetDirectories')") == "1": snippet_dirs = vim_helper.eval("b:UltiSnipsSnippetDirectories") else: snippet_dirs = vim_helper.eval("g:UltiSnipsSnippetDirectories") if len(snippet_dirs) == 1: # To reduce confusion and increase consistency with # `UltiSnipsSnippetsDir`, we expand ~ here too. full_path = os.path.expanduser(snippet_dirs[0]) if os.path.isabs(full_path): return [full_path] all_dirs = [] check_dirs = vim_helper.eval("&runtimepath").split(",") for rtp in check_dirs: for snippet_dir in snippet_dirs: if snippet_dir == "snippets": raise PebkacError( "You have 'snippets' in UltiSnipsSnippetDirectories. This " "directory is reserved for snipMate snippets. Use another " "directory for UltiSnips snippets.") pth = normalize_file_path( os.path.expanduser(os.path.join(rtp, snippet_dir))) # Runtimepath entries may contain wildcards. all_dirs.extend(glob.glob(pth)) return all_dirs
def reset(self): """Gets the spacing properties from Vim.""" self.shiftwidth = int( vim_helper.eval( "exists('*shiftwidth') ? shiftwidth() : &shiftwidth")) self._expandtab = vim_helper.eval("&expandtab") == "1" self._tabstop = int(vim_helper.eval("&tabstop"))
def _find_all_snippet_directories(): """Returns a list of the absolute path of all snippet directories to search.""" if vim_helper.eval("exists('b:UltiSnipsSnippetDirectories')") == "1": snippet_dirs = vim_helper.eval("b:UltiSnipsSnippetDirectories") else: snippet_dirs = vim_helper.eval("g:UltiSnipsSnippetDirectories") if len(snippet_dirs) == 1: # To reduce confusion and increase consistency with # `UltiSnipsSnippetsDir`, we expand ~ here too. full_path = os.path.expanduser(snippet_dirs[0]) if os.path.isabs(full_path): return [full_path] all_dirs = [] check_dirs = vim_helper.eval("&runtimepath").split(",") for rtp in check_dirs: for snippet_dir in snippet_dirs: if snippet_dir == "snippets": raise RuntimeError( "You have 'snippets' in UltiSnipsSnippetDirectories. This " "directory is reserved for snipMate snippets. Use another " "directory for UltiSnips snippets." ) pth = os.path.realpath(os.path.expanduser(os.path.join(rtp, snippet_dir))) if os.path.isdir(pth): all_dirs.append(pth) return all_dirs
def opt(self, option, default=None): # pylint:disable=no-self-use """Gets a Vim variable.""" if vim_helper.eval("exists('%s')" % option) == "1": try: return vim_helper.eval(option) except vim_helper.error: pass return default
def _file_to_edit(self, requested_ft, bang): """Returns a file to be edited for the given requested_ft. If 'bang' is empty a reasonable first choice is opened (see docs), otherwise all files are considered and the user gets to choose. """ filetypes = [] if requested_ft: filetypes.append(requested_ft) else: if bang: filetypes.extend(self.get_buffer_filetypes()) else: filetypes.append(self.get_buffer_filetypes()[0]) potentials = set() all_snippet_directories = find_all_snippet_directories() has_storage_dir = (vim_helper.eval( "exists('g:UltiSnipsSnippetStorageDirectoryForUltiSnipsEdit')") == "1") if has_storage_dir: snippet_storage_dir = vim_helper.eval( "g:UltiSnipsSnippetStorageDirectoryForUltiSnipsEdit") full_path = os.path.expanduser(snippet_storage_dir) potentials.update( _get_potential_snippet_filenames_to_edit(full_path, filetypes)) if len(all_snippet_directories) == 1: # Most likely the user has set g:UltiSnipsSnippetDirectories to a # single absolute path. potentials.update( _get_potential_snippet_filenames_to_edit( all_snippet_directories[0], filetypes)) if (len(all_snippet_directories) != 1 and not has_storage_dir) or (has_storage_dir and bang): # Likely the array contains things like ["UltiSnips", # "mycoolsnippets"] There is no more obvious way to edit than in # the users vim config directory. dot_vim_dir = vim_helper.get_dot_vim() for snippet_dir in all_snippet_directories: if Path(dot_vim_dir) != Path(snippet_dir).parent: continue potentials.update( _get_potential_snippet_filenames_to_edit( snippet_dir, filetypes)) if bang: for ft in filetypes: potentials.update(find_all_snippet_files(ft)) else: if not potentials: _show_user_warning( "UltiSnips was not able to find a default directory for snippets. " "Do you have a .vim directory? Try :UltiSnipsEdit! instead of :UltiSnipsEdit." ) return "" return _select_and_create_file_to_edit(potentials)
def _update(self, done, buf): regex = 'indent\([\'"]\.[\'"]\)' # don't update current line for express 'indent(".")' if not self._replaced and re.match(regex, self._code): line = vim_helper.eval('line(".")') self._code = re.sub(regex, 'indent("' + line + '")', self._code) self._replaced = line self.overwrite(buf, vim_helper.eval(self._code)) return True
def _track_change(self): self._should_update_textobjects = True try: inserted_char = vim_helper.eval("v:char") except UnicodeDecodeError: return if isinstance(inserted_char, bytes): return try: if inserted_char == "": before = vim_helper.buf.line_till_cursor if (before and self._last_change[0] != "" and before[-1] == self._last_change[0]): self._try_expand(autotrigger_only=True) finally: self._last_change = (inserted_char, vim_helper.buf.cursor) if self._should_reset_visual and self._visual_content.mode == "": self._visual_content.reset() self._should_reset_visual = True
def _track_change(self): self._should_update_textobjects = True try: inserted_char = vim_helper.as_unicode(vim_helper.eval("v:char")) except UnicodeDecodeError: return if sys.version_info >= (3, 0): if isinstance(inserted_char, bytes): return else: if not isinstance(inserted_char, unicode): return try: if inserted_char == "": before = vim_helper.buf.line_till_cursor if ( before and before[-1] == self._last_change[0] or self._last_change[1] != vim.current.window.cursor[0] ): self._try_expand(autotrigger_only=True) finally: self._last_change = (inserted_char, vim.current.window.cursor[0]) if self._should_reset_visual and self._visual_content.mode == "": self._visual_content.reset() self._should_reset_visual = True
def _parse_snippets(self, ft, filename): """Parse the 'filename' for the given 'ft'.""" with open(filename, "r", encoding="utf-8-sig") as to_read: file_data = to_read.read() self._snippets[ft] # Make sure the dictionary exists for event, data in self._parse_snippet_file(file_data, filename): if event == "error": msg, line_index = data filename = vim_helper.eval( """fnamemodify(%s, ":~:.")""" % vim_helper.escape(filename) ) raise SnippetSyntaxError(filename, line_index, msg) elif event == "clearsnippets": priority, triggers = data self._snippets[ft].clear_snippets(priority, triggers) elif event == "extends": # TODO(sirver): extends information is more global # than one snippet source. (filetypes,) = data self.update_extends(ft, filetypes) elif event == "snippet": (snippet,) = data self._snippets[ft].add_snippet(snippet) else: assert False, "Unhandled %s: %r" % (event, data)
def _teardown_inner_state(self): """Reverse _setup_inner_state.""" if not self._inner_state_up: return try: vim_helper.command( "silent doautocmd <nomodeline> User UltiSnipsExitLastSnippet") if self.expand_trigger != self.forward_trigger: vim_helper.command("iunmap <buffer> %s" % self.forward_trigger) vim_helper.command("sunmap <buffer> %s" % self.forward_trigger) vim_helper.command("iunmap <buffer> %s" % self.backward_trigger) vim_helper.command("sunmap <buffer> %s" % self.backward_trigger) vim_helper.command("augroup UltiSnips") vim_helper.command("autocmd!") vim_helper.command("augroup END") if vim_helper.eval( 'g:UltiSnipsDisablePreviewWindowInSnips') == '1': # restore completeopt vim_helper.command( 'let &completeopt = g:ultisnips_completopt_old') except vim_helper.error: # This happens when a preview window was opened. This issues # CursorMoved, but not BufLeave. We have no way to unmap, until we # are back in our buffer pass finally: self._inner_state_up = False
def _update(self, done, buf): path = vim_helper.eval('expand("%")') or "" ct = self.current_text self._locals.update({ "t": _Tabs(self._parent), "fn": os.path.basename(path), "path": path, "cur": ct, "res": ct, "snip": self._snip, }) self._snip._reset(ct) # pylint:disable=protected-access for code in self._codes: try: exec(code, self._locals) # pylint:disable=exec-used except Exception as exception: exception.snippet_code = code raise rv = str( self._snip.rv if self._snip._rv_changed else self._locals["res"]) # pylint:disable=protected-access if ct != rv: self.overwrite(buf, rv) return False return True
def conserve(self): """Save the last visual selection and the mode it was made in.""" sl, sbyte = map(int, (vim_helper.eval("""line("'<")"""), vim_helper.eval("""col("'<")"""))) el, ebyte = map(int, (vim_helper.eval("""line("'>")"""), vim_helper.eval("""col("'>")"""))) sc = byte2col(sl, sbyte - 1) ec = byte2col(el, ebyte - 1) self._mode = vim_helper.eval("visualmode()") # When 'selection' is 'exclusive', the > mark is one column behind the # actual content being copied, but never before the < mark. if vim_helper.eval("&selection") == "exclusive": if not (sl == el and sbyte == ebyte): ec -= 1 _vim_line_with_eol = lambda ln: vim_helper.buf[ln] + "\n" if sl == el: text = _vim_line_with_eol(sl - 1)[sc:ec + 1] else: text = _vim_line_with_eol(sl - 1)[sc:] for cl in range(sl, el - 1): text += _vim_line_with_eol(cl) text += _vim_line_with_eol(el - 1)[:ec + 1] self._text = text
def matches(self, before, visual_content=None): """Returns True if this snippet matches 'before'.""" # If user supplies both "w" and "i", it should perhaps be an # error, but if permitted it seems that "w" should take precedence # (since matching at word boundary and within a word == matching at word # boundary). self._matched = "" words = _words_for_line(self._trigger, before) if "r" in self._opts: try: match = self._re_match(before) except Exception as e: self._make_debug_exception(e) raise elif "w" in self._opts: words_len = len(self._trigger) words_prefix = words[:-words_len] words_suffix = words[-words_len:] match = words_suffix == self._trigger if match and words_prefix: # Require a word boundary between prefix and suffix. boundary_chars = escape(words_prefix[-1:] + words_suffix[:1], r"\"") match = vim_helper.eval( '"%s" =~# "\\\\v.<."' % boundary_chars) != "0" elif "i" in self._opts: match = words.endswith(self._trigger) else: match = words == self._trigger # By default, we match the whole trigger if match and not self._matched: self._matched = self._trigger # Ensure the match was on a word boundry if needed if "b" in self._opts and match: text_before = before.rstrip()[:-len(self._matched)] if text_before.strip(" \t") != "": self._matched = "" return False self._context = None if match and self._context_code: self._context = self._context_match(visual_content) if not self.context: match = False return match
def _setup_inner_state(self): """Map keys and create autocommands that should only be defined when a snippet is active.""" if self._inner_state_up: return if self.expand_trigger != self.forward_trigger: vim_helper.command("inoremap <buffer><nowait><silent> " + self.forward_trigger + " <C-R>=UltiSnips#JumpForwards()<cr>") vim_helper.command("snoremap <buffer><nowait><silent> " + self.forward_trigger + " <Esc>:call UltiSnips#JumpForwards()<cr>") vim_helper.command("inoremap <buffer><nowait><silent> " + self.backward_trigger + " <C-R>=UltiSnips#JumpBackwards()<cr>") vim_helper.command("snoremap <buffer><nowait><silent> " + self.backward_trigger + " <Esc>:call UltiSnips#JumpBackwards()<cr>") if vim_helper.eval('g:UltiSnipsDisablePreviewWindowInSnips') == '1': # backup completeopt vim_helper.command('let g:ultisnips_completopt_old = &completeopt') # and remove the preview option vim_helper.command('set completeopt-=preview') # Setup the autogroups. vim_helper.command("augroup UltiSnips") vim_helper.command("autocmd!") vim_helper.command( "autocmd CursorMovedI * call UltiSnips#CursorMoved()") vim_helper.command( "autocmd CursorMoved * call UltiSnips#CursorMoved()") vim_helper.command( "autocmd InsertLeave * call UltiSnips#LeavingInsertMode()") vim_helper.command("autocmd BufEnter * call UltiSnips#LeavingBuffer()") vim_helper.command( "autocmd CmdwinEnter * call UltiSnips#LeavingBuffer()") vim_helper.command( "autocmd CmdwinLeave * call UltiSnips#LeavingBuffer()") # Also exit the snippet when we enter a unite complete buffer. vim_helper.command( "autocmd Filetype unite call UltiSnips#LeavingBuffer()") vim_helper.command("augroup END") vim_helper.command( "silent doautocmd <nomodeline> User UltiSnipsEnterFirstSnippet") self._inner_state_up = True
def _file_to_edit(self, requested_ft, bang): """Returns a file to be edited for the given requested_ft. If 'bang' is empty only private files in g:UltiSnipsSnippetsDir are considered, otherwise all files are considered and the user gets to choose. """ snippet_dir = "" if vim_helper.eval("exists('g:UltiSnipsSnippetsDir')") == "1": dir = vim_helper.eval("g:UltiSnipsSnippetsDir") file = self._get_file_to_edit(dir, requested_ft, bang) if file: return file snippet_dir = dir if vim_helper.eval("exists('g:UltiSnipsSnippetDirectories')") == "1": dirs = vim_helper.eval("g:UltiSnipsSnippetDirectories") for dir in dirs: file = self._get_file_to_edit(dir, requested_ft, bang) if file: return file if not snippet_dir: snippet_dir = dir home = vim_helper.eval("$HOME") if platform.system() == "Windows": dir = os.path.join(home, "vimfiles", "UltiSnips") file = self._get_file_to_edit(dir, requested_ft, bang) if file: return file if not snippet_dir: snippet_dir = dir if vim_helper.eval("has('nvim')") == "1": xdg_home_config = vim_helper.eval("$XDG_CONFIG_HOME") or os.path.join( home, ".config" ) dir = os.path.join(xdg_home_config, "nvim", "UltiSnips") file = self._get_file_to_edit(dir, requested_ft, bang) if file: return file if not snippet_dir: snippet_dir = dir dir = os.path.join(home, ".vim", "UltiSnips") file = self._get_file_to_edit(dir, requested_ft, bang) if file: return file if not snippet_dir: snippet_dir = dir return self._get_file_to_edit(snippet_dir, requested_ft, bang, True)
def remember_unnamed_register(self, text_to_expect): """Save the unnamed register. 'text_to_expect' is text that we expect to be contained in the register the next time this method is called - this could be text from the tabstop that was selected and might have been overwritten. We will not cache that then. """ self._unnamed_reg_cached = True escaped_text = self._text_to_expect.replace("'", "''") res = int(vim_helper.eval('@" != ' + "'" + escaped_text + "'")) if res: vim_helper.command('let g:_ultisnips_unnamed_reg_cache = @"') self._text_to_expect = text_to_expect
def _snipmate_files_for(ft): """Returns all snipMate files we need to look at for 'ft'.""" if ft == "all": ft = "_" patterns = [ "%s.snippets" % ft, os.path.join(ft, "*.snippets"), os.path.join(ft, "*.snippet"), os.path.join(ft, "*/*.snippet"), ] ret = set() if vim_helper.eval("exists('g:UltiSnipsSnipMateAbsDirectories')") == '1': # specify the snipmate snippets by handle with absolute path paths = vim_helper.eval('g:UltiSnipsSnipMateAbsDirectories') else: paths = vim_helper.eval('&runtimepath').split(',') for rtp in paths: path = normalize_file_path( os.path.expanduser(os.path.join(rtp, "snippets"))) for pattern in patterns: for fn in glob.glob(os.path.join(path, pattern)): ret.add(fn) return ret
def could_match(self, before): """Return True if this snippet could match the (partial) 'before'.""" self._matched = "" # List all on whitespace. if before and before[-1] in (" ", "\t"): before = "" if before and before.rstrip() is not before: return False words = _words_for_line(self._trigger, before) if "r" in self._opts: # Test for full match only match = self._re_match(before) elif "w" in self._opts: # Trim non-empty prefix up to word boundary, if present. qwords = escape(words, r"\"") words_suffix = vim_helper.eval( 'substitute("%s", "\\\\v^.+<(.+)", "\\\\1", "")' % qwords ) match = self._trigger.startswith(words_suffix) self._matched = words_suffix # TODO: list_snippets() function cannot handle partial-trigger # matches yet, so for now fail if we trimmed the prefix. if words_suffix != words: match = False elif "i" in self._opts: # TODO: It is hard to define when a inword snippet could match, # therefore we check only for full-word trigger. match = self._trigger.startswith(words) else: match = self._trigger.startswith(words) # By default, we match the words from the trigger if match and not self._matched: self._matched = words # Ensure the match was on a word boundry if needed if "b" in self._opts and match: text_before = before.rstrip()[: -len(self._matched)] if text_before.strip(" \t") != "": self._matched = "" return False return match
def _snipmate_files_for(ft): """Returns all snipMate files we need to look at for 'ft'.""" if ft == "all": ft = "_" patterns = [ "%s.snippets" % ft, os.path.join(ft, "*.snippets"), os.path.join(ft, "*.snippet"), os.path.join(ft, "*/*.snippet"), ] ret = set() for rtp in vim_helper.eval("&runtimepath").split(","): path = os.path.expanduser(os.path.join(rtp, "snippets")) for pattern in patterns: for fn in glob.glob(os.path.join(path, pattern)): ret.add(fn) return ret
def _ask_user(a, formatted): """Asks the user using inputlist() and returns the selected element or None.""" try: rv = vim_helper.eval("inputlist(%s)" % vim_helper.escape(formatted)) if rv is None or rv == "0": return None rv = int(rv) if rv > len(a): rv = len(a) return a[rv - 1] except vim_helper.error: # Likely "invalid expression", but might be translated. We have no way # of knowing the exact error, therefore, we ignore all errors silently. return None except KeyboardInterrupt: return None
def __init__(self): pos = vim_helper.buf.cursor self._mode = vim_helper.eval("mode()") Position.__init__(self, pos.line, pos.col)
def is_blocking(): return bool(int(vim_helper.eval("g:UltiSnipsPMDebugBlocking")))
def is_enable(): return bool(int(vim_helper.eval("g:UltiSnipsDebugServerEnable")))
def get_host_port(host=None, port=None): if host is None: host = vim_helper.eval("g:UltiSnipsDebugHost") if port is None: port = int(vim_helper.eval("g:UltiSnipsDebugPort")) return host, port
def guess_edit(initial_line, last_text, current_text, vim_state): """Try to guess what the user might have done by heuristically looking at cursor movement, number of changed lines and if they got longer or shorter. This will detect most simple movements like insertion, deletion of a line or carriage return. 'initial_text' is the index of where the comparison starts, 'last_text' is the last text of the snippet, 'current_text' is the current text of the snippet and 'vim_state' is the cached vim state. Returns (True, edit_cmds) when the edit could be guessed, (False, None) otherwise. """ if not len(last_text) and not len(current_text): return True, () pos = vim_state.pos ppos = vim_state.ppos # All text deleted? if len(last_text) and (not current_text or (len(current_text) == 1 and not current_text[0])): es = [] if not current_text: current_text = [""] for i in last_text: es.append(("D", initial_line, 0, i)) es.append(("D", initial_line, 0, "\n")) es.pop() # Remove final \n because it is not really removed if is_complete_edit(initial_line, last_text, current_text, es): return True, es if ppos.mode == "v": # Maybe selectmode? sv = list(map(int, vim_helper.eval("""getpos("'<")"""))) sv = Position(sv[1] - 1, sv[2] - 1) ev = list(map(int, vim_helper.eval("""getpos("'>")"""))) ev = Position(ev[1] - 1, ev[2] - 1) if "exclusive" in vim_helper.eval("&selection"): ppos.col -= 1 # We want to be inclusive, sorry. ev.col -= 1 es = [] if sv.line == ev.line: es.append(( "D", sv.line, sv.col, last_text[sv.line - initial_line][sv.col:ev.col + 1], )) if sv != pos and sv.line == pos.line: es.append(( "I", sv.line, sv.col, current_text[sv.line - initial_line][sv.col:pos.col + 1], )) if is_complete_edit(initial_line, last_text, current_text, es): return True, es if pos.line == ppos.line: if len(last_text) == len(current_text): # Movement only in one line llen = len(last_text[ppos.line - initial_line]) clen = len(current_text[pos.line - initial_line]) if ppos < pos and clen > llen: # maybe only chars have been added es = (( "I", ppos.line, ppos.col, current_text[ppos.line - initial_line][ppos.col:pos.col], ), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es if clen < llen: if ppos == pos: # 'x' or DEL or dt or something es = (( "D", pos.line, pos.col, last_text[ppos.line - initial_line][ppos.col:ppos.col + (llen - clen)], ), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es if pos < ppos: # Backspacing or dT dF? es = (( "D", pos.line, pos.col, last_text[pos.line - initial_line][pos.col:pos.col + llen - clen], ), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es elif len(current_text) < len(last_text): # were some lines deleted? (dd or so) es = [] for i in range(len(last_text) - len(current_text)): es.append( ("D", pos.line, 0, last_text[pos.line - initial_line + i])) es.append(("D", pos.line, 0, "\n")) if is_complete_edit(initial_line, last_text, current_text, es): return True, es else: # Movement in more than one line if ppos.line + 1 == pos.line and pos.col == 0: # Carriage return? es = (("I", ppos.line, ppos.col, "\n"), ) if is_complete_edit(initial_line, last_text, current_text, es): return True, es return False, None
def fn(self): # pylint:disable=no-self-use,invalid-name """The filename.""" return vim_helper.eval('expand("%:t")') or ""
def basename(self): # pylint:disable=no-self-use """The filename without extension.""" return vim_helper.eval('expand("%:t:r")') or ""
def _cursor_moved(self): """Called whenever the cursor moved.""" self._should_update_textobjects = False self._vstate.remember_position() if vim_helper.eval("mode()") not in "in": return if self._ignore_movements: self._ignore_movements = False return if self._active_snippets: cstart = self._active_snippets[0].start.line cend = ( self._active_snippets[0].end.line + self._vstate.diff_in_buffer_length ) ct = vim_helper.buf[cstart : cend + 1] lt = self._vstate.remembered_buffer pos = vim_helper.buf.cursor lt_span = [0, len(lt)] ct_span = [0, len(ct)] initial_line = cstart # Cut down on lines searched for changes. Start from behind and # remove all equal lines. Then do the same from the front. if lt and ct: while ( lt[lt_span[1] - 1] == ct[ct_span[1] - 1] and self._vstate.ppos.line < initial_line + lt_span[1] - 1 and pos.line < initial_line + 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_span[0] < lt_span[1] and ct_span[0] < ct_span[1] and lt[lt_span[0]] == ct[ct_span[0]] and self._vstate.ppos.line >= initial_line and pos.line >= initial_line ): 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(cstart, initial_line - 1) lt = lt[lt_span[0] : lt_span[1]] ct = ct[ct_span[0] : ct_span[1]] try: rv, es = guess_edit(initial_line, lt, ct, self._vstate) if not rv: lt = "\n".join(lt) ct = "\n".join(ct) es = diff(lt, ct, initial_line) self._active_snippets[0].replay_user_edits(es, self._ctab) except IndexError: # Rather do nothing than throwing an error. It will be correct # most of the time pass self._check_if_still_inside_snippet() if self._active_snippets: self._active_snippets[0].update_textobjects(vim_helper.buf) self._vstate.remember_buffer(self._active_snippets[0])