18
18
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19
19
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20
20
21
- # Bah, this would be easier to test if curses/terminfo didn't have so
22
- # much non-introspectable global state.
23
-
24
21
from collections import deque
25
22
26
23
from . import keymap
31
28
import os
32
29
33
30
34
- _keynames = {
31
+ # Mapping of human-readable key names to their terminal-specific codes
32
+ TERMINAL_KEYNAMES = {
35
33
"delete" : "kdch1" ,
36
34
"down" : "kcud1" ,
37
35
"end" : "kend" ,
46
44
}
47
45
48
46
49
- #function keys x in 1-20 -> fX: kfX
50
- _keynames .update ((' f%d' % i , ' kf%d' % i ) for i in range (1 , 21 ))
47
+ # Function keys F1-F20 mapping
48
+ TERMINAL_KEYNAMES .update ((" f%d" % i , " kf%d" % i ) for i in range (1 , 21 ))
51
49
52
- # this is a bit of a hack: CTRL-left and CTRL-right are not standardized
53
- # termios sequences: each terminal emulator implements its own slightly
54
- # different incarnation, and as far as I know, there is no way to know
55
- # programmatically which sequences correspond to CTRL-left and
56
- # CTRL-right. In bash, these keys usually work because there are bindings
57
- # in ~/.inputrc, but pyrepl does not support it. The workaround is to
58
- # hard-code here a bunch of known sequences, which will be seen as "ctrl
59
- # left" and "ctrl right" keys, which can be finally be mapped to commands
60
- # by the reader's keymaps.
61
- #
62
- CTRL_ARROW_KEYCODE = {
50
+ # Known CTRL-arrow keycodes
51
+ CTRL_ARROW_KEYCODES = {
63
52
# for xterm, gnome-terminal, xfce terminal, etc.
64
53
b'\033 [1;5D' : 'ctrl left' ,
65
54
b'\033 [1;5C' : 'ctrl right' ,
68
57
b'\033 Oc' : 'ctrl right' ,
69
58
}
70
59
71
- def general_keycodes ():
60
+ def get_terminal_keycodes ():
61
+ """
62
+ Generates a dictionary mapping terminal keycodes to human-readable names.
63
+ """
72
64
keycodes = {}
73
- for key , tiname in _keynames .items ():
74
- keycode = curses .tigetstr (tiname )
65
+ for key , terminal_code in TERMINAL_KEYNAMES .items ():
66
+ keycode = curses .tigetstr (terminal_code )
75
67
trace ('key {key} tiname {tiname} keycode {keycode!r}' , ** locals ())
76
68
if keycode :
77
69
keycodes [keycode ] = key
78
- keycodes .update (CTRL_ARROW_KEYCODE )
70
+ keycodes .update (CTRL_ARROW_KEYCODES )
79
71
return keycodes
80
72
81
-
82
- def EventQueue (fd , encoding ):
83
- keycodes = general_keycodes ()
84
- if os .isatty (fd ):
85
- backspace = tcgetattr (fd )[6 ][VERASE ]
86
- keycodes [backspace ] = 'backspace'
87
- k = keymap .compile_keymap (keycodes )
88
- trace ('keymap {k!r}' , k = k )
89
- return EncodedQueue (k , encoding )
90
-
91
-
92
- class EncodedQueue (object ):
93
- def __init__ (self , keymap , encoding ):
94
- self .k = self .ck = keymap
73
+ class EventQueue (object ):
74
+ def __init__ (self , fd , encoding ):
75
+ self .keycodes = get_terminal_keycodes ()
76
+ if os .isatty (fd ):
77
+ backspace = tcgetattr (fd )[6 ][VERASE ]
78
+ self .keycodes [backspace ] = "backspace"
79
+ self .compiled_keymap = keymap .compile_keymap (self .keycodes )
80
+ self .keymap = self .compiled_keymap
81
+ trace ("keymap {k!r}" , k = self .keymap )
82
+ self .encoding = encoding
95
83
self .events = deque ()
96
84
self .buf = bytearray ()
97
- self .encoding = encoding
98
85
99
86
def get (self ):
87
+ """
88
+ Retrieves the next event from the queue.
89
+ """
100
90
if self .events :
101
91
return self .events .popleft ()
102
92
else :
103
93
return None
104
94
105
95
def empty (self ):
96
+ """
97
+ Checks if the queue is empty.
98
+ """
106
99
return not self .events
107
100
108
101
def flush_buf (self ):
102
+ """
103
+ Flushes the buffer and returns its contents.
104
+ """
109
105
old = self .buf
110
106
self .buf = bytearray ()
111
107
return old
112
108
113
109
def insert (self , event ):
110
+ """
111
+ Inserts an event into the queue.
112
+ """
114
113
trace ('added event {event}' , event = event )
115
114
self .events .append (event )
116
115
117
116
def push (self , char ):
117
+ """
118
+ Processes a character by updating the buffer and handling special key mappings.
119
+ """
118
120
ord_char = char if isinstance (char , int ) else ord (char )
119
121
char = bytes (bytearray ((ord_char ,)))
120
122
self .buf .append (ord_char )
121
- if char in self .k :
122
- if self .k is self .ck :
123
+ if char in self .keymap :
124
+ if self .keymap is self .compiled_keymap :
123
125
#sanity check, buffer is empty when a special key comes
124
126
assert len (self .buf ) == 1
125
- k = self .k [char ]
127
+ k = self .keymap [char ]
126
128
trace ('found map {k!r}' , k = k )
127
129
if isinstance (k , dict ):
128
- self .k = k
130
+ self .keymap = k
129
131
else :
130
132
self .insert (Event ('key' , k , self .flush_buf ()))
131
- self .k = self .ck
133
+ self .keymap = self .compiled_keymap
132
134
133
135
elif self .buf and self .buf [0 ] == 27 : # escape
134
136
# escape sequence not recognized by our keymap: propagate it
135
137
# outside so that i can be recognized as an M-... key (see also
136
- # the docstring in keymap.py, in particular the line \\E.
138
+ # the docstring in keymap.py
137
139
trace ('unrecognized escape sequence, propagating...' )
138
- self .k = self .ck
140
+ self .keymap = self .compiled_keymap
139
141
self .insert (Event ('key' , '\033 ' , bytearray (b'\033 ' )))
140
142
for c in self .flush_buf ()[1 :]:
141
143
self .push (chr (c ))
@@ -147,4 +149,4 @@ def push(self, char):
147
149
return
148
150
else :
149
151
self .insert (Event ('key' , decoded , self .flush_buf ()))
150
- self .k = self .ck
152
+ self .keymap = self .compiled_keymap
0 commit comments