1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
#!/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)
|