File: snippet_instance.py

package info (click to toggle)
vim-ultisnips 3.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,924 kB
  • sloc: python: 8,353; sh: 64; makefile: 38
file content (170 lines) | stat: -rw-r--r-- 5,209 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/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_helper
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
        self.current_placeholder = None

        EditableTextObject.__init__(self, parent, start, end, initial_text)

    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(buf)
            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, buf):
        """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, buf):
                    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_helper.buf.cursor,
            vim_helper.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_helper.buf.cursor = self._start