Towards using an internal buffer for all snippet updates. (#973)

This commit is contained in:
Holger Rapp 2018-04-14 17:59:48 +02:00 committed by UltiBot
parent 165dd4b3cd
commit 64a023a7ec
11 changed files with 78 additions and 83 deletions

View File

@ -75,8 +75,8 @@ class VimBuffer(object):
buf = VimBuffer() # pylint:disable=invalid-name buf = VimBuffer() # pylint:disable=invalid-name
@contextmanager @contextmanager
def toggle_opt(name, new_value): def option_set_to(name, new_value):
old_value = eval('&' + name) old_value = vim.eval('&' + name)
command('set {0}={1}'.format(name, new_value)) command('set {0}={1}'.format(name, new_value))
try: try:
yield yield

View File

@ -13,12 +13,47 @@ from UltiSnips.compatibility import as_unicode
from UltiSnips.indent_util import IndentUtil from UltiSnips.indent_util import IndentUtil
from UltiSnips.text import escape from UltiSnips.text import escape
from UltiSnips.text_objects import SnippetInstance from UltiSnips.text_objects import SnippetInstance
from UltiSnips.text_objects._python_code import \ from UltiSnips.text_objects._python_code import SnippetUtilForAction
SnippetUtilCursor, SnippetUtilForAction
__WHITESPACE_SPLIT = re.compile(r"\s") __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): def split_at_whitespace(string):
"""Like string.split(), but keeps empty words as empty words.""" """Like string.split(), but keeps empty words as empty words."""
return re.split(__WHITESPACE_SPLIT, string) return re.split(__WHITESPACE_SPLIT, string)
@ -124,7 +159,7 @@ class SnippetDefinition(object):
'buffer': current.buffer, 'buffer': current.buffer,
'line': current.window.cursor[0]-1, 'line': current.window.cursor[0]-1,
'column': current.window.cursor[1]-1, 'column': current.window.cursor[1]-1,
'cursor': SnippetUtilCursor(current.window.cursor), 'cursor': _SnippetUtilCursor(current.window.cursor),
} }
locals.update(additional_locals) locals.update(additional_locals)
@ -438,6 +473,6 @@ class SnippetDefinition(object):
last_re=self._last_re, globals=self._globals, last_re=self._last_re, globals=self._globals,
context=self._context) context=self._context)
self.instantiate(snippet_instance, initial_text, indent) self.instantiate(snippet_instance, initial_text, indent)
snippet_instance.replace_initial_text() snippet_instance.replace_initial_text(_vim.buf)
snippet_instance.update_textobjects() snippet_instance.update_textobjects(_vim.buf)
return snippet_instance return snippet_instance

View File

@ -336,7 +336,7 @@ class SnippetManager(object):
self._check_if_still_inside_snippet() self._check_if_still_inside_snippet()
if self._csnippets: if self._csnippets:
self._csnippets[0].update_textobjects() self._csnippets[0].update_textobjects(_vim.buf)
self._vstate.remember_buffer(self._csnippets[0]) self._vstate.remember_buffer(self._csnippets[0])
def _setup_inner_state(self): def _setup_inner_state(self):
@ -448,7 +448,7 @@ class SnippetManager(object):
# we need to set 'onemore' there, because of limitations of the vim # we need to set 'onemore' there, because of limitations of the vim
# API regarding cursor movements; without that test # API regarding cursor movements; without that test
# 'CanExpandAnonSnippetInJumpActionWhileSelected' will fail # 'CanExpandAnonSnippetInJumpActionWhileSelected' will fail
with _vim.toggle_opt('ve', 'onemore'): with _vim.option_set_to('ve', 'onemore'):
jumped = False jumped = False
# We need to remember current snippets stack here because of # We need to remember current snippets stack here because of
@ -489,7 +489,7 @@ class SnippetManager(object):
self._cs.current_placeholder = \ self._cs.current_placeholder = \
self._visual_content.placeholder self._visual_content.placeholder
self._should_reset_visual = False self._should_reset_visual = False
self._csnippets[0].update_textobjects() self._csnippets[0].update_textobjects(_vim.buf)
self._vstate.remember_buffer(self._csnippets[0]) self._vstate.remember_buffer(self._csnippets[0])
if ntab.number == 0 and self._csnippets: if ntab.number == 0 and self._csnippets:

View File

