4
"""Base classes for all text objects."""
6
from UltiSnips import _vim
7
from UltiSnips.position import Position
9
def _calc_end(text, start):
10
"""Calculate the end position of the 'text' starting at 'start."""
12
new_end = start + Position(0, len(text[0]))
14
new_end = Position(start.line + len(text)-1, len(text[-1]))
17
def _text_to_vim(start, end, text):
18
"""Copy the given text to the current buffer, overwriting the span 'start'
20
lines = text.split('\n')
22
new_end = _calc_end(lines, start)
24
before = _vim.buf[start.line][:start.col]
25
after = _vim.buf[end.line][end.col:]
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
34
# Open any folds this might have created
35
_vim.buf.cursor = start
36
_vim.command("normal! zv")
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."""
46
def __init__(self, parent, token, end=None,
47
initial_text="", tiebreaker=None):
50
if end is not None: # Took 4 arguments
53
self._initial_text = initial_text
54
else: # Initialize from token
55
self._start = token.start
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)
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)
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
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
85
ct = self.current_text
89
return "%s(%r->%r,%r)" % (self.__class__.__name__,
90
self._start, self._end, ct)
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]
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)
105
"""The start position."""
110
"""The end position."""
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
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:
124
self._end = _text_to_vim(
125
self._start, self._end, gtext or self._initial_text)
127
self._parent._child_has_moved(
128
self._parent._children.index(self), min(old_end, self._end),
129
self._end.delta(old_end)
132
def _update(self, done):
133
"""Update this object inside the Vim Buffer.
135
Return False if you need to be called again for this edit cycle.
136
Otherwise return True.
138
raise NotImplementedError("Must be implemented by subclasses.")
140
class EditableTextObject(TextObject):
142
This base class represents any object in the text
143
that can be changed by the user
145
def __init__(self, *args, **kwargs):
146
TextObject.__init__(self, *args, **kwargs)
155
"""List of all children."""
156
return self._children
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)]
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)
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)
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)):
193
elif ((child._start <= pos <= child._end) and
194
isinstance(child, EditableTextObject)):
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):
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
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))
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))
233
for child in to_kill:
234
self._del_child(child)
236
for child in new_cmds:
240
# We have to handle this ourselves
241
delta = Position(1, 0) if text == "\n" else Position(0, len(text))
243
# Makes no sense to delete in empty textobject
244
if self._start == self._end:
248
pivot = Position(line, col)
250
for cidx, child in enumerate(self._children):
251
if child._start < pivot <= child._end:
253
self._child_has_moved(idx, pivot, delta)
255
def _move(self, pivot, diff):
256
TextObject._move(self, pivot, diff)
258
for child in self._children:
259
child._move(pivot, diff)
261
def _child_has_moved(self, idx, pivot, diff):
262
"""Called when a the child with 'idx' has moved behind 'pivot' by
264
self._end.move(pivot, diff)
266
for child in self._children[idx+1:]:
267
child._move(pivot, diff)
270
self._parent._child_has_moved(
271
self._parent._children.index(self), pivot, diff
274
def _get_next_tab(self, number):
275
"""Returns the next tabstop after 'number'."""
276
if not len(self._tabstops.keys()):
278
tno_max = max(self._tabstops.keys())
283
if i in self._tabstops:
284
possible_sol.append((i, self._tabstops[i]))
288
child = [c._get_next_tab(number) for c in self._editable_children]
289
child = [c for c in child if c]
291
possible_sol += child
293
if not len(possible_sol):
296
return min(possible_sol)
299
def _get_prev_tab(self, number):
300
"""Returns the previous tabstop before 'number'."""
301
if not len(self._tabstops.keys()):
303
tno_min = min(self._tabstops.keys())
307
while i >= tno_min and i > 0:
308
if i in self._tabstops:
309
possible_sol.append((i, self._tabstops[i]))
313
child = [c._get_prev_tab(number) for c in self._editable_children]
314
child = [c for c in child if c]
316
possible_sol += child
318
if not len(possible_sol):
321
return max(possible_sol)
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:
331
rv = child._get_tabstop(self, number)
334
if self._parent and requester is not self._parent:
335
return self._parent._get_tabstop(self, number)
337
def _update(self, done):
338
if all((child in done) for child in self._children):
339
assert self not in done
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()
348
def _del_child(self, child):
349
"""Delete this 'child'."""
351
self._children.remove(child)
353
# If this is a tabstop, delete it
355
del self._tabstops[child.number]
356
except AttributeError:
359
class NoneditableTextObject(TextObject):
360
"""All passive text objects that the user can't edit by hand."""
361
def _update(self, done):