Completed
Push — master ( 2ddb7f...efffd2 )
by Lambda
01:00
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
    @classmethod
91
    def parse(cls, nvim: Nvim, expr: KeyExpr) -> 'Key':
92
        """Parse a key expression and return a Key instance.
93
94
        It returns a Key instance of a key expression. The instance is cached
95
        to individual expression so that the instance is exactly equal when
96
        same expression is spcified.
97
98
        Args:
99
            expr (int, bytes, or str): A key expression.
100
101
        Returns:
102
            Key: A Key instance.
103
        """
104
        if expr not in cls.__cached:
105
            code = _resolve(nvim, expr)
106
            if isinstance(code, int):
107
                char = int2chr(nvim, code)
108
            elif not code.startswith(b'\x80'):
109
                char = ensure_str(nvim, code)
110
            else:
111
                char = ''
112
            cls.__cached[expr] = cls(code, char)
113
        return cls.__cached[expr]
114
115
    def __str__(self) -> str:
116
        return self.char
117
118
119
def _resolve(nvim: Nvim, expr: KeyExpr) -> KeyCode:
120
    if isinstance(expr, int):
121
        return expr
122
    elif isinstance(expr, str):
123
        return _resolve(nvim, ensure_bytes(nvim, expr))
124
    elif isinstance(expr, bytes):
125
        if len(expr) == 1:
126
            return ord(expr)
127
        elif expr.startswith(b'\x80'):
128
            return expr
129
    else:
130
        raise AttributeError((
131
            '`expr` (%s) requires to be an instance of int|bytes|str but '
132
            '"%s" has specified.'
133
        ) % (expr, type(expr)))
134
    # Special key
135
    if expr.startswith(b'<') or expr.endswith(b'>'):
136
        inner = expr[1:-1]
137
        code = _resolve_from_special_keys(nvim, inner)
138
        if code != inner:
139
            return code
140
    return expr
141
142
143
def _resolve_from_special_keys(nvim: Nvim, inner: bytes) -> KeyCode:
144
    inner_upper = inner.upper()
145
    inner_upper_str = ensure_str(nvim, inner_upper)
146
    if inner_upper_str in SPECIAL_KEYS:
147
        return SPECIAL_KEYS[inner_upper_str]
148
    elif inner_upper.startswith(b'C-'):
149
        if len(inner) == 3:
150
            if inner_upper[-1] in b'@ABCDEFGHIKLMNOPQRSTUVWXYZ[\\]^_?':
151
                return ascii.ctrl(inner[-1])
152
        return b''.join([
153
            CTRL_KEY,
154
            cast(bytes, _resolve_from_special_keys(nvim, inner[2:])),
155
        ])
156
    elif inner_upper.startswith(b'M-') or inner_upper.startswith(b'A-'):
157
        return b''.join([
158
            META_KEY,
159
            cast(bytes, _resolve_from_special_keys(nvim, inner[2:])),
160
        ])
161
    elif inner_upper == b'LEADER':
162
        leader = nvim.vars['mapleader']
163
        leader = ensure_bytes(nvim, leader)
164
        return _resolve(nvim, leader)
165
    elif inner_upper == b'LOCALLEADER':
166
        leader = nvim.vars['maplocalleader']
167
        leader = ensure_bytes(nvim, leader)
168
        return _resolve(nvim, leader)
169
    return inner
170