Fixed vim and zsh
This commit is contained in:
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""Public facing classes for TextObjects."""
|
||||
|
||||
from UltiSnips.text_objects._escaped_char import EscapedChar
|
||||
from UltiSnips.text_objects._mirror import Mirror
|
||||
from UltiSnips.text_objects._python_code import PythonCode
|
||||
from UltiSnips.text_objects._shell_code import ShellCode
|
||||
from UltiSnips.text_objects._snippet_instance import SnippetInstance
|
||||
from UltiSnips.text_objects._tabstop import TabStop
|
||||
from UltiSnips.text_objects._transformation import Transformation
|
||||
from UltiSnips.text_objects._viml_code import VimLCode
|
||||
from UltiSnips.text_objects._visual import Visual
|
386
vim/plugins/ultisnips/pythonx/UltiSnips/text_objects/_base.py
Normal file
386
vim/plugins/ultisnips/pythonx/UltiSnips/text_objects/_base.py
Normal file
@ -0,0 +1,386 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""Base classes for all text objects."""
|
||||
|
||||
from UltiSnips import _vim
|
||||
from UltiSnips.position import Position
|
||||
|
||||
|
||||
def _calc_end(text, start):
|
||||
"""Calculate the end position of the 'text' starting at 'start."""
|
||||
if len(text) == 1:
|
||||
new_end = start + Position(0, len(text[0]))
|
||||
else:
|
||||
new_end = Position(start.line + len(text) - 1, len(text[-1]))
|
||||
return new_end
|
||||
|
||||
|
||||
def _text_to_vim(start, end, text):
|
||||
"""Copy the given text to the current buffer, overwriting the span 'start'
|
||||
to 'end'."""
|
||||
lines = text.split('\n')
|
||||
|
||||
new_end = _calc_end(lines, start)
|
||||
|
||||
before = _vim.buf[start.line][:start.col]
|
||||
after = _vim.buf[end.line][end.col:]
|
||||
|
||||
new_lines = []
|
||||
if len(lines):
|
||||
new_lines.append(before + lines[0])
|
||||
new_lines.extend(lines[1:])
|
||||
new_lines[-1] += after
|
||||
_vim.buf[start.line:end.line + 1] = new_lines
|
||||
|
||||
# Open any folds this might have created
|
||||
_vim.buf.cursor = start
|
||||
_vim.command('normal! zv')
|
||||
|
||||
return new_end
|
||||
|
||||
# These classes use their subclasses a lot and we really do not want to expose
|
||||
# their functions more globally.
|
||||
# pylint: disable=protected-access
|
||||
|
||||
|
||||
class TextObject(object):
|
||||
|
||||
"""Represents any object in the text that has a span in any ways."""
|
||||
|
||||
def __init__(self, parent, token_or_start, end=None,
|
||||
initial_text='', tiebreaker=None):
|
||||
self._parent = parent
|
||||
|
||||
if end is not None: # Took 4 arguments
|
||||
self._start = token_or_start
|
||||
self._end = end
|
||||
self._initial_text = initial_text
|
||||
else: # Initialize from token
|
||||
self._start = token_or_start.start
|
||||
self._end = token_or_start.end
|
||||
self._initial_text = token_or_start.initial_text
|
||||
self._tiebreaker = tiebreaker or Position(
|
||||
self._start.line, self._end.line)
|
||||
if parent is not None:
|
||||
parent._add_child(self)
|
||||
|
||||
def _move(self, pivot, diff):
|
||||
"""Move this object by 'diff' while 'pivot' is the point of change."""
|
||||
self._start.move(pivot, diff)
|
||||
self._end.move(pivot, diff)
|
||||
|
||||
def __lt__(self, other):
|
||||
me_tuple = (self.start.line, self.start.col,
|
||||
self._tiebreaker.line, self._tiebreaker.col)
|
||||
other_tuple = (other._start.line, other._start.col,
|
||||
other._tiebreaker.line, other._tiebreaker.col)
|
||||
return me_tuple < other_tuple
|
||||
|
||||
def __le__(self, other):
|
||||
me_tuple = (self._start.line, self._start.col,
|
||||
self._tiebreaker.line, self._tiebreaker.col)
|
||||
other_tuple = (other._start.line, other._start.col,
|
||||
other._tiebreaker.line, other._tiebreaker.col)
|
||||
return me_tuple <= other_tuple
|
||||
|
||||
def __repr__(self):
|
||||
ct = ''
|
||||
try:
|
||||
ct = self.current_text
|
||||
except IndexError:
|
||||
ct = '<err>'
|
||||
|
||||
return '%s(%r->%r,%r)' % (self.__class__.__name__,
|
||||
self._start, self._end, ct)
|
||||
|
||||
@property
|
||||
def current_text(self):
|
||||
"""The current text of this object."""
|
||||
if self._start.line == self._end.line:
|
||||
return _vim.buf[self._start.line][self._start.col:self._end.col]
|
||||
else:
|
||||
lines = [_vim.buf[self._start.line][self._start.col:]]
|
||||
lines.extend(_vim.buf[self._start.line + 1:self._end.line])
|
||||
lines.append(_vim.buf[self._end.line][:self._end.col])
|
||||
return '\n'.join(lines)
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
"""The start position."""
|
||||
return self._start
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
"""The end position."""
|
||||
return self._end
|
||||
|
||||
def overwrite(self, gtext=None):
|
||||
"""Overwrite the text of this object in the Vim Buffer and update its
|
||||
length information.
|
||||
|
||||
If 'gtext' is None use the initial text of this object.
|
||||
|
||||
"""
|
||||
# We explicitly do not want to move our children around here as we
|
||||
# either have non or we are replacing text initially which means we do
|
||||
# not want to mess with their positions
|
||||
if self.current_text == gtext:
|
||||
return
|
||||
old_end = self._end
|
||||
self._end = _text_to_vim(
|
||||
self._start, self._end, gtext or self._initial_text)
|
||||
if self._parent:
|
||||
self._parent._child_has_moved(
|
||||
self._parent._children.index(self), min(old_end, self._end),
|
||||
self._end.delta(old_end)
|
||||
)
|
||||
|
||||
def _update(self, done):
|
||||
"""Update this object inside the Vim Buffer.
|
||||
|
||||
Return False if you need to be called again for this edit cycle.
|
||||
Otherwise return True.
|
||||
|
||||
"""
|
||||
raise NotImplementedError('Must be implemented by subclasses.')
|
||||
|
||||
|
||||
class EditableTextObject(TextObject):
|
||||
|
||||
"""This base class represents any object in the text that can be changed by
|
||||
the user."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
TextObject.__init__(self, *args, **kwargs)
|
||||
self._children = []
|
||||
self._tabstops = {}
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
@property
|
||||
def children(self):
|
||||
"""List of all children."""
|
||||
return self._children
|
||||
|
||||
@property
|
||||
def _editable_children(self):
|
||||
"""List of all children that are EditableTextObjects."""
|
||||
return [child for child in self._children if
|
||||
isinstance(child, EditableTextObject)]
|
||||
|
||||
####################
|
||||
# Public Functions #
|
||||
####################
|
||||
def find_parent_for_new_to(self, pos):
|
||||
"""Figure out the parent object for something at 'pos'."""
|
||||
for children in self._editable_children:
|
||||
if children._start <= pos < children._end:
|
||||
return children.find_parent_for_new_to(pos)
|
||||
if children._start == pos and pos == children._end:
|
||||
return children.find_parent_for_new_to(pos)
|
||||
return self
|
||||
|
||||
###############################
|
||||
# Private/Protected functions #
|
||||
###############################
|
||||
def _do_edit(self, cmd, ctab=None):
|
||||
"""Apply the edit 'cmd' to this object."""
|
||||
ctype, line, col, text = cmd
|
||||
assert ('\n' not in text) or (text == '\n')
|
||||
pos = Position(line, col)
|
||||
|
||||
to_kill = set()
|
||||
new_cmds = []
|
||||
for child in self._children:
|
||||
if ctype == 'I': # Insertion
|
||||
if (child._start < pos <
|
||||
Position(child._end.line, child._end.col) and
|
||||
isinstance(child, NoneditableTextObject)):
|
||||
to_kill.add(child)
|
||||
new_cmds.append(cmd)
|
||||
break
|
||||
elif ((child._start <= pos <= child._end) and
|
||||
isinstance(child, EditableTextObject)):
|
||||
if pos == child.end and not child.children:
|
||||
try:
|
||||
if ctab.number != child.number:
|
||||
continue
|
||||
except AttributeError:
|
||||
pass
|
||||
child._do_edit(cmd, ctab)
|
||||
return
|
||||
else: # Deletion
|
||||
delend = pos + Position(0, len(text)) if text != '\n' \
|
||||
else Position(line + 1, 0)
|
||||
if ((child._start <= pos < child._end) and
|
||||
(child._start < delend <= child._end)):
|
||||
# this edit command is completely for the child
|
||||
if isinstance(child, NoneditableTextObject):
|
||||
to_kill.add(child)
|
||||
new_cmds.append(cmd)
|
||||
break
|
||||
else:
|
||||
child._do_edit(cmd, ctab)
|
||||
return
|
||||
elif ((pos < child._start and child._end <= delend and
|
||||
child.start < delend) or
|
||||
(pos <= child._start and child._end < delend)):
|
||||
# Case: this deletion removes the child
|
||||
to_kill.add(child)
|
||||
new_cmds.append(cmd)
|
||||
break
|
||||
elif (pos < child._start and
|
||||
(child._start < delend <= child._end)):
|
||||
# Case: partially for us, partially for the child
|
||||
my_text = text[:(child._start - pos).col]
|
||||
c_text = text[(child._start - pos).col:]
|
||||
new_cmds.append((ctype, line, col, my_text))
|
||||
new_cmds.append((ctype, line, col, c_text))
|
||||
break
|
||||
elif (delend >= child._end and (
|
||||
child._start <= pos < child._end)):
|
||||
# Case: partially for us, partially for the child
|
||||
c_text = text[(child._end - pos).col:]
|
||||
my_text = text[:(child._end - pos).col]
|
||||
new_cmds.append((ctype, line, col, c_text))
|
||||
new_cmds.append((ctype, line, col, my_text))
|
||||
break
|
||||
|
||||
for child in to_kill:
|
||||
self._del_child(child)
|
||||
if len(new_cmds):
|
||||
for child in new_cmds:
|
||||
self._do_edit(child)
|
||||
return
|
||||
|
||||
# We have to handle this ourselves
|
||||
delta = Position(1, 0) if text == '\n' else Position(0, len(text))
|
||||
if ctype == 'D':
|
||||
# Makes no sense to delete in empty textobject
|
||||
if self._start == self._end:
|
||||
return
|
||||
delta.line *= -1
|
||||
delta.col *= -1
|
||||
pivot = Position(line, col)
|
||||
idx = -1
|
||||
for cidx, child in enumerate(self._children):
|
||||
if child._start < pivot <= child._end:
|
||||
idx = cidx
|
||||
self._child_has_moved(idx, pivot, delta)
|
||||
|
||||
def _move(self, pivot, diff):
|
||||
TextObject._move(self, pivot, diff)
|
||||
|
||||
for child in self._children:
|
||||
child._move(pivot, diff)
|
||||
|
||||
def _child_has_moved(self, idx, pivot, diff):
|
||||
"""Called when a the child with 'idx' has moved behind 'pivot' by
|
||||
'diff'."""
|
||||
self._end.move(pivot, diff)
|
||||
|
||||
for child in self._children[idx + 1:]:
|
||||
child._move(pivot, diff)
|
||||
|
||||
if self._parent:
|
||||
self._parent._child_has_moved(
|
||||
self._parent._children.index(self), pivot, diff
|
||||
)
|
||||
|
||||
def _get_next_tab(self, number):
|
||||
"""Returns the next tabstop after 'number'."""
|
||||
if not len(self._tabstops.keys()):
|
||||
return
|
||||
tno_max = max(self._tabstops.keys())
|
||||
|
||||
possible_sol = []
|
||||
i = number + 1
|
||||
while i <= tno_max:
|
||||
if i in self._tabstops:
|
||||
possible_sol.append((i, self._tabstops[i]))
|
||||
break
|
||||
i += 1
|
||||
|
||||
child = [c._get_next_tab(number) for c in self._editable_children]
|
||||
child = [c for c in child if c]
|
||||
|
||||
possible_sol += child
|
||||
|
||||
if not len(possible_sol):
|
||||
return None
|
||||
|
||||
return min(possible_sol)
|
||||
|
||||
def _get_prev_tab(self, number):
|
||||
"""Returns the previous tabstop before 'number'."""
|
||||
if not len(self._tabstops.keys()):
|
||||
return
|
||||
tno_min = min(self._tabstops.keys())
|
||||
|
||||
possible_sol = []
|
||||
i = number - 1
|
||||
while i >= tno_min and i > 0:
|
||||
if i in self._tabstops:
|
||||
possible_sol.append((i, self._tabstops[i]))
|
||||
break
|
||||
i -= 1
|
||||
|
||||
child = [c._get_prev_tab(number) for c in self._editable_children]
|
||||
child = [c for c in child if c]
|
||||
|
||||
possible_sol += child
|
||||
|
||||
if not len(possible_sol):
|
||||
return None
|
||||
|
||||
return max(possible_sol)
|
||||
|
||||
def _get_tabstop(self, requester, number):
|
||||
"""Returns the tabstop 'number'.
|
||||
|
||||
'requester' is the class that is interested in this.
|
||||
|
||||
"""
|
||||
if number in self._tabstops:
|
||||
return self._tabstops[number]
|
||||
for child in self._editable_children:
|
||||
if child is requester:
|
||||
continue
|
||||
rv = child._get_tabstop(self, number)
|
||||
if rv is not None:
|
||||
return rv
|
||||
if self._parent and requester is not self._parent:
|
||||
return self._parent._get_tabstop(self, number)
|
||||
|
||||
def _update(self, done):
|
||||
if all((child in done) for child in self._children):
|
||||
assert self not in done
|
||||
done.add(self)
|
||||
return True
|
||||
|
||||
def _add_child(self, child):
|
||||
"""Add 'child' as a new child of this text object."""
|
||||
self._children.append(child)
|
||||
self._children.sort()
|
||||
|
||||
def _del_child(self, child):
|
||||
"""Delete this 'child'."""
|
||||
child._parent = None
|
||||
self._children.remove(child)
|
||||
|
||||
# If this is a tabstop, delete it. Might have been deleted already if
|
||||
# it was nested.
|
||||
try:
|
||||
del self._tabstops[child.number]
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
|
||||
class NoneditableTextObject(TextObject):
|
||||
|
||||
"""All passive text objects that the user can't edit by hand."""
|
||||
|
||||
def _update(self, done):
|
||||
return True
|
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""See module comment."""
|
||||
|
||||
from UltiSnips.text_objects._base import NoneditableTextObject
|
||||
|
||||
|
||||
class EscapedChar(NoneditableTextObject):
|
||||
|
||||
r"""
|
||||
This class is a escape char like \$. It is handled in a text object to make
|
||||
sure that siblings are correctly moved after replacing the text.
|
||||
|
||||
This is a base class without functionality just to mark it in the code.
|
||||
"""
|
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""A Mirror object contains the same text as its related tabstop."""
|
||||
|
||||
from UltiSnips.text_objects._base import NoneditableTextObject
|
||||
|
||||
|
||||
class Mirror(NoneditableTextObject):
|
||||
|
||||
"""See module docstring."""
|
||||
|
||||
def __init__(self, parent, tabstop, token):
|
||||
NoneditableTextObject.__init__(self, parent, token)
|
||||
self._ts = tabstop
|
||||
|
||||
def _update(self, done):
|
||||
if self._ts.is_killed:
|
||||
self.overwrite('')
|
||||
self._parent._del_child(self) # pylint:disable=protected-access
|
||||
return True
|
||||
|
||||
if self._ts not in done:
|
||||
return False
|
||||
|
||||
self.overwrite(self._get_text())
|
||||
return True
|
||||
|
||||
def _get_text(self):
|
||||
"""Returns the text used for mirroring.
|
||||
|
||||
Overwritten by base classes.
|
||||
|
||||
"""
|
||||
return self._ts.current_text
|
@ -0,0 +1,321 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""Implements `!p ` interpolation."""
|
||||
|
||||
import os
|
||||
from collections import namedtuple
|
||||
|
||||
from UltiSnips import _vim
|
||||
from UltiSnips.compatibility import as_unicode
|
||||
from UltiSnips.indent_util import IndentUtil
|
||||
from UltiSnips.text_objects._base import NoneditableTextObject
|
||||
from UltiSnips.vim_state import _Placeholder
|
||||
import UltiSnips.snippet_manager
|
||||
|
||||
|
||||
class _Tabs(object):
|
||||
|
||||
"""Allows access to tabstop content via t[] inside of python code."""
|
||||
|
||||
def __init__(self, to):
|
||||
self._to = to
|
||||
|
||||
def __getitem__(self, no):
|
||||
ts = self._to._get_tabstop(
|
||||
self._to,
|
||||
int(no)) # pylint:disable=protected-access
|
||||
if ts is None:
|
||||
return ''
|
||||
return ts.current_text
|
||||
|
||||
def __setitem__(self, no, value):
|
||||
ts = self._to._get_tabstop(
|
||||
self._to,
|
||||
int(no)) # pylint:disable=protected-access
|
||||
if ts is None:
|
||||
return
|
||||
ts.overwrite(value)
|
||||
|
||||
_VisualContent = namedtuple('_VisualContent', ['mode', 'text'])
|
||||
|
||||
|
||||
class SnippetUtilForAction(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SnippetUtilForAction, self).__init__(*args, **kwargs)
|
||||
self.__dict__ = self
|
||||
|
||||
def expand_anon(self, *args, **kwargs):
|
||||
UltiSnips.snippet_manager.UltiSnips_Manager.expand_anon(
|
||||
*args, **kwargs
|
||||
)
|
||||
self.cursor.preserve()
|
||||
|
||||
|
||||
class SnippetUtilCursor(object):
|
||||
def __init__(self, cursor):
|
||||
self._cursor = [cursor[0] - 1, cursor[1]]
|
||||
self._set = False
|
||||
|
||||
def preserve(self):
|
||||
self._set = True
|
||||
self._cursor = [
|
||||
_vim.buf.cursor[0],
|
||||
_vim.buf.cursor[1],
|
||||
]
|
||||
|
||||
def is_set(self):
|
||||
return self._set
|
||||
|
||||
def set(self, line, column):
|
||||
self.__setitem__(0, line)
|
||||
self.__setitem__(1, column)
|
||||
|
||||
def to_vim_cursor(self):
|
||||
return (self._cursor[0] + 1, self._cursor[1])
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._cursor[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
self._set = True
|
||||
self._cursor[index] = value
|
||||
|
||||
def __len__(self):
|
||||
return 2
|
||||
|
||||
def __str__(self):
|
||||
return str((self._cursor[0], self._cursor[1]))
|
||||
|
||||
|
||||
class SnippetUtil(object):
|
||||
|
||||
"""Provides easy access to indentation, etc.
|
||||
|
||||
This is the 'snip' object in python code.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, initial_indent, vmode, vtext, context, parent):
|
||||
self._ind = IndentUtil()
|
||||
self._visual = _VisualContent(vmode, vtext)
|
||||
self._initial_indent = self._ind.indent_to_spaces(initial_indent)
|
||||
self._reset('')
|
||||
self._context = context
|
||||
self._start = parent.start
|
||||
self._end = parent.end
|
||||
self._parent = parent
|
||||
|
||||
def _reset(self, cur):
|
||||
"""Gets the snippet ready for another update.
|
||||
|
||||
:cur: the new value for c.
|
||||
|
||||
"""
|
||||
self._ind.reset()
|
||||
self._cur = cur
|
||||
self._rv = ''
|
||||
self._changed = False
|
||||
self.reset_indent()
|
||||
|
||||
def shift(self, amount=1):
|
||||
"""Shifts the indentation level. Note that this uses the shiftwidth
|
||||
because thats what code formatters use.
|
||||
|
||||
:amount: the amount by which to shift.
|
||||
|
||||
"""
|
||||
self.indent += ' ' * self._ind.shiftwidth * amount
|
||||
|
||||
def unshift(self, amount=1):
|
||||
"""Unshift the indentation level. Note that this uses the shiftwidth
|
||||
because thats what code formatters use.
|
||||
|
||||
:amount: the amount by which to unshift.
|
||||
|
||||
"""
|
||||
by = -self._ind.shiftwidth * amount
|
||||
try:
|
||||
self.indent = self.indent[:by]
|
||||
except IndexError:
|
||||
self.indent = ''
|
||||
|
||||
def mkline(self, line='', indent=None):
|
||||
"""Creates a properly set up line.
|
||||
|
||||
:line: the text to add
|
||||
:indent: the indentation to have at the beginning
|
||||
if None, it uses the default amount
|
||||
|
||||
"""
|
||||
if indent is None:
|
||||
indent = self.indent
|
||||
# this deals with the fact that the first line is
|
||||
# already properly indented
|
||||
if '\n' not in self._rv:
|
||||
try:
|
||||
indent = indent[len(self._initial_indent):]
|
||||
except IndexError:
|
||||
indent = ''
|
||||
indent = self._ind.spaces_to_indent(indent)
|
||||
|
||||
return indent + line
|
||||
|
||||
def reset_indent(self):
|
||||
"""Clears the indentation."""
|
||||
self.indent = self._initial_indent
|
||||
|
||||
# Utility methods
|
||||
@property
|
||||
def fn(self): # pylint:disable=no-self-use,invalid-name
|
||||
"""The filename."""
|
||||
return _vim.eval('expand("%:t")') or ''
|
||||
|
||||
@property
|
||||
def basename(self): # pylint:disable=no-self-use
|
||||
"""The filename without extension."""
|
||||
return _vim.eval('expand("%:t:r")') or ''
|
||||
|
||||
@property
|
||||
def ft(self): # pylint:disable=invalid-name
|
||||
"""The filetype."""
|
||||
return self.opt('&filetype', '')
|
||||
|
||||
@property
|
||||
def rv(self): # pylint:disable=invalid-name
|
||||
"""The return value.
|
||||
|
||||
The text to insert at the location of the placeholder.
|
||||
|
||||
"""
|
||||
return self._rv
|
||||
|
||||
@rv.setter
|
||||
def rv(self, value): # pylint:disable=invalid-name
|
||||
"""See getter."""
|
||||
self._changed = True
|
||||
self._rv = value
|
||||
|
||||
@property
|
||||
def _rv_changed(self):
|
||||
"""True if rv has changed."""
|
||||
return self._changed
|
||||
|
||||
@property
|
||||
def c(self): # pylint:disable=invalid-name
|
||||
"""The current text of the placeholder."""
|
||||
return self._cur
|
||||
|
||||
@property
|
||||
def v(self): # pylint:disable=invalid-name
|
||||
"""Content of visual expansions."""
|
||||
return self._visual
|
||||
|
||||
@property
|
||||
def p(self):
|
||||
if self._parent.current_placeholder:
|
||||
return self._parent.current_placeholder
|
||||
else:
|
||||
return _Placeholder('', 0, 0)
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return self._context
|
||||
|
||||
def opt(self, option, default=None): # pylint:disable=no-self-use
|
||||
"""Gets a Vim variable."""
|
||||
if _vim.eval("exists('%s')" % option) == '1':
|
||||
try:
|
||||
return _vim.eval(option)
|
||||
except _vim.error:
|
||||
pass
|
||||
return default
|
||||
|
||||
def __add__(self, value):
|
||||
"""Appends the given line to rv using mkline."""
|
||||
self.rv += '\n' # pylint:disable=invalid-name
|
||||
self.rv += self.mkline(value)
|
||||
return self
|
||||
|
||||
def __lshift__(self, other):
|
||||
"""Same as unshift."""
|
||||
self.unshift(other)
|
||||
|
||||
def __rshift__(self, other):
|
||||
"""Same as shift."""
|
||||
self.shift(other)
|
||||
|
||||
@property
|
||||
def snippet_start(self):
|
||||
"""
|
||||
Returns start of the snippet in format (line, column).
|
||||
"""
|
||||
return self._start
|
||||
|
||||
@property
|
||||
def snippet_end(self):
|
||||
"""
|
||||
Returns end of the snippet in format (line, column).
|
||||
"""
|
||||
return self._end
|
||||
|
||||
@property
|
||||
def buffer(self):
|
||||
return _vim.buf
|
||||
|
||||
|
||||
class PythonCode(NoneditableTextObject):
|
||||
|
||||
"""See module docstring."""
|
||||
|
||||
def __init__(self, parent, token):
|
||||
|
||||
# Find our containing snippet for snippet local data
|
||||
snippet = parent
|
||||
while snippet:
|
||||
try:
|
||||
self._locals = snippet.locals
|
||||
text = snippet.visual_content.text
|
||||
mode = snippet.visual_content.mode
|
||||
context = snippet.context
|
||||
break
|
||||
except AttributeError as e:
|
||||
snippet = snippet._parent # pylint:disable=protected-access
|
||||
self._snip = SnippetUtil(token.indent, mode, text, context, snippet)
|
||||
|
||||
self._codes = ((
|
||||
'import re, os, vim, string, random',
|
||||
'\n'.join(snippet.globals.get('!p', [])).replace('\r\n', '\n'),
|
||||
token.code.replace('\\`', '`')
|
||||
))
|
||||
NoneditableTextObject.__init__(self, parent, token)
|
||||
|
||||
def _update(self, done):
|
||||
path = _vim.eval('expand("%")') or ''
|
||||
ct = self.current_text
|
||||
self._locals.update({
|
||||
't': _Tabs(self._parent),
|
||||
'fn': os.path.basename(path),
|
||||
'path': path,
|
||||
'cur': ct,
|
||||
'res': ct,
|
||||
'snip': self._snip,
|
||||
})
|
||||
self._snip._reset(ct) # pylint:disable=protected-access
|
||||
|
||||
for code in self._codes:
|
||||
try:
|
||||
exec(code, self._locals) # pylint:disable=exec-used
|
||||
except Exception as e:
|
||||
e.snippet_code = code
|
||||
raise
|
||||
|
||||
rv = as_unicode(
|
||||
self._snip.rv if self._snip._rv_changed # pylint:disable=protected-access
|
||||
else as_unicode(self._locals['res'])
|
||||
)
|
||||
|
||||
if ct != rv:
|
||||
self.overwrite(rv)
|
||||
return False
|
||||
return True
|
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""Implements `echo hi` shell code interpolation."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
from subprocess import Popen, PIPE
|
||||
import stat
|
||||
import tempfile
|
||||
|
||||
from UltiSnips.compatibility import as_unicode
|
||||
from UltiSnips.text_objects._base import NoneditableTextObject
|
||||
|
||||
|
||||
def _chomp(string):
|
||||
"""Rather than rstrip(), remove only the last newline and preserve
|
||||
purposeful whitespace."""
|
||||
if len(string) and string[-1] == '\n':
|
||||
string = string[:-1]
|
||||
if len(string) and string[-1] == '\r':
|
||||
string = string[:-1]
|
||||
return string
|
||||
|
||||
|
||||
def _run_shell_command(cmd, tmpdir):
|
||||
"""Write the code to a temporary file."""
|
||||
cmdsuf = ''
|
||||
if platform.system() == 'Windows':
|
||||
# suffix required to run command on windows
|
||||
cmdsuf = '.bat'
|
||||
# turn echo off
|
||||
cmd = '@echo off\r\n' + cmd
|
||||
handle, path = tempfile.mkstemp(text=True, dir=tmpdir, suffix=cmdsuf)
|
||||
os.write(handle, cmd.encode('utf-8'))
|
||||
os.close(handle)
|
||||
os.chmod(path, stat.S_IRWXU)
|
||||
|
||||
# Execute the file and read stdout
|
||||
proc = Popen(path, shell=True, stdout=PIPE, stderr=PIPE)
|
||||
proc.wait()
|
||||
stdout, _ = proc.communicate()
|
||||
os.unlink(path)
|
||||
return _chomp(as_unicode(stdout))
|
||||
|
||||
|
||||
def _get_tmp():
|
||||
"""Find an executable tmp directory."""
|
||||
userdir = os.path.expanduser('~')
|
||||
for testdir in [tempfile.gettempdir(), os.path.join(userdir, '.cache'),
|
||||
os.path.join(userdir, '.tmp'), userdir]:
|
||||
if (not os.path.exists(testdir) or
|
||||
not _run_shell_command('echo success', testdir) == 'success'):
|
||||
continue
|
||||
return testdir
|
||||
return ''
|
||||
|
||||
|
||||
class ShellCode(NoneditableTextObject):
|
||||
|
||||
"""See module docstring."""
|
||||
|
||||
def __init__(self, parent, token):
|
||||
NoneditableTextObject.__init__(self, parent, token)
|
||||
self._code = token.code.replace('\\`', '`')
|
||||
self._tmpdir = _get_tmp()
|
||||
|
||||
def _update(self, done):
|
||||
if not self._tmpdir:
|
||||
output = \
|
||||
'Unable to find executable tmp directory, check noexec on /tmp'
|
||||
else:
|
||||
output = _run_shell_command(self._code, self._tmpdir)
|
||||
self.overwrite(output)
|
||||
self._parent._del_child(self) # pylint:disable=protected-access
|
||||
return True
|
@ -0,0 +1,152 @@
|
||||
#!/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
|
||||
self.current_placeholder = None
|
||||
|
||||
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
|
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""This is the most important TextObject.
|
||||
|
||||
A TabStop is were the cursor comes to rest when the user taps through
|
||||
the Snippet.
|
||||
|
||||
"""
|
||||
|
||||
from UltiSnips.text_objects._base import EditableTextObject
|
||||
|
||||
|
||||
class TabStop(EditableTextObject):
|
||||
|
||||
"""See module docstring."""
|
||||
|
||||
def __init__(self, parent, token, start=None, end=None):
|
||||
if start is not None:
|
||||
self._number = token
|
||||
EditableTextObject.__init__(self, parent, start, end)
|
||||
else:
|
||||
self._number = token.number
|
||||
EditableTextObject.__init__(self, parent, token)
|
||||
parent._tabstops[
|
||||
self._number] = self # pylint:disable=protected-access
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
"""The tabstop number."""
|
||||
return self._number
|
||||
|
||||
@property
|
||||
def is_killed(self):
|
||||
"""True if this tabstop has been typed over and the user therefore can
|
||||
no longer jump to it."""
|
||||
return self._parent is None
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
text = self.current_text
|
||||
except IndexError:
|
||||
text = '<err>'
|
||||
return 'TabStop(%s,%r->%r,%r)' % (self.number, self._start,
|
||||
self._end, text)
|
@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""Implements TabStop transformations."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
from UltiSnips.text import unescape, fill_in_whitespace
|
||||
from UltiSnips.text_objects._mirror import Mirror
|
||||
|
||||
|
||||
def _find_closing_brace(string, start_pos):
|
||||
"""Finds the corresponding closing brace after start_pos."""
|
||||
bracks_open = 1
|
||||
escaped = False
|
||||
for idx, char in enumerate(string[start_pos:]):
|
||||
if char == '(':
|
||||
if not escaped:
|
||||
bracks_open += 1
|
||||
elif char == ')':
|
||||
if not escaped:
|
||||
bracks_open -= 1
|
||||
if not bracks_open:
|
||||
return start_pos + idx + 1
|
||||
if char == '\\':
|
||||
escaped = not escaped
|
||||
else:
|
||||
escaped = False
|
||||
|
||||
|
||||
def _split_conditional(string):
|
||||
"""Split the given conditional 'string' into its arguments."""
|
||||
bracks_open = 0
|
||||
args = []
|
||||
carg = ''
|
||||
escaped = False
|
||||
for idx, char in enumerate(string):
|
||||
if char == '(':
|
||||
if not escaped:
|
||||
bracks_open += 1
|
||||
elif char == ')':
|
||||
if not escaped:
|
||||
bracks_open -= 1
|
||||
elif char == ':' and not bracks_open and not escaped:
|
||||
args.append(carg)
|
||||
carg = ''
|
||||
escaped = False
|
||||
continue
|
||||
carg += char
|
||||
if char == '\\':
|
||||
escaped = not escaped
|
||||
else:
|
||||
escaped = False
|
||||
args.append(carg)
|
||||
return args
|
||||
|
||||
|
||||
def _replace_conditional(match, string):
|
||||
"""Replaces a conditional match in a transformation."""
|
||||
conditional_match = _CONDITIONAL.search(string)
|
||||
while conditional_match:
|
||||
start = conditional_match.start()
|
||||
end = _find_closing_brace(string, start + 4)
|
||||
args = _split_conditional(string[start + 4:end - 1])
|
||||
rv = ''
|
||||
if match.group(int(conditional_match.group(1))):
|
||||
rv = unescape(_replace_conditional(match, args[0]))
|
||||
elif len(args) > 1:
|
||||
rv = unescape(_replace_conditional(match, args[1]))
|
||||
string = string[:start] + rv + string[end:]
|
||||
conditional_match = _CONDITIONAL.search(string)
|
||||
return string
|
||||
|
||||
_ONE_CHAR_CASE_SWITCH = re.compile(r"\\([ul].)", re.DOTALL)
|
||||
_LONG_CASEFOLDINGS = re.compile(r"\\([UL].*?)\\E", re.DOTALL)
|
||||
_DOLLAR = re.compile(r"\$(\d+)", re.DOTALL)
|
||||
_CONDITIONAL = re.compile(r"\(\?(\d+):", re.DOTALL)
|
||||
|
||||
|
||||
class _CleverReplace(object):
|
||||
|
||||
"""Mimics TextMates replace syntax."""
|
||||
|
||||
def __init__(self, expression):
|
||||
self._expression = expression
|
||||
|
||||
def replace(self, match):
|
||||
"""Replaces 'match' through the correct replacement string."""
|
||||
transformed = self._expression
|
||||
# Replace all $? with capture groups
|
||||
transformed = _DOLLAR.subn(
|
||||
lambda m: match.group(int(m.group(1))), transformed)[0]
|
||||
|
||||
# Replace Case switches
|
||||
def _one_char_case_change(match):
|
||||
"""Replaces one character case changes."""
|
||||
if match.group(1)[0] == 'u':
|
||||
return match.group(1)[-1].upper()
|
||||
else:
|
||||
return match.group(1)[-1].lower()
|
||||
transformed = _ONE_CHAR_CASE_SWITCH.subn(
|
||||
_one_char_case_change, transformed)[0]
|
||||
|
||||
def _multi_char_case_change(match):
|
||||
"""Replaces multi character case changes."""
|
||||
if match.group(1)[0] == 'U':
|
||||
return match.group(1)[1:].upper()
|
||||
else:
|
||||
return match.group(1)[1:].lower()
|
||||
transformed = _LONG_CASEFOLDINGS.subn(
|
||||
_multi_char_case_change, transformed)[0]
|
||||
transformed = _replace_conditional(match, transformed)
|
||||
return unescape(fill_in_whitespace(transformed))
|
||||
|
||||
# flag used to display only one time the lack of unidecode
|
||||
UNIDECODE_ALERT_RAISED = False
|
||||
|
||||
|
||||
class TextObjectTransformation(object):
|
||||
|
||||
"""Base class for Transformations and ${VISUAL}."""
|
||||
|
||||
def __init__(self, token):
|
||||
self._convert_to_ascii = False
|
||||
|
||||
self._find = None
|
||||
if token.search is None:
|
||||
return
|
||||
|
||||
flags = 0
|
||||
self._match_this_many = 1
|
||||
if token.options:
|
||||
if 'g' in token.options:
|
||||
self._match_this_many = 0
|
||||
if 'i' in token.options:
|
||||
flags |= re.IGNORECASE
|
||||
if 'm' in token.options:
|
||||
flags |= re.MULTILINE
|
||||
if 'a' in token.options:
|
||||
self._convert_to_ascii = True
|
||||
|
||||
self._find = re.compile(token.search, flags | re.DOTALL)
|
||||
self._replace = _CleverReplace(token.replace)
|
||||
|
||||
def _transform(self, text):
|
||||
"""Do the actual transform on the given text."""
|
||||
global UNIDECODE_ALERT_RAISED # pylint:disable=global-statement
|
||||
if self._convert_to_ascii:
|
||||
try:
|
||||
import unidecode
|
||||
text = unidecode.unidecode(text)
|
||||
except Exception: # pylint:disable=broad-except
|
||||
if UNIDECODE_ALERT_RAISED == False:
|
||||
UNIDECODE_ALERT_RAISED = True
|
||||
sys.stderr.write(
|
||||
'Please install unidecode python package in order to '
|
||||
'be able to make ascii conversions.\n')
|
||||
if self._find is None:
|
||||
return text
|
||||
return self._find.subn(
|
||||
self._replace.replace, text, self._match_this_many)[0]
|
||||
|
||||
|
||||
class Transformation(Mirror, TextObjectTransformation):
|
||||
|
||||
"""See module docstring."""
|
||||
|
||||
def __init__(self, parent, ts, token):
|
||||
Mirror.__init__(self, parent, ts, token)
|
||||
TextObjectTransformation.__init__(self, token)
|
||||
|
||||
def _get_text(self):
|
||||
return self._transform(self._ts.current_text)
|
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""Implements `!v ` VimL interpolation."""
|
||||
|
||||
from UltiSnips import _vim
|
||||
from UltiSnips.text_objects._base import NoneditableTextObject
|
||||
|
||||
|
||||
class VimLCode(NoneditableTextObject):
|
||||
|
||||
"""See module docstring."""
|
||||
|
||||
def __init__(self, parent, token):
|
||||
self._code = token.code.replace('\\`', '`').strip()
|
||||
|
||||
NoneditableTextObject.__init__(self, parent, token)
|
||||
|
||||
def _update(self, done):
|
||||
self.overwrite(_vim.eval(self._code))
|
||||
return True
|
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
"""A ${VISUAL} placeholder that will use the text that was last visually
|
||||
selected and insert it here.
|
||||
|
||||
If there was no text visually selected, this will be the empty string.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
from UltiSnips import _vim
|
||||
from UltiSnips.indent_util import IndentUtil
|
||||
from UltiSnips.text_objects._transformation import TextObjectTransformation
|
||||
from UltiSnips.text_objects._base import NoneditableTextObject
|
||||
|
||||
_REPLACE_NON_WS = re.compile(r"[^ \t]")
|
||||
|
||||
|
||||
class Visual(NoneditableTextObject, TextObjectTransformation):
|
||||
|
||||
"""See module docstring."""
|
||||
|
||||
def __init__(self, parent, token):
|
||||
# Find our containing snippet for visual_content
|
||||
snippet = parent
|
||||
while snippet:
|
||||
try:
|
||||
self._text = snippet.visual_content.text
|
||||
self._mode = snippet.visual_content.mode
|
||||
break
|
||||
except AttributeError:
|
||||
snippet = snippet._parent # pylint:disable=protected-access
|
||||
if not self._text:
|
||||
self._text = token.alternative_text
|
||||
self._mode = 'v'
|
||||
|
||||
NoneditableTextObject.__init__(self, parent, token)
|
||||
TextObjectTransformation.__init__(self, token)
|
||||
|
||||
def _update(self, done):
|
||||
if self._mode == 'v': # Normal selection.
|
||||
text = self._text
|
||||
else: # Block selection or line selection.
|
||||
text_before = _vim.buf[self.start.line][:self.start.col]
|
||||
indent = _REPLACE_NON_WS.sub(' ', text_before)
|
||||
iu = IndentUtil()
|
||||
indent = iu.indent_to_spaces(indent)
|
||||
indent = iu.spaces_to_indent(indent)
|
||||
text = ''
|
||||
for idx, line in enumerate(textwrap.dedent(
|
||||
self._text).splitlines(True)):
|
||||
if idx != 0:
|
||||
text += indent
|
||||
text += line
|
||||
text = text[:-1] # Strip final '\n'
|
||||
|
||||
text = self._transform(text)
|
||||
self.overwrite(text)
|
||||
self._parent._del_child(self) # pylint:disable=protected-access
|
||||
|
||||
return True
|
Reference in New Issue
Block a user