Completed
Push — master ( 4daebe...82760b )
by Lambda
58s
created

_getcode()   A

Complexity

Conditions 4

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
dl 0
loc 7
rs 9.2
c 0
b 0
f 0
1
"""Keymap."""
2
import time
3
from datetime import datetime, timedelta
4
from typing import cast, Iterator, Optional, Sequence, Tuple, Union
5
from neovim import Nvim
6
from .key import Key, KeyCode
7
from .keystroke import Keystroke, KeystrokeExpr
8
from .util import getchar
9
10
Rule = Union[
11
    Tuple[KeystrokeExpr, KeystrokeExpr],
12
    Tuple[KeystrokeExpr, KeystrokeExpr, bool],
13
]
14
15
16
class Keymap:
17
    """Keymap."""
18
19
    __slots__ = ('registry',)
20
21
    def __init__(self) -> None:
22
        """Constructor."""
23
        self.registry = {}  # type: Dict[Keystroke, tuple]
24
25
    def register(self,
26
                 lhs: Keystroke,
27
                 rhs: Keystroke,
28
                 noremap: bool=False) -> None:
29
        """Register."""
30
        self.registry[lhs] = (lhs, rhs, noremap)
31
32
    def register_from_rule(self, nvim: Nvim, rule: Rule) -> None:
33
        """Register."""
34
        if len(rule) == 2:
35
            lhs, rhs = cast('Tuple[KeystrokeExpr, KeystrokeExpr]', rule)
36
            noremap = False
37
        else:
38
            lhs, rhs, noremap = cast(
39
                'Tuple[KeystrokeExpr, KeystrokeExpr, bool]',
40
                rule
41
            )
42
        lhs = Keystroke.parse(nvim, lhs)
43
        rhs = Keystroke.parse(nvim, rhs)
44
        self.register(lhs, rhs, noremap)
45
46
    def register_from_rules(self,
47
                            nvim: Nvim,
48
                            rules: Sequence[Rule]) -> None:
49
        """Register keymaps from rule tuple."""
50
        for rule in rules:
51
            self.register_from_rule(nvim, rule)
52
53
    def filter(self, lhs: Keystroke) -> Iterator[Keystroke]:
54
        """Filter."""
55
        candidates = (
56
            self.registry[k]
57
            for k in self.registry.keys() if k.startswith(lhs)
58
        )
59
        return cast('Iterator[Keystroke]', sorted(candidates))
60
61
    def resolve(self, lhs: Keystroke, nowait: bool) -> Optional[Keystroke]:
62
        """Resolve."""
63
        candidates = list(self.filter(lhs))
64
        n = len(candidates)
65
        if n == 0:
66
            return lhs
67
        elif n == 1:
68
            _lhs, rhs, noremap = candidates[0]
69
            if lhs == _lhs:
70
                return rhs if noremap else self.resolve(rhs, nowait)
71
        elif nowait:
72
            _lhs, rhs, noremap = candidates[0]
73
            if lhs == _lhs:
74
                return rhs if noremap else self.resolve(rhs, nowait)
75
        return None
76
77
    def harvest(self, nvim: Nvim) -> Keystroke:
78
        if nvim.options['timeout']:
79
            timeoutlen = timedelta(
80
                milliseconds=int(nvim.options['timeoutlen'])
81
            )
82
        else:
83
            timeoutlen = None
84
85
        previous = None
86
        while True:
87
            code = _getcode(nvim, datetime.now() + timeoutlen)
88
            if code is None and previous is None:
89
                continue
90
            elif code is None:
91
                return self.resolve(previous, nowait=True) or previous
92
            previous = Keystroke((previous or ()) + (Key.parse(nvim, code),))
93
            keystroke = self.resolve(previous, nowait=False)
94
            if keystroke:
95
                return keystroke
96
97
    @classmethod
98
    def from_rules(cls, nvim: Nvim, rules: Sequence[Rule]) -> 'Keymap':
99
        """Create keymap from rule."""
100
        keymap = cls()
101
        keymap.register_from_rules(nvim, rules)
102
        return keymap
103
104
105
def _getcode(nvim: 'Nvim', timeout: Optional[datetime]) -> Optional[KeyCode]:
106
    while not timeout or timeout > datetime.now():
107
        code = getchar(nvim, False)
108
        if code != 0:
109
            return code
110
        time.sleep(0.01)
111
    return None
112
113
114
DEFAULT_KEYMAP_RULES = (
115
    ('<CR>', '<prompt:accept>', True),
116
    ('<ESC>', '<prompt:cancel>', True),
117
    ('<INSERT>', '<prompt:toggle_insert_mode>', True),
118
    ('<BS>', '<prompt:delete_char_before_caret>', True),
119
    ('<DEL>', '<prompt:delete_char_under_caret>', True),
120
    ('<C-U>', '<prompt:delete_entire_text>', True),
121
    ('<Left>', '<prompt:move_caret_to_left>', True),
122
    ('<Right>', '<prompt:move_caret_to_right>', True),
123
    ('<Home>', '<prompt:move_caret_to_head>', True),
124
    ('<End>', '<prompt:move_caret_to_tail>', True),
125
    ('<C-P>', '<prompt:assign_previous_text>', True),
126
    ('<C-N>', '<prompt:assign_next_text>', True),
127
    ('<Up>', '<prompt:assign_previous_matched_text>', True),
128
    ('<Down>', '<prompt:assign_next_matched_text>', True),
129
    ('<C-R>', '<prompt:paste_from_register>', True),
130
)
131