Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell read raw keyboard input

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.

like image 226
MathematicalOrchid Avatar asked Apr 14 '14 19:04

MathematicalOrchid


3 Answers

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.

like image 87
Evi1M4chine Avatar answered Nov 10 '22 00:11

Evi1M4chine


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
like image 29
Stephen Diehl Avatar answered Nov 10 '22 00:11

Stephen Diehl


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.

like image 8
MathematicalOrchid Avatar answered Nov 09 '22 23:11

MathematicalOrchid