如何接受箭头键的输入或方向性输入?

7
这可能是一个xy问题,但我正在尝试构建一个基于内核的文本编辑器,类似于vimnano,我知道如何使用转义字符来清除屏幕,然后重新打印输出。我可以使其接受字符输入,但我不确定如何让它接受箭头输入以用于导航。我认为它们有ASCII值,但显然没有。是否有一种方法可以使用箭头键,或者我必须像vim那样制作导航模式和插入模式?
我也简要尝试了curses,但由于我所理解的原因,这是不可行的,需要为其打开一个全新的窗口,并且与我所想象的单个终端窗口不兼容。
请注意,curses无法正常工作,因为它会清除窗口,而这并不是我想要的。

2
我认为您可能误解了 curses 中窗口的含义。它是在终端屏幕中打印的窗口。因此,如果您已经打开了一个终端并运行了 curses 程序,它将显示在同一个终端中,就像 Vim 一样。 - user764357
@LegoStormtroopr 我觉得我可能有问题。我正在检查curses的功能,并在Python测试工具中尝试它,但它不像我之前看到的那样打开新窗口。也许我会再给“curses”一次机会。 - Jacqlyn
1
@JFA 你是指IDLE吗?IDLE 不是终端,因此当您尝试使用curses运行某些内容时,它必须打开一个终端才能使其工作。但是,如果您已经打开了终端,则会使用该终端。 - Bakuriu
对,IDLE。我只使用IDLE来测试代码片段,然后再将它们实现到我的代码中,或者尝试新的库。 - Jacqlyn
5个回答

10

curses 正是你想要的。事实上,我相信 vim 使用 curses 实现其界面。

尝试将以下代码放入一个名为 test_curses.py 的文件中:

import curses

screen = curses.initscr()
screen.addstr("Hello World!!!")
screen.refresh()
screen.getch()
curses.endwin()

现在打开一个终端(不是IDLE!是真正的终端!),然后通过以下方式运行:

python test_curses.py

你应该能看到终端被清空,并出现Hello World!!!的文字。按下任意键,程序将停止并恢复旧的终端内容。

请注意,curses库不像您习惯的那么易用和“用户友好”。建议阅读教程(很遗憾是针对C语言的,但Python接口基本相同)。


是的,昨晚我在玩curses,感觉有点像玩火,但我认为这正是我想要的。 - Jacqlyn
2
@JFA 有一个原因,为什么这个库被称为“curses”:当你使用它时,通常会诅咒整个世界!它没有用户友好的API。此外,它是很久以前编写的,当时终端和计算机图形很慢,因此很多API都是为了优化而编写的,当你需要时这很棒,但在普通情况下会使一切变得更加复杂。 - Bakuriu
有没有一种方法可以创建一个安全的文件来恢复我的终端,以便于使用curses?我总是得到一个不回显或打印的终端。另外,curses是否必须清除屏幕,还是可以在输入后的小区域内工作? - Jacqlyn
@JFA 你应该记得调用 curses.endwin()。如果你中途终止了应用程序(例如使用 Ctrl+C),你可能会陷入这种情况。为了避免这种情况,你可以捕获 KeyboardInterrupt,调用 curses.endwin() 然后重新引发异常,或者添加一个 sys.excepthook。据我所知,curses 只能在整个终端上工作。 - Bakuriu
如果是这样的话,它会做我试图避免的事情。curses - Jacqlyn
有没有办法将终端中到目前为止的所有内容复制到 curses 中?否则,我认为我将不得不使用插入和导航模式,这越来越友好,我越想到 curses - Jacqlyn

4
我知道我来晚了,但我真的很喜欢{{link1:click}}软件包,这个软件包是由@elbaschid提到的。我不知道为什么他没有被点赞 - 可能因为他的示例没有展示如何处理光标键特定的情况。
以下是我的意见:
#!/usr/bin/python

import click

printable = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

while True:
    click.echo('Continue? [yn] ', nl=False)
    c = click.getchar()
    click.echo()
    if c == 'y':
        click.echo('We will go on')
    elif c == 'n':
        click.echo('Abort!')
        break
    elif c == '\x1b[D':
        click.echo('Left arrow <-')
    elif c == '\x1b[C':
        click.echo('Right arrow ->')
    else:
        click.echo('Invalid input :(')
        click.echo('You pressed: "' + ''.join([ '\\'+hex(ord(i))[1:] if i not in printable else i for i in c ]) +'"' )

这个处理光标键,并且额外打印出任何尚未识别的键盘快捷方式的py字符串表示。例如,Ctrl-s是"\x13"。您可以稍后在另一个中使用它。
elif c == ??

我曾试图编辑@elbaschid的回答但被拒绝了 ¯\_(ツ)_/¯。如果你也喜欢我的回答,请给他点赞。
快速命令行原型设计的超棒库。

4

