~ubuntu-branches/ubuntu/vivid/vim-ultisnips/vivid

« back to all changes in this revision

Viewing changes to pythonx/UltiSnips/text_objects/_base.py

  • Committer: Package Import Robot
  • Author(s): Michael Fladischer
  • Date: 2014-10-12 18:11:54 UTC
  • Revision ID: package-import@ubuntu.com-20141012181154-1jeoj467dh2l5f2e
Tags: upstream-3.0
ImportĀ upstreamĀ versionĀ 3.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# encoding: utf-8
 
3
 
 
4
"""Base classes for all text objects."""
 
5
 
 
6
from UltiSnips import _vim
 
7
from UltiSnips.position import Position
 
8
 
 
9
def _calc_end(text, start):
 
10
    """Calculate the end position of the 'text' starting at 'start."""
 
11
    if len(text) == 1:
 
12
        new_end = start + Position(0, len(text[0]))
 
13
    else:
 
14
        new_end = Position(start.line + len(text)-1, len(text[-1]))
 
15
    return new_end
 
16
 
 
17
def _text_to_vim(start, end, text):
 
18
    """Copy the given text to the current buffer, overwriting the span 'start'
 
19
    to 'end'."""
 
20
    lines = text.split('\n')
 
21
 
 
22
    new_end = _calc_end(lines, start)
 
23
 
 
24
    before = _vim.buf[start.line][:start.col]
 
25
    after = _vim.buf[end.line][end.col:]
 
26
 
 
27
    new_lines = []
 
28
    if len(lines):
 
29
        new_lines.append(before + lines[0])
 
30
        new_lines.extend(lines[1:])
 
31
        new_lines[-1] += after
 
32
    _vim.buf[start.line:end.line + 1] = new_lines
 
33
 
 
34
    # Open any folds this might have created
 
35
    _vim.buf.cursor = start
 
36
    _vim.command("normal! zv")
 
37
 
 
38
    return new_end
 
39
 
 
40
# These classes use their subclasses a lot and we really do not want to expose
 
41
# their functions more globally.
 
42
# pylint: disable=protected-access
 
43
class TextObject(object):
 
44
    """Represents any object in the text that has a span in any ways."""
 
45
 
 
46
    def __init__(self, parent, token, end=None,
 
47
            initial_text="", tiebreaker=None):
 
48
        self._parent = parent
 
49
 
 
50
        if end is not None: # Took 4 arguments
 
51
            self._start = token
 
52
            self._end = end
 
53
            self._initial_text = initial_text
 
54
        else: # Initialize from token
 
55
            self._start = token.start
 
56
            self._end = token.end
 
57
            self._initial_text = token.initial_text
 
58
        self._tiebreaker = tiebreaker or Position(
 
59
                self._start.line, self._end.line)
 
60
        if parent is not None:
 
61
            parent._add_child(self)
 
62
 
 
63
    def _move(self, pivot, diff):
 
64
        """Move this object by 'diff' while 'pivot' is the point of change."""
 
65
        self._start.move(pivot, diff)
 
66
        self._end.move(pivot, diff)
 
67
 
 
68
    def __lt__(self, other):
 
69
        me_tuple = (self.start.line, self.start.col,
 
70
                self._tiebreaker.line, self._tiebreaker.col)
 
71
        other_tuple = (other._start.line, other._start.col,
 
72
                other._tiebreaker.line, other._tiebreaker.col)
 
73
        return me_tuple < other_tuple
 
74
 
 
75
    def __le__(self, other):
 
76
        me_tuple = (self._start.line, self._start.col,
 
77
                self._tiebreaker.line, self._tiebreaker.col)
 
78
        other_tuple = (other._start.line, other._start.col,
 
79
                other._tiebreaker.line, other._tiebreaker.col)
 
80
        return me_tuple <= other_tuple
 
81
 
 
82
    def __repr__(self):
 
83
        ct = ""
 
84
        try:
 
85
            ct = self.current_text
 
86
        except IndexError:
 
87
            ct = "<err>"
 
88
 
 
89
        return "%s(%r->%r,%r)" % (self.__class__.__name__,
 
90
                self._start, self._end, ct)
 
91
 
 
92
    @property
 
93
    def current_text(self):
 
94
        """The current text of this object."""
 
95
        if self._start.line == self._end.line:
 
96
            return _vim.buf[self._start.line][self._start.col:self._end.col]
 
97
        else:
 
98
            lines = [_vim.buf[self._start.line][self._start.col:]]
 
99
            lines.extend(_vim.buf[self._start.line+1:self._end.line])
 
100
            lines.append(_vim.buf[self._end.line][:self._end.col])
 
101
            return '\n'.join(lines)
 
102
 
 
103
    @property
 
104
    def start(self):
 
105
        """The start position."""
 
106
        return self._start
 
107
 
 
108
    @property
 
109
    def end(self):
 
