module Hedgehog.Internal.Show (
Name
, Value(..)
, ValueDiff(..)
, LineDiff(..)
, mkValue
, showPretty
, valueDiff
, lineDiff
, toLineDiff
, renderValue
, renderValueDiff
, renderLineDiff
, takeLeft
, takeRight
) where
import Data.Bifunctor (second)
import Text.Show.Pretty (Value(..), Name, reify, valToStr, ppShow)
data ValueDiff =
ValueCon Name [ValueDiff]
| ValueRec Name [(Name, ValueDiff)]
| ValueTuple [ValueDiff]
| ValueList [ValueDiff]
| ValueSame Value
| ValueDiff Value Value
deriving (Eq, Show)
data LineDiff =
LineSame String
| LineRemoved String
| LineAdded String
deriving (Eq, Show)
data DocDiff =
DocSame Int String
| DocRemoved Int String
| DocAdded Int String
| DocOpen Int String
| DocItem Int String [DocDiff]
| DocClose Int String
deriving (Eq, Show)
renderValue :: Value -> String
renderValue =
valToStr
renderValueDiff :: ValueDiff -> String
renderValueDiff =
unlines .
fmap renderLineDiff .
toLineDiff
renderLineDiff :: LineDiff -> String
renderLineDiff = \case
LineSame x ->
" " ++ x
LineRemoved x ->
"- " ++ x
LineAdded x ->
"+ " ++ x
mkValue :: Show a => a -> Maybe Value
mkValue =
reify
showPretty :: Show a => a -> String
showPretty =
ppShow
lineDiff :: Value -> Value -> [LineDiff]
lineDiff x y =
toLineDiff $ valueDiff x y
toLineDiff :: ValueDiff -> [LineDiff]
toLineDiff =
concatMap (mkLineDiff 0 "") .
collapseOpen .
dropLeadingSep .
mkDocDiff 0
valueDiff :: Value -> Value -> ValueDiff
valueDiff x y =
if x == y then
ValueSame x
else
case (x, y) of
(Con nx xs, Con ny ys)
| nx == ny
, length xs == length ys
->
ValueCon nx (zipWith valueDiff xs ys)
(Rec nx nxs, Rec ny nys)
| nx == ny
, fmap fst nxs == fmap fst nys
, ns <- fmap fst nxs
, xs <- fmap snd nxs
, ys <- fmap snd nys
->
ValueRec nx (zip ns (zipWith valueDiff xs ys))
(Tuple xs, Tuple ys)
| length xs == length ys
->
ValueTuple (zipWith valueDiff xs ys)
(List xs, List ys)
| length xs == length ys
->
ValueList (zipWith valueDiff xs ys)
_ ->
ValueDiff x y
takeLeft :: ValueDiff -> Value
takeLeft = \case
ValueCon n xs ->
Con n (fmap takeLeft xs)
ValueRec n nxs ->
Rec n (fmap (second takeLeft) nxs)
ValueTuple xs ->
Tuple (fmap takeLeft xs)
ValueList xs ->
List (fmap takeLeft xs)
ValueSame x ->
x
ValueDiff x _ ->
x
takeRight :: ValueDiff -> Value
takeRight = \case
ValueCon n xs ->
Con n (fmap takeRight xs)
ValueRec n nxs ->
Rec n (fmap (second takeRight) nxs)
ValueTuple xs ->
Tuple (fmap takeRight xs)
ValueList xs ->
List (fmap takeRight xs)
ValueSame x ->
x
ValueDiff _ x ->
x
mkLineDiff :: Int -> String -> DocDiff -> [LineDiff]
mkLineDiff indent0 prefix0 diff =
let
mkLinePrefix indent =
spaces indent0 ++ prefix0 ++ spaces indent
mkLineIndent indent =
indent0 + length prefix0 + indent
in
case diff of
DocSame indent x ->
[LineSame $ mkLinePrefix indent ++ x]
DocRemoved indent x ->
[LineRemoved $ mkLinePrefix indent ++ x]
DocAdded indent x ->
[LineAdded $ mkLinePrefix indent ++ x]
DocOpen indent x ->
[LineSame $ mkLinePrefix indent ++ x]
DocItem _ _ [] ->
[]
DocItem indent prefix (x@DocRemoved{} : y@DocAdded{} : xs) ->
mkLineDiff (mkLineIndent indent) prefix x ++
mkLineDiff (mkLineIndent indent) prefix y ++
concatMap (mkLineDiff (mkLineIndent (indent + length prefix)) "") xs
DocItem indent prefix (x : xs) ->
mkLineDiff (mkLineIndent indent) prefix x ++
concatMap (mkLineDiff (mkLineIndent (indent + length prefix)) "") xs
DocClose indent x ->
[LineSame $ spaces (mkLineIndent indent) ++ x]
spaces :: Int -> String
spaces indent =
replicate indent ' '
collapseOpen :: [DocDiff] -> [DocDiff]
collapseOpen = \case
DocSame indent line : DocOpen _ bra : xs ->
DocSame indent (line ++ " " ++ bra) : collapseOpen xs
DocItem indent prefix xs : ys ->
DocItem indent prefix (collapseOpen xs) : collapseOpen ys
x : xs ->
x : collapseOpen xs
[] ->
[]
dropLeadingSep :: [DocDiff] -> [DocDiff]
dropLeadingSep = \case
DocOpen oindent bra : DocItem indent prefix xs : ys ->
DocOpen oindent bra : DocItem (indent + length prefix) "" (dropLeadingSep xs) : dropLeadingSep ys
DocItem indent prefix xs : ys ->
DocItem indent prefix (dropLeadingSep xs) : dropLeadingSep ys
x : xs ->
x : dropLeadingSep xs
[] ->
[]
mkDocDiff :: Int -> ValueDiff -> [DocDiff]
mkDocDiff indent = \case
ValueSame x ->
same indent (renderValue x)
diff
| x <- takeLeft diff
, y <- takeRight diff
, oneLiner x
, oneLiner y
->
removed indent (renderValue x) ++
added indent (renderValue y)
ValueCon n xs ->
same indent n ++
concatMap (mkDocDiff (indent + 2)) xs
ValueRec n nxs ->
same indent n ++
[DocOpen indent "{"] ++
fmap (\(name, x) -> DocItem (indent + 2) ", " (same 0 (name ++ " =") ++ mkDocDiff 2 x)) nxs ++
[DocClose (indent + 2) "}"]
ValueTuple xs ->
[DocOpen indent "("] ++
fmap (DocItem indent ", " . mkDocDiff 0) xs ++
[DocClose indent ")"]
ValueList xs ->
[DocOpen indent "["] ++
fmap (DocItem indent ", " . mkDocDiff 0) xs ++
[DocClose indent "]"]
ValueDiff x y ->
removed indent (renderValue x) ++
added indent (renderValue y)
oneLiner :: Value -> Bool
oneLiner x =
case lines (renderValue x) of
_ : _ : _ ->
False
_ ->
True
same :: Int -> String -> [DocDiff]
same indent =
fmap (DocSame indent) . lines
removed :: Int -> String -> [DocDiff]
removed indent =
fmap (DocRemoved indent) . lines
added :: Int -> String -> [DocDiff]
added indent =
fmap (DocAdded indent) . lines