rio-prettyprint-0.1.2.0: src/Text/PrettyPrint/Leijen/Extended.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
-- | This module re-exports some of the interface for
-- "Text.PrettyPrint.Annotated.Leijen" along with additional definitions
-- useful for stack.
--
-- It defines a 'Monoid' instance for 'Doc'.
module Text.PrettyPrint.Leijen.Extended
(
-- * Pretty-print typeclass
Pretty (..),
-- * Ansi terminal Doc
--
-- See "System.Console.ANSI" for 'SGR' values to use beyond the colors
-- provided.
StyleDoc (..), StyleAnn(..),
-- hDisplayAnsi,
displayAnsi, displayPlain, renderDefault,
-- * Selective re-exports from "Text.PrettyPrint.Annotated.Leijen"
--
-- Documentation of omissions up-to-date with @annotated-wl-pprint-0.7.0@
-- ** Documents, parametrized by their annotations
--
-- Omitted compared to original: @putDoc, hPutDoc@
-- Doc,
-- ** Basic combinators
--
-- Omitted compared to original: @empty, char, text, (<>)@
--
-- Instead of @text@ and @char@, use 'fromString'.
--
-- Instead of @empty@, use 'mempty'.
nest, line, linebreak, group, softline, softbreak,
-- ** Alignment
--
-- The combinators in this section can not be described by Wadler's
-- original combinators. They align their output relative to the
-- current output position - in contrast to @nest@ which always
-- aligns to the current nesting level. This deprives these
-- combinators from being \`optimal\'. In practice however they
-- prove to be very useful. The combinators in this section should
-- be used with care, since they are more expensive than the other
-- combinators. For example, @align@ shouldn't be used to pretty
-- print all top-level declarations of a language, but using @hang@
-- for let expressions is fine.
--
-- Omitted compared to original: @list, tupled, semiBraces@
align, hang, indent, encloseSep,
-- ** Operators
--
-- Omitted compared to original: @(<$>), (</>), (<$$>), (<//>)@
(<+>),
-- ** List combinators
hsep, vsep, fillSep, sep, hcat, vcat, fillCat, cat, punctuate,
-- ** Fillers
fill, fillBreak,
-- ** Bracketing combinators
enclose, squotes, dquotes, parens, angles, braces, brackets,
-- ** Character documents
-- Entirely omitted:
--
-- @
-- lparen, rparen, langle, rangle, lbrace, rbrace, lbracket, rbracket,
-- squote, dquote, semi, colon, comma, space, dot, backslash, equals,
-- pipe
-- @
-- ** Primitive type documents
-- Entirely omitted:
--
-- @
-- string, int, integer, float, double, rational, bool,
-- @
-- ** Semantic annotations
annotate, noAnnotate, styleAnn
-- ** Rendering
-- Original entirely omitted:
-- @
-- SimpleDoc(..), renderPretty, renderCompact, displayDecorated, displayDecoratedA, display, displayS, displayIO,
-- SpanList(..), displaySpans
-- @
-- ** Undocumented
-- Entirely omitted:
-- @
-- column, nesting, width
-- @
) where
import Control.Monad.Reader (runReader, local)
import Data.Array.IArray ((!), (//))
import qualified Data.Text as T
import Distribution.ModuleName (ModuleName)
import qualified Distribution.Text (display)
import Path
import RIO
import qualified RIO.Map as M
import RIO.PrettyPrint.DefaultStyles (defaultStyles)
import RIO.PrettyPrint.Types (Style (Dir, File), Styles)
import RIO.PrettyPrint.StylesUpdate (StylesUpdate (..), HasStylesUpdate, stylesUpdateL)
import System.Console.ANSI (ConsoleLayer (..), SGR (..), setSGRCode)
import qualified Text.PrettyPrint.Annotated.Leijen as P
import Text.PrettyPrint.Annotated.Leijen
( Doc, SimpleDoc (..)
)
-- TODO: consider smashing together the code for wl-annotated-pprint and
-- wl-pprint-text. The code here already handles doing the
-- ansi-wl-pprint stuff (better!) atop wl-annotated-pprint. So the
-- result would be a package unifying 3 different wl inspired packages.
--
-- Perhaps it can still have native string support, by adding a type
-- parameter to Doc?
instance Semigroup StyleDoc where
StyleDoc x <> StyleDoc y = StyleDoc (x P.<> y)
instance Monoid StyleDoc where
mappend = (<>)
mempty = StyleDoc P.empty
--------------------------------------------------------------------------------
-- Pretty-Print class
class Pretty a where
pretty :: a -> StyleDoc
default pretty :: Show a => a -> StyleDoc
pretty = StyleDoc . fromString . show
instance Pretty StyleDoc where
pretty = id
instance Pretty (Path b File) where
pretty = styleAnn File . StyleDoc . fromString . toFilePath
instance Pretty (Path b Dir) where
pretty = styleAnn Dir . StyleDoc . fromString . toFilePath
instance Pretty ModuleName where
pretty = StyleDoc . fromString . Distribution.Text.display
--------------------------------------------------------------------------------
-- Style Doc
-- |A style annotation.
newtype StyleAnn = StyleAnn (Maybe Style)
deriving (Eq, Show, Semigroup)
instance Monoid StyleAnn where
mempty = StyleAnn Nothing
mappend = (<>)
-- |A document annotated by a style
newtype StyleDoc = StyleDoc { unStyleDoc :: Doc StyleAnn }
deriving IsString
-- |An ANSI code(s) annotation.
newtype AnsiAnn = AnsiAnn [SGR]
deriving (Eq, Show, Semigroup, Monoid)
-- |Convert a 'SimpleDoc' annotated with 'StyleAnn' to one annotated with
-- 'AnsiAnn', by reference to a 'Styles'.
toAnsiDoc :: Styles -> SimpleDoc StyleAnn -> SimpleDoc AnsiAnn
toAnsiDoc styles = go
where
go SEmpty = SEmpty
go (SChar c d) = SChar c (go d)
go (SText l s d) = SText l s (go d)
go (SLine i d) = SLine i (go d)
go (SAnnotStart (StyleAnn (Just s)) d) =
SAnnotStart (AnsiAnn (snd $ styles ! s)) (go d)
go (SAnnotStart (StyleAnn Nothing) d) = SAnnotStart (AnsiAnn []) (go d)
go (SAnnotStop d) = SAnnotStop (go d)
displayPlain
:: (Pretty a, HasLogFunc env, HasStylesUpdate env,
MonadReader env m, HasCallStack)
=> Int -> a -> m Utf8Builder
displayPlain w =
displayAnsiSimple . renderDefault w . fmap (const mempty) . unStyleDoc . pretty
-- TODO: tweak these settings more?
-- TODO: options for settings if this is released as a lib
renderDefault :: Int -> Doc a -> SimpleDoc a
renderDefault = P.renderPretty 1
displayAnsi
:: (Pretty a, HasLogFunc env, HasStylesUpdate env,
MonadReader env m, HasCallStack)
=> Int -> a -> m Utf8Builder
displayAnsi w =
displayAnsiSimple . renderDefault w . unStyleDoc . pretty
{- Not used --------------------------------------------------------------------
hDisplayAnsi
:: (Display a, HasAnsiAnn (Ann a), MonadIO m)
=> Handle -> Int -> a -> m ()
hDisplayAnsi h w x = liftIO $ do
useAnsi <- hSupportsANSI h
T.hPutStr h $ if useAnsi then displayAnsi w x else displayPlain w x
-}
displayAnsiSimple
:: (HasLogFunc env, HasStylesUpdate env, MonadReader env m, HasCallStack)
=> SimpleDoc StyleAnn -> m Utf8Builder
displayAnsiSimple doc = do
update <- view stylesUpdateL
let styles = defaultStyles // stylesUpdate update
doc' = toAnsiDoc styles doc
return $
flip runReader mempty $ displayDecoratedWrap go doc'
where
go (AnsiAnn sgrs) inner = do
old <- ask
let sgrs' = mapMaybe (\sgr -> if sgr == Reset
then Nothing
else Just (getSGRTag sgr, sgr)) sgrs
new = if Reset `elem` sgrs
then M.fromList sgrs'
else foldl' (\mp (tag, sgr) -> M.insert tag sgr mp) old sgrs'
(extra, contents) <- local (const new) inner
return (extra, transitionCodes old new <> contents <> transitionCodes new old)
transitionCodes old new =
case (null removals, null additions) of
(True, True) -> mempty
(True, False) -> fromString (setSGRCode additions)
(False, _) -> fromString (setSGRCode (Reset : M.elems new))
where
(removals, additions) = partitionEithers $ M.elems $
M.mergeWithKey
(\_ o n -> if o == n then Nothing else Just (Right n))
(fmap Left)
(fmap Right)
old
new
displayDecoratedWrap
:: forall a m. Monad m
=> (forall b. a -> m (b, Utf8Builder) -> m (b, Utf8Builder))
-> SimpleDoc a
-> m Utf8Builder
displayDecoratedWrap f doc = do
(mafter, result) <- go doc
case mafter of
Just _ -> error "Invariant violated by input to displayDecoratedWrap: no matching SAnnotStart for SAnnotStop."
Nothing -> return result
where
spaces n = display (T.replicate n " ")
go :: SimpleDoc a -> m (Maybe (SimpleDoc a), Utf8Builder)
go SEmpty = return (Nothing, mempty)
go (SChar c x) = fmap (fmap (display c <>)) (go x)
-- NOTE: Could actually use the length to guess at an initial
-- allocation. Better yet would be to just use Text in pprint..
go (SText _l s x) = fmap (fmap (fromString s <>)) (go x)
go (SLine n x) = fmap (fmap ((display '\n' <>) . (spaces n <>))) (go x)
go (SAnnotStart ann x) = do
(mafter, contents) <- f ann (go x)
case mafter of
Just after -> fmap (fmap (contents <>)) (go after)
Nothing -> error "Invariant violated by input to displayDecoratedWrap: no matching SAnnotStop for SAnnotStart."
go (SAnnotStop x) = return (Just x, mempty)
{- Not used --------------------------------------------------------------------
-- Foreground color combinators
black, red, green, yellow, blue, magenta, cyan, white,
dullblack, dullred, dullgreen, dullyellow, dullblue, dullmagenta, dullcyan, dullwhite,
onblack, onred, ongreen, onyellow, onblue, onmagenta, oncyan, onwhite,
ondullblack, ondullred, ondullgreen, ondullyellow, ondullblue, ondullmagenta, ondullcyan, ondullwhite
:: Doc AnsiAnn -> Doc AnsiAnn
(black, dullblack, onblack, ondullblack) = colorFunctions Black
(red, dullred, onred, ondullred) = colorFunctions Red
(green, dullgreen, ongreen, ondullgreen) = colorFunctions Green
(yellow, dullyellow, onyellow, ondullyellow) = colorFunctions Yellow
(blue, dullblue, onblue, ondullblue) = colorFunctions Blue
(magenta, dullmagenta, onmagenta, ondullmagenta) = colorFunctions Magenta
(cyan, dullcyan, oncyan, ondullcyan) = colorFunctions Cyan
(white, dullwhite, onwhite, ondullwhite) = colorFunctions White
type EndoAnsiDoc = Doc AnsiAnn -> Doc AnsiAnn
colorFunctions :: Color -> (EndoAnsiDoc, EndoAnsiDoc, EndoAnsiDoc, EndoAnsiDoc)
colorFunctions color =
( ansiAnn [SetColor Foreground Vivid color]
, ansiAnn [SetColor Foreground Dull color]
, ansiAnn [SetColor Background Vivid color]
, ansiAnn [SetColor Background Dull color]
)
-}
styleAnn :: Style -> StyleDoc -> StyleDoc
styleAnn s = StyleDoc . P.annotate (StyleAnn (Just s)) . unStyleDoc
{- Not used --------------------------------------------------------------------
-- Intensity combinators
bold, faint, normal :: Doc AnsiAnn -> Doc AnsiAnn
bold = ansiAnn [SetConsoleIntensity BoldIntensity]
faint = ansiAnn [SetConsoleIntensity FaintIntensity]
normal = ansiAnn [SetConsoleIntensity NormalIntensity]
-}
-- | Tags for each field of state in SGR (Select Graphics Rendition).
--
-- It's a bit of a hack that 'TagReset' is included.
data SGRTag
= TagReset
| TagConsoleIntensity
| TagItalicized
| TagUnderlining
| TagBlinkSpeed
| TagVisible
| TagSwapForegroundBackground
| TagColorForeground
| TagColorBackground
| TagRGBColor
| TagPaletteColor
deriving (Eq, Ord)
getSGRTag :: SGR -> SGRTag
getSGRTag Reset{} = TagReset
getSGRTag SetConsoleIntensity{} = TagConsoleIntensity
getSGRTag SetItalicized{} = TagItalicized
getSGRTag SetUnderlining{} = TagUnderlining
getSGRTag SetBlinkSpeed{} = TagBlinkSpeed
getSGRTag SetVisible{} = TagVisible
getSGRTag SetSwapForegroundBackground{} = TagSwapForegroundBackground
getSGRTag (SetColor Foreground _ _) = TagColorForeground
getSGRTag (SetColor Background _ _) = TagColorBackground
getSGRTag SetRGBColor{} = TagRGBColor
getSGRTag SetPaletteColor{} = TagPaletteColor
(<+>) :: StyleDoc -> StyleDoc -> StyleDoc
StyleDoc x <+> StyleDoc y = StyleDoc (x P.<+> y)
align :: StyleDoc -> StyleDoc
align = StyleDoc . P.align . unStyleDoc
noAnnotate :: StyleDoc -> StyleDoc
noAnnotate = StyleDoc . P.noAnnotate . unStyleDoc
braces :: StyleDoc -> StyleDoc
braces = StyleDoc . P.braces . unStyleDoc
angles :: StyleDoc -> StyleDoc
angles = StyleDoc . P.angles . unStyleDoc
parens :: StyleDoc -> StyleDoc
parens = StyleDoc . P.parens . unStyleDoc
dquotes :: StyleDoc -> StyleDoc
dquotes = StyleDoc . P.dquotes . unStyleDoc
squotes :: StyleDoc -> StyleDoc
squotes = StyleDoc . P.squotes . unStyleDoc
brackets :: StyleDoc -> StyleDoc
brackets = StyleDoc . P.brackets . unStyleDoc
annotate :: StyleAnn -> StyleDoc -> StyleDoc
annotate a = StyleDoc . P.annotate a . unStyleDoc
nest :: Int -> StyleDoc -> StyleDoc
nest a = StyleDoc . P.nest a . unStyleDoc
line :: StyleDoc
line = StyleDoc P.line
linebreak :: StyleDoc
linebreak = StyleDoc P.linebreak
fill :: Int -> StyleDoc -> StyleDoc
fill a = StyleDoc . P.fill a . unStyleDoc
fillBreak :: Int -> StyleDoc -> StyleDoc
fillBreak a = StyleDoc . P.fillBreak a . unStyleDoc
enclose :: StyleDoc -> StyleDoc -> StyleDoc -> StyleDoc
enclose l r x = l <> x <> r
cat :: [StyleDoc] -> StyleDoc
cat = StyleDoc . P.cat . map unStyleDoc
punctuate :: StyleDoc -> [StyleDoc] -> [StyleDoc]
punctuate (StyleDoc x) = map StyleDoc . P.punctuate x . map unStyleDoc
fillCat :: [StyleDoc] -> StyleDoc
fillCat = StyleDoc . P.fillCat . map unStyleDoc
hcat :: [StyleDoc] -> StyleDoc
hcat = StyleDoc . P.hcat . map unStyleDoc
vcat :: [StyleDoc] -> StyleDoc
vcat = StyleDoc . P.vcat . map unStyleDoc
sep :: [StyleDoc] -> StyleDoc
sep = StyleDoc . P.sep . map unStyleDoc
vsep :: [StyleDoc] -> StyleDoc
vsep = StyleDoc . P.vsep . map unStyleDoc
hsep :: [StyleDoc] -> StyleDoc
hsep = StyleDoc . P.hsep . map unStyleDoc
fillSep :: [StyleDoc] -> StyleDoc
fillSep = StyleDoc . P.fillSep . map unStyleDoc
encloseSep :: StyleDoc -> StyleDoc -> StyleDoc -> [StyleDoc] -> StyleDoc
encloseSep (StyleDoc x) (StyleDoc y) (StyleDoc z) =
StyleDoc . P.encloseSep x y z . map unStyleDoc
indent :: Int -> StyleDoc -> StyleDoc
indent a = StyleDoc . P.indent a . unStyleDoc
hang :: Int -> StyleDoc -> StyleDoc
hang a = StyleDoc . P.hang a . unStyleDoc
softbreak :: StyleDoc
softbreak = StyleDoc P.softbreak
softline :: StyleDoc
softline = StyleDoc P.softline
group :: StyleDoc -> StyleDoc
group = StyleDoc . P.group . unStyleDoc