Completed
Push — master ( 58bfc2...2ddb7f )
by Lambda
01:01
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
SPECIAL_KEYS_SWAP = {v: k for k, v in SPECIAL_KEYS.items()}
63
# Add aliases used in Vim. This requires to be AFTER making swap dictionary
64
SPECIAL_KEYS.update(dict(
65
    NOP=SPECIAL_KEYS['NUL'],
66
    RETURN=SPECIAL_KEYS['CR'],
67
    ENTER=SPECIAL_KEYS['CR'],
68
    SPACE=SPECIAL_KEYS['SP'],
69
    BS=SPECIAL_KEYS['BACKSPACE'],
70
    INS=SPECIAL_KEYS['INSERT'],
71
    DEL=SPECIAL_KEYS['DELETE'],
72
))
73
74
75
KeyBase = namedtuple('KeyBase', ['code', 'char'])
76
77
78
class Key(KeyBase):
79
    """Key class which indicate a single key.
80
81
    Attributes:
82
        code (int or bytes): A code of the key. A bytes is used when the key is
83
            a special key in Vim (a key which starts from 0x80 in getchar()).
84
        char (str): A printable represantation of the key. It might be an empty
85
            string when the key is not printable.
86
    """
87
88
    __slots__ = ()  # type: Tuple[str, ...]
89
    __cached = {}   # type: Dict[KeyExpr, Key]
90
91
    def __str__(self) -> str:
92
        return self.char
93
94
    def __repr__(self) -> str:
95
        if isinstance(self.code, bytes) and self.code.startswith('\x80'):
96
            if self.code.startswith(CTRL_KEY):
97
                pass
98
            elif self.code.startswith(META_KEY):
99
                pass
100
            elif self.code in SPECIAL_KEYS_SWAP:
101
                pass
102
            pass
103
        return self.char
104
105
    @classmethod
106
    def parse(cls, nvim: Nvim, expr: KeyExpr) -> 'Key':
107
        """Parse a key expression and return a Key instance.
108
109
        It returns a Key instance of a key expression. The instance is cached
110
        to individual expression so that the instance is exactly equal when
111
        same expression is spcified.
112
113
        Args:
114
            expr (int, bytes, or str): A key expression.
115
116
        Returns:
117
            Key: A Key instance.
118
        """
119
        if expr not in cls.__cached:
120
            code = _resolve(nvim, expr)
121
            if isinstance(code, int):
122
                char = int2chr(nvim, code)
123
            elif not code.startswith(b'\x80'):
124
                char = ensure_str(nvim, code)
125
            else:
126
                char = ''
127
            cls.__cached[expr] = cls(code, char)
128
        return cls.__cached[expr]
129
130
131
def _resolve(nvim: Nvim, expr: KeyExpr) -> KeyCode:
132
    if isinstance(expr, int):
133
        return expr
134
    elif isinstance(expr, str):
135
        return _resolve(nvim, ensure_bytes(nvim, expr))
136
    elif isinstance(expr, bytes):
137
        if len(expr) == 1:
138
            return ord(expr)
139
        elif expr.startswith(b'\x80'):
140
            return expr
141
    else:
142
        raise AttributeError((
143
            '`expr` (%s) requires to be an instance of int|bytes|str but '
144
            '"%s" has specified.'
145
        ) % (expr, type(expr)))
146
    # Special key
147
    if expr.startswith(b'<') or expr.endswith(b'>'):
148
        inner = expr[1:-1]
149
        code = _resolve_from_special_keys(nvim, inner)
150
        if code != inner:
151
            return code
152
    return expr
153
154
155
def _resolve_from_special_keys(nvim: Nvim, inner: bytes) -> KeyCode:
156
    inner_upper = inner.upper()
157
    inner_upper_str = ensure_str(nvim, inner_upper)
158
    if inner_upper_str in SPECIAL_KEYS:
159
        return SPECIAL_KEYS[inner_upper_str]
160
    elif inner_upper.startswith(b'C-'):
161
        if len(inner) == 3:
162
            if inner_upper[-1] in b'@ABCDEFGHIKLMNOPQRSTUVWXYZ[\\]^_?':
163
                return ascii.ctrl(inner[-1])
164
        return b''.join([
165
            CTRL_KEY,
166
            cast(bytes, _resolve_from_special_keys(nvim, inner[2:])),
167
        ])
168
    elif inner_upper.startswith(b'M-') or inner_upper.startswith(b'A-'):
169
        return b''.join([
170
            META_KEY,
171
            cast(bytes, _resolve_from_special_keys(nvim, inner[2:])),
172
        ])
173
    elif inner_upper == b'LEADER':
174
        leader = nvim.vars['mapleader']
175
        leader = ensure_bytes(nvim, leader)
176
        return _resolve(nvim, leader)
177
    elif inner_upper == b'LOCALLEADER':
178
        leader = nvim.vars['maplocalleader']
179
        leader = ensure_bytes(nvim, leader)
180
        return _resolve(nvim, leader)
181
    return inner
182