最终我使用了来自这个问题的代码,并修改了__init__语句,以便它可以接受一个包含3个字符的列表。

import sys

class _Getch:
   """Gets a single character from standard input.  Does not echo to the
screen."""
   def __init__(self):
      self.impl = _GetchUnix()

   def __call__(self):# return self.impl()
      charlist = []
      counter = 0
      for i in range(3):
         try:charlist.append(self.impl())
         except:pass
         if charlist[i] not in [chr(27),chr(91)]:#TODO sort out escape vs arrow duh use len()
            break
         if len(charlist) > 1:
            if charlist == [chr(27),chr(27)]:
               break
      if len(charlist) == 3:
         if charlist[2] == 'a'
            return 'u-arr'
         if charlist[2] == 'b'
            return 'd-arr'
         if charlist[2] == 'c'
            return 'r-arr'
         if charlist[2] == 'd'
            return 'l-arr'
      if len(charlist == 2):
         if charlist == [chr(27),chr(27)]
            return chr(27)
      if len(charlist == 1)
         return charlist[0]
      return ''

class _GetchUnix:
   def __init__(self):
      import tty, sys

   def __call__(self):
      import sys, tty, termios
      fd = sys.stdin.fileno()
      old_settings = termios.tcgetattr(fd)
      try:
         tty.setraw(sys.stdin.fileno())
         ch = sys.stdin.read(1)
      finally:
         termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
      return ch

这使我可以从键盘上获取箭头键以及其他字符和转义序列,以供编辑器使用。它使 "Getch" 类不再严格是一个 "get char" 克隆,因为它返回一个字符串,但最终变得更加有用。

3
Python的包click用于构建命令行客户端,并提供了一个实现,可让您获取按键事件:
import click

key = click.getchar()

它以Unicode字符的形式返回键表示,并且“像箭头键这样的东西将显示在平台的本机转义格式中。”

以下是从Click文档中getchar直接获取的示例:

import click

click.echo('Continue? [yn] ', nl=False)
c = click.getchar()
click.echo()
if c == 'y':
    click.echo('We will go on')
elif c == 'n':
    click.echo('Abort!')
else:
    click.echo('Invalid input :(')

这其实是一个非常不错的包和非常不错的答案——只是它缺少处理箭头键的功能——我已经自己修复了这个小问题。 - yatsek

2

如何在按下箭头键或其他键时执行所需操作

# key_event_handler.py

import sys
import select
import pty
import os
import time
import fcntl
import tty
import termios
def __select( iwtd, owtd, ewtd, timeout=None):

   '''This is a wrapper around select.select() that ignores signals. If
   select.select raises a select.error exception and errno is an EINTR
   error then it is ignored. Mainly this is used to ignore sigwinch
   (terminal resize). '''

   # if select() is interrupted by a signal (errno==EINTR) then
   # we loop back and enter the select() again.
   if timeout is not None:
       end_time = time.time() + timeout
   while True:
       try:
           return select.select(iwtd, owtd, ewtd, timeout)
       except select.error:
           err = sys.exc_info()[1]
           if err.args[0] == errno.EINTR:
               # if we loop back we have to subtract the
               # amount of time we already waited.
               if timeout is not None:
                   timeout = end_time - time.time()
                   if timeout < 0:
                       return([], [], [])
           else:
               # something else caused the select.error, so
               # this actually is an exception.
               raise

STDIN_FILENO=pty.STDIN_FILENO
STDOUT_FILENO=pty.STDOUT_FILENO
string_type=bytes
sys.stdout.write(string_type())
sys.stdout.flush()
buffer = string_type()
mode = tty.tcgetattr(STDIN_FILENO)
tty.setraw(STDIN_FILENO)
try:
    while True:
        r, w, e = __select([STDIN_FILENO], [], [],timeout=1)
        if STDIN_FILENO in r:
            #It accepts all keys from keyboard 
            data=os.read(STDIN_FILENO, 1)
            #Bellow line returns ASCII value of a charector
            ascii_value=ord(data[0])
            ##########################################################################
            ##                      Your code goes here                             ## 
            ##                                                                      ##
            # Do some action here by matching the ASCII value                        #
            # you can handle your program by making use of special keys like         #
            # Backspace, Ctrl, Ctrl+A,Ctrl+B, Ctrl+C, ...Ctrl+Z, Esc,F1, ...,F12 ....#
            # Tab,Enter,Arrow keys,Alphabetic and Numeric keys are also supported    #  
            ##########################################################################
            #                                                                        #
            #To Print use bellow line rather than print or sys.stdout.write(data)    #
            #os.write(STDOUT_FILENO,data)                                            #
            ##                                                                       #
            ##########################################################################


finally:
    tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode) 

然后打开终端并运行 key_event_handler.py


该程序主要用于捕获按键并获取按下的ASCII码,该程序还可用于多线程应用程序中的非阻塞I/O。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接