Completed
Push — master ( 1dc3dc...4daebe )
by Lambda
01:07
created

Key.__repr__()   B

Complexity

Conditions 6

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
c 1
b 0
f 0
dl 0
loc 10
rs 8
1
"""Key module."""
2
from curses import ascii  # type: ignore
3
from collections import namedtuple
4
from typing import cast, Union, Tuple, Dict     # noqa: F401
5
from neovim import Nvim
6
from .util import ensure_bytes, ensure_str, int2chr
7
8
9
KeyCode = Union[int, bytes]
10
KeyExpr = Union[KeyCode, str]
11
12
13
ESCAPE_QUOTE = str.maketrans({
14
    '"': '\\"',
15
})
16
17
CTRL_KEY = b'\x80\xfc\x04'
18
META_KEY = b'\x80\xfc\x08'
19
20
# https://github.com/vim/vim/blob/d58b0f982ad758c59abe47627216a15497e9c3c1/src/gui_w32.c#L389-L456
21
SPECIAL_KEYS = {k: getattr(ascii, k) for k in ascii.controlnames}
22
SPECIAL_KEYS.update(dict(
23
    BSLASH=ord('\\'),
24
    LT=ord('<'),
25
    UP=b'\x80ku',
26
    DOWN=b'\x80kd',
27
    LEFT=b'\x80kl',
28
    RIGHT=b'\x80kr',
29
    F1=b'\x80k1',
30
    F2=b'\x80k2',
31
    F3=b'\x80k3',
32
    F4=b'\x80k4',
33
    F5=b'\x80k5',
34
    F6=b'\x80k6',
35
    F7=b'\x80k7',
36
    F8=b'\x80k8',
37
    F9=b'\x80k9',
38
    F10=b'\x80k;',
39
    F11=b'\x80F1',
40
    F12=b'\x80F2',
41
    F13=b'\x80F3',
42
    F14=b'\x80F4',
43
    F15=b'\x80F5',
44
    F16=b'\x80F6',
45
    F17=b'\x80F7',
46
    F18=b'\x80F8',
47
    F19=b'\x80F9',
48
    F20=b'\x80FA',
49
    F21=b'\x80FB',
50
    F22=b'\x80FC',
51
    F23=b'\x80FD',
52
    F24=b'\x80FE',
53
    HELP=b'\x80%1',
54
    BACKSPACE=b'\x80kb',
55
    INSERT=b'\x80kI',
56
    DELETE=b'\x80kD',
57
    HOME=b'\x80kh',
58
    END=b'\x80@7',
59
    PAGEUP=b'\x80kP',
60
    PAGEDOWN=b'\x80kN',
61
))
62
# Add aliases used in Vim. This requires to be AFTER making swap dictionary
63
SPECIAL_KEYS.update(dict(
64
    NOP=SPECIAL_KEYS['NUL'],
65
    RETURN=SPECIAL_KEYS['CR'],
66
    ENTER=SPECIAL_KEYS['CR'],
67
    SPACE=SPECIAL_KEYS['SP'],
68
    BS=SPECIAL_KEYS['BACKSPACE'],
69
    INS=SPECIAL_KEYS['INSERT'],
70
    DEL=SPECIAL_KEYS['DELETE'],
71
))
72
73
74
KeyBase = namedtuple('KeyBase', ['code', 'char'])
75
76
77
class Key(KeyBase):
78
    """Key class which indicate a single key.
79
80
    Attributes:
81
        code (int or bytes): A code of the key. A bytes is used when the key is
82
            a special key in Vim (a key which starts from 0x80 in getchar()).
83
        char (str): A printable represantation of the key. It might be an empty
84
            string when the key is not printable.
85
    """
86
87
    __slots__ = ()  # type: Tuple[str, ...]
88
    __cached = {}   # type: Dict[KeyExpr, Key]
89
90
    def __str__(self) -> str:
91
        return self.char
92
93
    def __repr__(self) -> str:
94
        if isinstance(self.code, bytes) and self.code.startswith('\x80'):
95
            if self.code.startswith(CTRL_KEY):
96
                pass
97
            elif self.code.startswith(META_KEY):
98
                pass
99
            elif self.code in SPECIAL_KEYS_SWAP:
100
                pass
101
            pass
102
        return self.char
103
104
    @classmethod
105
    def parse(cls, nvim: Nvim, expr: KeyExpr) -> 'Key':
106
        """Parse a key expression and return a Key instance.
107
108
        It returns a Key instance of a key expression. The instance is cached
109
        to individual expression so that the instance is exactly equal when
110
        same expression is spcified.
111
112
        Args:
113
            expr (int, bytes, or str): A key expression.
114
115
        Returns:
116
            Key: A Key instance.
117
        """
118
        if expr not in cls.__cached:
119
            code = _resolve(nvim, expr)
120
            if isinstance(code, int):
121
                char = int2chr(nvim, code)
122
            elif not code.startswith(b'\x80'):
123
                char = ensure_str(nvim, code)
124
            else:
125
                char = ''
126
            cls.__cached[expr] = cls(code, char)
127
        return cls.__cached[expr]
128
129
    def __str__(self) -> str:
130
        return self.char
131
132
133
def _resolve(nvim: Nvim, expr: KeyExpr) -> KeyCode:
134
    if isinstance(expr, int):
135
        return expr
136
    elif isinstance(expr, str):
137
        return _resolve(nvim, ensure_bytes(nvim, expr))
138
    elif isinstance(expr, bytes):
139
        if len(expr) == 1:
140
            return ord(expr)
141
        elif expr.startswith(b'\x80'):
142
            return expr
143
    else:
144
        raise AttributeError((
145
            '`expr` (%s) requires to be an instance of int|bytes|str but '
146
            '"%s" has specified.'
147
        ) % (expr, type(expr)))
148
    # Special key
149
    if expr.startswith(b'<') or expr.endswith(b'>'):
150
        inner = expr[1:-1]
151
        code = _resolve_from_special_keys(nvim, inner)
152
        if code != inner:
153
            return code
154
    return expr
155
156
157
def _resolve_from_special_keys(nvim: Nvim, inner: bytes) -> KeyCode:
158
    inner_upper = inner.upper()
159
    inner_upper_str = ensure_str(nvim, inner_upper)
160
    if inner_upper_str in SPECIAL_KEYS:
161
        return SPECIAL_KEYS[inner_upper_str]
162
    elif inner_upper.startswith(b'C-'):
163
        if len(inner) == 3:
164
            if inner_upper[-1] in b'@ABCDEFGHIKLMNOPQRSTUVWXYZ[\\]^_?':
165
                return ascii.ctrl(inner[-1])
166
        return b''.join([
167
            CTRL_KEY,
168
            cast(bytes, _resolve_from_special_keys(nvim, inner[2:])),
169
        ])
170
    elif inner_upper.startswith(b'M-') or inner_upper.startswith(b'A-'):
171
        return b''.join([
172
            META_KEY,
173
            cast(bytes, _resolve_from_special_keys(nvim, inner[2:])),
174
        ])
175
    elif inner_upper == b'LEADER':
176
        leader = nvim.vars['mapleader']
177
        leader = ensure_bytes(nvim, leader)
178
        return _resolve(nvim, leader)
179
    elif inner_upper == b'LOCALLEADER':
180
        leader = nvim.vars['maplocalleader']
181
        leader = ensure_bytes(nvim, leader)
182
        return _resolve(nvim, leader)
183
    return inner
184