4
"""Implements TabStop transformations."""
9
from UltiSnips.text import unescape, fill_in_whitespace
10
from UltiSnips.text_objects._mirror import Mirror
12
def _find_closing_brace(string, start_pos):
13
"""Finds the corresponding closing brace after start_pos."""
15
for idx, char in enumerate(string[start_pos:]):
17
if string[idx+start_pos-1] != '\\':
20
if string[idx+start_pos-1] != '\\':
23
return start_pos+idx+1
25
def _split_conditional(string):
26
"""Split the given conditional 'string' into its arguments."""
30
for idx, char in enumerate(string):
32
if string[idx-1] != '\\':
35
if string[idx-1] != '\\':
37
elif char == ':' and not bracks_open and not string[idx-1] == '\\':
45
def _replace_conditional(match, string):
46
"""Replaces a conditional match in a transformation."""
47
conditional_match = _CONDITIONAL.search(string)
48
while conditional_match:
49
start = conditional_match.start()
50
end = _find_closing_brace(string, start+4)
51
args = _split_conditional(string[start+4:end-1])
53
if match.group(int(conditional_match.group(1))):
54
rv = unescape(_replace_conditional(match, args[0]))
56
rv = unescape(_replace_conditional(match, args[1]))
57
string = string[:start] + rv + string[end:]
58
conditional_match = _CONDITIONAL.search(string)
61
_ONE_CHAR_CASE_SWITCH = re.compile(r"\\([ul].)", re.DOTALL)
62
_LONG_CASEFOLDINGS = re.compile(r"\\([UL].*?)\\E", re.DOTALL)
63
_DOLLAR = re.compile(r"\$(\d+)", re.DOTALL)
64
_CONDITIONAL = re.compile(r"\(\?(\d+):", re.DOTALL)
65
class _CleverReplace(object):
66
"""Mimics TextMates replace syntax."""
68
def __init__(self, expression):
69
self._expression = expression
71
def replace(self, match):
72
"""Replaces 'match' through the correct replacement string."""
73
transformed = self._expression
74
# Replace all $? with capture groups
75
transformed = _DOLLAR.subn(
76
lambda m: match.group(int(m.group(1))), transformed)[0]
78
# Replace Case switches
79
def _one_char_case_change(match):
80
"""Replaces one character case changes."""
81
if match.group(1)[0] == 'u':
82
return match.group(1)[-1].upper()
84
return match.group(1)[-1].lower()
85
transformed = _ONE_CHAR_CASE_SWITCH.subn(
86
_one_char_case_change, transformed)[0]
88
def _multi_char_case_change(match):
89
"""Replaces multi character case changes."""
90
if match.group(1)[0] == 'U':
91
return match.group(1)[1:].upper()
93
return match.group(1)[1:].lower()
94
transformed = _LONG_CASEFOLDINGS.subn(
95
_multi_char_case_change, transformed)[0]
96
transformed = _replace_conditional(match, transformed)
97
return unescape(fill_in_whitespace(transformed))
99
# flag used to display only one time the lack of unidecode
100
UNIDECODE_ALERT_RAISED = False
101
class TextObjectTransformation(object):
102
"""Base class for Transformations and ${VISUAL}."""
104
def __init__(self, token):
105
self._convert_to_ascii = False
108
if token.search is None:
112
self._match_this_many = 1
114
if "g" in token.options:
115
self._match_this_many = 0
116
if "i" in token.options:
117
flags |= re.IGNORECASE
118
if "a" in token.options:
119
self._convert_to_ascii = True
121
self._find = re.compile(token.search, flags | re.DOTALL)
122
self._replace = _CleverReplace(token.replace)
124
def _transform(self, text):
125
"""Do the actual transform on the given text."""
126
global UNIDECODE_ALERT_RAISED # pylint:disable=global-statement
127
if self._convert_to_ascii:
130
text = unidecode.unidecode(text)
131
except Exception: # pylint:disable=broad-except
132
if UNIDECODE_ALERT_RAISED == False:
133
UNIDECODE_ALERT_RAISED = True
135
"Please install unidecode python package in order to "
136
"be able to make ascii conversions.\n")
137
if self._find is None:
139
return self._find.subn(
140
self._replace.replace, text, self._match_this_many)[0]
142
class Transformation(Mirror, TextObjectTransformation):
143
"""See module docstring."""
145
def __init__(self, parent, ts, token):
146
Mirror.__init__(self, parent, ts, token)
147
TextObjectTransformation.__init__(self, token)
150
return self._transform(self._ts.current_text)