diff --git a/pythonx/UltiSnips/buffer_helper.py b/pythonx/UltiSnips/buffer_proxy.py similarity index 64% rename from pythonx/UltiSnips/buffer_helper.py rename to pythonx/UltiSnips/buffer_proxy.py index 40366d1..312f258 100644 --- a/pythonx/UltiSnips/buffer_helper.py +++ b/pythonx/UltiSnips/buffer_proxy.py @@ -9,8 +9,13 @@ from UltiSnips import _vim from contextlib import contextmanager + @contextmanager def use_proxy_buffer(snippets_stack): + """ + Forward all changes made in the buffer to the current snippet stack while + function call. + """ buffer_proxy = VimBufferProxy(snippets_stack) old_buffer = _vim.buf try: @@ -20,8 +25,12 @@ def use_proxy_buffer(snippets_stack): _vim.buf = old_buffer buffer_proxy.validate_buffer() + @contextmanager def suspend_proxy_edits(): + """ + Prevents changes being applied to the snippet stack while function call. + """ if not isinstance(_vim.buf, VimBufferProxy): yield else: @@ -31,17 +40,47 @@ def suspend_proxy_edits(): finally: _vim.buf._enable_edits() + class VimBufferProxy(_vim.VimBuffer): + """ + Proxy object used for tracking changes that made from snippet actions. + + Unfortunately, vim by itself lacks of the API for changing text in + trackable maner. + + Vim marks offers limited functionality for tracking line additions and + deletions, but nothing offered for tracking changes withing single line. + + Instance of this class is passed to all snippet actions and behaves as + internal vim.current.window.buffer. + + All changes that are made by user passed to diff algorithm, and resulting + diff applied to internal snippet structures to ensure they are in sync with + actual buffer contents. + """ + def __init__(self, snippets_stack): + """ + Instantiate new object. + + snippets_stack is a slice of currently active snippets. + """ self._snippets_stack = snippets_stack self._buffer = vim.current.buffer self._change_tick = int(vim.eval("b:changedtick")) self._forward_edits = True def is_buffer_changed_outside(self): + """ + Returns true, if buffer was changed without using proxy object, like + with vim.command() or through internal vim.current.window.buffer. + """ return self._change_tick < int(vim.eval("b:changedtick")) def validate_buffer(self): + """ + Raises exception if buffer is changes beyound proxy object. + """ if self.is_buffer_changed_outside(): raise RuntimeError('buffer was modified using vim.command or ' + 'vim.current.buffer; that changes are untrackable and leads to ' + @@ -49,6 +88,10 @@ class VimBufferProxy(_vim.VimBuffer): 'for buffer modifications') def __setitem__(self, key, value): + """ + Behaves as vim.current.window.buffer.__setitem__ except it tracks + changes and applies them to the current snippet stack. + """ if isinstance(key, slice): value = [as_vimencoding(l) for l in value] changes = list(self._get_diff(key.start, key.stop, value)) @@ -65,21 +108,36 @@ class VimBufferProxy(_vim.VimBuffer): self._apply_change(change) def __setslice__(self, i, j, text): + """ + Same as __setitem__. + """ self.__setitem__(slice(i, j), text) def __getitem__(self, key): + """ + Just passing call to the vim.current.window.buffer.__getitem__. + """ if isinstance(key, slice): return [as_unicode(l) for l in self._buffer[key.start:key.stop]] else: return as_unicode(self._buffer[key]) def __getslice__(self, i, j): + """ + Same as __getitem__. + """ return self.__getitem__(slice(i, j)) def __len__(self): + """ + Same as len(vim.current.window.buffer). + """ return len(self._buffer) def append(self, line, line_number=-1): + """ + Same as vim.current.window.buffer.append(), but with tracking changes. + """ if line_number < 0: line_number = len(self) if not isinstance(line, list): @@ -93,6 +151,9 @@ class VimBufferProxy(_vim.VimBuffer): self.__setitem__(slice(key, key+1), []) def _get_diff(self, start, end, new_value): + """ + Very fast diffing algorithm when changes are across many lines. + """ for line_number in range(start, end): yield ('D', line_number, 0, self._buffer[line_number]) @@ -100,6 +161,9 @@ class VimBufferProxy(_vim.VimBuffer): yield ('I', start+line_number, 0, new_value[line_number]) def _get_line_diff(self, line_number, before, after): + """ + Use precise diffing for tracking changes in single line. + """ if before == '': for change in self._get_diff(line_number, line_number+1, [after]): yield change @@ -108,6 +172,10 @@ class VimBufferProxy(_vim.VimBuffer): yield (change[0], line_number, change[2], change[3]) def _apply_change(self, change): + """ + Apply changeset to current snippets stack, correctly moving around + snippet itself or its child. + """ if not self._snippets_stack: return @@ -128,7 +196,15 @@ class VimBufferProxy(_vim.VimBuffer): self._snippets_stack[0]._do_edit(change) def _disable_edits(self): + """ + Temporary disable applying changes to snippets stack. Should be done + while expanding anonymous snippet in the middle of jump to prevent + double tracking. + """ self._forward_edits = False def _enable_edits(self): + """ + Enables changes forwarding back. + """ self._forward_edits = True