# Noncanonical terminal input in SML In a Unix terminal environment, when we ask for input from stdin, the terminal will usually buffer the user's input and not send it until the user enters a linebreak. This is usually desirable, as it allows line editing (backspaces and such) to occur without the programs knowledge. However, sometimes a program does not want to wait for a linebreak to be entered - perhaps because it wants to present a responsive user interface. In such cases we have to ask the terminal not to perform such buffering. On POSIX systems this is done via the interface in `termios.h`. This small program demonstrates how to use `termios.h` facilities to perform raw terminal input in Standard ML. The Basis Library provides the [`Posix.TTY` structure](https://smlfamily.github.io/Basis/posix-tty.html), which seems to cover most of `termios.h`. We use the function `Posix.TTY.TC.getattr` to retrieve the terminal attributes from stdin (as a value of type `termios`), which we can then manipulate through various functions, and finally we replace the terminal attribute set with our modified one. In particular, we disable "canonical mode" (which is what provides line buffering and similar things), and we also disable echoing, which means the user input is not echoed by the terminal. We also return a function that restores the original attribute set of the terminal, which must be called when our program is done, as otherwise we will leave the terminal in an unsuitable state for future programs. ```SML fun noncanon () = let val oldattr = Posix.TTY.TC.getattr Posix.FileSys.stdin val {iflag, oflag, cflag, lflag, cc, ispeed, ospeed} = Posix.TTY.fieldsOf oldattr val lflag = Posix.TTY.L.clear (lflag, Posix.TTY.L.flags [Posix.TTY.L.echo, Posix.TTY.L.icanon]) val newattr = Posix.TTY.termios { iflag = iflag , oflag = oflag , cflag = cflag , lflag = lflag , cc = cc , ispeed = ispeed , ospeed = ospeed } val () = Posix.TTY.TC.setattr (Posix.FileSys.stdin, Posix.TTY.TC.sanow, newattr) in fn () => Posix.TTY.TC.setattr (Posix.FileSys.stdin, Posix.TTY.TC.sanow, oldattr) end ``` We can use `noncanon` as follows: ```SML fun main () = let val restore = noncanon () fun loop () = case TextIO.input1 TextIO.stdIn of NONE => (print "EOF\n"; restore ()) | SOME #"q" => restore () | SOME c => ( print ("char: " ^ str c ^ " (" ^ Int.toString (ord c) ^ ")\n") ; loop () ) in loop () end val () = main () ``` Run this program and note how each character is processed immediately, without waiting for a linebreak. Note also that some input (such as arrow keys) are sent as multiple bytes, and that interrupts such as `Ctrl-c` are also made available as characters.