-- | Partially taken from Hugs AnsiScreen.hs library: module Language.Haskell.HsColour.ANSI ( highlightOnG,highlightOn , highlightOff , highlightG,highlight , cleareol, clearbol, clearline, clearDown, clearUp, cls , goto , cursorUp, cursorDown, cursorLeft, cursorRight , savePosition, restorePosition , Highlight(..) , Colour(..) , colourCycle , enableScrollRegion, scrollUp, scrollDown , lineWrap , TerminalType(..) ) where import Data.List (intersperse,isPrefixOf) import Data.Char (isDigit) import Data.Word data TerminalType = Ansi16Colour -- ^ @\\033[Xm@-style escape sequences (with /X/ =~ [34][0-7]) | XTerm256Compatible -- ^ 'Ansi16Colour', and also @\\033[Y8;5;Zm]@-style escape sequences (with /Y/ =~ [3,4] and /Z/ an integer in [0,255] with the XTerm colour cube semantics). deriving (Show,Eq,Ord) -- | The supported output formats. data Output = TTY -- ^ ANSI terminal codes. Equivalent to 'TTYg' 'Ansi16Colour' but left in for backwards compatibility. | TTYg TerminalType -- ^ Terminal codes appropriate for the 'TerminalType'. | LaTeX -- ^ TeX macros | HTML -- ^ HTML with font tags | CSS -- ^ HTML with CSS. | ACSS -- ^ HTML with CSS and mouseover types. | ICSS -- ^ HTML with inline CSS. | MIRC -- ^ mIRC chat clients deriving (Eq,Show) -- | Colours supported by ANSI codes. data Colour = Black | Red | Green | Yellow | Blue | Magenta | Cyan | White | Rgb Word8 Word8 Word8 deriving (Eq,Show,Read) -- | Convert an integer in the range [0,2^24-1] to its base 256-triplet, passing the result to the given continuation (avoid unnecessary tupleism). base256 :: Integral int => (Word8 -> Word8 -> Word8 -> r) -> int -> r base256 kont x = let (r,gb) = divMod x 256 (g,b) = divMod gb 256 fi = fromIntegral in kont (fi r) (fi g) (fi b) -- | Convert a three-digit numeral in the given (as arg 1) base to its integer value. unbase :: Integral int => int -> Word8 -> Word8 -> Word8 -> int unbase base r g b = (fi r*base+fi g)*base+fi b where fi = fromIntegral -- | Approximate a 24-bit Rgb colour with a colour in the xterm256 6x6x6 colour cube, returning its index. rgb24bit_to_xterm256 :: (Integral t) => Word8 -> Word8 -> Word8 -> t rgb24bit_to_xterm256 r g b = let f = (`div` 43) in 16 + unbase 6 (f r) (f g) (f b) -- | Ap\"proxi\"mate a 24-bit Rgb colour with an ANSI8 colour. Will leave other colours unchanged and will never return an 'Rgb' constructor value. projectToBasicColour8 :: Colour -> Colour projectToBasicColour8 (Rgb r g b) = let f = (`div` 128) in toEnum ( unbase 2 (f r) (f g) (f b) ) projectToBasicColour8 x = x -- | Lift 'projectToBasicColour8' to 'Highlight's hlProjectToBasicColour8 :: Highlight -> Highlight hlProjectToBasicColour8 (Foreground c) = Foreground (projectToBasicColour8 c) hlProjectToBasicColour8 (Background c) = Background (projectToBasicColour8 c) hlProjectToBasicColour8 h = h instance Enum Colour where toEnum 0 = Black toEnum 1 = Red toEnum 2 = Green toEnum 3 = Yellow toEnum 4 = Blue toEnum 5 = Magenta toEnum 6 = Cyan toEnum 7 = White -- Arbitrary extension; maybe just 'error' out instead toEnum x = base256 Rgb (x-8) fromEnum Black = 0 fromEnum Red = 1 fromEnum Green = 2 fromEnum Yellow = 3 fromEnum Blue = 4 fromEnum Magenta = 5 fromEnum Cyan = 6 fromEnum White = 7 -- Arbitrary extension; maybe just 'error' out instead fromEnum (Rgb r g b) = 8 + unbase 256 r g b -- | Types of highlighting supported by ANSI codes (and some extra styles). data Highlight = Normal | Bold | Dim | Underscore | Blink | ReverseVideo | Concealed | Foreground Colour | Background Colour -- The above styles are ANSI-supported, with the exception of the 'Rgb' constructor for 'Colour's. Below are extra styles (e.g. for Html rendering). | Italic deriving (Eq,Show,Read) -- Basic screen control codes: type Pos = (Int,Int) at :: Pos -> String -> String -- | Move the screen cursor to the given position. goto :: Int -> Int -> String home :: String -- | Clear the screen. cls :: String at (x,y) s = goto x y ++ s goto x y = '\ESC':'[':(show y ++(';':show x ++ "H")) home = goto 1 1 cursorUp = "\ESC[A" cursorDown = "\ESC[B" cursorRight = "\ESC[C" cursorLeft = "\ESC[D" cleareol = "\ESC[K" clearbol = "\ESC[1K" clearline = "\ESC[2K" clearDown = "\ESC[J" clearUp = "\ESC[1J" -- Choose whichever of the following lines is suitable for your system: cls = "\ESC[2J" -- for PC with ANSI.SYS --cls = "\^L" -- for Sun window savePosition = "\ESC7" restorePosition = "\ESC8" -- data Colour -- imported from ColourHighlight -- data Highlight -- imported from ColourHighlight instance Enum Highlight where fromEnum Normal = 0 fromEnum Bold = 1 fromEnum Dim = 2 fromEnum Underscore = 4 fromEnum Blink = 5 fromEnum ReverseVideo = 7 fromEnum Concealed = 8 -- The translation of these depends on the terminal type, and they don't translate to single numbers anyway. Should we really use the Enum class for this purpose rather than simply moving this table to 'renderAttrG'? fromEnum (Foreground (Rgb _ _ _)) = error "Internal error: fromEnum (Foreground (Rgb _ _ _))" fromEnum (Background (Rgb _ _ _)) = error "Internal error: fromEnum (Background (Rgb _ _ _))" fromEnum (Foreground c) = 30 + fromEnum c fromEnum (Background c) = 40 + fromEnum c fromEnum Italic = 2 -- | = 'highlightG' 'Ansi16Colour' highlight :: [Highlight] -> String -> String highlight = highlightG Ansi16Colour -- | = 'highlightOn' 'Ansi16Colour' highlightOn :: [Highlight] -> String highlightOn = highlightOnG Ansi16Colour -- | Make the given string appear with all of the listed highlights highlightG :: TerminalType -> [Highlight] -> String -> String highlightG tt attrs s = highlightOnG tt attrs ++ s ++ highlightOff highlightOnG :: TerminalType -> [Highlight] -> String highlightOnG tt [] = highlightOnG tt [Normal] highlightOnG tt attrs = "\ESC[" ++ concat (intersperse ";" (concatMap (renderAttrG tt) attrs)) ++"m" highlightOff :: [Char] highlightOff = "\ESC[0m" renderAttrG :: TerminalType -> Highlight -> [String] renderAttrG XTerm256Compatible (Foreground (Rgb r g b)) = [ "38", "5", show ( rgb24bit_to_xterm256 r g b ) ] renderAttrG XTerm256Compatible (Background (Rgb r g b)) = [ "48", "5", show ( rgb24bit_to_xterm256 r g b ) ] renderAttrG _ a = [ show (fromEnum (hlProjectToBasicColour8 a)) ] -- | An infinite supply of colours. colourCycle :: [Colour] colourCycle = cycle [Red,Blue,Magenta,Green,Cyan] -- | Scrolling enableScrollRegion :: Int -> Int -> String enableScrollRegion start end = "\ESC["++show start++';':show end++"r" scrollDown :: String scrollDown = "\ESCD" scrollUp :: String scrollUp = "\ESCM" -- Line-wrapping mode lineWrap :: Bool -> [Char] lineWrap True = "\ESC[7h" lineWrap False = "\ESC[7l"