You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.9 KiB
Python
152 lines
4.9 KiB
Python
8 years ago
|
#!/usr/bin/env python
|
||
|
# encoding: utf-8
|
||
|
|
||
|
"""A Snippet instance is an instance of a Snippet Definition.
|
||
|
|
||
|
That is, when the user expands a snippet, a SnippetInstance is created
|
||
|
to keep track of the corresponding TextObjects. The Snippet itself is
|
||
|
also a TextObject.
|
||
|
|
||
|
"""
|
||
|
|
||
|
from UltiSnips import _vim
|
||
|
from UltiSnips.position import Position
|
||
|
from UltiSnips.text_objects._base import EditableTextObject, \
|
||
|
NoneditableTextObject
|
||
|
from UltiSnips.text_objects._tabstop import TabStop
|
||
|
|
||
|
|
||
|
class SnippetInstance(EditableTextObject):
|
||
|
|
||
|
"""See module docstring."""
|
||
|
# pylint:disable=protected-access
|
||
|
|
||
|
def __init__(self, snippet, parent, initial_text,
|
||
|
start, end, visual_content, last_re, globals, context):
|
||
|
if start is None:
|
||
|
start = Position(0, 0)
|
||
|
if end is None:
|
||
|
end = Position(0, 0)
|
||
|
self.snippet = snippet
|
||
|
self._cts = 0
|
||
|
|
||
|
self.context = context
|
||
|
self.locals = {'match': last_re, 'context': context}
|
||
|
self.globals = globals
|
||
|
self.visual_content = visual_content
|
||
|
|
||
|
EditableTextObject.__init__(self, parent, start, end, initial_text)
|
||
|
|
||
|
def replace_initial_text(self):
|
||
|
"""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()
|
||
|
if isinstance(obj, EditableTextObject):
|
||
|
for child in obj._children:
|
||
|
_place_initial_text(child)
|
||
|
_place_initial_text(self)
|
||
|
|
||
|
def replay_user_edits(self, cmds, ctab=None):
|
||
|
"""Replay the edits the user has done to keep endings of our Text
|
||
|
objects in sync with reality."""
|
||
|
for cmd in cmds:
|
||
|
self._do_edit(cmd, ctab)
|
||
|
|
||
|
def update_textobjects(self):
|
||
|
"""Update the text objects that should change automagically after the
|
||
|
users edits have been replayed.
|
||
|
|
||
|
This might also move the Cursor
|
||
|
|
||
|
"""
|
||
|
vc = _VimCursor(self)
|
||
|
done = set()
|
||
|
not_done = set()
|
||
|
|
||
|
def _find_recursive(obj):
|
||
|
"""Finds all text objects and puts them into 'not_done'."""
|
||
|
if isinstance(obj, EditableTextObject):
|
||
|
for child in obj._children:
|
||
|
_find_recursive(child)
|
||
|
not_done.add(obj)
|
||
|
_find_recursive(self)
|
||
|
|
||
|
counter = 10
|
||
|
while (done != not_done) and counter:
|
||
|
# Order matters for python locals!
|
||
|
for obj in sorted(not_done - done):
|
||
|
if obj._update(done):
|
||
|
done.add(obj)
|
||
|
counter -= 1
|
||
|
if not counter:
|
||
|
raise RuntimeError(
|
||
|
'The snippets content did not converge: Check for Cyclic '
|
||
|
'dependencies or random strings in your snippet. You can use '
|
||
|
"'if not snip.c' to make sure to only expand random output "
|
||
|
'once.')
|
||
|
vc.to_vim()
|
||
|
self._del_child(vc)
|
||
|
|
||
|
def select_next_tab(self, backwards=False):
|
||
|
"""Selects the next tabstop or the previous if 'backwards' is True."""
|
||
|
if self._cts is None:
|
||
|
return
|
||
|
|
||
|
if backwards:
|
||
|
cts_bf = self._cts
|
||
|
|
||
|
res = self._get_prev_tab(self._cts)
|
||
|
if res is None:
|
||
|
self._cts = cts_bf
|
||
|
return self._tabstops.get(self._cts, None)
|
||
|
self._cts, ts = res
|
||
|
return ts
|
||
|
else:
|
||
|
res = self._get_next_tab(self._cts)
|
||
|
if res is None:
|
||
|
self._cts = None
|
||
|
|
||
|
ts = self._get_tabstop(self, 0)
|
||
|
if ts:
|
||
|
return ts
|
||
|
|
||
|
# TabStop 0 was deleted. It was probably killed through some
|
||
|
# edit action. Recreate it at the end of us.
|
||
|
start = Position(self.end.line, self.end.col)
|
||
|
end = Position(self.end.line, self.end.col)
|
||
|
return TabStop(self, 0, start, end)
|
||
|
else:
|
||
|
self._cts, ts = res
|
||
|
return ts
|
||
|
|
||
|
return self._tabstops[self._cts]
|
||
|
|
||
|
def _get_tabstop(self, requester, no):
|
||
|
# SnippetInstances are completely self contained, therefore, we do not
|
||
|
# need to ask our parent for Tabstops
|
||
|
cached_parent = self._parent
|
||
|
self._parent = None
|
||
|
rv = EditableTextObject._get_tabstop(self, requester, no)
|
||
|
self._parent = cached_parent
|
||
|
return rv
|
||
|
|
||
|
def get_tabstops(self):
|
||
|
return self._tabstops
|
||
|
|
||
|
|
||
|
class _VimCursor(NoneditableTextObject):
|
||
|
|
||
|
"""Helper class to keep track of the Vim Cursor when text objects expand
|
||
|
and move."""
|
||
|
|
||
|
def __init__(self, parent):
|
||
|
NoneditableTextObject.__init__(
|
||
|
self, parent, _vim.buf.cursor, _vim.buf.cursor,
|
||
|
tiebreaker=Position(-1, -1))
|
||
|
|
||
|
def to_vim(self):
|
||
|
"""Moves the cursor in the Vim to our position."""
|
||
|
assert self._start == self._end
|
||
|
_vim.buf.cursor = self._start
|