Lisp 无需按下回车按钮即可读取字符

Lisp 无需按下回车按钮即可读取字符,lisp,common-lisp,sbcl,Lisp,Common Lisp,Sbcl,读取行和读取字符都要求您在键入内容后按Enter键。Common Lisp中是否有任何机制允许程序在按下任何单个字符后立即继续,而不需要按Enter键的额外步骤 我正在尝试为一个程序构建一个快速、动态的文本输入界面,这样用户就可以通过按屏幕菜单上对应的数字或字母来快速导航和做不同的事情。所有额外按下Enter键都会严重中断工作流。这也类似于“y/n”类型的提示询问,只需按“y”或“n”即可 我正在使用SBCL,如果这有区别的话。也许这是特定于实现的,因为我尝试了这两个示例,但它不起作用(我仍然需

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

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

我正在使用SBCL,如果这有区别的话。也许这是特定于实现的,因为我尝试了这两个示例,但它不起作用(我仍然需要按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)))

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.
SBCL可能在这一领域与CMUCL有很大分歧,但 SBCL也应该可以做类似的事情。首先,从看电视开始 SB-UNIX或SB-POSIX软件包

提供了一个链接,指向最具可移植性的解决方案

因为您想做一些可能无法移植到 微控制器(但好处是用户界面增强得多),使用 非标准库,例如


我发现,这似乎是一个被遗弃的cl诅咒叉。但是,附带的示例程序
charmspaint
使用100%的CPU来运行简单的paint应用程序。问题似乎是主循环忙着等待输入。

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

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


只需与termios接口就可以了,不需要外部库。您可以在termios的手册页上找到更多信息(),但实际上,当终端处于规范模式(ICANON)时,它需要等待行分隔符,然后缓冲区的内容才可用。我希望这有帮助

太好了,这正是我看到问题时的想法。现在我不必写答案了。
(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)))))
(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))))))