PageRenderTime 35ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/Plugins/UltiSnips/Plugin/pythonx/UltiSnips/text_objects/_base.py

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