{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module System.Terminal.TerminalT ( TerminalT () , runTerminalT ) where import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.STM import Control.Monad.Trans.Class import Control.Monad.Trans.Reader import Data.Foldable (forM_) import qualified Data.Text as Text import Prelude hiding (putChar) import System.Terminal.MonadInput import System.Terminal.MonadPrinter import System.Terminal.MonadScreen import System.Terminal.MonadTerminal import qualified System.Terminal.Terminal as T -- | This monad transformer represents terminal applications. -- -- It implements all classes in this module and should serve as a good -- foundation for most use cases. -- -- Note that it is not necessary nor recommended to have this type in -- every signature. Keep your application abstract and mention `TerminalT` -- only once at the top level. -- -- Example: -- -- @ -- main :: IO () -- main = `withTerminal` (`runTerminalT` myApplication) -- -- myApplication :: (`MonadPrinter` m) => m () -- myApplication = do -- `putTextLn` "Hello world!" -- `flush` -- @ newtype TerminalT t m a = TerminalT (ReaderT t m a) deriving (Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask) -- | Run a `TerminalT` application on the given terminal. runTerminalT :: (MonadIO m, MonadMask m, T.Terminal t) => TerminalT t m a -> t -> m a runTerminalT tma t = runReaderT ma t where TerminalT ma = tma instance MonadTrans (TerminalT t) where lift = TerminalT . lift instance (MonadIO m, T.Terminal t) => MonadInput (TerminalT t m) where awaitWith f = TerminalT do t <- ask liftIO $ atomically $ f (T.termInterrupt t) (T.termEvent t) instance (MonadIO m, MonadThrow m, T.Terminal t) => MonadPrinter (TerminalT t m) where putLn = command T.PutLn putChar c = command (T.PutText $ Text.singleton c) putString cs = forM_ cs (command . T.PutText . Text.singleton) putText t = command (T.PutText t) flush = TerminalT do t <- ask liftIO $ T.termFlush t getLineWidth = TerminalT do t <- ask liftIO (width <$> T.termGetWindowSize t) instance (MonadIO m, MonadThrow m, T.Terminal t) => MonadMarkupPrinter (TerminalT t m) where data Attribute (TerminalT t m) = AttributeT T.Attribute deriving (Eq, Ord, Show) setAttribute (AttributeT a) = command (T.SetAttribute a) resetAttribute (AttributeT a) = command (T.ResetAttribute a) resetAttributes = command T.ResetAttributes resetsAttribute (AttributeT T.Bold {}) (AttributeT T.Bold {}) = True resetsAttribute (AttributeT T.Italic {}) (AttributeT T.Italic {}) = True resetsAttribute (AttributeT T.Underlined {}) (AttributeT T.Underlined {}) = True resetsAttribute (AttributeT T.Inverted {}) (AttributeT T.Inverted {}) = True resetsAttribute (AttributeT T.Foreground {}) (AttributeT T.Foreground {}) = True resetsAttribute (AttributeT T.Foreground {}) (AttributeT T.Background {}) = True resetsAttribute _ _ = False instance (MonadIO m, MonadThrow m, T.Terminal t) => MonadFormattingPrinter (TerminalT t m) where bold = AttributeT T.Bold italic = AttributeT T.Italic underlined = AttributeT T.Underlined inverted = AttributeT T.Inverted instance (MonadIO m, MonadThrow m, T.Terminal t) => MonadColorPrinter (TerminalT t m) where data Color (TerminalT t m) = ColorT T.Color deriving (Eq, Ord, Show) black = ColorT T.Black red = ColorT T.Red green = ColorT T.Green yellow = ColorT T.Yellow blue = ColorT T.Blue magenta = ColorT T.Magenta cyan = ColorT T.Cyan white = ColorT T.White bright (ColorT T.Black ) = ColorT T.BrightBlack bright (ColorT T.Red ) = ColorT T.BrightRed bright (ColorT T.Green ) = ColorT T.BrightGreen bright (ColorT T.Yellow ) = ColorT T.BrightYellow bright (ColorT T.Blue ) = ColorT T.BrightBlue bright (ColorT T.Magenta) = ColorT T.BrightMagenta bright (ColorT T.Cyan ) = ColorT T.BrightCyan bright (ColorT T.White ) = ColorT T.BrightWhite bright (ColorT c ) = ColorT c foreground (ColorT c) = AttributeT (T.Foreground c) background (ColorT c) = AttributeT (T.Background c) instance (MonadIO m, MonadThrow m, T.Terminal t) => MonadScreen (TerminalT t m) where getWindowSize = TerminalT (liftIO . T.termGetWindowSize =<< ask) moveCursorUp i | i > 0 = command (T.MoveCursorUp i) | i < 0 = moveCursorDown i | otherwise = pure () moveCursorDown i | i > 0 = command (T.MoveCursorDown i) | i < 0 = moveCursorUp i | otherwise = pure () moveCursorForward i | i > 0 = command (T.MoveCursorForward i) | i < 0 = moveCursorBackward i | otherwise = pure () moveCursorBackward i | i > 0 = command (T.MoveCursorBackward i) | i < 0 = moveCursorForward i | otherwise = pure () getCursorPosition = TerminalT (liftIO . T.termGetCursorPosition =<< ask) setCursorPosition pos = command (T.SetCursorPosition pos) setCursorRow i = command (T.SetCursorRow i) setCursorColumn i = command (T.SetCursorColumn i) saveCursor = command T.SaveCursor restoreCursor = command T.RestoreCursor insertChars i = do command (T.InsertChars i) deleteChars i = do command (T.DeleteChars i) eraseChars i = do command (T.EraseChars i) insertLines i = do command (T.InsertLines i) deleteLines i = do command (T.DeleteLines i) eraseInLine = command . T.EraseInLine eraseInDisplay = command . T.EraseInDisplay showCursor = command T.ShowCursor hideCursor = command T.HideCursor setAutoWrap x = command (T.SetAutoWrap x) setAlternateScreenBuffer x = command (T.SetAlternateScreenBuffer x) instance (MonadIO m, MonadThrow m, T.Terminal t) => MonadTerminal (TerminalT t m) where command :: (MonadIO m, MonadThrow m, T.Terminal t) => T.Command -> TerminalT t m () command c = TerminalT do t <- ask liftIO $ T.termCommand t c