{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Text.PrettyPrint.GHCi.Value ( prettyPrintValue, value2Doc, ValuePrintConf(..), defaultValueConf, ) where import Text.PrettyPrint.GHCi.Value.Lexer import Text.PrettyPrint.GHCi.Value.Parser import System.Terminal.Utils -- base import Data.String ( fromString ) import Control.Exception ( catch, ErrorCall ) import System.IO ( stdout ) import qualified Data.List.NonEmpty as N -- prettyprinter, prettyprinter-ansi-terminal import Data.Text.Prettyprint.Doc import Data.Text.Prettyprint.Doc.Render.Terminal -- | Given a 'Show'-ed value, print that value out to the terminal, add helpful -- indentation and colours whenever possible. If a structured value cannot be -- parsed out, this falls back on 'print'. -- -- The 'Bool' is to enable a slower but potentially smarter layout algorithm. prettyPrintValue :: Bool -> String -> IO () prettyPrintValue smarter str = do termSize <- getTerminalSize let layoutOpts = LayoutOptions (AvailablePerLine (maybe 80 snd termSize) 1.0) layoutAlgo = if smarter then layoutSmart else layoutPretty rendered = layoutAlgo layoutOpts (value2Doc str) renderIO stdout rendered `catch` \(_ :: ErrorCall) -> putStr str -- | Parse a shown value into a pretty 'Doc'. Can throw an error on outputs -- that could not be parsed properly, but should not throw errors for inputs -- which are the outputs of 'show' from derived 'Show' instances. value2Doc :: String -> Doc AnsiStyle value2Doc shown = case parseValue shown of Just v -> renderValue defaultValueConf v <> hardline Nothing -> renderTokens defaultValueConf tokens where tokens = lexTokens shown -- | A Good Enough colour scheme defaultValueConf :: ValuePrintConf defaultValueConf = ValuePrintConf { vpc_number = color Cyan , vpc_character = color Blue , vpc_string = color Green , vpc_control = bold <> color Magenta , vpc_comma = color Yellow , vpc_operator = color White , vpc_field = italicized <> colorDull Red , vpc_indent = 2 } -- | Options for how to colour the terminal output data ValuePrintConf = ValuePrintConf { vpc_number :: AnsiStyle -- ^ all sorts of numeric literals , vpc_character :: AnsiStyle -- ^ character literals , vpc_string :: AnsiStyle -- ^ string literals , vpc_control :: AnsiStyle -- ^ various control characters (ex: parens) , vpc_comma :: AnsiStyle -- ^ commas , vpc_operator :: AnsiStyle -- ^ general operators , vpc_field :: AnsiStyle -- ^ field in a record , vpc_indent :: Int -- ^ how many spaces is one indent? } -- | Function for turning a 'Value' into a 'Doc' renderValue :: ValuePrintConf -> Value -> Doc AnsiStyle renderValue vpc = renderVal where renderVal v = case v of Num i -> num (fromString i) Char c -> char (fromString c) Str s -> string (fromString s) List vs -> renderSeq (ctrl "[") (map (align . renderVal) vs) (ctrl "]") Tuple vs -> renderSeq (ctrl "(") (map (align . renderVal) vs) (ctrl ")") -- Either everything goes on one line or the constructor and args each -- start on a new line (with args indented) Prefix c [] -> fromString c Prefix c vs -> let args = map (align . renderVal) vs in fromString c <> group (nest n (line <> align (vsep args))) -- Either everything goes on one line, or each argument gets its own -- line with operators at the beginning of the lines Infix arg0 ops -> let tails = fmap (\(op,arg) -> optr (fromString op) <+> align (renderVal arg)) ops in renderVal arg0 <> group (nest n (line <> align (vsep (N.toList tails)))) -- Either everything goes on one line or the constructor and fields each -- start on a new line (with fields indented) Record c vs -> let fields = zipWith (\l (f,x) -> hsep [ l, field (fromString f) , ctrl "=", align (renderVal x) ]) (ctrl "{" : repeat (coma ",")) (N.toList vs) in fromString c <> group (nest n (line <> align (vcat fields) <+> ctrl "}")) Paren x -> ctrl "(" <> align (renderVal x) <> ctrl ")" -- Haskell style formatting of sequence-like things, with the comma at the -- start of the line renderSeq :: Doc AnsiStyle -> [Doc AnsiStyle] -> Doc AnsiStyle -> Doc AnsiStyle renderSeq opn [] cls = opn <> cls renderSeq opn vs cls = align . group . encloseSep opn' cls' (coma ", ") $ vs where opn' = flatAlt (opn <> space) opn cls' = flatAlt (space <> cls) cls n = vpc_indent vpc -- Useful annotations num = annotate (vpc_number vpc) char = annotate (vpc_character vpc) string = annotate (vpc_string vpc) ctrl = annotate (vpc_control vpc) coma = annotate (vpc_comma vpc) optr = annotate (vpc_operator vpc) field = annotate (vpc_field vpc) -- | Function for turning a list of 'Token's into a 'Doc' renderTokens :: ValuePrintConf -> [Token] -> Doc AnsiStyle renderTokens vpc = mconcat . map renderTok where renderTok tok = case tok of WhiteTok w -> renderWhite w NumberTok i -> num (fromString i) CharacterTok c -> char (fromString c) StringTok s -> string (fromString s) OpenBracket -> ctrl "[" CloseBracket -> ctrl "]" OpenParen -> ctrl "(" CloseParen -> ctrl ")" OpenBrace -> ctrl "{" CloseBrace -> ctrl "}" Equal -> ctrl "=" OperatorTok op -> optr (fromString op) IdentifierTok c -> fromString c Comma -> coma "," -- Render whitespace (which might have newlines) renderWhite :: String -> Doc AnsiStyle renderWhite "" = mempty renderWhite str = let (ln, str') = span (/= '\n') str in fromString ln <> hardline <> renderWhite (drop 1 str') -- Useful annotations num = annotate (vpc_number vpc) char = annotate (vpc_character vpc) string = annotate (vpc_string vpc) ctrl = annotate (vpc_control vpc) coma = annotate (vpc_comma vpc) optr = annotate (vpc_operator vpc)