{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS_HADDOCK hide #-} {- This file is part of the package byline. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at git://pmade.com/byline/LICENSE. No part of the byline package, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. -} -------------------------------------------------------------------------------- -- | Internal module containing the @Byline@ monad transformer. module System.Console.Byline.Internal.Byline ( Byline (..) , Env (..) , eof , liftBase , liftInputT , runByline ) where -------------------------------------------------------------------------------- -- Library imports: import Control.Applicative import Control.Monad.Catch import Control.Monad.Reader import Control.Monad.Trans.Maybe import Data.IORef import System.Environment (lookupEnv) import System.IO (Handle, stdout) import qualified System.Terminfo as Term import qualified System.Terminfo.Caps as Term -------------------------------------------------------------------------------- -- Import Haskeline, which is used to do the heavy lifting. import qualified System.Console.Haskeline as H import qualified System.Console.Haskeline.IO as H -------------------------------------------------------------------------------- -- Byline imports: import System.Console.Byline.Internal.Completion import System.Console.Byline.Internal.Render -------------------------------------------------------------------------------- -- The following is a kludge to avoid the "redundant import" warning -- when using GHC >= 7.10.x. This should be removed after we decide -- to stop supporting GHC < 7.10.x. import Prelude -------------------------------------------------------------------------------- -- | Reader environment for Byline. data Env = Env { sayMode :: RenderMode , askMode :: RenderMode , outHandle :: Handle , inputState :: H.InputState , compFunc :: IORef (Maybe CompletionFunc) } -------------------------------------------------------------------------------- -- | A monad transformer that encapsulates interactive actions. newtype Byline m a = Byline {unByline :: ReaderT Env (MaybeT m) a} deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO) instance MonadTrans Byline where lift = liftBase -------------------------------------------------------------------------------- -- | Calculate the default rendering modes based on the terminal type. defRenderMode :: H.InputT IO (RenderMode, RenderMode) defRenderMode = do termHint <- H.haveTerminalUI maxColors <- liftIO (runMaybeT getMaxColors) return $ case (termHint, maxColors) of (True, Just n) | n < 256 -> (Simple, Simple) | otherwise -> (Term256, Term256) (True, Nothing) -> (Simple, Plain) (False, _) -> (Plain, Plain) where getMaxColors :: MaybeT IO Int getMaxColors = do term <- MaybeT (lookupEnv "TERM") db <- liftIO (Term.acquireDatabase term) case db of Left _ -> MaybeT (return Nothing) Right d -> MaybeT (return $ Term.queryNumTermCap d Term.MaxColors) -------------------------------------------------------------------------------- -- | Create the default reader environment. defEnv :: H.InputState -> (RenderMode, RenderMode) -> IORef (Maybe CompletionFunc) -> Env defEnv state (smode, amode) comp = Env { sayMode = smode , askMode = amode , outHandle = stdout , inputState = state , compFunc = comp } -------------------------------------------------------------------------------- -- | Signal an EOF and terminate all Byline actions. eof :: (Monad m) => Byline m a eof = Byline $ lift (MaybeT $ return Nothing) -------------------------------------------------------------------------------- -- | Lift an operation in the base monad into Byline. liftBase :: (Monad m) => m a -> Byline m a liftBase = Byline . lift . lift -------------------------------------------------------------------------------- -- | Lift an 'InputT' action into 'Byline'. liftInputT :: (MonadIO m) => H.InputT IO a -> Byline m a liftInputT input = do state <- asks inputState liftIO (H.queryInput state $ H.withInterrupt input) -------------------------------------------------------------------------------- -- | Execute 'Byline' actions and produce a result within the base monad. -- -- /A note about EOF:/ -- -- If an End of File (EOF) is encountered during an input action then -- this function will return @Nothing@. This can occur when the user -- manually enters an EOF character by pressing @Control-d@ or if -- standard input is a file. -- -- This decision was made to simplify the @Byline@ interface for -- actions that read user input and is a typical strategy for terminal -- applications. If this isn't desirable, you may want to break your -- actions up into groups and call 'runByline' multiple times. runByline :: (MonadIO m, MonadMask m) => Byline m a -> m (Maybe a) runByline (Byline byline) = do comp <- liftIO (newIORef Nothing) let settings = H.setComplete (runCompletionFunction comp) H.defaultSettings bracketOnError (liftIO $ H.initializeInput settings) -- Acquire. (liftIO . H.cancelInput) -- Release. (go comp) -- Use. where go comp state = do modes <- liftIO (H.queryInput state defRenderMode) output <- runMaybeT $ runReaderT byline (defEnv state modes comp) liftIO (H.closeInput state) return output