【问题标题】:Reading a character without requiring the Enter button pressed无需按下 Enter 按钮即可读取字符
【发布时间】:2013-12-15 02:49:32
【问题描述】:

read-lineread-char 都要求您在输入内容后按 Enter 键。 Common Lisp 中是否有任何机制允许程序在按下任何单个字符后立即继续运行,而无需额外的按 Enter 的步骤?

我正在尝试为程序构建一个快速、动态的文本输入界面,以便用户可以通过按与屏幕菜单对应的数字或字母来快速导航并执行不同的操作。所有额外的 Enter 键都会严重中断工作流程。这也类似于来自提示的“y/n”类型的询问,只需按“y”或“n”就足够了。

我正在使用 SBCL,如果这有什么不同的话。也许这是特定于实现的,因为我在this page 上尝试了这两个示例,但它不起作用(我仍然需要按 Enter);这是第一个:

(defun y-or-n ()
(clear-input *standard-input*)
(loop as dum = (format t "Y or N for yes or no: ")
    as c = (read-char)
    as q = (and (not (equal c #\n)) (not (equal c #\y)))
    when q do (format t "~%Need Y or N~%")
    unless q return (if (equal c #\y) 'yes 'no)))

【问题讨论】:

    标签: lisp common-lisp sbcl


    【解决方案1】:

    read-char 不需要您按回车键。例如,

    CL-USER> (with-input-from-string (x "hello")
               (print (read-char x)))
    
    #\h 
    

    同样,如果你从命令行发送一些输入 SBCL,它会在没有换行符的情况下被读取:

    $ echo -n hello | sbcl --eval "(print (read-char))"
    …
    #\h 
    

    在阅读和打印#\h后,SBCL看到了ello

    *  
    debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD
                                                       "initial thread" RUNNING
                                                        {1002979011}>:
      The variable ELLO is unbound.
    

    我认为这足以确认不是read-char 需要换行符,而是输入的缓冲 是问题所在。我认为这与 2008 年的 comp.lang.lisp 线程中描述的问题(或非问题)相同:Re: A problem with read-char。用户问:

    工作时是否可以使 read-char 的行为类似于 С 中的 getch 使用交互式流(标准输入)?在 SBCL 中 read-char 想要 "enter" 键从 REPL 中解除挂起,在 C 中 getchar 立即返回 用户按下键盘上的键后。可能可以运行代码 除了 REPL,它使用 read-char 和直接控制台访问?

    有四个响应(请参阅thread index 以获取所有响应)。这些解释了为什么观察到这种行为(即,Lisp 进程没有从终端获取原始输入,而是缓冲输入)。 Pascal Bourguignon described the problem,以及一种使用 CLISP 处理此问题的方法(但除了通常的好建议外,并没有提供太多帮助)关于在 SBCL 中解决此问题:

    不同之处在于,curses 将终端置于原始模式以能够 一次从键盘接收一个字符,而不是 将终端置于熟模式,unix 驱动程序在此进行缓冲 线条和处理退格,以及其他细节。

    现在,我不知道 SBCL,(查看 SBCL 的手册)。我只有 我的湿件中加载了 CLISP 的实施说明。在 CLISP 你 可以使用 EXT:WITH-KEYBOARD 宏(而基本输出功能 诅咒由 SCREEN 包提供)。

    Rob Warnock's response 包含一些 CMUCL 的变通方法代码,这些代码可能适用于 SBCL,也可能不适用于 SBCL:

    我曾经为 CMUCL 为一个想要的应用程序编写了以下内容 能够在没有提示的情况下键入单个字符的响应 弄乱终端屏幕:

    (defun read-char-no-echo-cbreak (&optional (stream *query-io*))
      (with-alien ((old (struct termios))
                   (new (struct termios)))
        (let ((e0 (unix-tcgetattr 0 old))
              (e1 (unix-tcgetattr 0 new))
              (bits (logior tty-icanon tty-echo tty-echoe
                            tty-echok tty-echonl)))
          (declare (ignorable e0 e1)) ;[probably should test for error here]
          (unwind-protect
               (progn
                 (setf (slot new 'c-lflag) (logandc2 (slot old 'c-lflag) bits))
                 (setf (deref (slot new 'c-cc) vmin) 1)
                 (setf (deref (slot new 'c-cc) vtime) 0)
                 (unix-tcsetattr 0 tcsadrain new)
                 (read-char stream))
            (unix-tcsetattr 0 tcsadrain old)))))
    

    SBCL 在这方面可能与 CMUCL 大相径庭,但 SBCL 应该可以做类似的事情。首先查看 SB-UNIX 或者 SB-POSIX 软件包...

    User vippstar's response 提供了可能是最便携的解决方案的链接

    由于您想做一些可能无法移植到 微控制器(但好处是更增强的 UI),使用 非标准库,如CL-ncurses

    【讨论】:

    • 太好了,我看到这个问题时的想法。现在我不用写答案了。
    【解决方案2】:

    添加另一个答案以指出本教程的存在:cl-charms crash course,作者 Daniel "jackdaniel" Kochmański。 禁用缓冲与终端的配置方式有关,而 cl-charms 是利用 ncurses C 库来配置终端以进行交互使用的库。

    【讨论】:

      【解决方案3】:

      我找到了cl-charms,它似乎是废弃的 cl-curses 的一个分支。但是,包含的示例程序charms-paint 使用 100 % CPU 来运行琐碎的绘画应用程序。问题似乎是主循环忙于等待输入。

      【讨论】:

        【解决方案4】:

        我最近遇到了同样的问题,我最终通过使用 FFI 与 termios 交互并禁用规范模式来解决它。基本上就是@JoshuaTaylor 的回复中提到的内容。无论出于何种原因,我无法让该代码与 SBCL 一起使用,因此我做了一些更改。这是完整的工作代码(仅使用 SBCL 测试):

        (define-alien-type nil
          (struct termios
                  (c_iflag unsigned-long)
                  (c_oflag unsigned-long)
                  (c_cflag unsigned-long)
                  (c_lflag unsigned-long)
                  (c_cc (array unsigned-char 20))
                  (c_ispeed unsigned-long)
                  (c_ospeed unsigned-long)))
        
        (declaim (inline tcgetattr))
        (define-alien-routine "tcgetattr" int
                              (fd int)
                              (term (* (struct termios))))
        
        (declaim (inline tcsetattr))
        (define-alien-routine "tcsetattr" int
                              (fd int)
                              (action int)
                              (term (* (struct termios))))
        
        (defun read-single-byte (&optional (s *standard-input*))
          (with-alien ((old (struct termios))
                       (new (struct termios)))
            (let ((e0 (tcgetattr 0 (addr old)))
                  (e1 (tcgetattr 0 (addr new)))
                  (n-lflag (slot new 'c_lflag)))
              (declare (ignorable e0 e1))           
              (unwind-protect
                (progn
                  (setf (ldb (byte 1 8) n-lflag) 0) ; disables canonical mode
                  (setf (ldb (byte 1 3) n-lflag) 0) ; disables echoing input char
                  (setf (slot new 'c_lflag) n-lflag)
                  (tcsetattr 0 0 (addr new))
                  (read-byte s))
                (tcsetattr 0 0 (addr old))))))
        

        只需与 termios 接口就可以解决问题,不需要外部库。您可以在 termios 的手册页上找到更多信息 (https://man7.org/linux/man-pages/man3/termios.3.html),但本质上是当终端处于规范模式 (ICANON) 时,它需要在缓冲区内容可用之前等待行分隔符。我希望这会有所帮助!

        【讨论】:

          【解决方案5】:

          您可以使用trivial-raw-io 库来读取单个字符,而无需按Enter。用法:(trivial-raw-io:read-char)。它适用于 Linux 上的 SBCL、CCL、CMUCL 和 CLISP,也应该适用于其他类 Unix 操作系统。该库只有一个简单的依赖项(alexandria 库),并根据 BSD 2-clause 许可证获得许可。

          【讨论】:

            猜你喜欢
            • 2016-03-06
            • 2013-10-23
            • 2018-09-04
            • 2011-02-26
            • 2018-06-24
            • 2015-02-04
            • 1970-01-01
            • 2018-10-23
            相关资源
            最近更新 更多