Skip to content

Commit

Permalink
Cleanup and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
pablogsal committed Apr 28, 2024
1 parent 2a64680 commit 702b59e
Showing 1 changed file with 46 additions and 44 deletions.
90 changes: 46 additions & 44 deletions Lib/_pyrepl/unix_eventqueue.py
Expand Up @@ -18,9 +18,6 @@
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# Bah, this would be easier to test if curses/terminfo didn't have so
# much non-introspectable global state.

from collections import deque

from . import keymap
Expand All @@ -31,7 +28,8 @@
import os


_keynames = {
# Mapping of human-readable key names to their terminal-specific codes
TERMINAL_KEYNAMES = {
"delete": "kdch1",
"down": "kcud1",
"end": "kend",
Expand All @@ -46,20 +44,11 @@
}


#function keys x in 1-20 -> fX: kfX
_keynames.update(('f%d' % i, 'kf%d' % i) for i in range(1, 21))
# Function keys F1-F20 mapping
TERMINAL_KEYNAMES.update(("f%d" % i, "kf%d" % i) for i in range(1, 21))

# this is a bit of a hack: CTRL-left and CTRL-right are not standardized
# termios sequences: each terminal emulator implements its own slightly
# different incarnation, and as far as I know, there is no way to know
# programmatically which sequences correspond to CTRL-left and
# CTRL-right. In bash, these keys usually work because there are bindings
# in ~/.inputrc, but pyrepl does not support it. The workaround is to
# hard-code here a bunch of known sequences, which will be seen as "ctrl
# left" and "ctrl right" keys, which can be finally be mapped to commands
# by the reader's keymaps.
#
CTRL_ARROW_KEYCODE = {
# Known CTRL-arrow keycodes
CTRL_ARROW_KEYCODES= {
# for xterm, gnome-terminal, xfce terminal, etc.
b'\033[1;5D': 'ctrl left',
b'\033[1;5C': 'ctrl right',
Expand All @@ -68,74 +57,87 @@
b'\033Oc': 'ctrl right',
}

def general_keycodes():
def get_terminal_keycodes():
"""
Generates a dictionary mapping terminal keycodes to human-readable names.
"""
keycodes = {}
for key, tiname in _keynames.items():
keycode = curses.tigetstr(tiname)
for key, terminal_code in TERMINAL_KEYNAMES.items():
keycode = curses.tigetstr(terminal_code)
trace('key {key} tiname {tiname} keycode {keycode!r}', **locals())
if keycode:
keycodes[keycode] = key
keycodes.update(CTRL_ARROW_KEYCODE)
keycodes.update(CTRL_ARROW_KEYCODES)
return keycodes


def EventQueue(fd, encoding):
keycodes = general_keycodes()
if os.isatty(fd):
backspace = tcgetattr(fd)[6][VERASE]
keycodes[backspace] = 'backspace'
k = keymap.compile_keymap(keycodes)
trace('keymap {k!r}', k=k)
return EncodedQueue(k, encoding)


class EncodedQueue(object):
def __init__(self, keymap, encoding):
self.k = self.ck = keymap
class EventQueue(object):
def __init__(self, fd, encoding):
self.keycodes = get_terminal_keycodes()
if os.isatty(fd):
backspace = tcgetattr(fd)[6][VERASE]
self.keycodes[backspace] = "backspace"
self.compiled_keymap = keymap.compile_keymap(self.keycodes)
self.keymap = self.compiled_keymap
trace("keymap {k!r}", k=self.keymap)
self.encoding = encoding
self.events = deque()
self.buf = bytearray()
self.encoding = encoding

def get(self):
"""
Retrieves the next event from the queue.
"""
if self.events:
return self.events.popleft()
else:
return None

def empty(self):
"""
Checks if the queue is empty.
"""
return not self.events

def flush_buf(self):
"""
Flushes the buffer and returns its contents.
"""
old = self.buf
self.buf = bytearray()
return old

def insert(self, event):
"""
Inserts an event into the queue.
"""
trace('added event {event}', event=event)
self.events.append(event)

def push(self, char):
"""
Processes a character by updating the buffer and handling special key mappings.
"""
ord_char = char if isinstance(char, int) else ord(char)
char = bytes(bytearray((ord_char,)))
self.buf.append(ord_char)
if char in self.k:
if self.k is self.ck:
if char in self.keymap:
if self.keymap is self.compiled_keymap:
#sanity check, buffer is empty when a special key comes
assert len(self.buf) == 1
k = self.k[char]
k = self.keymap[char]
trace('found map {k!r}', k=k)
if isinstance(k, dict):
self.k = k
self.keymap = k
else:
self.insert(Event('key', k, self.flush_buf()))
self.k = self.ck
self.keymap = self.compiled_keymap

elif self.buf and self.buf[0] == 27: # escape
# escape sequence not recognized by our keymap: propagate it
# outside so that i can be recognized as an M-... key (see also
# the docstring in keymap.py, in particular the line \\E.
# the docstring in keymap.py
trace('unrecognized escape sequence, propagating...')
self.k = self.ck
self.keymap = self.compiled_keymap
self.insert(Event('key', '\033', bytearray(b'\033')))
for c in self.flush_buf()[1:]:
self.push(chr(c))
Expand All @@ -147,4 +149,4 @@ def push(self, char):
return
else:
self.insert(Event('key', decoded, self.flush_buf()))
self.k = self.ck
self.keymap = self.compiled_keymap

0 comments on commit 702b59e

Please sign in to comment.