{- - - Copyright 2005-2007, Robert Dockins. - -} {- | This module implements a Shellac backend based on the GNU readline and GNU history modules. For readline, we use the bindings from the standard library. For history, we import directily using FFI. This Shellac backend supports command completion, history buffer and all the line editing and character binding features of GNU readline. Beware that while the code for this Shellac binding is licensed under a BSD3 license, GNU readline itself is licensed under the GPL. This means that your project needs to be GPL compatible for this Shellac backend to be useful to you. -} module System.Console.Shell.Backend.Readline ( readlineBackend ) where import System.IO ( stdin, stdout, stderr, hFlush, hPutStr, hPutStrLn, hGetChar , hSetBuffering, hGetBuffering , BufferMode(..) ) import Foreign.Ptr ( Ptr ) import Foreign.C ( CInt, CString, withCString ) import Foreign.C.Error ( Errno, eOK, errnoToIOError ) import Foreign.Storable ( peek ) import qualified Control.Exception as Ex import qualified System.Console.Readline as RL import System.Console.Shell.Backend readlineBackend :: ShellBackend () readlineBackend = ShBackend { initBackend = doReadlineInit , shutdownBackend = \_ -> doReadlineShutdown , outputString = \_ -> readlineOutput , flushOutput = \_ -> hFlush stdout , getInput = \_ -> RL.readline , getSingleChar = \_ -> readlineGetSingleChar , addHistory = \_ -> RL.addHistory , getWordBreakChars = \_ -> RL.getBasicWordBreakCharacters , setWordBreakChars = \_ -> RL.setBasicWordBreakCharacters , onCancel = \_ -> hPutStrLn stdout "canceled..." , setAttemptedCompletionFunction = \_ -> readlineCompletionFunction , setDefaultCompletionFunction = \_ -> RL.setCompletionEntryFunction , completeFilename = \_ -> RL.filenameCompletionFunction , completeUsername = \_ -> RL.usernameCompletionFunction , clearHistoryState = \_ -> doClearHistoryState , setMaxHistoryEntries = \_ -> doSetMaxHistoryEntries , getMaxHistoryEntries = \_ -> doGetMaxHistoryEntries , readHistory = \_ -> doReadHistory , writeHistory = \_ -> doWriteHistory } readlineCompletionFunction :: CompletionFunction -> IO () readlineCompletionFunction f = RL.setAttemptedCompletionFunction (Just complete) where complete word begin end = do buffer <- RL.getLineBuffer let before = take begin buffer let after = drop end buffer f (before,word,after) readlineGetSingleChar :: String -> IO (Maybe Char) readlineGetSingleChar prompt = do hPutStr stdout prompt hFlush stdout Ex.bracket (hGetBuffering stdin) (hSetBuffering stdin) $ \_ -> do hSetBuffering stdin NoBuffering c <- hGetChar stdin hPutStrLn stdout "" return (Just c) foreign import ccall "readline/history.h clear_history" clear_history :: IO () foreign import ccall "readline/history.h stifle_history" stifle_history :: CInt -> IO () foreign import ccall "readline/history.h read_history" read_history :: CString -> IO Errno foreign import ccall "readline/history.h write_history" write_history :: CString -> IO Errno foreign import ccall "readline/history.h &history_max_entries" history_max_entries :: Ptr CInt foreign import ccall "readline/history.h using_history" using_history :: IO () doReadlineInit :: IO () doReadlineInit = do using_history return () doReadlineShutdown :: IO () doReadlineShutdown = do return () doClearHistoryState :: IO () doClearHistoryState = clear_history doSetMaxHistoryEntries :: Int -> IO () doSetMaxHistoryEntries m = stifle_history (fromIntegral m) doGetMaxHistoryEntries :: IO Int doGetMaxHistoryEntries = peek history_max_entries >>= return . fromIntegral doReadHistory :: FilePath -> IO () doReadHistory path = do err <- withCString path read_history if err == eOK then return () else ioError $ errnoToIOError "System.Console.Shell.Backend.Readline.doReadHistory" err Nothing (Just path) doWriteHistory :: FilePath -> IO () doWriteHistory path = do err <- withCString path write_history if err == eOK then return () else ioError $ errnoToIOError "System.Console.Shell.Backend.Readline.doWriteHistory" err Nothing (Just path) readlineOutput :: BackendOutput -> IO () readlineOutput (RegularOutput str) = hPutStr stdout str readlineOutput (InfoOutput str) = hPutStr stdout str readlineOutput (ErrorOutput str) = hPutStr stderr str