无需按下Enter按钮即可读取字符

时间:2013-11-29 00:41:32

标签: lisp common-lisp sbcl

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)))

4 个答案:

答案 0 :(得分:11)

read-char不要求您按Enter键。例如,

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想要   从REPL“输入”键到unhang,在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包含了一些可能会或可能不适用于SBCL的CMUCL解决方法代码:

  

我曾经为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

答案 1 :(得分:2)

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

答案 2 :(得分:1)

我找到cl-charms,这似乎是被遗弃的cl-curses的一个分支。但是,包含的示例程序charms-paint使用100%CPU来运行普通的绘图应用程序。问题似乎是主循环忙等待输入。

答案 3 :(得分:0)

我最近在同一个问题上苦苦挣扎,最终通过使用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)时,它需要等待行定界符才能使缓冲区的内容可用。我希望这会有所帮助!