Python,"过滤"行编辑,通过char读取stdin,没有回声

sle*_*ica 9 python curses readline

我需要一个能够将输入读入缓冲区的函数raw_input(),但不是在返回完整行之前回显输入和阻塞,它应该抑制echo并在每次缓冲区更改时调用回调.

我说"缓冲区更改"而不是"字符被读取",因为raw_input(),我希望它能够识别特殊键.例如,Backspace应该可以工作.

例如,如果我想使用回调来模拟输入的大写回声,代码将如下所示:

def callback(text):
    print '\r' + text.upper()

read_input(callback)
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?

注意:我一直在尝试使用readlinecurses满足我的目的,但两个Python绑定都不完整.curses不能在不清除整个屏幕的情况下启动,并readline在任何输入开始之前提供单个挂钩.

sle*_*ica 11

好吧,我亲自编写了代码.我将留下一个解释供将来参考.

要求

import sys, tty, termios, codecs, unicodedata
from contextlib import contextmanager
Run Code Online (Sandbox Code Playgroud)

禁用行缓冲

简单地读取标准输入时出现的第一个问题是线缓冲.我们希望单个字符在没有所需换行符的情况下到达我们的程序,这不是终端操作的默认方式.

为此,我编写了一个处理tty配置的上下文管理器:

@contextmanager
def cbreak():
    old_attrs = termios.tcgetattr(sys.stdin)
    tty.setcbreak(sys.stdin)
    try:
        yield
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attrs)
Run Code Online (Sandbox Code Playgroud)

该经理启用以下习语:

with cbreak():
    single_char_no_newline = sys.stdin.read(1)
Run Code Online (Sandbox Code Playgroud)

在我们完成时执行清理很重要,否则终端可能需要清理reset.

解码stdin

仅读取stdin的第二个问题是编码.非ascii unicode字符将逐字节到达我们,这是完全不合需要的.

为了正确解码stdin,我写了一个生成器,我们可以迭代unicode字符:

def uinput():
    reader = codecs.getreader(sys.stdin.encoding)(sys.stdin)
    with cbreak():
        while True:
            yield reader.read(1)
Run Code Online (Sandbox Code Playgroud)

这可能会导致管道故障.我不确定.但是,对于我的用例,它会选择正确的编码并生成一个字符流.

处理特殊字符

首先,我们应该能够告诉可打印的角色除了控制角色之外:

def is_printable(c):
    return not unicodedata.category(c).startswith('C')
Run Code Online (Sandbox Code Playgroud)

除了printables,现在,我只想处理← backspaceCtrlD序列:

def is_backspace(c):
    return c in ('\x08','\x7F')

def is_interrupt(c):
    return c == '\x04'
Run Code Online (Sandbox Code Playgroud)

把它放在一起: xinput()

现在一切都到位了.我想要的函数的原始契约是读取输入,处理特殊字符,调用回调.实施反映了这样:

def xinput(callback):
    text = ''

    for c in uinput():
        if   is_printable(c): text += c
        elif is_backspace(c): text = text[:-1]
        elif is_interrupt(c): break

        callback(text)

    return text
Run Code Online (Sandbox Code Playgroud)

尝试一下

def test(text):
    print 'Buffer now holds', text

xinput(test)
Run Code Online (Sandbox Code Playgroud)

运行它并输入Hellx← backspaceo World显示:

Buffer now holds H
Buffer now holds He
Buffer now holds Hel
Buffer now holds Hell
Buffer now holds Hellx
Buffer now holds Hell
Buffer now holds Hello
Buffer now holds Hello 
Buffer now holds Hello w
Buffer now holds Hello wo
Buffer now holds Hello wor
Buffer now holds Hello worl
Buffer now holds Hello world
Run Code Online (Sandbox Code Playgroud)