_resolve_from_special_keys()   C
last analyzed

Complexity

Conditions 11

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 11
c 6
b 0
f 0
dl 0
loc 32
rs 5.4

How to fix   Complexity   

Complexity

Complex classes like _resolve_from_special_keys() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Key module."""
2
from collections import namedtuple
3
from .util import ensure_bytes, ensure_str, int2char
4
from typing import Dict  # noqa: F401
5
6
7
ESCAPE_QUOTE = str.maketrans({
8
    '"': '\\"',
9
})
10
11
CTRL_KEY = b'\x80\xfc\x04'
12
META_KEY = b'\x80\xfc\x08'
13
CTRL_SHIFT_KEY = b'\x80\xfc\x06'
14
15
# :help key-notation
16
SPECIAL_KEYS = {
17
    'C-@': b'\x80\xffX',    # Vim internally use <80><ff>X for <C-@>
18
    'NUL': 10,
19
    'BS': b'\x80kb',
20
    'TAB': 9,
21
    'S-TAB': b'\x80kB',
22
    'NL': 10,
23
    'FE': 12,
24
    'CR': 13,
25
    'ESC': 27,
26
    'SPACE': 32,
27
    'LT': 60,
28
    'BSLASH': 92,
29
    'BAR': 124,
30
    'DEL': b'\x80kD',
31
    'CSI': b'\x9B',
32
    'XCSI': b'\x80\xfdP',
33
    'UP': b'\x80ku',
34
    'DOWN': b'\x80kd',
35
    'LEFT': b'\x80kl',
36
    'RIGHT': b'\x80kr',
37
    'S-UP': b'\x80\xfd',
38
    'S-DOWN': b'\x80\xfd',
39
    'S-LEFT': b'\x80#4',
40
    'S-RIGHT': b'\x80%i',
41
    'C-LEFT': b'\x80\xfdT',
42
    'C-RIGHT': b'\x80\xfdU',
43
    'F1': b'\x80k1',
44
    'F2': b'\x80k2',
45
    'F3': b'\x80k3',
46
    'F4': b'\x80k4',
47
    'F5': b'\x80k5',
48
    'F6': b'\x80k6',
49
    'F7': b'\x80k7',
50
    'F8': b'\x80k8',
51
    'F9': b'\x80k9',
52
    'F10': b'\x80k;',
53
    'F11': b'\x80F1',
54
    'F12': b'\x80F2',
55
    'S-F1': b'\x80\xfd\x06',
56
    'S-F2': b'\x80\xfd\x07',
57
    'S-F3': b'\x80\xfd\x08',
58
    'S-F4': b'\x80\xfd\x09',
59
    'S-F5': b'\x80\xfd\x0A',
60
    'S-F6': b'\x80\xfd\x0B',
61
    'S-F7': b'\x80\xfd\x0C',
62
    'S-F8': b'\x80\xfd\x0D',
63
    'S-F9': b'\x80\xfd\x0E',
64
    'S-F10': b'\x80\xfd\x0F',
65
    'S-F11': b'\x80\xfd\x10',
66
    'S-F12': b'\x80\xfd\x11',
67
    'HELP': b'\x80%1',
68
    'UNDO': b'\x80&8',
69
    'INSERT': b'\x80kI',
70
    'HOME': b'\x80kh',
71
    'END': b'\x80@7',
72
    'PAGEUP': b'\x80kP',
73
    'PAGEDOWN': b'\x80kN',
74
    'KHOME': b'\x80K1',
75
    'KEND': b'\x80K4',
76
    'KPAGEUP': b'\x80K3',
77
    'KPAGEDOWN': b'\x80K5',
78
    'KPLUS': b'\x80K6',
79
    'KMINUS': b'\x80K7',
80
    'KMULTIPLY': b'\x80K9',
81
    'KDIVIDE': b'\x80K8',
82
    'KENTER': b'\x80KA',
83
    'KPOINT': b'\x80KB',
84
    'K0': b'\x80KC',
85
    'K1': b'\x80KD',
86
    'K2': b'\x80KE',
87
    'K3': b'\x80KF',
88
    'K4': b'\x80KG',
89
    'K5': b'\x80KH',
90
    'K6': b'\x80KI',
91
    'K7': b'\x80KJ',
92
    'K8': b'\x80KK',
93
    'K9': b'\x80KL',
94
}
95
SPECIAL_KEYS_REVRESE = {v: k for k, v in SPECIAL_KEYS.items()}
96
97
# Add aliases used in Vim. This requires to be AFTER making swap dictionary
98
SPECIAL_KEYS.update({
99
    'NOP': SPECIAL_KEYS['NUL'],
100
    'RETURN': SPECIAL_KEYS['CR'],
101
    'ENTER': SPECIAL_KEYS['CR'],
102
    'BACKSPACE': SPECIAL_KEYS['BS'],
103
    'DELETE': SPECIAL_KEYS['DEL'],
104
    'INS': SPECIAL_KEYS['INSERT'],
105
})
106
107
108
KeyBase = namedtuple('KeyBase', ['code', 'char'])
109
110
111
class Key(KeyBase):
112
    """Key class which indicate a single key.
