File: transformation.py

package info (click to toggle)
vim-ultisnips 3.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,924 kB
  • sloc: python: 8,353; sh: 64; makefile: 38
file content (178 lines) | stat: -rw-r--r-- 5,656 bytes parent folder | download | duplicates (2)
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)