I need to real lines until ESC button is pressed. How can I check it?
lines
= do
line <- getLine
if (== "/ESC") --this condition is wrong
then ...
else do
ln <- lines
return ...
Could anybody fix my problem?
The correct way to escape is with a backslash, the character is '\ESC', so the condition would be
if line == "\ESC"
But I'm not sure every terminal passes an '\ESC' through to the application.
If you want to stop immediately when the ESC key is pressed, something along the lines of
module Main (main) where
import System.IO
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
getUntilEsc ""
getUntilEsc :: String -> IO ()
getUntilEsc acc = do
c <- getChar
case c of
'\ESC' -> return ()
'\n' -> do putStrLn $ "You entered " ++ reverse acc
getUntilEsc ""
_ -> getUntilEsc (c:acc)
is what you need. You have to read character-wise, and you need to turn off the buffering of stdin, so the characters are immediately available for reading, and not only after a newline has been entered.
Note that on Windows turning off buffering didn't work. I don't know if this has been fixed recently.
Also, as #Daniel Wagner reported, it may well be that the Windows command prompt doesn't pass the ESC to the application.
Related
I'm writing a terminal-mode program in Haskell. How would I go about reading raw keypress information?
In particular, there seems to be something providing line-editing facilities on top of Haskell. If I do getLine, I seem to be able to use the up-arrow to get previous lines, edit the text, and only when I press Enter does the text become visible to the Haskell application itself.
What I'm after is the ability to read individual keypresses, so I can implement the line-editing stuff myself.
Perhaps my question was unclear. Basically I want to build something like Vi or Emacs (or Yi). I already know there are terminal bindings that will let me do fancy console-mode printing, so the output side shouldn't be an issue. I'm just looking for a way to get at raw keypress input, so I can do things like (for example) add K to the current line of text when the user presses the letter K, or save the file to disk when the user presses Ctrl+S.
This might be the simplest solution, resembling typical code in other programming languages:
import System.IO (stdin, hReady)
getKey :: IO [Char]
getKey = reverse <$> getKey' ""
where getKey' chars = do
char <- getChar
more <- hReady stdin
(if more then getKey' else return) (char:chars)
It works by reading more than one character “at a time”. Allowing E.g. the ↑ key, which consists of the three characters ['\ESC','[','A'] to be distinguished from an actual \ESC character input.
Usage example:
import System.IO (stdin, hSetEcho, hSetBuffering, NoBuffering)
import Control.Monad (when)
-- Simple menu controller
main = do
hSetBuffering stdin NoBuffering
hSetEcho stdin False
key <- getKey
when (key /= "\ESC") $ do
case key of
"\ESC[A" -> putStr "↑"
"\ESC[B" -> putStr "↓"
"\ESC[C" -> putStr "→"
"\ESC[D" -> putStr "←"
"\n" -> putStr "⎆"
"\DEL" -> putStr "⎋"
_ -> return ()
main
This is a bit hackish, since in theory, a user could input more keys before the program gets to the hReady. Which could happen if the terminal allows pasting. But in practice, for interactive input, this is not a realistic scenario.
Fun fact: The cursor strings can be putStrd to actually move the cursor programmatically.
Sounds like you want readline support. There are a couple of packages to do this, but haskeline is probably the easiest to use with the most supported platforms.
import Control.Monad.Trans
import System.Console.Haskeline
type Repl a = InputT IO a
process :: String -> IO ()
process = putStrLn
repl :: Repl ()
repl = do
minput <- getInputLine "> "
case minput of
Nothing -> outputStrLn "Goodbye."
Just input -> (liftIO $ process input) >> repl
main :: IO ()
main = runInputT defaultSettings repl
Incomplete:
After several hours of web surfing, I can report the following:
readline has a huge interface with virtually no documentation whatsoever. From the function names and type signatures you could maybe guess what this stuff does... but it's far from trivial. At any rate, this library seems to provide a high-level editing interface - which is the thing I'm trying to implement myself. I need something more low-level.
After wading through the source of haskeline, it seems it has a huge tangle low-level code, seperately for Win32 and POSIX. If there is an easy way to do console I/O, this library does not demonstrate it. The code appears to be so tightly integrated and highly specific to haskeline that I doubt I can reuse any of it. But perhaps by reading it I can learn enough to write my own?
Yi is... freaking massive. The Cabal file lists > 150 exposed modules. (!!) It appears, though, that underneath it's using a package called vty, which is POSIX-only. (I wonder how the hell Yi works on Windows then?) vty looks like it might be directly useful to me without further modification. (But again, not on Windows.)
unix has... basically nothing interesting. It has a bunch of stuff to set things on a terminal, but absolutely nothing for reading from a terminal. (Except maybe to check whether echo is on, etc. Nothing about keypresses.)
unix-compat has absolutely nothing of interest.
One option would be to use ncurses. A minimalistic example:
import Control.Monad
import UI.NCurses
main :: IO ()
main = runCurses $ do
w <- defaultWindow
forever $ do
e <- getEvent w Nothing
updateWindow w $ do
moveCursor 0 0
drawString (show e)
render
I think you are looking for hSetBuffering. StdIn is line buffered by default, but
you want to receive the keys right away.
I think the unix library provides the most lightweight solution for this, especially if you have some familiarity with termios, which is mirrored by the System.Posix.Terminal module.
There's a good page over at gnu.org that describes using termios to set up non-canonical input mode for a terminal, and you can do this with System.Posix.Terminal.
Here's my solution, which transforms a computation in IO to use non-canonical mode:
{- from unix library -}
import System.Posix.Terminal
import System.Posix.IO (fdRead, stdInput)
{- from base -}
import System.IO (hFlush, stdout)
import Control.Exception (finally, catch, IOException)
{- run an application in raw input / non-canonical mode with given
- VMIN and VTIME settings. for a description of these, see:
- http://www.gnu.org/software/libc/manual/html_node/Noncanonical-Input.html
- as well as `man termios`.
-}
withRawInput :: Int -> Int -> IO a -> IO a
withRawInput vmin vtime application = do
{- retrieve current settings -}
oldTermSettings <- getTerminalAttributes stdInput
{- modify settings -}
let newTermSettings =
flip withoutMode EnableEcho . -- don't echo keystrokes
flip withoutMode ProcessInput . -- turn on non-canonical mode
flip withTime vtime . -- wait at most vtime decisecs per read
flip withMinInput vmin $ -- wait for >= vmin bytes per read
oldTermSettings
{- install new settings -}
setTerminalAttributes stdInput newTermSettings Immediately
{- restore old settings no matter what; this prevents the terminal
- from becoming borked if the application halts with an exception
-}
application
`finally` setTerminalAttributes stdInput oldTermSettings Immediately
{- sample raw input method -}
tryGetArrow = (do
(str, bytes) <- fdRead stdInput 3
case str of
"\ESC[A" -> putStrLn "\nUp"
"\ESC[B" -> putStrLn "\nDown"
"\ESC[C" -> putStrLn "\nRight"
"\ESC[D" -> putStrLn "\nLeft"
_ -> return ()
) `catch` (
{- if vmin bytes have not been read by vtime, fdRead will fail
- with an EOF exception. catch this case and do nothing.
- The type signature is necessary to allow other exceptions
- to get through.
-}
(const $ return ()) :: IOException -> IO ()
)
{- sample application -}
loop = do
tryGetArrow
putStr "." >> hFlush stdout
loop
{- run with:
- VMIN = 0 (don't wait for a fixed number of bytes)
- VTIME = 1 (wait for at most 1/10 sec before fdRead returns)
-}
main = withRawInput 0 1 $ loop
In contrast to the information in "learn you a haskell", on my windows system, ghci translates CTRL-D to EOT, not EOF.
Thus, when I do something like:
input <- getContents
doSomething input
, where doSomething is a function which consumes the input.
Doing that, I have to press CTRL-Z to end my input text, which makes sense since getContents is intended for process piping...
But if I repeat the above steps a second time, it fails because stdin is closed.
So, while browsing System.IO, I could not find an alternative to getContents, which would react on EOT.
Do I have to write such a function myself or is it to be found in another package, maybe?
Btw, the version of GHCI, I use is 8.2.2.
Also, I do not want single line processing. I am aware of getLine but it is not what I want in this case.
Here is the function I was looking for:
getContentsEOT :: IO String
getContentsEOT =
getChar >>= \c ->
if c == '\EOT'
then return ""
else getContentsEOT >>= \s ->
return (c:s)
I'm a Haskell beginner, I'm just beginning to wrap my head around Monads, but I don't really get it yet. I'm writing a game that consists of asking the user for input, and responding. Here is a simplified version of my function:
getPoint :: IO Point
getPoint = do
putStr "Enter x: "
xStr <- getLine
putStr "Enter y: "
yStr <- getLine
return $ Point (read xStr) (read yStr)
completeUserTurn :: (Board, Player) -> IO (Board, Player)
completeUserTurn (board, player) = do
putStr $ "Enter some value: "
var1 <- getLine
putStr $ "Enter another value: "
var2 <- getLine
putStr $ "Enter a point this time: "
point <- getPoint
if (... the player entered legal values ...) then do
putStr $ "This is what would happen if you did that: {stuff} do you want to do that? (y/n) "
continue <- getLine
if continue == "y" then
return (...updated board..., ...updated player...)
else
completeUserTurn (board, player)
else do
putStr "Invalid Move!\n"
completeUserTurn (board, player)
What's happening is that the prompts will appear out of order with the text that is supposed to appear before the prompt.
Here's an example of what's happening after I compiled the code above:
1
Enter some value: Enter another value:2
3
4
Enter a point this time: Enter x: Enter y: y
Is this correct? (y/n):
The bold are the things I typed in.
Obviously, I have some major conceptual error, but I don't know what. Note that it works correctly in the interpreter and fails when compiled.
As Michael said, the issue is buffering. By default, output is buffered until you print a newline (or until the buffer is full if you have really long lines), so you'll most often see this issue when trying to do same-line prompts using putStr like you're doing.
I suggest defining a small helper function like this to take care of doing the flushing for you:
import System.IO
prompt :: String -> IO String
prompt text = do
putStr text
hFlush stdout
getLine
Now you can simply do
getPoint = do
xStr <- prompt "Enter x: "
yStr <- prompt "Enter y: "
return $ Point (read xStr) (read yStr)
The IO is happening in the correct order. The issue is buffering. If you flush stdout after each putStr, it should work as expecting. You'll need to import hFlush and stdout from System.IO.
The problem wasn't with the order of operations in the IO code. The issue was input and output is by default buffered when using stdin and stdout. This increases the performance of IO in an app, but can cause operations to appear to occur out of order when both stdin and stdout are used.
There is two solutions to this. You can use the hFlush method to force a handle (either stdin or stdout) to be flushed. Eg hFlush stdout, hFlush stdin. A simpler solution (which works fine for interactive apps) is to disable buffering altogether. You can do this by calling the methods hSetBuffering stdout NoBuffering and hSetBuffering stdin NoBuffering before you start your program (ie put those lines in your main method.
Writing a program in Haskell, I am struggling to handle key presses of the form ctrl-s and ctrl-l
I am using the following code:
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
x <- getChar
putStrLn ("You pressed: " ++ [x])
How can I make it recognise the Ctrl button being pressed?
getChar gives you access to characters, not keypresses. Which character you get depends on your user's operating system, keyboard layout, and choice of input method. There is no 'standard' Character which will be generated by the keypresses Ctrl-S or Ctrl-L (although, certainly, some systems will give the standard ASCII codes for those control characters, others will not).
If you want proper keypress handling you need a real input library - like, for example, SDL or WxWidgets or GTK; each of which is much more than just an input library but they do have keypress abstractions.
I am currently on a non-unix system (causing unix, a dependency of vty, to fail to install), but the following may work.
import Control.Exception
import Graphics.Vty.LLInput
import System.Console.Terminfo
main :: IO ()
main = do
term <- setupTermFromEnv
bracket (initTermInput 0 term) (\ (_, exit) -> exit) $ \ (readEvent, _) -> do
let readKeyEvent = do
ev <- readEvent
case ev of
EvKey k ms -> return (k, ms)
_ -> readKeyEvent
readKeyEvent >>= print
I'm pretty new to Haskell, so I'm looking for a simple-ish way to detect keypresses, rather than using getLine.
If anyone knows any libraries, or some trick to doing this, it would be great!
And if there is a better place to ask this, please direct me there, I'd appreciate it.
If you don't want blocking you can use hReady to detect whether a key has been pressed yet. This is useful for games where you want the program to run and pick up a key press whenever it has happened without pausing the game.
Here's a convenience function I use for this:
ifReadyDo :: Handle -> IO a -> IO (Maybe a)
ifReadyDo hnd x = hReady hnd >>= f
where f True = x >>= return . Just
f _ = return Nothing
Which can be used like this:
stdin `ifReadyDo` getChar
Returning a Maybe that is Just if a key was pressed and Nothing otherwise.
import System.IO
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
x <- getChar
putStrLn ("You pressed: " ++ [x])
I don't know when this is guaranteed to work. Putting the terminal into a "raw" mode is a system-dependent process. But it works for me with GHC 6.12.1 on Linux.