Fixed vim and zsh

This commit is contained in:
2018-04-05 13:06:54 +02:00
parent f9db886bd3
commit 0331f6518a
2009 changed files with 256303 additions and 0 deletions

View File

@ -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

View 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

View File

@ -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.
"""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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