@ -15,25 +15,26 @@ def _calc_end(text, start):
new_end = Position(start.line + len(text) - 1, len(text[-1])) new_end = Position(start.line + len(text) - 1, len(text[-1]))
return new_end return new_end
def _replace_text(buf, start, end, text):
def _text_to_vim(start, end, text):
"""Copy the given text to the current buffer, overwriting the span 'start' """Copy the given text to the current buffer, overwriting the span 'start'
to 'end'.""" to 'end'."""
lines = text.split('\n') lines = text.split('\n')
new_end = _calc_end(lines, start) new_end = _calc_end(lines, start)
before = _vim.buf[start.line][:start.col] before = buf[start.line][:start.col]
after = _vim.buf[end.line][end.col:] after = buf[end.line][end.col:]
new_lines = [] new_lines = []
if len(lines): if len(lines):
new_lines.append(before + lines[0]) new_lines.append(before + lines[0])
new_lines.extend(lines[1:]) new_lines.extend(lines[1:])
new_lines[-1] += after 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 # 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.buf.cursor = start
_vim.command('normal! zv') _vim.command('normal! zv')
@ -115,10 +116,10 @@ class TextObject(object):
"""The end position.""" """The end position."""
return self._end return self._end
def overwrite_with_initial_text(self): def overwrite_with_initial_text(self, buf):
self.overwrite(self._initial_text) 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 """Overwrite the text of this object in the Vim Buffer and update its
length information. length information.
@ -131,16 +132,16 @@ class TextObject(object):
if self.current_text == gtext: if self.current_text == gtext:
return return
old_end = self._end old_end = self._end
self._end = _text_to_vim( self._end = _replace_text(
self._start, self._end, gtext) buf, self._start, self._end, gtext)
if self._parent: if self._parent:
self._parent._child_has_moved( self._parent._child_has_moved(
self._parent._children.index(self), min(old_end, self._end), self._parent._children.index(self), min(old_end, self._end),
self._end.delta(old_end) self._end.delta(old_end)
) )
def _update(self, done): def _update(self, done, buf):
"""Update this object inside the Vim Buffer. """Update this object inside 'buf' which is a list of lines.
Return False if you need to be called again for this edit cycle. Return False if you need to be called again for this edit cycle.
Otherwise return True. Otherwise return True.
@ -357,7 +358,7 @@ class EditableTextObject(TextObject):
if self._parent and requester is not self._parent: if self._parent and requester is not self._parent:
return self._parent._get_tabstop(self, number) 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): if all((child in done) for child in self._children):
assert self not in done assert self not in done
done.add(self) done.add(self)
@ -385,5 +386,5 @@ class NoneditableTextObject(TextObject):
"""All passive text objects that the user can't edit by hand.""" """All passive text objects that the user can't edit by hand."""
def _update(self, done): def _update(self, done, buf):
return True return True

View File

@ -14,16 +14,16 @@ class Mirror(NoneditableTextObject):
NoneditableTextObject.__init__(self, parent, token) NoneditableTextObject.__init__(self, parent, token)
self._ts = tabstop self._ts = tabstop
def _update(self, done): def _update(self, done, buf):
if self._ts.is_killed: if self._ts.is_killed:
self.overwrite('') self.overwrite(buf, '')
self._parent._del_child(self) # pylint:disable=protected-access self._parent._del_child(self) # pylint:disable=protected-access
return True return True
if self._ts not in done: if self._ts not in done:
return False return False
self.overwrite(self._get_text()) self.overwrite(buf, self._get_text())
return True return True
def _get_text(self): def _get_text(self):

View File

@ -35,7 +35,8 @@ class _Tabs(object):
int(no)) # pylint:disable=protected-access int(no)) # pylint:disable=protected-access
if ts is None: if ts is None:
return 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']) _VisualContent = namedtuple('_VisualContent', ['mode', 'text'])
@ -52,42 +53,6 @@ class SnippetUtilForAction(dict):
self.cursor.preserve() 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): class SnippetUtil(object):
"""Provides easy access to indentation, etc. """Provides easy access to indentation, etc.
@ -290,7 +255,7 @@ class PythonCode(NoneditableTextObject):
)) ))
NoneditableTextObject.__init__(self, parent, token) NoneditableTextObject.__init__(self, parent, token)
def _update(self, done): def _update(self, done, buf):
path = _vim.eval('expand("%")') or '' path = _vim.eval('expand("%")') or ''
ct = self.current_text ct = self.current_text
self._locals.update({ self._locals.update({
@ -316,6 +281,6 @@ class PythonCode(NoneditableTextObject):
) )
if ct != rv: if ct != rv:
self.overwrite(rv) self.overwrite(buf, rv)
return False return False
return True return True

View File

@ -65,12 +65,12 @@ class ShellCode(NoneditableTextObject):
self._code = token.code.replace('\\`', '`') self._code = token.code.replace('\\`', '`')
self._tmpdir = _get_tmp() self._tmpdir = _get_tmp()
def _update(self, done): def _update(self, done, buf):
if not self._tmpdir: if not self._tmpdir:
output = \ output = \
'Unable to find executable tmp directory, check noexec on /tmp' 'Unable to find executable tmp directory, check noexec on /tmp'
else: else:
output = _run_shell_command(self._code, self._tmpdir) output = _run_shell_command(self._code, self._tmpdir)
self.overwrite(output) self.overwrite(buf, output)
self._parent._del_child(self) # pylint:disable=protected-access self._parent._del_child(self) # pylint:disable=protected-access
return True return True

View File

@ -38,11 +38,11 @@ class SnippetInstance(EditableTextObject):
EditableTextObject.__init__(self, parent, start, end, initial_text) 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.""" """Puts the initial text of all text elements into Vim."""
def _place_initial_text(obj): def _place_initial_text(obj):
"""recurses on the children to do the work.""" """recurses on the children to do the work."""
obj.overwrite_with_initial_text() obj.overwrite_with_initial_text(buf)
if isinstance(obj, EditableTextObject): if isinstance(obj, EditableTextObject):
for child in obj._children: for child in obj._children:
_place_initial_text(child) _place_initial_text(child)
@ -54,7 +54,7 @@ class SnippetInstance(EditableTextObject):
for cmd in cmds: for cmd in cmds:
self._do_edit(cmd, ctab) self._do_edit(cmd, ctab)
def update_textobjects(self): def update_textobjects(self, buf):
"""Update the text objects that should change automagically after the """Update the text objects that should change automagically after the
users edits have been replayed. users edits have been replayed.
@ -77,7 +77,7 @@ class SnippetInstance(EditableTextObject):
while (done != not_done) and counter: while (done != not_done) and counter:
# Order matters for python locals! # Order matters for python locals!
for obj in sorted(not_done - done): for obj in sorted(not_done - done):
if obj._update(done): if obj._update(done, buf):
done.add(obj) done.add(obj)
counter -= 1 counter -= 1
if not counter: if not counter:

