module System.Console.Haskeline.Command( -- * Commands Effect(..), KeyMap(..), CmdM(..), Command, KeyCommand, KeyConsumed(..), withoutConsuming, keyCommand, (>|>), (>+>), try, effect, clearScreenCmd, finish, failCmd, simpleCommand, charCommand, setState, change, changeFromChar, (+>), useChar, choiceCmd, keyChoiceCmd, keyChoiceCmdM, doBefore ) where import Data.Char(isPrint) import Control.Applicative(Applicative(..)) import Control.Monad(ap, mplus, liftM) import Control.Monad.Trans.Class import System.Console.Haskeline.LineState import System.Console.Haskeline.Key data Effect = LineChange (Prefix -> LineChars) | PrintLines [String] | ClearScreen | RingBell lineChange :: LineState s => s -> Effect lineChange = LineChange . flip lineChars data KeyMap a = KeyMap {lookupKM :: Key -> Maybe (KeyConsumed a)} data KeyConsumed a = NotConsumed a | Consumed a instance Functor KeyMap where fmap f km = KeyMap $ fmap (fmap f) . lookupKM km instance Functor KeyConsumed where fmap f (NotConsumed x) = NotConsumed (f x) fmap f (Consumed x) = Consumed (f x) data CmdM m a = GetKey (KeyMap (CmdM m a)) | DoEffect Effect (CmdM m a) | CmdM (m (CmdM m a)) | Result a type Command m s t = s -> CmdM m t instance Monad m => Functor (CmdM m) where fmap = liftM instance Monad m => Applicative (CmdM m) where pure = Result (<*>) = ap instance Monad m => Monad (CmdM m) where return = pure GetKey km >>= g = GetKey $ fmap (>>= g) km DoEffect e f >>= g = DoEffect e (f >>= g) CmdM f >>= g = CmdM $ liftM (>>= g) f Result x >>= g = g x type KeyCommand m s t = KeyMap (Command m s t) instance MonadTrans CmdM where lift m = CmdM $ do x <- m return $ Result x keyCommand :: KeyCommand m s t -> Command m s t keyCommand km = \s -> GetKey $ fmap ($ s) km useKey :: Key -> a -> KeyMap a useKey k x = KeyMap $ \k' -> if k==k' then Just (Consumed x) else Nothing -- TODO: could just be a monadic action that returns a Char. useChar :: (Char -> Command m s t) -> KeyCommand m s t useChar act = KeyMap $ \k -> case k of Key m (KeyChar c) | isPrint c && m==noModifier -> Just $ Consumed (act c) _ -> Nothing withoutConsuming :: Command m s t -> KeyCommand m s t withoutConsuming = KeyMap . const . Just . NotConsumed choiceCmd :: [KeyMap a] -> KeyMap a choiceCmd = foldl orKM nullKM where nullKM = KeyMap $ const Nothing orKM (KeyMap f) (KeyMap g) = KeyMap $ \k -> f k `mplus` g k keyChoiceCmd :: [KeyCommand m s t] -> Command m s t keyChoiceCmd = keyCommand . choiceCmd keyChoiceCmdM :: [KeyMap (CmdM m a)] -> CmdM m a keyChoiceCmdM = GetKey . choiceCmd infixr 6 >|> (>|>) :: Monad m => Command m s t -> Command m t u -> Command m s u f >|> g = \x -> f x >>= g infixr 6 >+> (>+>) :: Monad m => KeyCommand m s t -> Command m t u -> KeyCommand m s u km >+> g = fmap (>|> g) km -- attempt to run the command (predicated on getting a valid key); but if it fails, just keep -- going. try :: Monad m => KeyCommand m s s -> Command m s s try f = keyChoiceCmd [f,withoutConsuming return] infixr 6 +> (+>) :: Key -> a -> KeyMap a (+>) = useKey finish :: (Monad m, Result s) => Command m s (Maybe String) finish = return . Just . toResult failCmd :: Monad m => Command m s (Maybe a) failCmd _ = return Nothing effect :: Effect -> CmdM m () effect e = DoEffect e $ Result () clearScreenCmd :: Command m s s clearScreenCmd = DoEffect ClearScreen . Result simpleCommand :: (LineState s, Monad m) => (s -> m (Either Effect s)) -> Command m s s simpleCommand f = \s -> do et <- lift (f s) case et of Left e -> effect e >> return s Right t -> setState t charCommand :: (LineState s, Monad m) => (Char -> s -> m (Either Effect s)) -> KeyCommand m s s charCommand f = useChar $ simpleCommand . f setState :: (Monad m, LineState s) => Command m s s setState s = effect (lineChange s) >> return s change :: (LineState t, Monad m) => (s -> t) -> Command m s t change = (setState .) changeFromChar :: (LineState t, Monad m) => (Char -> s -> t) -> KeyCommand m s t changeFromChar f = useChar $ change . f doBefore :: Monad m => Command m s t -> KeyCommand m t u -> KeyCommand m s u doBefore cmd = fmap (cmd >|>)