需要逐个字符的键盘输入,可与粘贴和ANSI转义序列很好地交互 [英] Need character-by-character keyboard input that interacts well with paste and ANSI escape sequences

查看:70
本文介绍了需要逐个字符的键盘输入,可与粘贴和ANSI转义序列很好地交互的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的程序("TRAC处理器" )使用逐字符输入.我为字符串实现了类似于readline的输入功能,这些字符串以enter(通常为')以外的字符终止,并且它们本身可能是多行的.因此,我在输入字符之间输出终端转义序列,包括查询终端仿真器的转义序列(光标位置和屏幕尺寸).为了进行跨平台的单字符输入,我使用了 http://code.activestate.com/recipes/134892/,这非常有帮助.

My program (a "TRAC Processor") uses character-by-character input. I am implementing readline-like input features for strings which are terminated with characters other than enter (usually ') and may themselves be multi-line. So I output terminal escape sequences between input characters, including escape sequences which query the terminal emulator (cursor position and screen size). To do cross-platform single-character input, I used http://code.activestate.com/recipes/134892/, which was very helpful.

这对粘贴效果很好...直到我需要在粘贴的第一个字符之后获得终端响应.似乎粘贴的文本与对转义序列的响应混杂在一起.我以为我可以通过在启动转义序列查询之前刷新输入缓冲区来解决此问题:等待10毫秒,如果没有输入,请继续;否则,请执行以下操作.如果有输入,则对其进行缓冲,然后再次等待,直到无输入为止.根据这篇文章,我尝试使用select()轮询标准输入.这是个好主意,但是没有用,并且产生了非常奇怪的行为.我在该问题的原始版本中发布了这种奇怪的行为,以为我误解了select,并且有一种解决方法.似乎没有,但我发现了另一种刷新(并保存)输入流的方法.我决定保留这个问题,并将该方法发布为答案.

This worked fine with paste... until I needed to get a terminal response after the first character of the paste. It seemed like the pasted text was getting mingled with the response to the escape sequence. I thought I would fix it by flushing the input buffer before initiating the escape-sequence query: wait 10ms, and if there is no input proceed; if there is input, buffer it and wait again until no input. Based on this post, I tried to poll stdin using select(). Great idea, but it didn't work, and it produced very strange behavior. I posted that strange behavior in the original version of this question, thinking I was misunderstanding select and there was a way to fix it. There doesn't seem to be, but I have found another way to flush (and save) the input stream. I decided to keep this question, and post that method as answer.

此处中解释了select()的问题.在粘贴的第一个字符之后,其他字符已经被缓冲,并且select仅在存在超出已缓冲内容的新输入时才返回新输入.我无法删除这个行为所产生的MWE,所以您可以在下面看到它.

The problem with select() is explained here. After the first character of the paste, the other characters are already buffered, and select only returns new input when there is new input beyond what is already buffered. I couldn't bring myself to delete the MWE I'd produced of this behavior, so you can see it below.

不幸的是,该帖子中提出的答案对我不起作用或需要更多解释. @slowdog建议使用无缓冲输入(os.read(stdin.fileno(),1)代替stdin.read(1)).这就解决了选择问题,但它破坏了粘贴:似乎粘贴了第一个字符之后的所有粘贴字符无论什么,所以您永远看不到它们.它也似乎无法与转义序列响应配合使用,转义序列响应似乎也得到缓冲.这也很烦人,因为您需要刷新输出缓冲区,但这并不那么糟糕. @Omnafarious在评论中说:尽管如此,处理Python缓冲问题的另一种方法是简单地进行无参数读取,这应该读取当前可用的所有内容."最终,这就是我所做的,如下所述,但是简单地"证明事实并非如此简单.在此处还有另一种解决方案,但我想在那里必须是一种无需线程即可执行此操作的方法.

Unfortunately, the answers proposed in that post either don't work for me or need a lot more explanation. @slowdog suggests using unbuffered input (os.read(stdin.fileno(), 1) instead of stdin.read(1)). That solves the select problem, but it breaks paste: it seems that all the characters of the paste after the first one are buffered no matter what, so you never see them. It also didn't seem to work well with the escape-sequence responses, which seem to also get buffered. It's also annoying because you need to flush the output buffer, but that's not so terrible. @Omnafarious, in a comment, said "Though, another way to handle the Python buffering issue to to simply do a no-parameter read, which should read everything currently available." That is ultimately what I did, as posted below, but "simply" turns out not to be so simple. There is another solution here, but I figured there must be a way to do this without threading.

顺便说一句,有一个相对简单的解决方法,因为事实证明,粘贴不随散逸序列的响应而随机散布.粘贴的其余所有内容都在转义序列响应之前被读取,因此,当您寻找转义序列响应(其本身以转义开始)时,您可以仅缓冲在转义之前读取的所有字符,然后进行处理他们以后.仅当您可能在终端上输入ESC字符时,此操作才会失败.无论如何,到这个时候我已经非常着迷于解决这个问题,并且我认为其他人可能会找到有价值的答案.

Incidentally, there is a relatively simple work-around, because it turns out that the paste is not randomly interspersed with response to the escape sequence. The entire remainder of the paste gets read before the escape sequence response, so that when you are looking for the escape-sequence response (which itself starts with an escape), you can just buffer all the characters you read before the escape, and process them later. This only fails if you might be typing ESC characters in at the terminal. In any case, by this time I was pretty much hooked on solving this problem, and I thought others might find the answer valuable.

无论如何,FWIW是我的select问题的MWE,它只是回显文本而不是对其进行缓冲:

Anyway, FWIW here is my MWE for the select problem, which just echoes the text rather than buffering it:

def flush():
    import sys, tty, termios
    from select import select
    tty.setraw(sys.stdin.fileno())
    while True:
        rlist, wlist, xlist = select([sys.stdin], [], [], 1)
        if rlist == []: return
        sys.stdout.write(sys.stdin.read(1))

将此内容粘贴到Python提示符(2.7.9)中,并在末尾添加另一个空白行.如果您调用flush()并以每秒多于一个字母的速度键入某些文本,它将回传给您.例如,我键入"hello",然后暂停,并得到以下结果:

Paste this into the Python prompt (2.7.9) and put another blank line at the end. If you invoke flush() and type some text more quickly than one letter per second, it types it back to you. For example, I typed "hello" and then paused, and got this result:

>>> flush()
hello>>> 

(至少)在OSX Terminal应用程序中,如果将单词text复制到剪贴板,则调用该函数并在一秒钟内单击粘贴,这就是您得到的:

In the OSX Terminal app (at least), if you copy the word text to the clipboard, invoke the function and hit paste within one second, here's what you get:

>>> flush()
t>>> 

奇怪!只有第一个字母.再试一次,不输入任何内容:

Odd! Only the first letter. Try it again, typing nothing:

>>> flush()
>>> 

它暂停了一秒钟,什么也没做,就像没有输入等待,对吗?再试一次,然后按?:

It paused for a second and does nothing, like no input waiting, right? Try it again, and hit ?:

>>> flush()
ext?>>> 

?之前,您将剩下的所有糊状物保存起来!!同样,奇怪的是,在键入我不理解的?之前,有一个1秒钟的暂停.如果您在这一点上再试一次,它的行为就像正常情况一样.

You get the rest of the paste, saved up, before the ?!! Also, strangely, there is a 1-second pause before it types the ? which I don't understand. If you try again at this point, it behaves like normal.

好的,让我们再试一次,首先粘贴text,然后粘贴WTF,然后键入!:

OK, let's try it again, first pasting text, then pasting WTF, then typing !:

>>> flush()
t>>> flush()
extW>>> flush()
TF!>>> 

因此,粘贴再次仅给出第一个字母,并将其他字母保留在输入缓冲区中,并在W!之前暂停一秒钟.另一个奇怪的事情是:没有在Python >>>提示符下输入缓冲的字符.

So again the paste only gives the first letter, and holds the others in the input buffer, and pauses for a second before W and !. Yet another strange thing: the buffered characters are not entered at the Python >>> prompt.

一个挥之不去的问题:为什么在回响下一个字母之前,您会得到额外的1秒暂停? Select并不总是等待整个时间段...

One lingering question: why do you get the additional 1-second pause before the next letter is echoed? Select does not always wait for the whole time period...

推荐答案

通常会引用无参数读取"作为读取所有可用字节的方法,这对于此应用程序来说似乎很完美.不幸的是,当您查看文档时,请阅读( )与readall()相同,后者在EOF之前一直阻塞.因此,您需要将stdin设置为非阻止模式.

The "no-parameter read" is often cited as a way to read all available bytes, which sounds perfect for this application. Unfortunately, when you look at the documentation, read() is the same as readall(), which blocks until EOF. So you need to set stdin to non-blocking mode.

执行此操作后,您将开始获得:

Once you do this, you start getting:

IOError: [Errno 35] Resource temporarily unavailable

当您搜索google时,绝大多数回答都说解决此问题的方法是摆脱非阻塞模式...因此在这里无济于事.但是,这篇文章解释说,这仅仅是当没有字符要返回时,非阻塞read()会执行此操作.

When you google this, the vast majority of responses say that the solution to this problem is to get rid of non-blocking mode... so that's not helpful here. However, this post explains that this is simply what the non-blocking read() does when there are no characters to return.

这是我的flush()函数,大部分功能是从该帖子中复制的:

Here is my flush() function, much of which copied from that post:

def flush():
    import sys, tty, termios, fcntl, os
    fd = sys.stdin.fileno()
    old_attr = termios.tcgetattr(fd)
    old_fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    try:
        tty.setraw(fd)
        fcntl.fcntl(fd, fcntl.F_SETFL, old_fl | os.O_NONBLOCK)
        inp = sys.stdin.read()
    except IOError, ex1:  #if no chars available generates exception
        try: #need to catch correct exception
            errno = ex1.args[0] #if args not sequence get TypeError
            if errno == 35:
                return '' #No characters available
            else:
                raise #re-raise exception ex1
        except TypeError, ex2:  #catch args[0] mismatch above
            raise ex1 #ignore TypeError, re-raise exception ex1
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_attr)
        fcntl.fcntl(fd, fcntl.F_SETFL, old_fl)
    return inp

希望对某人有帮助!

这篇关于需要逐个字符的键盘输入,可与粘贴和ANSI转义序列很好地交互的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