{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} -- | Combinators used for printing. module HIndent.Combinators ( -- * Insertion write , newline , space , comma , int , string -- * Common node types , maybeCtx -- * Interspersing , inter , spaced , lined , prefixedLined , commas -- * Wrapping , parens , brackets , braces -- * Indentation , indented , column , depend , swing , getIndentSpaces , getColumnLimit -- * Predicates , nullBinds -- * Sandboxing , sandbox -- * Fallback , pretty' ) where import HIndent.Types import Control.Monad.State hiding (state) import Data.Int import Data.List import Data.Monoid import qualified Data.Text as T import qualified Data.Text.Lazy as LT import Data.Text.Lazy.Builder (Builder) import qualified Data.Text.Lazy.Builder as T import Data.Text.Lazy.Builder.Int import qualified Language.Haskell.Exts.Pretty as P import Language.Haskell.Exts.Syntax import Prelude hiding (exp) -- | Increase indentation level by n spaces for the given printer. indented :: Int64 -> Printer a -> Printer a indented i p = do level <- gets psIndentLevel modify (\s -> s {psIndentLevel = level + i}) m <- p modify (\s -> s {psIndentLevel = level}) return m -- | Print all the printers separated by spaces. spaced :: [Printer ()] -> Printer () spaced = inter space -- | Print all the printers separated by commas. commas :: [Printer ()] -> Printer () commas = inter comma -- | Print all the printers separated by sep. inter :: Printer () -> [Printer ()] -> Printer () inter sep ps = foldr (\(i,p) next -> depend (do p if i < length ps then sep else return ()) next) (return ()) (zip [1 ..] ps) -- | Print all the printers separated by spaces. lined :: [Printer ()] -> Printer () lined ps = sequence_ (intersperse newline ps) -- | Print all the printers separated newlines and optionally a line -- prefix. prefixedLined :: Char -> [Printer ()] -> Printer () prefixedLined pref ps' = case ps' of [] -> return () (p:ps) -> do p indented (-1) (mapM_ (\p' -> do newline depend (string [pref]) p') ps) -- | Set the (newline-) indent level to the given column for the given -- printer. column :: Int64 -> Printer a -> Printer a column i p = do level <- gets psIndentLevel modify (\s -> s {psIndentLevel = i}) m <- p modify (\s -> s {psIndentLevel = level}) return m -- | Output a newline. newline :: Printer () newline = do write "\n" modify (\s -> s {psNewline = True}) -- | Make the latter's indentation depend upon the end column of the -- former. depend :: Printer () -> Printer b -> Printer b depend maker dependent = do state <- get maker st <- get col <- gets psColumn if state /= st then column col dependent else dependent -- | Wrap in parens. parens :: Printer a -> Printer a parens p = depend (write "(") (do v <- p write ")" return v) -- | Wrap in braces. braces :: Printer a -> Printer a braces p = depend (write "{") (do v <- p write "}" return v) -- | Wrap in brackets. brackets :: Printer a -> Printer a brackets p = depend (write "[") (do v <- p write "]" return v) -- | Write a space. space :: Printer () space = write " " -- | Write a comma. comma :: Printer () comma = write "," -- | Write an integral. int :: Integral n => n -> Printer () int = write . decimal -- | Write out a string, updating the current position information. write :: Builder -> Printer () write x = do state <- get let out = if psNewline state then T.fromText (T.replicate (fromIntegral (psIndentLevel state)) " ") <> x else x out' = T.toLazyText out modify (\s -> s {psOutput = psOutput state <> out ,psNewline = False ,psLine = psLine state + additionalLines ,psColumn = if additionalLines > 0 then LT.length (LT.concat (take 1 (reverse srclines))) else psColumn state + LT.length out'}) where x' = T.toLazyText x srclines = LT.lines x' additionalLines = LT.length (LT.filter (== '\n') x') -- | Pretty print using HSE's own printer. The 'P.Pretty' class here is -- HSE's. pretty' :: P.Pretty a => a -> Printer () pretty' = write . T.fromText . T.pack . P.prettyPrint -- | Write a string. string :: String -> Printer () string = write . T.fromText . T.pack -- | Indent spaces, e.g. 2. getIndentSpaces :: Printer Int64 getIndentSpaces = gets (configIndentSpaces . psConfig) -- | Column limit, e.g. 80 getColumnLimit :: Printer Int64 getColumnLimit = gets (configMaxColumns . psConfig) -- | Play with a printer and then restore the state to what it was -- before. sandbox :: MonadState s m => m a -> m s sandbox p = do orig <- get _ <- p new <- get put orig return new -- | No binds? nullBinds :: Binds -> Bool nullBinds (BDecls x) = null x nullBinds (IPBinds x) = null x -- | Maybe render a class context. maybeCtx :: Pretty a => [a] -> Printer () maybeCtx ctx = unless (null ctx) (do write "(" commas (map pretty ctx) write ") => ") -- | Swing the second printer below and indented with respect to the first. swing :: Printer () -> Printer b -> Printer b swing a b = do orig <- gets psIndentLevel a newline indentSpaces <- getIndentSpaces column (orig + indentSpaces) b