judah / haskeline

A Haskell library for line input in command-line programs.
https://hackage.haskell.org/package/haskeline
BSD 3-Clause "New" or "Revised" License
221 stars 75 forks source link

Stacking InputT leads to loss of functionality #158

Open jrp2014 opened 3 years ago

jrp2014 commented 3 years ago

I have started with a n mtl stack type Forth w a r = ExceptT VMSignal (StateT (VM w a) IO) r and want to use haskeline to provide command line editing to my repl. I've refactored to use type Forth w a r = CME.ExceptT VMSignal (StateT (VM w a) (InputT IO)) r.

And functions which previously ran the stack now have calls like

repl :: (ForthType a, Eq a) => VM w a -> ForthStep w a -> IO ()
repl vm initF = do
  catchSigint vm
  let behaviour = maybe defaultBehavior useFileHandle (inputPort vm)
  (r, vm') <- runInputTBehavior behaviour
                                defaultSettings
                                (runStateT (runExceptT initF) vm)
:

and

fwRefill :: ForthStep w a
fwRefill = do
  vm <- getVm
  case inputPort vm of
    Nothing -> throwError VMNoInput
    Just h  -> do
--      eof <- liftIO (hIsEOF h)
--      when eof (CME.throwError VMEOF)
      trace 2 "REFILL"
      line <- runInputTBehavior (useFileHandle h) defaultSettings
        $ getInputLine "> "
      maybe (throwError VMEOF) (\b -> put (vm { buffer = b })) line
      next

The program works as before (and the > prompt appears. but I can't scroll back into history and the arrow keys still don't work (you get > ^[[A on pushing the up arrow, for example (as before). I don't want to put the InputT on the outside as I'd then have to sprinkle all my code with lifts, which I'd prefer to avoid.

What can I do to get command line editing to function, please?

judah commented 3 years ago

The isue you're having is that you're creating/destroying an InputT context every time you read user input. Ideally you'd only call runInputT once, in your main function. (I believe could still keep InputT at the bottom of your stack as InputT IO as you have now.)

Adapting the example from the Haddocks: https://hackage.haskell.org/package/haskeline-0.8.1.3/docs/System-Console-Haskeline.html

main :: IO ()
main = runInputT defaultSettings (evalStateT loop 0)

loop :: StateT Int (InputT IO) ()
loop = do
    n <- get
    minput <-  lift $ getInputLine (show n ++ ":")
    case minput of
        Nothing -> return ()
        Just "quit" -> return ()
        Just "q" -> return ()
        Just s -> do
                    lift $ outputStrLn ("line " ++ show n ++ ":" ++ s)
                    modify (+1)
                    loop
judah commented 3 years ago

Another option, if you're having issues with the monad transformer, is the System.Console.Haskeline.IO module: https://hackage.haskell.org/package/haskeline-0.8.1.3/docs/System-Console-Haskeline-IO.html

That lets you run it in raw IO, at the expense of having to bracket your own initialization/cleanup.