From 64a023a7ec28d85a00badcaa9abe7490563597ed Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Sat, 14 Apr 2018 17:59:48 +0200 Subject: [PATCH] Towards using an internal buffer for all snippet updates. (#973) --- pythonx/UltiSnips/_vim.py | 4 +- pythonx/UltiSnips/snippet/definition/_base.py | 45 ++++++++++++++++--- pythonx/UltiSnips/snippet_manager.py | 6 +-- pythonx/UltiSnips/text_objects/_base.py | 29 ++++++------ pythonx/UltiSnips/text_objects/_mirror.py | 6 +-- .../UltiSnips/text_objects/_python_code.py | 43 ++---------------- pythonx/UltiSnips/text_objects/_shell_code.py | 4 +- .../text_objects/_snippet_instance.py | 8 ++-- pythonx/UltiSnips/text_objects/_viml_code.py | 4 +- pythonx/UltiSnips/text_objects/_visual.py | 7 ++- test/test_Mirror.py | 5 --- 11 files changed, 78 insertions(+), 83 deletions(-) diff --git a/pythonx/UltiSnips/_vim.py b/pythonx/UltiSnips/_vim.py index 59188e3..162a225 100755 --- a/pythonx/UltiSnips/_vim.py +++ b/pythonx/UltiSnips/_vim.py @@ -75,8 +75,8 @@ class VimBuffer(object): buf = VimBuffer() # pylint:disable=invalid-name @contextmanager -def toggle_opt(name, new_value): - old_value = eval('&' + name) +def option_set_to(name, new_value): + old_value = vim.eval('&' + name) command('set {0}={1}'.format(name, new_value)) try: yield diff --git a/pythonx/UltiSnips/snippet/definition/_base.py b/pythonx/UltiSnips/snippet/definition/_base.py index e8ece6b..4106ac3 100644 --- a/pythonx/UltiSnips/snippet/definition/_base.py +++ b/pythonx/UltiSnips/snippet/definition/_base.py @@ -13,12 +13,47 @@ from UltiSnips.compatibility import as_unicode from UltiSnips.indent_util import IndentUtil from UltiSnips.text import escape from UltiSnips.text_objects import SnippetInstance -from UltiSnips.text_objects._python_code import \ - SnippetUtilCursor, SnippetUtilForAction +from UltiSnips.text_objects._python_code import SnippetUtilForAction __WHITESPACE_SPLIT = re.compile(r"\s") +class _SnippetUtilCursor(object): + def __init__(self, cursor): + self._cursor = [cursor[0] - 1, cursor[1]] + self._set = False + + def preserve(self): + self._set = True + self._cursor = [ + _vim.buf.cursor[0], + _vim.buf.cursor[1], + ] + + def is_set(self): + return self._set + + def set(self, line, column): + self.__setitem__(0, line) + self.__setitem__(1, column) + + def to_vim_cursor(self): + return (self._cursor[0] + 1, self._cursor[1]) + + def __getitem__(self, index): + return self._cursor[index] + + def __setitem__(self, index, value): + self._set = True + self._cursor[index] = value + + def __len__(self): + return 2 + + def __str__(self): + return str((self._cursor[0], self._cursor[1])) + + def split_at_whitespace(string): """Like string.split(), but keeps empty words as empty words.""" return re.split(__WHITESPACE_SPLIT, string) @@ -124,7 +159,7 @@ class SnippetDefinition(object): 'buffer': current.buffer, 'line': current.window.cursor[0]-1, 'column': current.window.cursor[1]-1, - 'cursor': SnippetUtilCursor(current.window.cursor), + 'cursor': _SnippetUtilCursor(current.window.cursor), } locals.update(additional_locals) @@ -438,6 +473,6 @@ class SnippetDefinition(object): last_re=self._last_re, globals=self._globals, context=self._context) self.instantiate(snippet_instance, initial_text, indent) - snippet_instance.replace_initial_text() - snippet_instance.update_textobjects() + snippet_instance.replace_initial_text(_vim.buf) + snippet_instance.update_textobjects(_vim.buf) return snippet_instance diff --git a/pythonx/UltiSnips/snippet_manager.py b/pythonx/UltiSnips/snippet_manager.py index dbb20d1..316fe9b 100644 --- a/pythonx/UltiSnips/snippet_manager.py +++ b/pythonx/UltiSnips/snippet_manager.py @@ -336,7 +336,7 @@ class SnippetManager(object): self._check_if_still_inside_snippet() if self._csnippets: - self._csnippets[0].update_textobjects() + self._csnippets[0].update_textobjects(_vim.buf) self._vstate.remember_buffer(self._csnippets[0]) def _setup_inner_state(self): @@ -448,7 +448,7 @@ class SnippetManager(object): # we need to set 'onemore' there, because of limitations of the vim # API regarding cursor movements; without that test # 'CanExpandAnonSnippetInJumpActionWhileSelected' will fail - with _vim.toggle_opt('ve', 'onemore'): + with _vim.option_set_to('ve', 'onemore'): jumped = False # We need to remember current snippets stack here because of @@ -489,7 +489,7 @@ class SnippetManager(object): self._cs.current_placeholder = \ self._visual_content.placeholder self._should_reset_visual = False - self._csnippets[0].update_textobjects() + self._csnippets[0].update_textobjects(_vim.buf) self._vstate.remember_buffer(self._csnippets[0]) if ntab.number == 0 and self._csnippets: diff --git a/pythonx/UltiSnips/text_objects/_base.py b/pythonx/UltiSnips/text_objects/_base.py index df83954..678bd2a 100644 --- a/pythonx/UltiSnips/text_objects/_base.py +++ b/pythonx/UltiSnips/text_objects/_base.py @@ -15,25 +15,26 @@ def _calc_end(text, start): new_end = Position(start.line + len(text) - 1, len(text[-1])) return new_end - -def _text_to_vim(start, end, text): +def _replace_text(buf, start, end, text): """Copy the given text to the current buffer, overwriting the span 'start' to 'end'.""" lines = text.split('\n') new_end = _calc_end(lines, start) - before = _vim.buf[start.line][:start.col] - after = _vim.buf[end.line][end.col:] + before = buf[start.line][:start.col] + after = buf[end.line][end.col:] new_lines = [] if len(lines): new_lines.append(before + lines[0]) new_lines.extend(lines[1:]) new_lines[-1] += after - _vim.buf[start.line:end.line + 1] = new_lines + buf[start.line:end.line + 1] = new_lines # Open any folds this might have created + # TODO(sirver): This leaks that we are still inside Vim, while this code should + # only care that it is modifying 'buf'. _vim.buf.cursor = start _vim.command('normal! zv') @@ -115,10 +116,10 @@ class TextObject(object): """The end position.""" return self._end - def overwrite_with_initial_text(self): - self.overwrite(self._initial_text) + def overwrite_with_initial_text(self, buf): + self.overwrite(buf, self._initial_text) - def overwrite(self, gtext): + def overwrite(self, buf, gtext): """Overwrite the text of this object in the Vim Buffer and update its length information. @@ -131,16 +132,16 @@ class TextObject(object): if self.current_text == gtext: return old_end = self._end - self._end = _text_to_vim( - self._start, self._end, gtext) + self._end = _replace_text( + buf, self._start, self._end, gtext) if self._parent: self._parent._child_has_moved( self._parent._children.index(self), min(old_end, self._end), self._end.delta(old_end) ) - def _update(self, done): - """Update this object inside the Vim Buffer. + def _update(self, done, buf): + """Update this object inside 'buf' which is a list of lines. Return False if you need to be called again for this edit cycle. Otherwise return True. @@ -357,7 +358,7 @@ class EditableTextObject(TextObject): if self._parent and requester is not self._parent: return self._parent._get_tabstop(self, number) - def _update(self, done): + def _update(self, done, buf): if all((child in done) for child in self._children): assert self not in done done.add(self) @@ -385,5 +386,5 @@ class NoneditableTextObject(TextObject): """All passive text objects that the user can't edit by hand.""" - def _update(self, done): + def _update(self, done, buf): return True diff --git a/pythonx/UltiSnips/text_objects/_mirror.py b/pythonx/UltiSnips/text_objects/_mirror.py index 7f8c961..0e62acd 100644 --- a/pythonx/UltiSnips/text_objects/_mirror.py +++ b/pythonx/UltiSnips/text_objects/_mirror.py @@ -14,16 +14,16 @@ class Mirror(NoneditableTextObject): NoneditableTextObject.__init__(self, parent, token) self._ts = tabstop - def _update(self, done): + def _update(self, done, buf): if self._ts.is_killed: - self.overwrite('') + self.overwrite(buf, '') self._parent._del_child(self) # pylint:disable=protected-access return True if self._ts not in done: return False - self.overwrite(self._get_text()) + self.overwrite(buf, self._get_text()) return True def _get_text(self): diff --git a/pythonx/UltiSnips/text_objects/_python_code.py b/pythonx/UltiSnips/text_objects/_python_code.py index 5404dca..00bec6f 100644 --- a/pythonx/UltiSnips/text_objects/_python_code.py +++ b/pythonx/UltiSnips/text_objects/_python_code.py @@ -35,7 +35,8 @@ class _Tabs(object): int(no)) # pylint:disable=protected-access if ts is None: return - ts.overwrite(value) + # TODO(sirver): The buffer should be passed into the object on construction. + ts.overwrite(_vim.buf, value) _VisualContent = namedtuple('_VisualContent', ['mode', 'text']) @@ -52,42 +53,6 @@ class SnippetUtilForAction(dict): self.cursor.preserve() -class SnippetUtilCursor(object): - def __init__(self, cursor): - self._cursor = [cursor[0] - 1, cursor[1]] - self._set = False - - def preserve(self): - self._set = True - self._cursor = [ - _vim.buf.cursor[0], - _vim.buf.cursor[1], - ] - - def is_set(self): - return self._set - - def set(self, line, column): - self.__setitem__(0, line) - self.__setitem__(1, column) - - def to_vim_cursor(self): - return (self._cursor[0] + 1, self._cursor[1]) - - def __getitem__(self, index): - return self._cursor[index] - - def __setitem__(self, index, value): - self._set = True - self._cursor[index] = value - - def __len__(self): - return 2 - - def __str__(self): - return str((self._cursor[0], self._cursor[1])) - - class SnippetUtil(object): """Provides easy access to indentation, etc. @@ -290,7 +255,7 @@ class PythonCode(NoneditableTextObject): )) NoneditableTextObject.__init__(self, parent, token) - def _update(self, done): + def _update(self, done, buf): path = _vim.eval('expand("%")') or '' ct = self.current_text self._locals.update({ @@ -316,6 +281,6 @@ class PythonCode(NoneditableTextObject): ) if ct != rv: - self.overwrite(rv) + self.overwrite(buf, rv) return False return True diff --git a/pythonx/UltiSnips/text_objects/_shell_code.py b/pythonx/UltiSnips/text_objects/_shell_code.py index a7ad964..4fd4601 100644 --- a/pythonx/UltiSnips/text_objects/_shell_code.py +++ b/pythonx/UltiSnips/text_objects/_shell_code.py @@ -65,12 +65,12 @@ class ShellCode(NoneditableTextObject): self._code = token.code.replace('\\`', '`') self._tmpdir = _get_tmp() - def _update(self, done): + def _update(self, done, buf): if not self._tmpdir: output = \ 'Unable to find executable tmp directory, check noexec on /tmp' else: output = _run_shell_command(self._code, self._tmpdir) - self.overwrite(output) + self.overwrite(buf, output) self._parent._del_child(self) # pylint:disable=protected-access return True diff --git a/pythonx/UltiSnips/text_objects/_snippet_instance.py b/pythonx/UltiSnips/text_objects/_snippet_instance.py index d63f8ae..592beec 100644 --- a/pythonx/UltiSnips/text_objects/_snippet_instance.py +++ b/pythonx/UltiSnips/text_objects/_snippet_instance.py @@ -38,11 +38,11 @@ class SnippetInstance(EditableTextObject): EditableTextObject.__init__(self, parent, start, end, initial_text) - def replace_initial_text(self): + def replace_initial_text(self, buf): """Puts the initial text of all text elements into Vim.""" def _place_initial_text(obj): """recurses on the children to do the work.""" - obj.overwrite_with_initial_text() + obj.overwrite_with_initial_text(buf) if isinstance(obj, EditableTextObject): for child in obj._children: _place_initial_text(child) @@ -54,7 +54,7 @@ class SnippetInstance(EditableTextObject): for cmd in cmds: self._do_edit(cmd, ctab) - def update_textobjects(self): + def update_textobjects(self, buf): """Update the text objects that should change automagically after the users edits have been replayed. @@ -77,7 +77,7 @@ class SnippetInstance(EditableTextObject): while (done != not_done) and counter: # Order matters for python locals! for obj in sorted(not_done - done): - if obj._update(done): + if obj._update(done, buf): done.add(obj) counter -= 1 if not counter: diff --git a/pythonx/UltiSnips/text_objects/_viml_code.py b/pythonx/UltiSnips/text_objects/_viml_code.py index 86329bf..3e21474 100644 --- a/pythonx/UltiSnips/text_objects/_viml_code.py +++ b/pythonx/UltiSnips/text_objects/_viml_code.py @@ -16,6 +16,6 @@ class VimLCode(NoneditableTextObject): NoneditableTextObject.__init__(self, parent, token) - def _update(self, done): - self.overwrite(_vim.eval(self._code)) + def _update(self, done, buf): + self.overwrite(buf, _vim.eval(self._code)) return True diff --git a/pythonx/UltiSnips/text_objects/_visual.py b/pythonx/UltiSnips/text_objects/_visual.py index 0be64b3..24735c3 100644 --- a/pythonx/UltiSnips/text_objects/_visual.py +++ b/pythonx/UltiSnips/text_objects/_visual.py @@ -11,7 +11,6 @@ If there was no text visually selected, this will be the empty string. import re import textwrap -from UltiSnips import _vim from UltiSnips.indent_util import IndentUtil from UltiSnips.text_objects._transformation import TextObjectTransformation from UltiSnips.text_objects._base import NoneditableTextObject @@ -40,11 +39,11 @@ class Visual(NoneditableTextObject, TextObjectTransformation): NoneditableTextObject.__init__(self, parent, token) TextObjectTransformation.__init__(self, token) - def _update(self, done): + def _update(self, done, buf): if self._mode == 'v': # Normal selection. text = self._text else: # Block selection or line selection. - text_before = _vim.buf[self.start.line][:self.start.col] + text_before = buf[self.start.line][:self.start.col] indent = _REPLACE_NON_WS.sub(' ', text_before) iu = IndentUtil() indent = iu.indent_to_spaces(indent) @@ -58,7 +57,7 @@ class Visual(NoneditableTextObject, TextObjectTransformation): text = text[:-1] # Strip final '\n' text = self._transform(text) - self.overwrite(text) + self.overwrite(buf, text) self._parent._del_child(self) # pylint:disable=protected-access return True diff --git a/test/test_Mirror.py b/test/test_Mirror.py index a6076bf..9a94b6a 100644 --- a/test/test_Mirror.py +++ b/test/test_Mirror.py @@ -1,9 +1,6 @@ from test.vim_test_case import VimTestCase as _VimTest from test.constant import * -# Mirrors {{{# - - class TextTabStopTextAfterTab_ExpectCorrectResult(_VimTest): snippets = ('test', '$1 Hinten\n$1') keys = 'test' + EX + 'hallo' @@ -268,5 +265,3 @@ class Mirror_TestKillTabstop_Kill(_VimTest): snippets = 'test', 'welt${1:welt${2:welt}welt} $2' keys = 'hallo test' + EX + 'elt' wanted = 'hallo weltelt ' - -# End: Mirrors #}}}