{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Skylighting.Format.ANSI (
formatANSI
) where
import Control.Monad (mplus)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe, isNothing, maybeToList)
import Data.Text (Text)
import qualified Data.Text as Text
import Skylighting.Types
import qualified System.Console.ANSI.Codes as ANSI
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup
#endif
formatANSI :: FormatOptions -> Style -> [SourceLine] -> Text
formatANSI opts sty = (beforeText <>)
. (<> afterText)
. Text.intercalate (Text.singleton '\n')
. zipWith (sourceLineToANSI opts sty) [startNum..]
where beforeText = ansiResetText <> ansiStyleText clv (defaultColor sty) (backgroundColor sty) False False False
afterText = ansiResetText
startNum = LineNo $ startNumber opts
clv = ansiColorLevel opts
sourceLineToANSI :: FormatOptions -> Style -> LineNo -> SourceLine -> Text
sourceLineToANSI opts sty lno = prependLineNoText
. mconcat
. map (tokenToANSI clv sty)
where prependLineNoText = if numberLines opts
then (lineNoText <>)
else id
lineNoText = ansiStyleText clv lineNoFgc lineNoBgc False False False
<> Text.pack (show $ lineNo lno)
<> ansiStyleText clv (defaultColor sty) (backgroundColor sty) False False False
<> "\t"
lineNoFgc = lineNumberColor sty `mplus` defaultColor sty
lineNoBgc = lineNumberBackgroundColor sty `mplus` backgroundColor sty
clv = ansiColorLevel opts
tokenToANSI :: ANSIColorLevel -> Style -> Token -> Text
tokenToANSI clv sty (tokTy, tokText) = ansiStyleText clv tokFgc tokBgc tokB tokI tokU
<> tokText
<> ansiStyleText clv (defaultColor sty) (backgroundColor sty) False False False
where TokenStyle tokFgcRaw tokBgcRaw tokB tokI tokU = fromMaybe defStyle . Map.lookup tokTy $ tokenStyles sty
tokFgc = tokFgcRaw `mplus` defaultColor sty
tokBgc = tokBgcRaw `mplus` backgroundColor sty
ansiStyleText :: ANSIColorLevel
-> Maybe Color
-> Maybe Color
-> Bool
-> Bool
-> Bool
-> Text
ansiStyleText clv fgc bgc b i u = optReset <> sgrTextFg <> sgrTextBg
<> (Text.pack . ANSI.setSGRCode $ concat [sgrCodeFg,
sgrCodeBg,
sgrCodeBold,
sgrCodeItal,
sgrCodeUndl])
where (sgrCodeFg, sgrTextFg) = case clv of
ANSITrueColor -> (maybeToList $ fmap (ANSI.SetRGBColor ANSI.Foreground . fromColor) fgc, "")
ANSI256Color -> ([], fromMaybe "" $ fmap (\c -> Text.pack $ ANSI.csi [38, 5,
fromIntegral . getXterm256ColorCode $ fromColor c] "m") fgc)
ANSI16Color -> (maybeToList $ fmap (uncurry (ANSI.SetColor ANSI.Foreground) . fromColor) fgc, "")
(sgrCodeBg, sgrTextBg) = case clv of
ANSITrueColor -> (maybeToList $ fmap (ANSI.SetRGBColor ANSI.Background . fromColor) bgc, "")
ANSI256Color -> ([], fromMaybe "" $ fmap (\c -> Text.pack $ ANSI.csi [48, 5,
fromIntegral . getXterm256ColorCode $ fromColor c] "m") bgc)
ANSI16Color -> (maybeToList $ fmap (uncurry (ANSI.SetColor ANSI.Background) . fromColor) bgc, "")
optReset = if isNothing fgc && isNothing bgc then ansiResetText else ""
sgrCodeBold = [ANSI.SetConsoleIntensity $ if b then ANSI.BoldIntensity else ANSI.NormalIntensity]
sgrCodeItal = [ANSI.SetItalicized i]
sgrCodeUndl = [ANSI.SetUnderlining $ if u then ANSI.SingleUnderline else ANSI.NoUnderline]
ansiResetText :: Text
ansiResetText = Text.pack $ ANSI.setSGRCode [ANSI.Reset]