ztail-1.2: Display.hs
{-# LANGUAGE PatternGuards, ViewPatterns, GeneralizedNewtypeDeriving #-}
module Display
( TermColor
, parseColor
, rawErrMsg
, Output(..)
, runOutput
) where
import Control.Concurrent (forkIO)
import Control.Concurrent.Chan (newChan, readChan, writeChan)
import qualified Data.ByteString.Char8 as BS
import Data.Char (isDigit, isAlphaNum)
import Data.List (stripPrefix)
import Data.Monoid ((<>))
import System.IO (stdout, stderr, hPutStrLn)
newtype TermColor = TermColor [Int] deriving (Monoid)
displayColor :: TermColor -> BS.ByteString
displayColor (TermColor []) = BS.empty
displayColor (TermColor c) = esc <> BS.intercalate semi (map (BS.pack . show) c) `BS.snoc` 'm' where
esc = BS.pack "\ESC["
semi = BS.singleton ';'
displayResetColor :: BS.ByteString
displayResetColor = displayColor colorReset
isInteger :: String -> Bool
isInteger [] = False
isInteger x = all isDigit x
colorReset :: TermColor
colorReset = colorValue "reset"
-- TODO: use terminfo!
colorValue :: String -> TermColor
colorValue "reset" = TermColor [0]
colorValue "bo" = TermColor [1]
--colorValue "dark" = TermColor [2]
colorValue "so" = TermColor [3]
colorValue "hl" = TermColor [3]
colorValue "ul" = TermColor [4]
colorValue "bl" = TermColor [5]
colorValue "rev" = TermColor [7]
colorValue "hidden" = TermColor [8]
colorValue "nobo" = TermColor [22]--[21]
--colorValue "nodark" = TermColor [22]
colorValue "noso" = TermColor [23]
colorValue "nohl" = TermColor [23]
colorValue "noul" = TermColor [24]
colorValue "nobl" = TermColor [25]
colorValue "norev" = TermColor [27]
colorValue "nohidden" = TermColor [28]
colorValue "black" = TermColor [30]
colorValue "red" = TermColor [31]
colorValue "green" = TermColor [32]
colorValue "yellow" = TermColor [33]
colorValue "blue" = TermColor [34]
colorValue "magenta" = TermColor [35]
colorValue "cyan" = TermColor [36]
colorValue "white" = TermColor [37]
colorValue "default" = TermColor [39]
colorValue ('c':'o':'l':'o':'r':n) | isInteger n = TermColor [38,5,read n]
colorValue ('m':'o':'d':'e':n) | isInteger n = TermColor [read n]
colorValue ('b':'r':'i':'g':'h':'t':(colorValue -> TermColor [n])) | n >= 30 && n < 40 = TermColor [60 + n]
colorValue ('b':'r':(colorValue -> TermColor [n])) | n >= 30 && n < 40 = TermColor [60 + n]
colorValue ('/':(colorValue -> TermColor (n:r))) = TermColor $ 10+n:r
colorValue "normal" = colorValue "reset"
colorValue "bold" = colorValue "bo"
colorValue "nobold" = colorValue "nobo"
colorValue "dim" = colorValue "dark"
colorValue "nodim" = colorValue "nodark"
colorValue "standout" = colorValue "so"
colorValue "nostandout" = colorValue "noso"
colorValue "hilite" = colorValue "hl"
colorValue "nohilite" = colorValue "nohl"
colorValue "underline" = colorValue "ul"
colorValue "nounderline"= colorValue "noul"
colorValue "blink" = colorValue "bl"
colorValue "noblink" = colorValue "nobl"
colorValue "reverse" = colorValue "rev"
colorValue "noreverse" = colorValue "norev"
colorValue x = error ("unknown color name: " ++ x)
colorSep :: Char -> Bool
colorSep = not . isAlphaNum
parseColor :: String -> TermColor
parseColor [] = mempty
parseColor s@(c:s')
| colorSep c = parseColor s'
| otherwise = colorValue x <> parseColor r
where
(x, r) = break colorSep s
displayColorFrom :: TermColor -> TermColor -> BS.ByteString
displayColorFrom (TermColor o) c@(TermColor n)
| Just d <- stripPrefix o n = displayColor $ TermColor d
| otherwise = displayColor $ colorReset <> c
rawErrMsg :: String -> IO ()
rawErrMsg m = BS.hPutStr stderr displayResetColor >> hPutStrLn stderr m
data Output
= OutputLine
{ outputColor :: !TermColor
, outputText :: !BS.ByteString
}
| OutputError
{ outputText :: !BS.ByteString
}
runOutput :: IO (Output -> IO ())
runOutput = do
chan <- newChan
let
loop p = do
o <- readChan chan
let (h, c, t) = case o of
OutputLine{ outputColor = c', outputText = t' } -> (stdout, c', t')
OutputError{ outputText = t' } -> (stderr, mempty, t')
BS.hPutStr h $ displayColorFrom p c
BS.hPutStrLn h t
loop c
_ <- forkIO $ loop mempty
return $ writeChan chan