113
114
    Attributes:
115
        code (int or bytes): A code of the key. A bytes is used when the key is
116
            a special key in Vim (a key which starts from 0x80 in getchar()).
117
        char (str): A printable represantation of the key. It might be an empty
118
            string when the key is not printable.
119
    """
120
121
    __slots__ = ()
122
    __cached = {}  # type: Dict[str, Key]
123
124
    def __str__(self):
125
        """Return string representation of the key."""
126
        return self.char
127
128
    @classmethod
129
    def represent(cls, nvim, code):
130
        """Return a string representation of a Keycode."""
131
        if isinstance(code, int):
132
            return int2char(nvim, code)
133
        if code in SPECIAL_KEYS_REVRESE:
134
            char = SPECIAL_KEYS_REVRESE.get(code)
135
            return '<%s>' % char
136
        else:
137
            return ensure_str(nvim, code)
138
139
    @classmethod
140
    def parse(cls, nvim, expr):
141
        r"""Parse a key expression and return a Key instance.
142
143
        It returns a Key instance of a key expression. The instance is cached
144
        to individual expression so that the instance is exactly equal when
145
        same expression is spcified.
146
147
        Args:
148
            expr (int, bytes, or str): A key expression.
149
150
        Example:
151
            >>> from unittest.mock import MagicMock
152
            >>> nvim = MagicMock()
153
            >>> nvim.options = {'encoding': 'utf-8'}
154
            >>> Key.parse(nvim, ord('a'))
155
            Key(code=97, char='a')
156
            >>> Key.parse(nvim, '<Insert>')
157
            Key(code=b'\x80kI', char='')
158
159
        Returns:
160
            Key: A Key instance.
161
        """
162
        if expr not in cls.__cached:
163
            code = _resolve(nvim, expr)
164
            if isinstance(code, int):
165
                char = int2char(nvim, code)
166
            elif not code.startswith(b'\x80'):
167
                char = ensure_str(nvim, code)
168
            else:
169
                char = ''
170
            cls.__cached[expr] = cls(code, char)
171
        return cls.__cached[expr]
172
173
174
def _resolve(nvim, expr):
175
    if isinstance(expr, int):
176
        return expr
177
    elif isinstance(expr, str):
178
        return _resolve(nvim, ensure_bytes(nvim, expr))
179
    elif isinstance(expr, bytes):
180
        if len(expr) == 1:
181
            return ord(expr)
182
        elif expr.startswith(b'\x80'):
183
            return expr
184
    else:
185
        raise AttributeError((
186
            '`expr` (%s) requires to be an instance of int|bytes|str but '
187
            '"%s" has specified.'
188
        ) % (expr, type(expr)))
189
    # Special key
190
    if expr.startswith(b'<') or expr.endswith(b'>'):
191
        inner = expr[1:-1]
192
        code = _resolve_from_special_keys(nvim, inner)
193
        if code != inner:
194
            return code
195
    return expr
196
197
198
def _resolve_from_special_keys(nvim, inner):
199
    inner_upper = inner.upper()
200
    inner_upper_str = ensure_str(nvim, inner_upper)
201
    if inner_upper_str in SPECIAL_KEYS:
202
        return SPECIAL_KEYS[inner_upper_str]
203
    elif inner_upper.startswith(b'C-S-') or inner_upper.startswith(b'S-C-'):
204
        return b''.join([
205
            CTRL_SHIFT_KEY,
206
            _resolve_from_special_keys_inner(nvim, inner[4:]),
207
        ])
208
    elif inner_upper.startswith(b'C-'):
209
        if len(inner) == 3:
210
            if inner_upper[-1] in b'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_?':
211
                return inner[-1] & 0x1f
212
        return b''.join([
213
            CTRL_KEY,
214
            _resolve_from_special_keys_inner(nvim, inner[2:]),
215
        ])
216
    elif inner_upper.startswith(b'M-') or inner_upper.startswith(b'A-'):
217
        return b''.join([
218
            META_KEY,
219
            _resolve_from_special_keys_inner(nvim, inner[2:]),
220
        ])
221
    elif inner_upper == b'LEADER':
222
        leader = nvim.vars.get('mapleader', '\\')
223
        leader = ensure_bytes(nvim, leader)
224
        return _resolve(nvim, leader)
225
    elif inner_upper == b'LOCALLEADER':
226
        leader = nvim.vars.get('maplocalleader', '\\')
227
        leader = ensure_bytes(nvim, leader)
228
        return _resolve(nvim, leader)
229
    return inner
230
231
232
def _resolve_from_special_keys_inner(nvim, inner):
233
    code = _resolve_from_special_keys(nvim, inner)
234
    if isinstance(code, int):
235
        return ensure_bytes(nvim, int2char(nvim, code))
236
    return ensure_bytes(nvim, code)
237