Reading a character without requiring the Enter button 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.