read-line
and read-char
both require you press Enter key after typing something. Is there any mechanism in Common Lisp that would allow the program to continue upon the press of any single character immediately, without requiring the additional step of pressing Enter?
I'm trying to build a quick, dynamic text input interface for a program so users can quickly navigate around and do different things by pressing numbers or letters corresponding to onscreen menus. All the extra presses of the Enter key seriously interrupt the workflow. This would also be similar to a "y/n" type of interrogation from a prompt, where just pressing "y" or "n" is sufficient.
I am using SBCL, if that makes a difference. Perhaps this is implementation specific, as I tried both examples on this page and it does not work (I still need to press Enter); here's the first one:
(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)))
In C, the curses (cursor control for C) package has a command called cbreak , which puts the terminal in single character mode. Thus, the getchar command will not wait for the end of a line ( Enter ) to read input.
getchar() to read one character from keyboard We can use getchar function to read a single character from the keyboard. We will try to take one sample character and display by using printf function. Once the program encounters the getchar() function it will wait for the user to enter data by keyboard.
scanf() without displaying on screen.
Thus to read a single keyboard character with getchar() will not be possible until the newline (enter key) is pressed.
read-char
doesn't require you to press enter. E.g.,
CL-USER> (with-input-from-string (x "hello")
(print (read-char x)))
#\h
Similarly, if you send some input into SBCL from the command line, it will be read without a newline:
$ echo -n hello | sbcl --eval "(print (read-char))"
…
#\h
After reading and printing #\h
, SBCL saw the ello
:
*
debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD
"initial thread" RUNNING
{1002979011}>:
The variable ELLO is unbound.
I think this is enough to confirm that it's not that read-char
needs a newline, but rather that the buffering of the input is the problem. I think this is the same problem (or non-problem) that's described in a comp.lang.lisp thread from 2008: Re: A problem with read-char. The user asks:
Is it possible to make read-char behave like getch in С when working with interactive stream (standard-input)? In SBCL read-char wants "enter" key to unhang from REPL, in C getchar returns immediately after user press key on keyboard. Probably is possible to run code that uses read-char with direct console access, aside REPL?
There were four responses (see the thread index to get to all of them). These explain why this behavior is observed (viz., that the Lisp process isn't getting raw input from the terminal, but rather buffered input). Pascal Bourguignon described the problem, and a way to handle this with CLISP (but doesn't provide all that much help, aside from the usual good advice) about working around this in SBCL:
The difference is that curses puts the terminal in raw mode to be able to receive the characters from the keyboard one at a time, instead of leaving the terminal in cooked mode, where the unix driver bufferize lines and handles backspace, amongst other niceties.
…
Now, I don't know about SBCL, (check the manual of SBCL). I only have the Implementation Notes of CLISP loaded in my wetware. In CLISP you can use the EXT:WITH-KEYBOARD macro (while the basic output features of curses are provided by the SCREEN package).
Rob Warnock's response included some workaround code for CMUCL that might or might not work for SBCL:
I once wrote the following for CMUCL for an application that wanted to be able to type a single character response to a prompt without messing up the terminal screen:
(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 has probably diverged considerably from CMUCL in this area, but something similar should be doable with SBCL. Start by looking in the SB-UNIX or maybe the SB-POSIX packages...
User vippstar's response provided a link to what might be the most portable solution
Since you want to do something that might not be portable to a microcontroller (but the benifit is the much more enhanced UI), use a non-standard library, such as CL-ncurses.
Adding another answer to point out the existence of this tutorial: cl-charms crash course, by Daniel "jackdaniel" Kochmański. Disabling buffering is related to how the terminal is configured, and cl-charms is a library that exploit the ncurses C library to configure the terminal for interactive usage.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With