1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""Keyboard helpers for pyratatui KeyEvent values."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol, runtime_checkable
@runtime_checkable
class KeyLike(Protocol):
@property
def code(self) -> str: ...
@property
def ctrl(self) -> bool: ...
@property
def alt(self) -> bool: ...
@property
def shift(self) -> bool: ...
@dataclass(frozen=True, slots=True)
class Key:
"""Test-friendly stand-in for ``pyratatui.KeyEvent``."""
code: str
ctrl: bool = False
alt: bool = False
shift: bool = False
def key(code: str, *, ctrl: bool = False, alt: bool = False, shift: bool = False) -> Key:
return Key(code=code, ctrl=ctrl, alt=alt, shift=shift)
def is_quit(ev: KeyLike) -> bool:
return ev.code.casefold() == "q" or (ev.code == "c" and ev.ctrl)
def is_up(ev: KeyLike) -> bool:
return ev.code in {"Up", "k", "K"}
def is_down(ev: KeyLike) -> bool:
return ev.code in {"Down", "j", "J"}
def is_left(ev: KeyLike) -> bool:
return ev.code in {"Left", "h", "H"}
def is_right(ev: KeyLike) -> bool:
return ev.code in {"Right", "l", "L"}
def is_enter(ev: KeyLike) -> bool:
return ev.code == "Enter"
def is_esc(ev: KeyLike) -> bool:
return ev.code == "Esc"
def is_tab(ev: KeyLike) -> bool:
return ev.code == "Tab" and not ev.shift
def is_backtab(ev: KeyLike) -> bool:
return ev.code in {"BackTab", "Backtab"} or (ev.code == "Tab" and ev.shift)
def is_backspace(ev: KeyLike) -> bool:
return ev.code == "Backspace"
def is_space(ev: KeyLike) -> bool:
return ev.code == " "
def printable_char(ev: KeyLike) -> str | None:
if ev.ctrl or ev.alt:
return None
if len(ev.code) == 1 and ev.code.isprintable():
return ev.code
return None