tree-diff-0: src/Data/TreeDiff/Pretty.hs
-- | Utilities to pretty print 'Expr' and 'EditExpr'
module Data.TreeDiff.Pretty (
-- * Explicit dictionary
Pretty (..),
ppExpr,
ppEditExpr,
-- * pretty
prettyPretty,
prettyExpr,
prettyEditExpr,
-- * ansi-wl-pprint
ansiWlPretty,
ansiWlExpr,
ansiWlEditExpr,
-- ** background
ansiWlBgPretty,
ansiWlBgExpr,
ansiWlBgEditExpr,
-- * Utilities
escapeName,
) where
import Data.Char (isAlphaNum, isPunctuation, isSymbol, ord)
import Data.TreeDiff.Expr
import Numeric (showHex)
import Text.Read (readMaybe)
import qualified Data.Map as Map
import qualified Text.PrettyPrint as HJ
import qualified Text.PrettyPrint.ANSI.Leijen as WL
-- | Because we don't want to commit to single pretty printing library,
-- we use explicit dictionary.
data Pretty doc = Pretty
{ ppCon :: ConstructorName -> doc
, ppRec :: [(FieldName, doc)] -> doc
, ppLst :: [doc] -> doc
, ppCpy :: doc -> doc
, ppIns :: doc -> doc
, ppDel :: doc -> doc
, ppSep :: [doc] -> doc
, ppParens :: doc -> doc
, ppHang :: doc -> doc -> doc
}
-- | Escape field or constructor name
--
-- >>> putStrLn $ escapeName "Foo"
-- Foo
--
-- >>> putStrLn $ escapeName "_×_"
-- _×_
--
-- >>> putStrLn $ escapeName "-3"
-- `-3`
--
-- >>> putStrLn $ escapeName "kebab-case"
-- kebab-case
--
-- >>> putStrLn $ escapeName "inner space"
-- `inner space`
--
-- >>> putStrLn $ escapeName $ show "looks like a string"
-- "looks like a string"
--
-- >>> putStrLn $ escapeName $ show "tricky" ++ " "
-- `"tricky" `
--
-- >>> putStrLn $ escapeName "[]"
-- `[]`
--
-- >>> putStrLn $ escapeName "_,_"
-- `_,_`
--
escapeName :: String -> String
escapeName n
| null n = "``"
| isValidString n = n
| all valid' n && headNotMP n = n
| otherwise = "`" ++ concatMap e n ++ "`"
where
e '`' = "\\`"
e '\\' = "\\\\"
e ' ' = " "
e c | not (valid c) = "\\x" ++ showHex (ord c) ";"
e c = [c]
valid c = isAlphaNum c || isSymbol c || isPunctuation c
valid' c = valid c && c `notElem` "[](){}`\","
headNotMP ('-' : _) = False
headNotMP ('+' : _) = False
headNotMP _ = True
isValidString s
| length s >= 2 && head s == '"' && last s == '"' =
case readMaybe s :: Maybe String of
Just _ -> True
Nothing -> False
isValidString _ = False
-- | Pretty print an 'Expr' using explicit pretty-printing dictionary.
ppExpr :: Pretty doc -> Expr -> doc
ppExpr p = ppExpr' p False
ppExpr' :: Pretty doc -> Bool -> Expr -> doc
ppExpr' p = impl where
impl _ (App x []) = ppCon p (escapeName x)
impl b (App x xs) = ppParens' b $ ppHang p (ppCon p (escapeName x)) $
ppSep p $ map (impl True) xs
impl _ (Rec x xs) = ppHang p (ppCon p (escapeName x)) $ ppRec p $
map ppField' $ Map.toList xs
impl _ (Lst xs) = ppLst p (map (impl False) xs)
ppField' (n, e) = (escapeName n, impl False e)
ppParens' True = ppParens p
ppParens' False = id
-- | Pretty print an @'Edit' 'EditExpr'@ using explicit pretty-printing dictionary.
ppEditExpr :: Pretty doc -> Edit EditExpr -> doc
ppEditExpr p = ppSep p . ppEdit False
where
ppEdit b (Cpy (EditExp expr)) = [ ppCpy p $ ppExpr' p b expr ]
ppEdit b (Cpy expr) = [ ppEExpr b expr ]
ppEdit b (Ins expr) = [ ppIns p (ppEExpr b expr) ]
ppEdit b (Del expr) = [ ppDel p (ppEExpr b expr) ]
ppEdit b (Swp x y) =
[ ppDel p (ppEExpr b x)
, ppIns p (ppEExpr b y)
]
ppEExpr _ (EditApp x []) = ppCon p (escapeName x)
ppEExpr b (EditApp x xs) = ppParens' b $ ppHang p (ppCon p (escapeName x)) $
ppSep p $ concatMap (ppEdit True) xs
ppEExpr _ (EditRec x xs) = ppHang p (ppCon p (escapeName x)) $ ppRec p $
map ppField' $ Map.toList xs
ppEExpr _ (EditLst xs) = ppLst p (concatMap (ppEdit False) xs)
ppEExpr b (EditExp x) = ppExpr' p b x
ppField' (n, e) = (escapeName n, ppSep p $ ppEdit False e)
ppParens' True = ppParens p
ppParens' False = id
-------------------------------------------------------------------------------
-- pretty
-------------------------------------------------------------------------------
-- | 'Pretty' via @pretty@ library.
prettyPretty :: Pretty HJ.Doc
prettyPretty = Pretty
{ ppCon = HJ.text
, ppRec = HJ.braces . HJ.sep . HJ.punctuate HJ.comma
. map (\(fn, d) -> HJ.text fn HJ.<+> HJ.equals HJ.<+> d)
, ppLst = HJ.brackets . HJ.sep . HJ.punctuate HJ.comma
, ppCpy = id
, ppIns = \d -> HJ.char '+' HJ.<> d
, ppDel = \d -> HJ.char '-' HJ.<> d
, ppSep = HJ.sep
, ppParens = HJ.parens
, ppHang = \d1 d2 -> HJ.hang d1 2 d2
}
-- | Pretty print 'Expr' using @pretty@.
--
-- >>> prettyExpr $ Rec "ex" (Map.fromList [("[]", App "bar" [])])
-- ex {`[]` = bar}
prettyExpr :: Expr -> HJ.Doc
prettyExpr = ppExpr prettyPretty
-- | Pretty print @'Edit' 'EditExpr'@ using @pretty@.
prettyEditExpr :: Edit EditExpr -> HJ.Doc
prettyEditExpr = ppEditExpr prettyPretty
-------------------------------------------------------------------------------
-- ansi-wl-pprint
-------------------------------------------------------------------------------
-- | 'Pretty' via @ansi-wl-pprint@ library (with colors).
ansiWlPretty :: Pretty WL.Doc
ansiWlPretty = Pretty
{ ppCon = WL.text
, ppRec = WL.encloseSep WL.lbrace WL.rbrace WL.comma
. map (\(fn, d) -> WL.text fn WL.<+> WL.equals WL.</> d)
, ppLst = WL.list
, ppCpy = WL.dullwhite
, ppIns = \d -> WL.green $ WL.plain $ WL.char '+' WL.<> d
, ppDel = \d -> WL.red $ WL.plain $ WL.char '-' WL.<> d
, ppSep = WL.sep
, ppParens = WL.parens
, ppHang = \d1 d2 -> WL.hang 2 (d1 WL.</> d2)
}
-- | Pretty print 'Expr' using @ansi-wl-pprint@.
ansiWlExpr :: Expr -> WL.Doc
ansiWlExpr = ppExpr ansiWlPretty
-- | Pretty print @'Edit' 'EditExpr'@ using @ansi-wl-pprint@.
ansiWlEditExpr :: Edit EditExpr -> WL.Doc
ansiWlEditExpr = ppEditExpr ansiWlPretty
-------------------------------------------------------------------------------
-- Background
-------------------------------------------------------------------------------
-- | Like 'ansiWlPretty' but color the background.
ansiWlBgPretty :: Pretty WL.Doc
ansiWlBgPretty = ansiWlPretty
{ ppIns = \d -> WL.ondullgreen $ WL.white $ WL.plain $ WL.char '+' WL.<> d
, ppDel = \d -> WL.ondullred $ WL.white $ WL.plain $ WL.char '-' WL.<> d
}
-- | Pretty print 'Expr' using @ansi-wl-pprint@.
ansiWlBgExpr :: Expr -> WL.Doc
ansiWlBgExpr = ppExpr ansiWlBgPretty
-- | Pretty print @'Edit' 'EditExpr'@ using @ansi-wl-pprint@.
ansiWlBgEditExpr :: Edit EditExpr -> WL.Doc
ansiWlBgEditExpr = ppEditExpr ansiWlBgPretty