You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
7.6 KiB
Python

#!/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
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
_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):
self._ind = IndentUtil()
self._visual = _VisualContent(vmode, vtext)
self._initial_indent = self._ind.indent_to_spaces(initial_indent)
self._reset('')
self._context = context
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 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)
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:
snippet = snippet._parent # pylint:disable=protected-access
self._snip = SnippetUtil(token.indent, mode, text, context)
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