packages feed

sindre-0.1: Sindre/ANSI.hs

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Sindre.ANSI
-- License     :  MIT-style (see LICENSE)
--
-- Stability   :  provisional
-- Portability :  unportable
--
-- ANSI backend for Sindre.
--
-----------------------------------------------------------------------------
module Sindre.ANSI( SindreANSIM
                  , sindreANSI
                  )
    where

import Sindre.Sindre
import Sindre.Compiler
import Sindre.Lib
import Sindre.Runtime
import Sindre.Util
import Sindre.Widgets

import System.Console.ANSI

import System.Environment
import System.Exit
import System.IO
import System.Posix.Types

import Control.Arrow(first,second)
import Control.Concurrent
import Control.Applicative
import Control.Exception
import Control.Monad.Reader
import Control.Monad.State
import Data.Bits
import Data.Char hiding (Control)
import Data.Maybe
import Data.List
import qualified Data.ByteString as B
import qualified Data.Map as M
import Data.Monoid
import Data.Ord
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Encoding as E

import Prelude hiding (catch)


-- | The read-only configuration of the ANSI backend, created during
-- backend initialisation.
data SindreANSIConf = SindreANSIConf {
    sindreTerminal   :: Handle -- ^ Where we're being displayed.
  , sindreVisualOpts :: VisualOpts
  -- ^ The default visual options used if no others are specified for
  -- a widget.
  , sindreEvtVar     :: MVar Event
  -- ^ Channel through which events are sent by other threads to the
  -- Sindre command loop.
  }

-- | Sindre backend using ANSI.
newtype SindreANSIM a = SindreANSIM (ReaderT SindreANSIConf (StateT Rectangle IO) a)
  deriving ( Functor, Monad, MonadIO, MonadReader SindreANSIConf
           , MonadState Rectangle, Applicative)

runSindreANSI :: SindreANSIM a -> SindreANSIConf -> Rectangle -> IO a
runSindreANSI (SindreANSIM m) = evalStateT . runReaderT m

instance MonadBackend SindreANSIM where
  type BackEvent SindreANSIM = Char
  type RootPosition SindreANSIM = ()

  redrawRoot = do
    (orient, rootwr) <- gets rootWidget
    reqs <- compose rootwr
    winsize <- back get
    let orient' = fromMaybe () orient
        rect = fitRect winsize reqs
    draw rootwr $ Just rect
    return ()

  redrawRegion _ = return ()
  
  waitForBackEvent = do
    evvar <- back $ asks sindreEvtVar
    io $ takeMVar evvar
  
  getBackEvent = do
    io yield
    back (io . tryTakeMVar =<< asks sindreEvtVar)

  printVal s = io $ putStr s *> hFlush stdout

setupTerminal :: IO Handle
setupTerminal = do h <- openFile "/dev/tty" ReadWriteMode
                   hSetBuffering h NoBuffering
                   hSetEcho h False
                   return h

getKeypress :: Handle -> IO Chord
getKeypress h = (S.empty,) <$> CharKey <$> hGetChar h

eventReader :: Handle -> MVar Event -> IO ()
eventReader h evvar = forever $ (putMVar evvar . KeyPress) =<< getKeypress h

sindreANSICfg :: IO SindreANSIConf
sindreANSICfg = do
  h <- setupTerminal
  visopts <- defVisualOpts
  evvar <- newEmptyMVar
  xlock <- newMVar ()
  _ <- forkIO $ eventReader h evvar
  return SindreANSIConf { sindreTerminal = h
                        , sindreVisualOpts = visopts
                        , sindreEvtVar = evvar }

-- | Options regarding visual appearance of widgets (colours and
-- fonts).
data VisualOpts = VisualOpts {
      foreground      :: Color
    , background      :: Color
    , focusForeground :: Color
    , focusBackground :: Color
    }

defVisualOpts :: IO VisualOpts
defVisualOpts = pure $ VisualOpts Black White White Blue

-- | Execute Sindre in the ANSI backend.
sindreANSI :: SindreANSIM ExitCode
           -- ^ The function returned by
           -- 'Sindre.Compiler.compileSindre' after command line
           -- options have been given
           -> IO ExitCode
sindreANSI start = do
  cfg <- sindreANSICfg
  rows <- read <$> getEnv "LINES"
  cols <- read <$> getEnv "COLUMNS"
  runSindreANSI start cfg $ Rectangle 0 0 rows cols