Completed
Push — master ( cfe5b2...6017e5 )
by Lambda
01:17
created

_resolve_from_special_keys_inner()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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