module Data.TreeDiff.Pretty (
Pretty (..),
ppExpr,
ppEditExpr,
ppEditExprCompact,
prettyPretty,
prettyExpr,
prettyEditExpr,
prettyEditExprCompact,
ansiWlPretty,
ansiWlExpr,
ansiWlEditExpr,
ansiWlEditExprCompact,
ansiWlBgPretty,
ansiWlBgExpr,
ansiWlBgEditExpr,
ansiWlBgEditExprCompact,
escapeName,
) where
import Data.Char (isAlphaNum, isPunctuation, isSymbol, ord)
import Data.Either (partitionEithers)
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
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
}
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
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
ppEditExpr :: Pretty doc -> Edit EditExpr -> doc
ppEditExpr = ppEditExpr' False
ppEditExprCompact :: Pretty doc -> Edit EditExpr -> doc
ppEditExprCompact = ppEditExpr' True
ppEditExpr' :: Bool -> Pretty doc -> Edit EditExpr -> doc
ppEditExpr' compact 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 $
justs ++ [ (n, ppCon p "...") | n <- take 1 nothings ]
where
xs' = map ppField' $ Map.toList xs
(nothings, justs) = partitionEithers xs'
ppEExpr _ (EditLst xs) = ppLst p (concatMap (ppEdit False) xs)
ppEExpr b (EditExp x) = ppExpr' p b x
ppField' (n, Cpy (EditExp e)) | compact, not (isScalar e) = Left n
ppField' (n, e) = Right (escapeName n, ppSep p $ ppEdit False e)
ppParens' True = ppParens p
ppParens' False = id
isScalar (App _ []) = True
isScalar _ = False
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
}
prettyExpr :: Expr -> HJ.Doc
prettyExpr = ppExpr prettyPretty
prettyEditExpr :: Edit EditExpr -> HJ.Doc
prettyEditExpr = ppEditExpr prettyPretty
prettyEditExprCompact :: Edit EditExpr -> HJ.Doc
prettyEditExprCompact = ppEditExprCompact prettyPretty
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)
}
ansiWlExpr :: Expr -> WL.Doc
ansiWlExpr = ppExpr ansiWlPretty
ansiWlEditExpr :: Edit EditExpr -> WL.Doc
ansiWlEditExpr = ppEditExpr ansiWlPretty
ansiWlEditExprCompact :: Edit EditExpr -> WL.Doc
ansiWlEditExprCompact = ppEditExprCompact ansiWlPretty
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
}
ansiWlBgExpr :: Expr -> WL.Doc
ansiWlBgExpr = ppExpr ansiWlBgPretty
ansiWlBgEditExpr :: Edit EditExpr -> WL.Doc
ansiWlBgEditExpr = ppEditExpr ansiWlBgPretty
ansiWlBgEditExprCompact :: Edit EditExpr -> WL.Doc
ansiWlBgEditExprCompact = ppEditExprCompact ansiWlBgPretty