View File

@ -16,6 +16,6 @@ class VimLCode(NoneditableTextObject):
NoneditableTextObject.__init__(self, parent, token) NoneditableTextObject.__init__(self, parent, token)
def _update(self, done): def _update(self, done, buf):
self.overwrite(_vim.eval(self._code)) self.overwrite(buf, _vim.eval(self._code))
return True return True

View File

@ -11,7 +11,6 @@ If there was no text visually selected, this will be the empty string.
import re import re
import textwrap import textwrap
from UltiSnips import _vim
from UltiSnips.indent_util import IndentUtil from UltiSnips.indent_util import IndentUtil
from UltiSnips.text_objects._transformation import TextObjectTransformation from UltiSnips.text_objects._transformation import TextObjectTransformation
from UltiSnips.text_objects._base import NoneditableTextObject from UltiSnips.text_objects._base import NoneditableTextObject
@ -40,11 +39,11 @@ class Visual(NoneditableTextObject, TextObjectTransformation):
NoneditableTextObject.__init__(self, parent, token) NoneditableTextObject.__init__(self, parent, token)
TextObjectTransformation.__init__(self, token) TextObjectTransformation.__init__(self, token)
def _update(self, done): def _update(self, done, buf):
if self._mode == 'v': # Normal selection. if self._mode == 'v': # Normal selection.
text = self._text text = self._text
else: # Block selection or line selection. 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) indent = _REPLACE_NON_WS.sub(' ', text_before)
iu = IndentUtil() iu = IndentUtil()
indent = iu.indent_to_spaces(indent) indent = iu.indent_to_spaces(indent)
@ -58,7 +57,7 @@ class Visual(NoneditableTextObject, TextObjectTransformation):
text = text[:-1] # Strip final '\n' text = text[:-1] # Strip final '\n'
text = self._transform(text) text = self._transform(text)
self.overwrite(text) self.overwrite(buf, text)
self._parent._del_child(self) # pylint:disable=protected-access self._parent._del_child(self) # pylint:disable=protected-access
return True return True

View File

@ -1,9 +1,6 @@
from test.vim_test_case import VimTestCase as _VimTest from test.vim_test_case import VimTestCase as _VimTest
from test.constant import * from test.constant import *
# Mirrors {{{#
class TextTabStopTextAfterTab_ExpectCorrectResult(_VimTest): class TextTabStopTextAfterTab_ExpectCorrectResult(_VimTest):
snippets = ('test', '$1 Hinten\n$1') snippets = ('test', '$1 Hinten\n$1')
keys = 'test' + EX + 'hallo' keys = 'test' + EX + 'hallo'
@ -268,5 +265,3 @@ class Mirror_TestKillTabstop_Kill(_VimTest):
snippets = 'test', 'welt${1:welt${2:welt}welt} $2' snippets = 'test', 'welt${1:welt${2:welt}welt} $2'
keys = 'hallo test' + EX + 'elt' keys = 'hallo test' + EX + 'elt'
wanted = 'hallo weltelt ' wanted = 'hallo weltelt '
# End: Mirrors #}}}