{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-} module Text.Colour.Chunk where import Data.ByteString (ByteString) import Data.ByteString.Builder (Builder) import qualified Data.ByteString.Builder as SBB import qualified Data.ByteString.Lazy as LB import Data.Maybe import Data.String import Data.Text (Text) import qualified Data.Text.Encoding as TE import Data.Word import GHC.Generics (Generic) import Text.Colour.Capabilities import Text.Colour.Code data Chunk = Chunk { chunkText :: !Text, chunkItalic :: !(Maybe Bool), chunkConsoleIntensity :: !(Maybe ConsoleIntensity), chunkUnderlining :: !(Maybe Underlining), chunkForeground :: !(Maybe Colour), chunkBackground :: !(Maybe Colour) } deriving (Show, Eq, Generic) instance IsString Chunk where fromString = chunk . fromString plainChunk :: TerminalCapabilities -> Chunk -> Bool plainChunk tc Chunk {..} = and [ isNothing chunkItalic, isNothing chunkConsoleIntensity, isNothing chunkUnderlining, maybe True (plainColour tc) chunkForeground, maybe True (plainColour tc) chunkBackground ] plainColour :: TerminalCapabilities -> Colour -> Bool plainColour tc = \case Colour8 {} -> tc < With8Colours Colour8Bit {} -> tc < With8BitColours Colour24Bit {} -> tc < With24BitColours -- | Render a chunk directly to bytestring. renderChunksBS :: Foldable f => TerminalCapabilities -> f Chunk -> ByteString renderChunksBS tc = LB.toStrict . SBB.toLazyByteString . renderChunks tc -- | Render chunks to a bytestring builder renderChunks :: Foldable f => TerminalCapabilities -> f Chunk -> Builder renderChunks tc = foldMap (renderChunk tc) -- | Render a chunk directly to bytestring. renderChunkBS :: TerminalCapabilities -> Chunk -> ByteString renderChunkBS tc = LB.toStrict . SBB.toLazyByteString . renderChunk tc -- | Render a chunk to a bytestring builder renderChunk :: TerminalCapabilities -> Chunk -> Builder renderChunk tc c@Chunk {..} = if plainChunk tc c then SBB.byteString (TE.encodeUtf8 chunkText) else mconcat [ renderCSI (SGR $ chunkSGR tc c), SBB.byteString (TE.encodeUtf8 chunkText), renderCSI (SGR [Reset]) ] chunkSGR :: TerminalCapabilities -> Chunk -> [SGR] chunkSGR tc Chunk {..} = catMaybes [ SetItalic <$> chunkItalic, SetUnderlining <$> chunkUnderlining, SetConsoleIntensity <$> chunkConsoleIntensity, chunkForeground >>= colourSGR tc Foreground, chunkBackground >>= colourSGR tc Background ] -- Turn a text into a plain chunk, without any styling chunk :: Text -> Chunk chunk t = Chunk { chunkText = t, chunkItalic = Nothing, chunkConsoleIntensity = Nothing, chunkUnderlining = Nothing, chunkForeground = Nothing, chunkBackground = Nothing } fore :: Colour -> Chunk -> Chunk fore col chu = chu {chunkForeground = Just col} back :: Colour -> Chunk -> Chunk back col chu = chu {chunkBackground = Just col} bold :: Chunk -> Chunk bold chu = chu {chunkConsoleIntensity = Just BoldIntensity} faint :: Chunk -> Chunk faint chu = chu {chunkConsoleIntensity = Just FaintIntensity} italic :: Chunk -> Chunk italic chu = chu {chunkItalic = Just True} underline :: Chunk -> Chunk underline chu = chu {chunkUnderlining = Just SingleUnderline} doubleUnderline :: Chunk -> Chunk doubleUnderline chu = chu {chunkUnderlining = Just DoubleUnderline} -- TODO consider allowing an 8-colour alternative to a given 256-colour data Colour = Colour8 !ColourIntensity !TerminalColour | Colour8Bit !Word8 -- The 8-bit colour | Colour24Bit !Word8 !Word8 !Word8 deriving (Show, Eq, Generic) colourSGR :: TerminalCapabilities -> ConsoleLayer -> Colour -> Maybe SGR colourSGR tc layer = let cap tc' sgr = if tc >= tc' then Just sgr else Nothing in \case Colour8 intensity terminalColour -> cap With8Colours $ SetColour intensity layer terminalColour Colour8Bit w -> cap With8BitColours $ Set8BitColour layer w Colour24Bit r g b -> cap With24BitColours $ Set24BitColour layer r g b black :: Colour black = Colour8 Dull Black red :: Colour red = Colour8 Dull Red green :: Colour green = Colour8 Dull Green yellow :: Colour yellow = Colour8 Dull Yellow blue :: Colour blue = Colour8 Dull Blue magenta :: Colour magenta = Colour8 Dull Magenta cyan :: Colour cyan = Colour8 Dull Cyan white :: Colour white = Colour8 Dull White brightBlack :: Colour brightBlack = Colour8 Bright Black brightRed :: Colour brightRed = Colour8 Bright Red brightGreen :: Colour brightGreen = Colour8 Bright Green brightYellow :: Colour brightYellow = Colour8 Bright Yellow brightBlue :: Colour brightBlue = Colour8 Bright Blue brightMagenta :: Colour brightMagenta = Colour8 Bright Magenta brightCyan :: Colour brightCyan = Colour8 Bright Cyan brightWhite :: Colour brightWhite = Colour8 Bright White -- | Bulid an 8-bit RGB Colour -- -- This will not be rendered unless 'With8BitColours' is used. colour256 :: Word8 -> Colour colour256 = Colour8Bit -- | Alias for 'colour256', bloody americans... color256 :: Word8 -> Colour color256 = colour256 -- | Bulid a 24-bit RGB Colour -- -- This will not be rendered unless 'With24BitColours' is used. colourRGB :: Word8 -> Word8 -> Word8 -> Colour colourRGB = Colour24Bit -- | Alias for 'colourRGB', bloody americans... colorRGB :: Word8 -> Word8 -> Word8 -> Colour colorRGB = Colour24Bit