110
        """The end position."""
 
111
        return self._end
 
112
 
 
113
    def overwrite(self, gtext=None):
 
114
        """Overwrite the text of this object in the Vim Buffer and update its
 
115
        length information. If 'gtext' is None use the initial text of this
 
116
        object.
 
117
        """
 
118
        # We explicitly do not want to move our children around here as we
 
119
        # either have non or we are replacing text initially which means we do
 
120
        # not want to mess with their positions
 
121
        if self.current_text == gtext:
 
122
            return
 
123
        old_end = self._end
 
124
        self._end = _text_to_vim(
 
125
                self._start, self._end, gtext or self._initial_text)
 
126
        if self._parent:
 
127
            self._parent._child_has_moved(
 
128
                self._parent._children.index(self), min(old_end, self._end),
 
129
                self._end.delta(old_end)
 
130
            )
 
131
 
 
132
    def _update(self, done):
 
133
        """Update this object inside the Vim Buffer.
 
134
 
 
135
        Return False if you need to be called again for this edit cycle.
 
136
        Otherwise return True.
 
137
        """
 
138
        raise NotImplementedError("Must be implemented by subclasses.")
 
139
 
 
140
class EditableTextObject(TextObject):
 
141
    """
 
142
    This base class represents any object in the text
 
143
    that can be changed by the user
 
144
    """
 
145
    def __init__(self, *args, **kwargs):
 
146
        TextObject.__init__(self, *args, **kwargs)
 
147
        self._children = []
 
148
        self._tabstops = {}
 
149
 
 
150
    ##############
 
151
    # Properties #
 
152
    ##############
 
153
    @property
 
154
    def children(self):
 
155
        """List of all children."""
 
156
        return self._children
 
157
 
 
158
    @property
 
159
    def _editable_children(self):
 
160
        """List of all children that are EditableTextObjects"""
 
161
        return [child for child in self._children if
 
162
                isinstance(child, EditableTextObject)]
 
163
 
 
164
    ####################
 
165
    # Public Functions #
 
166
    ####################
 
167
    def find_parent_for_new_to(self, pos):
 
168
        """Figure out the parent object for something at 'pos'."""
 
169
        for children in self._editable_children:
 
170
            if children._start <= pos < children._end:
 
171
                return children.find_parent_for_new_to(pos)
 
172
        return self
 
173
 
 
174
    ###############################
 
175
    # Private/Protected functions #
 
176
    ###############################
 
177
    def _do_edit(self, cmd):
 
178
        """Apply the edit 'cmd' to this object."""
 
179
        ctype, line, col, text = cmd
 
180
        assert ('\n' not in text) or (text == "\n")
 
181
        pos = Position(line, col)
 
182
 
 
183
        to_kill = set()
 
184
        new_cmds = []
 
185
        for child in self._children:
 
186
            if ctype == "I": # Insertion
 
187
                if (child._start < pos <
 
188
                        Position(child._end.line, child._end.col) and
 
189
                        isinstance(child, NoneditableTextObject)):
 
190
                    to_kill.add(child)
 
191
                    new_cmds.append(cmd)
 
192
                    break
 
193
                elif ((child._start <= pos <= child._end) and
 
194
                        isinstance(child, EditableTextObject)):
 
195
                    child._do_edit(cmd)
 
196
                    return
 
197
            else: # Deletion
 
198
                delend = pos + Position(0, len(text)) if text != "\n" \
 
199
                        else Position(line + 1, 0)
 
200
                if ((child._start <= pos < child._end) and
 
201
                        (child._start < delend <= child._end)):
 
202
                    # this edit command is completely for the child
 
203
                    if isinstance(child, NoneditableTextObject):
 
204
                        to_kill.add(child)
 
205
                        new_cmds.append(cmd)
 
206
                        break
 
207
                    else:
 
208
                        child._do_edit(cmd)
 
209
                        return
 
210
                elif ((pos < child._start and child._end <= delend) or
 
211
                        (pos <= child._start and child._end < delend)):
 
212
                    # Case: this deletion removes the child
 
213
                    to_kill.add(child)
 
214
                    new_cmds.append(cmd)
 
215
                    break
 
216
                elif (pos < child._start and
 
217
                        (child._start < delend <= child._end)):
 
218
                    # Case: partially for us, partially for the child
 
219
                    my_text = text[:(child._start-pos).col]
 
220
                    c_text = text[(child._start-pos).col:]
 
221
                    new_cmds.append((ctype, line, col, my_text))
 
222
                    new_cmds.append((ctype, line, col, c_text))
 
223
                    break
 
224
                elif (delend >= child._end and (
 
225
                        child._start <= pos < child._end)):
 
226
                    # Case: partially for us, partially for the child
 
227
                    c_text = text[(child._end-pos).col:]
 
228
                    my_text = text[:(child._end-pos).col]
 
229
                    new_cmds.append((ctype, line, col, c_text))
 
230
                    new_cmds.append((ctype, line, col, my_text))
 
231
                    break
 
232
 
 
233
        for child in to_kill:
 
234
            self._del_child(child)
 
235
        if len(new_cmds):
 
236
            for child in new_cmds:
 
237
                self._do_edit(child)
 
238
            return
 
239
 
 
240
        # We have to handle this ourselves
 
241
        delta = Position(1, 0) if text == "\n" else Position(0, len(text))
 
242
        if ctype == "D":
 
243
             # Makes no sense to delete in empty textobject
 
244
            if self._start == self._end:
 
245
                return
 
246
            delta.line *= -1
 
247
            delta.col *= -1
 
248
        pivot = Position(line, col)
 
249
        idx = -1
 
250
        for cidx, child in enumerate(self._children):
 
251
            if child._start < pivot <= child._end:
 
252
                idx = cidx
 
253
        self._child_has_moved(idx, pivot, delta)
 
254
 
 
255
    def _move(self, pivot, diff):
 
256
        TextObject._move(self, pivot, diff)
 
257
 
 
258
        for child in self._children:
 
259
            child._move(pivot, diff)
 
260
 
 
261
    def _child_has_moved(self, idx, pivot, diff):
 
262
        """Called when a the child with 'idx' has moved behind 'pivot' by
 
263
        'diff'."""
 
264
        self._end.move(pivot, diff)
 
265
 
 
266
        for child in self._children[idx+1:]:
 
267
            child._move(pivot, diff)
 
268
 
 
269
        if self._parent:
 
270
            self._parent._child_has_moved(
 
271
                self._parent._children.index(self), pivot, diff
 
272
            )
 
273
 
 
274
    def _get_next_tab(self, number):
 
275
        """Returns the next tabstop after 'number'."""
 
276
        if not len(self._tabstops.keys()):
 
277
            return
 
278
        tno_max = max(self._tabstops.keys())
 
279
 
 
280
        possible_sol = []
 
281
        i = number + 1
 
282
        while i <= tno_max:
 
283
            if i in self._tabstops:
 
284
                possible_sol.append((i, self._tabstops[i]))
 
285
                break
 
286
            i += 1
 
287
 
 
288
        child = [c._get_next_tab(number) for c in self._editable_children]
 
289
        child = [c for c in child if c]
 
290
 
 
291
        possible_sol += child
 
292
 
 
293
        if not len(possible_sol):
 
294
            return None
 
295
 
 
296
        return min(possible_sol)
 
297
 
 
298
 
 
299
    def _get_prev_tab(self, number):
 
300
        """Returns the previous tabstop before 'number'."""
 
301
        if not len(self._tabstops.keys()):
 
302
            return
 
303
        tno_min = min(self._tabstops.keys())
 
304
 
 
305
        possible_sol = []
 
306
        i = number - 1
 
307
        while i >= tno_min and i > 0:
 
308
            if i in self._tabstops:
 
309
                possible_sol.append((i, self._tabstops[i]))
 
310
                break
 
311
            i -= 1
 
312
 
 
313
        child = [c._get_prev_tab(number) for c in self._editable_children]
 
314
        child = [c for c in child if c]
 
315
 
 
316
        possible_sol += child
 
317
 
 
318
        if not len(possible_sol):
 
319
            return None
 
320
 
 
321
        return max(possible_sol)
 
322
 
 
323
    def _get_tabstop(self, requester, number):
 
324
        """Returns the tabstop 'number'. 'requester' is the class that is
 
325
        interested in this."""
 
326
        if number in self._tabstops:
 
327
            return self._tabstops[number]
 
328
        for child in self._editable_children:
 
329
            if child is requester:
 
330
                continue
 
331
            rv = child._get_tabstop(self, number)
 
332
            if rv is not None:
 
333
                return rv
 
334
        if self._parent and requester is not self._parent:
 
335
            return self._parent._get_tabstop(self, number)
 
336
 
 
337
    def _update(self, done):
 
338
        if all((child in done) for child in self._children):
 
339
            assert self not in done
 
340
            done.add(self)
 
341
        return True
 
342
 
 
343
    def _add_child(self, child):
 
344
        """Add 'child' as a new child of this text object."""
 
345
        self._children.append(child)
 
346
        self._children.sort()
 
347
 
 
348
    def _del_child(self, child):
 
349
        """Delete this 'child'."""
 
350
        child._parent = None
 
351
        self._children.remove(child)
 
352
 
 
353
        # If this is a tabstop, delete it
 
354
        try:
 
355
            del self._tabstops[child.number]
 
356
        except AttributeError:
 
357
            pass
 
358
 
 
359
class NoneditableTextObject(TextObject):
 
360
    """All passive text objects that the user can't edit by hand."""
 
361
    def _update(self, done):
 
362
        return True