diff --git a/BENCHMARK.md b/BENCHMARK.md
new file mode 100644
--- /dev/null
+++ b/BENCHMARK.md
@@ -0,0 +1,90 @@
+# Large inputs
+
+Bunch of declarations
+
+``` haskell
+listPrinters =
+  [(''[]
+   ,\(typeVariable:_) _automaticPrinter ->
+      (let presentVar = varE (presentVarName typeVariable)
+       in lamE [varP (presentVarName typeVariable)]
+               [|(let typeString = "[" ++ fst $(presentVar) ++ "]"
+                  in (typeString
+                     ,\xs ->
+                        case fst $(presentVar) of
+                          "GHC.Types.Char" ->
+                            ChoicePresentation
+                              "String"
+                              [("String",undefined)
+                              ,("List of characters",undefined)]
+                          _ ->
+                            ListPresentation typeString
+                                             (map (snd $(presentVar)) xs)))|]))]
+printComments loc' ast = do
+  let correctLocation comment = comInfoLocation comment == Just loc'
+      commentsWithLocation = filter correctLocation (nodeInfoComments info)
+  comments <- return $ map comInfoComment commentsWithLocation
+
+  forM_ comments $ \comment -> do
+    -- Preceeding comments must have a newline before them.
+    hasNewline <- gets psNewline
+    when (not hasNewline && loc' == Before) newline
+
+    printComment (Just $ srcInfoSpan $ nodeInfoSpan info) comment
+  where info = ann ast
+exp' (App _ op a) =
+  do (fits,st) <-
+       fitsOnOneLine (spaced (map pretty (f : args)))
+     if fits
+        then put st
+        else do pretty f
+                newline
+                spaces <- getIndentSpaces
+                indented spaces (lined (map pretty args))
+  where (f,args) = flatten op [a]
+        flatten :: Exp NodeInfo
+                -> [Exp NodeInfo]
+                -> (Exp NodeInfo,[Exp NodeInfo])
+        flatten (App _ f' a') b =
+          flatten f' (a' : b)
+        flatten f' as = (f',as)
+infixApp :: Exp NodeInfo
+         -> Exp NodeInfo
+         -> QOp NodeInfo
+         -> Exp NodeInfo
+         -> Maybe Int64
+         -> Printer ()
+```
+
+# Complex inputs
+
+Quasi-quotes with nested lets and operators
+
+``` haskell
+quasiQuotes =
+  [(''[]
+   ,\(typeVariable:_) _automaticPrinter ->
+      (let presentVar = varE (presentVarName typeVariable)
+       in lamE [varP (presentVarName typeVariable)]
+               [|(let typeString = "[" ++ fst $(presentVar) ++ "]"
+                  in (typeString
+                     ,\xs ->
+                        case fst $(presentVar) of
+                          "GHC.Types.Char" ->
+                            ChoicePresentation
+                              "String"
+                              [("String"
+                               ,StringPresentation "String"
+                                                   (concatMap getCh (map (snd $(presentVar)) xs)))
+                              ,("List of characters"
+                               ,ListPresentation typeString
+                                                 (map (snd $(presentVar)) xs))]
+                            where getCh (CharPresentation "GHC.Types.Char" ch) =
+                                    ch
+                                  getCh (ChoicePresentation _ ((_,CharPresentation _ ch):_)) =
+                                    ch
+                                  getCh _ = ""
+                          _ ->
+                            ListPresentation typeString
+                                             (map (snd $(presentVar)) xs)))|]))]
+```
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Floskell 1.0.0
+
+* Initial release
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,29 @@
+Copyright (c) 2014, Chris Done
+Copyright (c) 2016-2019, Enno Cramer
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,273 @@
+# Floskell [![Build Status](https://travis-ci.org/ennocramer/floskell.png)](https://travis-ci.org/ennocramer/floskell)
+
+Floskell is a flexible Haskell source code pretty printer.
+
+[Documentation](https://github.com/ennocramer/floskell/blob/master/README.md)
+
+[Examples](https://github.com/ennocramer/floskell/blob/master/styles)
+
+Floskell started as a fork of version 4 of [Chris Done's
+hindent](https://github.com/commercialhaskell/hindent).  The
+formatting styles present in hindent 4 have been preserved in spirit,
+but generally will not produce exactly the same output.
+
+
+## Installation
+
+    $ git clone https://github.com/ennocramer/floskell
+    $ cd floskell
+    $ stack install
+
+
+## Usage
+
+Floskell can be used to reformat Haskell source files in place
+
+    $ floskell path/to/sourcefile.hs
+
+or as a pipeline processor
+
+    $ cat path/to/sourcefile.hs | floskell > outfile.hs
+
+One of the predefined formatting styles can be selected with the
+`--style` option
+
+    $ floskell --style cramer path/to/sourcefile.hs
+
+Or the style can be read from a configuration file
+
+    $ floskell --config path/to/config.json path/to/sourcefile.hs
+
+If neither style nor configuration file is given on the command line,
+Floskell will try to find a configuration file in the current working
+directory or any of its parent directories, or fall back to the users
+global configuration file.
+
+
+## Formatting Process
+
+A style in Floskell is a set of formatting possibilities for different
+language constructs.  Floskell formats Haskell code according to a
+given style by finding the combination of allowed formatting choices
+that result in the best overall layout.
+
+### Penalty
+
+The overall layout of the generated output is judged by a penalty
+function.  This function takes into account the number of lines
+generated, whether lines are longer than a defined limit, and the
+indentation of each line.
+
+In general, Floskell will try to generate
+
+* the smallest number of lines,
+
+* the least amount of indentation, and
+
+* the least amount of overflow.
+
+### Layout
+
+A number of language constructs can be formatted in different ways.
+Floskell generally defines two layout choices for these constructs,
+`flex` and `vertical`, and three modes to apply these choices, `flex`,
+`vertical`, and `try-oneline`.
+
+The layout choice `flex` generally tries to fit as much on each line
+as possible, but allows linebreaks in a number of places, while the
+`vertical` layout choice forces linebreaks in various places.
+
+The `flex` and `vertical` layout modes simply select the respective
+layout choice, while `try-oneline` will first try `flex`, but replace
+the choice with `vertical` if the `flex` layout would more than one
+line or an overfull line.
+
+An example:
+
+```haskell
+-- flex layout for con-decls
+data Enum = One | Two | Three
+
+-- vertical layout for con-decls
+data Enum = One
+          | Two
+          | Three
+```
+
+### Indentation
+
+A number of language constructs can apply indentation to sub-elements.
+Floskell provides two different indentation choices, `aligned` and
+`indented`, and three modes to apply these choices, `align`,
+`indent-by n`, and `align-or-indent-by n`.
+
+`align` will start the sub-element on the same line and raise the
+indentation to align following lines, while `indent-by n` will start
+the sub-element on the following line with the indentation raised by
+`n`.
+
+`align-or-indent-by n` will allow either choice and select the
+formatting with the least penalty.
+
+An example:
+
+```haskell
+-- align for do
+foo = do x <- xs
+         y <- ys
+         return (x, y)
+
+-- indent-by 4 for do
+foo = do
+    x <- xs
+    y <- ys
+    return (x, y)
+```
+
+### Tabstop Alignment
+
+Some language constructs allow for tabstop alignment.  Alignment is
+optional and subject to configurable limits, regarding the amount of
+added whitespace.
+
+An example:
+
+```haskell
+-- let without alignment
+let foo = bar
+    quux = quuz
+in foo quux
+
+-- let with alignment
+let foo    = bar
+    quuuux = quuz
+in foo quuuux
+```
+
+### Whitespace
+
+Floskell allows the customization of whitespace around infix
+operators, as well as inside parentheses and other enclosing
+punctuation characters.
+
+The presence of whitespace or linebreaks is as `before`, meaning
+before the operator/enclosed item, `after`, meaning after the
+operator/enclosed item, or `both`, meaning both before and after the
+operator/enclosed item.
+
+Whitespace configuration can depend on the context where an operator
+or enclosing punctuation is used.  The context can be one of
+`declaration`, `type`, `pattern`, `expression`, or `other`.
+
+An example:
+
+```haskell
+-- tuple with space after/before parentheses and after comma
+tuple = ( 1, 2 )
+-- tuple without any spaces
+tuple = (1,2)
+```
+
+
+## Customization
+
+Floskell's behaviour and the style of its output can be modified with
+a configuration file.
+
+See the documentation on the [Configuration Format](CONFIGURATION.md)
+for a detailed description of the contents of the configuration file.
+
+### Initial Configuration
+
+The `--print-config` command line option can be used to create an
+initial configuration file.
+
+    $ floskell --style cramer --print-config > ~/.floskell
+
+This command will create a configuration file with all fields and the
+entire definition of the selected style in the `formatting` block.
+
+### Configuration File Location
+
+* If a style is given on the command line, but no explicit
+  configuration file, the style will be used as-is and not
+  configuration file will be loaded.
+
+* If both a style and an explicit configuration file are given on the
+  command line, the explicit configuration file will be loaded and the
+  style parameter will replace any style setting in the configuration
+  file.
+
+* If neither style nor explicit configuration file are given on the
+  command line, Floskell will try to find an applicable configuration
+  file.  Floskell will look for
+
+  * a file called `floskell.json` in the current working directory and
+    all its parent directories,
+
+  * a file called `config.json` in `~/.floskell`, `~/config/floskell`,
+    or `%APPDATA%/floskell`, and lastly
+
+  * a file called `.floskell.json` in `~` or `~/.config`.
+
+  Only the first file found will be loaded.
+
+
+## Editor Integration
+
+### Emacs
+
+In
+[contrib/floskell.el](https://github.com/ennocramer/floskell/blob/master/contrib/floskell.el)
+there is `floskell-mode`, which provides keybindings to reindent parts
+of the buffer:
+
+- `M-q` reformats the current declaration.  When inside a comment, it
+  fills the current paragraph instead, like the standard `M-q`.
+- `C-M-\` reformats the current region.
+
+To enable it, add the following to your init file:
+
+```lisp
+(add-to-list 'load-path "/path/to/floskell/contrib")
+(require 'floskell)
+(add-hook 'haskell-mode-hook #'floskell-mode)
+```
+
+By default, Floskell uses the style called `base`.  If you want to use
+another, run `M-x customize-variable floskell-style` or create a
+Floskell configuration file in your home directory. If you want to
+configure per-project, add a configuration file in the project root or
+make a file called `.dir-locals.el` in the project root directory like
+this:
+
+``` lisp
+((nil . ((floskell-style . "johan-tibell"))))
+```
+
+### Vim
+
+The `'formatprg'` option lets you use an external program (like
+floskell) to format your text. Put the following line into
+~/.vim/ftplugin/haskell.vim to set this option for Haskell files:
+
+    setlocal formatprg=floskell\ --style\ chris-done
+
+Then you can format with floskell using `gq`. Read `:help gq` and `help
+'formatprg'` for more details.
+
+Note that unlike in emacs you have to take care of selecting a
+sensible buffer region as input to floskell yourself. If that is too
+much trouble you can try
+[vim-textobj-haskell](https://github.com/gilligan/vim-textobj-haskell)
+which provides a text object for top level bindings.
+
+### Atom
+
+Basic support is provided through
+[contrib/floskell.coffee](https://github.com/ennocramer/floskell/blob/master/contrib/floskell.coffee),
+which adds floskell to atom menu with each available style, and
+`Default` which will use the appropriate configuration file. Mode
+should be installed as package into `.atom\packages\${PACKAGE_NAME}`,
+here is simple example of atom
+[package](https://github.com/Heather/atom-hindent).
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,3 @@
+import           Distribution.Simple
+
+main = defaultMain
diff --git a/TEST.md b/TEST.md
new file mode 100644
--- /dev/null
+++ b/TEST.md
@@ -0,0 +1,750 @@
+# Introduction
+
+This file acts both as a presentation of the Floskell formatting
+styles, as well as a set of regression tests.
+
+You can see how a particular style will format Haskell source by
+reading the matching Markdown file in the styles/ directory.
+
+For regression testing, the canonical source, TEST.md in the root
+directory, is parsed and each Haskell code block formatted according
+to all predefined styles.  The formatted output is then compared with
+the corresponding, already formatted code block in the <style\>.md file
+in the styles/ subdirectory.
+
+The regression test will also verify that repeated invocations of the
+pretty printer will not modify an already formatted piece of code.
+
+The following code block acts as a quick presentation for the
+different formatting styles, by presenting a mixture of common Haskell
+constructs.
+
+``` haskell
+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+{- |
+Module: Style.Haskell.Example
+
+Haskell Code Style Example.
+-}
+module Style.Haskell.Example (
+ -- * Types
+ Enum(..)
+ ,Either(..)
+ ,Point(..)
+ -- * Functions
+ ,hello
+ ) where
+
+-- Module imports
+import qualified Control.Monad.Trans.State (State,evalState,execState,get,modify,put,runState)
+import qualified Data.Map as Map
+import qualified Data.Text as Text
+import Prelude hiding (map)
+
+-- Data declarations
+data Enum=CaseA|CaseB|CaseC deriving(Eq,Enum,Show)
+
+data Either a b=Left a|Right b deriving(Eq,Show)
+
+data Point=Point{pointX::Float,pointY::Float,pointLabel::String}deriving(Eq,Show)
+
+-- Type classes
+class Functor f=>Applicative a where
+    pure::b->a b
+    ap::a (b->c)->a b->a c
+
+class Fundep a b|a->b where
+    convert::a->b
+
+instance Functor f=>Functor(Wrap f)where
+    fmap f (Wrap x)=Wrap $ fmap f x
+
+-- Values
+origin::Point
+origin=Point{pointX=0,pointY=0,pointLabel="Origin"}
+
+lorem::[String]
+lorem=["Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+ "Curabitur nec ante nec mauris ornare suscipit.",
+ "In ac vulputate libero.",
+ "Duis eget magna non purus imperdiet molestie nec quis mauris.",
+ "Praesent blandit quam vel arcu pellentesque, id aliquet turpis faucibus."]
+
+-- Functions
+facs::[Int]
+facs=[1,1]++zipWith(+)(tailfacs)
+
+hello::MonadIO m=>m ()
+hello=do name<-liftIO getLine
+         liftIO . putStrLn $ greetings name
+  where
+    greetings n="Hello "++n++"!"
+
+letExpr::Point->String
+letExp x=let y=1
+             z=2
+  in if x>0 then y else z
+
+ifExpr::Bool->Bool
+ifExpr b=if b == True then False else True
+
+caseExpr::[a]->Maybe a
+caseExpr xs=case xs of
+    [] -> Nothing
+    (x:_) -> Just x
+
+guarded::Int->Int
+guarded x|x == 0=1
+         |x == 1=1
+         |otherwise=guarded (x - 2) + guarded (x - 1)
+
+someLongFunctionNameWithALotOfParameters::
+  (MonadIO m,MonadRandom m)=>String->(String->String)->m ()
+someLongFunctionNameWithALotOfParameters=undefined
+```
+
+# Unit Tests
+
+## ModuleHead and ExportSpecList
+
+Without exports
+
+``` haskell
+module Main where
+```
+
+With exports
+
+``` haskell
+module Main (foo,bar,baz,main) where
+```
+
+With exports and comments
+
+``` haskell
+module Main (
+  -- * Main Program
+  main
+  -- * Functions
+  , foo -- foo function
+  , bar -- bar function
+  , baz -- baz function
+  ) where
+```
+
+With deprecation
+
+``` haskell
+module Main {-# DEPRECATED "no longer supported" #-} where
+```
+
+With warnings
+
+``` haskell
+module Main {-# WARNING "do not use" #-} where
+```
+
+## ImportDecl
+
+``` haskell
+import Prelude
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.ByteString (ByteString,pack,unpack)
+import qualified Data.ByteString as BS (pack, unpack)
+import Control.Monad hiding (forM)
+```
+
+## Decl
+
+### TypeDecl
+
+``` haskell
+type Name = String
+type Pair a = (a, a)
+type Fun a b = a -> b
+```
+
+### DataDecl and GDataDecl
+
+``` haskell
+data Void
+data Unit = Unit
+data Maybe a = Nothing | Just a
+data Num a => SomeNum = SomeNum a
+
+newtype RWS r w s = RWS (ReaderT r (WriterT w (StateT s Identity)))
+ deriving (Functor, Applicative, Monad)
+
+data Enum =
+    One   -- Foo
+  | Two   -- Bar
+  | Three -- Baz
+
+data Foo deriving ()
+data Foo deriving Show
+data Foo deriving (Show)
+data Foo deriving (Eq, Ord)
+
+data Expr :: * -> * where
+  Const :: Int -> Expr Int
+  Plus :: Expr Int -> Expr Int -> Expr Int
+  Eq :: Expr Int -> Expr Int -> Expr Bool
+  deriving (Show)
+
+data Term a where
+  Lit :: { val  :: Int } -> Term Int
+  Succ :: { num  :: Term Int } -> Term Int
+  Pred :: { num  :: Term Int } -> Term Int
+  IsZero :: { arg  :: Term Int } -> Term Bool
+  Pair :: { arg1 :: Term a, arg2 :: Term b } -> Term (a,b)
+  If :: { cnd  :: Term Bool, tru  :: Term a, fls  :: Term a } -> Term a
+```
+
+### TypeFamDecl, TypeInsDecl, and ClosedTypeFamDecl
+
+``` haskell
+type family Mutable v
+type family Mutable v = (r :: *)
+type family Mutable v = r | r -> v
+
+type instance Mutable Int = MIntVector
+
+type family Store a where
+  Store Bool = [Int]
+  Store a = [a]
+
+type family Store a = (r :: *) where
+  Store a = [a]
+
+type family Store a = r | r -> a where
+  Store a = [a]
+```
+
+### DataFamDecl, DataInsDecl, and GDataInsDecl
+
+``` haskell
+data family List a
+
+data instance List () = NilList Int
+data instance List Char = CharNil | CharCons Char (List Char)
+  deriving (Eq, Ord, Show)
+
+data instance List Int :: * where
+  IntNil :: List Int
+  IntCons :: Int -> List Int
+  deriving (Eq, Ord, Show)
+
+data instance List Int :: * where
+  IntNil :: List Int
+  IntCons :: { val :: Int } -> List Int
+  deriving (Eq, Ord, Show)
+```
+
+### ClassDecl and InstDecl
+
+``` haskell
+class Monoid a where
+  mempty :: a
+  mappend :: a -> a -> a
+
+class Applicative m => Monad m where
+  fail :: m a
+  return :: a -> m a
+  (>>=) :: a -> (a -> m b) -> m b
+
+class Monad m => MonadState s m | m -> s where
+  get :: m s
+  put :: s -> m ()
+  state :: (s -> (a, s)) -> m a
+
+class ToJSON a where
+  toJSON :: a -> Value
+  default toJSON :: (Generic a, GToJSON (Rep a)) => a -> Value
+  toJSON = genericToJSON defaultOptions
+
+instance ToJSON ()
+
+instance Bounded Bool where
+  minBound = False
+  maxBound = True
+
+instance Semigroup a => Monoid (Maybe a) where
+  mempty = Nothing
+  Nothing `mappend` m = m
+  m `mappend` Nothing = m
+  Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
+
+instance Data () where
+  type Base = ()
+  newtype Wrapped = Wrapped { unWrap :: () }
+  data Expr :: * -> * where
+    Const :: Int -> Expr Int
+    Plus :: Expr Int -> Expr Int -> Expr Int
+    Eq :: Expr Int -> Expr Int -> Expr Bool
+```
+
+### DerivDecl
+
+``` haskell
+deriving instance Eq a => Eq (Sum a)
+deriving instance {-# OVERLAP #-} Eq a => Eq (Sum a)
+deriving stock instance {-# OVERLAPS #-} Eq a => Eq (Sum a)
+deriving anyclass instance {-# OVERLAPPING #-} Eq a => Eq (Sum a)
+deriving newtype instance {-# OVERLAPPABLE #-} Eq a => Eq (Sum a)
+```
+
+### InfixDecl
+
+``` haskell
+infix 4 ==, /=, <, <=, >, >=
+infixr 0 $
+infixl !!
+```
+
+### DefaultDecl
+
+``` haskell
+default ()
+default (Integer, Double)
+```
+
+### SpliceDecl
+
+``` haskell
+$foo
+$(bar baz)
+```
+
+### TypeSig
+
+``` haskell
+id :: a -> a
+sort :: Ord a => [a] -> [a]
+long :: (IsString a, Monad m) => ByteString -> ByteString -> ByteString -> ByteString -> ByteString -> a -> m ()
+mktime :: Int -- hours
+       -> Int -- minutes
+       -> Int -- seconds
+       -> Time
+transform :: forall a. St -> State St a -> EitherT ServantErr IO a
+```
+
+### PatSyn and PatSynSig
+
+``` haskell
+{-# LANGUAGE PatternSynonyms #-}
+
+pattern MyJust :: a -> Maybe a
+pattern MyJust a = Just a
+
+pattern MyPoint :: Int -> Int -> (Int, Int)
+pattern MyPoint{x, y} = (x,y)
+
+pattern ErrorCall :: String -> ErrorCall
+pattern ErrorCall s <- ErrorCallWithLocation s _
+  where
+    ErrorCall s = ErrorCallWithLocation s ""
+
+pattern IsTrue :: Show a => a
+pattern IsTrue <- ((== "True") . show -> True)
+
+pattern ExNumPat :: () => Show b => b -> T
+pattern ExNumPat x = MkT x
+
+pattern Foo, Bar :: Show a => a
+```
+
+### FunBind and PatBind
+
+``` haskell
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE UnboxedSums #-}
+{-# LANGUAGE RecordWildCards #-}
+
+pi = 3.14
+
+id x = x
+
+not False = True
+not _ = False
+
+head (x : _) = x
+
+maybe x _ Nothing = x
+maybe _ f (Some x) = f x
+
+fst (x, _) = x
+fst' (# x, _ #) = x
+
+fstPrism (# x | | #) = Just x
+fstPrism (# | _ | #) = Nothing
+fstPrism (# | | _ #) = Nothing
+
+empty [] = True
+empty _ = False
+
+unSum (Sum { getSum = s }) = s
+
+mag2 Point{x, y} = sqr x + sqr y
+mag2 Point{..} = sqr x + sqr y
+
+strict !x = x
+irrefutable ~x = x
+
+(//) a b = undefined
+a // b = undefined
+
+main = do
+    greet "World"
+  where
+    greet who = putStrLn $ "Hello, " ++ who ++ "!"
+```
+
+### ForImp and ForExp
+
+``` haskell
+{-# LANGUAGE ForeignFunctionInterface #-}
+foreign import ccall sin :: Double -> Double
+foreign import ccall "sin" sin :: Double -> Double
+foreign import ccall "sin" sin :: Double -> Double
+foreign import ccall unsafe exit :: Double -> Double
+
+foreign export ccall callback :: Int -> Int
+```
+
+### Pragmas
+
+``` haskell
+{-# RULES #-}
+{-# RULES "map/map" forall f g xs. map f (map g xs) = map (f.g) xs #-}
+{-# RULES "map/append" [2] forall f xs ys. map f (xs ++ ys) = map f xs ++ map f ys #-}
+
+{-# DEPRECATED #-}
+{-# DEPRECATED foo "use bar instead" #-}
+{-# DEPRECATED foo, bar, baz "no longer supported" #-}
+
+{-# WARNING #-}
+{-# WARNING foo "use bar instead" #-}
+{-# WARNING foo, bar, baz "no longer supported" #-}
+
+{-# INLINE foo #-}
+{-# INLINE [3] foo #-}
+{-# INLINE [~3] foo #-}
+{-# NOINLINE foo #-}
+
+{-# INLINE CONLIKE foo #-}
+{-# INLINE CONLIKE [3] foo #-}
+
+{-# SPECIALISE foo :: Int -> Int #-}
+{-# SPECIALISE [3] foo :: Int -> Int, Float -> Float #-}
+
+{-# SPECIALISE INLINE foo :: Int -> Int #-}
+{-# SPECIALISE NOINLINE foo :: Int -> Int #-}
+
+{-# SPECIALISE instance Foo Int #-}
+{-# SPECIALISE instance forall a. (Ord a) => Foo a #-}
+
+{-# ANN foo (Just "Foo") #-}
+{-# ANN type Foo (Just "Foo") #-}
+{-# ANN module (Just "Foo") #-}
+
+{-# MINIMAL foo | bar, (baz | quux) #-}
+```
+
+## Exp
+
+### Var, Con, Lit, Tuple, UnboxedSum, List, and ExpTypeSig
+
+``` haskell
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE UnboxedSums #-}
+
+foo = foo
+
+foo = Nothing
+
+foo = 123
+
+foo = 'x'
+
+foo = ""
+foo = "Lorem Ipsum Dolor Amet Sit"
+
+foo = ()
+foo = (1, 2)
+foo = (1 -- the one
+ , 2)
+
+foo = (# #)
+foo = (# 1, 2 #)
+foo = (# 1 -- the one
+ , 2 #)
+
+
+foo = (# 1 #)
+foo = (# | 1 | | #)
+foo = (# | 1 -- the one
+      | | #)
+
+foo = []
+foo = [1]
+foo = [1,2]
+foo = [1 -- the one
+ , 2]
+
+foo = 1 :: Int
+```
+
+### App, InfixApp, NegApp, LeftSection, RightSection
+
+``` haskell
+foo = foldl fn init list
+foo = foldl fn -- reducer
+  init -- initial value
+  list
+
+foo = 1 + 2
+foo = fn `map` list
+
+foo = -3
+
+foo = (+ arg)
+foo = (`op` arg)
+
+foo = (arg +)
+foo = (arg `op`)
+```
+
+### EnumFrom, EnumFromTo, EnumFromThen, EnumFromThenTo, ParArrayFromTo, ParArrayFromThenTo
+
+``` haskell
+foo = [1..]
+foo = [1..10]
+foo = [1, 2..]
+foo = [1, 2..10]
+foo = [:1..10:]
+foo = [:1, 2..10:]
+```
+
+### ListComp, ParComp, and ParArrayComp
+
+``` haskell
+{-# LANGUAGE TransformListComp #-}
+
+foo = [ (x, y) | x <- xs, y <- ys ]
+foo = [ (x, y) -- cartesian product
+      | x <- xs -- first list
+      , y <- ys -- second list
+      ]
+foo = [ (x,y) | x <- xs | y <- ys ]
+foo = [ (x,y) -- zip
+      | x <- xs -- first list
+      | y <- ys -- second list
+      ]
+foo = [: (x,y) | x <- xs | y <- ys :]
+foo = [: (x,y) -- zip
+      | x <- xs -- first list
+      | y <- ys -- second list
+      :]
+
+foo = [ (x, y)
+      | x <- xs
+      , y <- ys
+      , then reverse
+      , then sortWith by (x+y)
+      , then group using permutations
+      , then group by (x+y) using groupWith
+      ]
+```
+
+### RecConstr and RecUpdate
+
+``` haskell
+{-# LANGUAGE RecordWildCards #-}
+
+foo = Point { x = 1, y = 2 }
+foo = Point { x = 1 -- the one
+            , y
+            , ..
+            }
+
+foo = bar { x = 1 }
+foo = bar { x = 1 -- the one
+          , y
+          , ..
+          }
+```
+
+### Let, If, MultiIf, and Case
+
+``` haskell
+{-# LANGUAGE MultiWayIf #-}
+
+foo = let x = x in x
+
+foo = let x = x -- bottom
+ in
+  -- bottom
+  x
+
+foo = if null xs then None else Some $ head xs
+
+foo = if null xs -- condition
+  then None -- it's empty
+  else Some $ head xs -- it's not
+
+foo = if | null xs -> None
+         | otherwise -> Some $ head xs
+
+foo = if | null xs ->
+           -- it's empty
+           None
+         | otherwise ->
+           -- it's not
+           Some $ head x
+
+foo = case x of
+  True -> False
+  False -> True
+
+foo = case xs of
+  [] ->
+    -- it's empty
+    None
+  x : _ ->
+    -- it's not
+    Some x
+
+foo = case xs of
+    _ | null xs -> None
+    _ -> Some $ head x
+```
+
+### Do and MDo
+
+``` haskell
+{-# LANGUAGE RecursiveDo #-}
+
+foo = do { return () }
+
+foo = do
+  return ()
+
+foo = do
+  this <- that
+  let this' = tail this
+  if this -- condition
+  then that
+  else those
+
+foo = mdo
+  return ()
+```
+
+### Lambda, LCase
+
+``` haskell
+{-# LANGUAGE LambdaCase #-}
+
+foo = \x -> x
+foo = \ ~x -> x
+foo = \ !x -> x
+foo d = \case
+  Nothing -> d
+  Some x -> x
+```
+
+### BracketExp, SpliceExp, QuasiQuote, VarQuote, and TypQuote
+
+``` haskell
+{-# LANGUAGE TemplateHaskell #-}
+
+mkDecl :: Q Decl
+mkDecl = [d|id x = x|]
+
+mkType :: Q Type
+mkType = [t|(a, b) -> a|]
+
+mkPat :: Q Pat
+mkPat = [p|(a, b)|]
+
+mkExp :: Q Exp
+mkExp = [e|a|]
+
+fst :: $(mkType)
+fst $(mkPat) = $(mkExp)
+
+html = [html|<p>Lorem Ipsum Dolor Amet Sit</p>|]
+
+foo = mkSomething 'id 'Nothing ''Maybe
+```
+
+# Regression Tests
+
+## Do
+
+Before comments and onside indent do not mix well.
+
+``` haskell
+foo = do
+  -- comment
+  some expression
+```
+
+## Onside
+
+Indent within onside started on non-empty line should still not stack.
+
+``` haskell
+foo = if cond
+  then do
+    this
+  else do
+    that
+```
+
+Before comments at the start of onside do not trigger onside.
+
+``` haskell
+foo = do
+  -- comment
+  some expression
+```
+
+Matche arms have individual onside.
+
+``` haskell
+foo True = some -- comment
+  expression
+foo False = some -- comment
+  other expression
+```
+
+Where binds are considered outside of onside.
+
+``` haskell
+foo = some -- comment
+  expression
+  where
+    expression = other
+```
+
+Align overrides onside.
+
+``` haskell
+foo = some expr [ 1 -- comment
+                , 2
+                ]
+```
+
+If-then-else must always indent in do blocks.
+
+``` haskell
+foo = do
+  if condition -- comment
+    then this
+    else that
+```
diff --git a/contrib/floskell.coffee b/contrib/floskell.coffee
new file mode 100644
--- /dev/null
+++ b/contrib/floskell.coffee
@@ -0,0 +1,83 @@
+{CompositeDisposable} = require 'atom'
+{BufferedProcess} = require 'atom'
+{dirname} = require 'path'
+{statSync} = require 'fs'
+
+prettify = (args, text, workingDirectory, {onComplete, onFailure}) ->
+  lines = []
+  proc = new BufferedProcess
+    command: 'floskell'
+    args: args
+    options:
+      cwd: workingDirectory
+    stdout: (line) -> lines.push(line)
+    exit: -> onComplete?(lines.join(''))
+  proc.onWillThrowError ({error, handle}) ->
+    atom.notifications.addError "Floskell could not spawn",
+      detail: "#{error}"
+    onFailure?()
+    handle()
+  proc.process.stdin.write(text)
+  proc.process.stdin.end()
+
+prettifyFile = (args, editor, format = 'haskell') ->
+  [firstCursor, cursors...] = editor.getCursors().map (cursor) ->
+    cursor.getBufferPosition()
+  try
+    workDir = dirname(editor.getPath())
+    if not statSync(workDir).isDirectory()
+      workDir = '.'
+  catch
+    workDir = '.'
+  prettify args, editor.getText(), workDir,
+    onComplete: (text) ->
+      editor.setText(text)
+      if editor.getLastCursor()?
+        editor.getLastCursor().setBufferPosition firstCursor,
+          autoscroll: false
+        cursors.forEach (cursor) ->
+          editor.addCursorAtBufferPosition cursor,
+            autoscroll: false
+
+module.exports = Floskell =
+  disposables: null
+  menu: null
+
+  activate: (state) ->
+    @disposables = new CompositeDisposable
+    @menu = new CompositeDisposable
+
+    @disposables.add \
+      atom.commands.add 'atom-text-editor[data-grammar~="haskell"]',
+        'floskell:prettify-none': ({target}) =>
+          prettifyFile [], target.getModel()
+        'floskell:prettify-chris-done': ({target}) =>
+          prettifyFile ['--style', 'chris-done'], target.getModel()
+        'floskell:prettify-cramer': ({target}) =>
+          prettifyFile ['--style', 'cramer'], target.getModel()
+        'floskell:prettify-gibiansky': ({target}) =>
+          prettifyFile ['--style', 'gibiansky'], target.getModel()
+        'floskell:prettify-johan-tibell': ({target}) =>
+          prettifyFile ['--style', 'johan-tibell'], target.getModel()
+
+    @menu.add atom.menu.add [
+      label: 'floskell'
+      submenu : [
+        {label: 'Default', command: 'floskell:prettify-none'}
+        {label: 'Cramer', command: 'floskell:prettify-cramer'}
+        {label: 'Chris Done', command: 'floskell:prettify-chris-done'}
+        {label: 'Gibiansky', command: 'floskell:prettify-gibiansky'}
+        {label: 'Johan Tibell', command: 'floskell:prettify-johan-tibell'}
+      ]
+    ]
+
+  deactivate: ->
+    @disposables.dispose()
+    @disposables = null
+
+    @clearMenu()
+
+  clearMenu: ->
+    @menu.dispose()
+    @menu = null
+    atom.menu.update()
diff --git a/contrib/floskell.el b/contrib/floskell.el
new file mode 100644
--- /dev/null
+++ b/contrib/floskell.el
@@ -0,0 +1,263 @@
+;;; floskell.el --- Indent haskell code using the "floskell" program
+
+;; Copyright (c) 2014 Chris Done. All rights reserved.
+
+;; Author: Chris Done <chrisdone@gmail.com>
+;; URL: https://github.com/ennocramer/floskell
+;; Package-Requires: ((cl-lib "0.5"))
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'cl-lib)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Minor mode
+
+(defvar floskell-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [remap indent-region] #'floskell-reformat-region)
+    (define-key map [remap fill-paragraph] #'floskell-reformat-decl-or-fill)
+    map)
+  "Keymap for `floskell-mode'.")
+
+;;;###autoload
+(define-minor-mode floskell-mode
+  "Indent code with the floskell program.
+
+Provide the following keybindings:
+
+\\{floskell-mode-map}"
+  :init-value nil
+  :keymap floskell-mode-map
+  :lighter " HI"
+  :group 'haskell
+  :require 'floskell)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Customization properties
+
+(defcustom floskell-style
+  nil
+  "The style to use for formatting."
+  :group 'haskell
+  :type 'string
+  :safe #'stringp)
+
+(defcustom floskell-process-path
+  "floskell"
+  "Location where the floskell executable is located."
+  :group 'haskell
+  :type 'string
+  :safe #'stringp)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Interactive functions
+
+;;;###autoload
+(defun floskell-reformat-decl ()
+  "Re-format the current declaration by parsing and pretty
+  printing it. Comments are preserved, although placement may be
+  funky."
+  (interactive)
+  (let ((start-end (floskell-decl-points)))
+    (when start-end
+      (let ((beg (car start-end))
+            (end (cdr start-end)))
+        (floskell-reformat-region beg end)))))
+
+;;;###autoload
+(defun floskell-reformat-buffer ()
+  "Reformat the whole buffer."
+  (interactive)
+  (floskell-reformat-region (point-min)
+                           (point-max)))
+
+;;;###autoload
+(defun floskell-reformat-decl-or-fill (justify)
+  "Re-format current declaration, or fill paragraph.
+
+Fill paragraph if in a comment, otherwise reformat the current
+declaration."
+  (interactive (progn
+                 ;; Copied from `fill-paragraph'
+                 (barf-if-buffer-read-only)
+                 (list (if current-prefix-arg 'full))))
+  (if (floskell-in-comment)
+      (fill-paragraph justify t)
+    (floskell/reformat-decl)))
+
+;;;###autoload
+(defun floskell-reformat-region (beg end)
+  "Reformat the given region, accounting for indentation."
+  (interactive "r")
+  (if (= (save-excursion (goto-char beg)
+                         (line-beginning-position))
+         beg)
+      (floskell-reformat-region-as-is beg end)
+    (let* ((column (- beg (line-beginning-position)))
+           (string (buffer-substring-no-properties beg end))
+           (new-string (with-temp-buffer
+                         (insert (make-string column ? ) string)
+                         (floskell-reformat-region-as-is (point-min)
+                                                        (point-max))
+                         (delete-region (point-min) (1+ column))
+                         (buffer-substring (point-min)
+                                           (point-max)))))
+      (save-excursion
+        (goto-char beg)
+        (delete-region beg end)
+        (insert new-string)))))
+
+;;;###autoload
+(defun floskell/reformat-decl ()
+  "See `floskell-reformat-decl'."
+  (floskell-reformat-decl))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Internal library
+
+(defun floskell-reformat-region-as-is (beg end)
+  "Reformat the given region as-is.
+
+This is the place where floskell is actually called."
+  (let* ((original (current-buffer))
+         (orig-str (buffer-substring-no-properties beg end)))
+    (with-temp-buffer
+      (let ((temp (current-buffer)))
+        (with-current-buffer original
+          (let ((ret (apply #'call-process-region
+                            (append (list beg
+                                          end
+                                          floskell-process-path
+                                          nil ; delete
+                                          temp ; output
+                                          nil)
+                                    (when floskell-style
+                                      (list "--style" floskell-style))
+                                    (floskell-extra-arguments)))))
+            (cond
+             ((= ret 1)
+              (let ((error-string
+                     (with-current-buffer temp
+                       (let ((string (progn (goto-char (point-min))
+                                            (buffer-substring (line-beginning-position)
+                                                              (line-end-position)))))
+                         string))))
+                (if (string= error-string "floskell: Parse error: EOF")
+                    (message "language pragma")
+                  (error error-string))))
+             ((= ret 0)
+              (let ((new-str (with-current-buffer temp
+                               (buffer-string))))
+                (if (not (string= new-str orig-str))
+                    (let ((line (line-number-at-pos))
+                          (col (current-column)))
+                      (delete-region beg
+                                     end)
+                      (let ((new-start (point)))
+                        (insert new-str)
+                        (let ((new-end (point)))
+                          (goto-char (point-min))
+                          (forward-line (1- line))
+                          (goto-char (+ (line-beginning-position) col))
+                          (when (looking-back "^[ ]+")
+                            (back-to-indentation))
+                          (delete-trailing-whitespace new-start new-end)))
+                      (message "Formatted."))
+                  (message "Already formatted.")))))))))))
+
+(defun floskell-decl-points (&optional use-line-comments)
+  "Get the start and end position of the current
+declaration. This assumes that declarations start at column zero
+and that the rest is always indented by one space afterwards, so
+Template Haskell uses with it all being at column zero are not
+expected to work."
+  (cond
+   ;; If we're in a block comment spanning multiple lines then let's
+   ;; see if it starts at the beginning of the line (or if any comment
+   ;; is at the beginning of the line, we don't care to treat it as a
+   ;; proper declaration.
+   ((and (not use-line-comments)
+         (floskell-in-comment)
+         (save-excursion (goto-char (line-beginning-position))
+                         (floskell-in-comment)))
+    nil)
+   ((save-excursion
+      (goto-char (line-beginning-position))
+      (or (looking-at "^-}$")
+          (looking-at "^{-$")))
+    nil)
+   ;; Otherwise we just do our line-based hack.
+   (t
+    (save-excursion
+      (let ((start
+             (or (cl-letf
+                     (((symbol-function 'jump)
+                       #'(lambda ()
+                           (search-backward-regexp "^[^ \n]" nil t 1)
+                           (cond
+                            ((save-excursion (goto-char (line-beginning-position))
+                                             (looking-at "|]"))
+                             (jump))
+                            (t (unless (or (looking-at "^-}$")
+                                           (looking-at "^{-$"))
+                                 (point)))))))
+                   (goto-char (line-end-position))
+                   (jump))
+                 0))
+            (end
+             (progn
+               (goto-char (1+ (point)))
+               (or (cl-letf
+                       (((symbol-function 'jump)
+                         #'(lambda ()
+                             (when (search-forward-regexp "[\n]+[^ \n]" nil t 1)
+                               (cond
+                                ((save-excursion (goto-char (line-beginning-position))
+                                                 (looking-at "|]"))
+                                 (jump))
+                                (t (forward-char -1)
+                                   (search-backward-regexp "[^\n ]" nil t)
+                                   (forward-char)
+                                   (point)))))))
+                     (jump))
+                   (point-max)))))
+        (cons start end))))))
+
+(defun floskell-in-comment ()
+  "Are we currently in a comment?"
+  (save-excursion
+    (when (and (= (line-end-position)
+                  (point))
+               (/= (line-beginning-position) (point)))
+      (forward-char -1))
+    (and
+     (elt (syntax-ppss) 4)
+     ;; Pragmas {-# SPECIALIZE .. #-} etc are not to be treated as
+     ;; comments, even though they are highlighted as such
+     (not (save-excursion (goto-char (line-beginning-position))
+                          (looking-at "{-# "))))))
+
+(defun floskell-extra-arguments ()
+  "Pass in extra arguments, such as extensions and optionally
+other things later."
+  (if (boundp 'haskell-language-extensions)
+      haskell-language-extensions
+    '()))
+
+(provide 'floskell)
+
+;;; floskell.el ends here
diff --git a/floskell.cabal b/floskell.cabal
new file mode 100644
--- /dev/null
+++ b/floskell.cabal
@@ -0,0 +1,106 @@
+name:                floskell
+version:             0.9.0
+synopsis:            A flexible Haskell source code pretty printer
+description:         A flexible Haskell source code pretty printer.
+                     .
+                     See the Github page for usage\/explanation: <https://github.com/ennocramer/floskell>
+license:             BSD3
+stability:           Unstable
+license-file:        LICENSE.md
+author:              Chris Done, Andrew Gibiansky, Tobias Pflug, Pierre Radermecker, Enno Cramer
+maintainer:          ecramer@memfrob.de
+copyright:           2014 Chris Done, 2015 Andrew Gibiansky, 2016-2019 Enno Cramer
+category:            Development
+build-type:          Simple
+cabal-version:       >=1.8
+homepage:            https://www.github.com/ennocramer/floskell
+bug-reports:         https://github.com/ennocramer/floskell/issues
+data-files:          contrib/floskell.el
+                     contrib/floskell.coffee
+extra-source-files:  README.md
+                     CHANGELOG.md
+                     BENCHMARK.md
+                     TEST.md
+
+source-repository head
+    type:           git
+    location:       https://github.com/ennocramer/floskell
+
+library
+  hs-source-dirs:    src/
+  ghc-options:       -Wall
+  exposed-modules:   Floskell
+                     Floskell.Buffer
+                     Floskell.Comments
+                     Floskell.Config
+                     Floskell.Pretty
+                     Floskell.Printers
+                     Floskell.Styles
+                     Floskell.Types
+  build-depends:     base >= 4.7 && <5
+                   , aeson
+                   , containers
+                   , unordered-containers
+                   , data-default
+                   , haskell-src-exts >= 1.20
+                   , monad-loops
+                   , monad-dijkstra
+                   , mtl
+                   , bytestring
+                   , utf8-string
+                   , transformers
+                   , text
+
+executable floskell
+  hs-source-dirs:    src/main
+  ghc-options:       -Wall -Wno-missing-home-modules -optP-Wno-nonportable-include-path
+  main-is:           Main.hs
+  build-depends:     base >= 4 && < 5
+                   , floskell
+                   , aeson
+                   , aeson-pretty
+                   , bytestring
+                   , optparse-applicative
+                   , haskell-src-exts
+                   , ghc-prim
+                   , directory
+                   , filepath
+                   , unordered-containers
+                   , text
+
+test-suite floskell-test
+  type: exitcode-stdio-1.0
+  hs-source-dirs: src/main
+  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N
+  main-is:           Test.hs
+  other-modules:     Markdone
+  build-depends:     base >= 4 && <5
+                   , floskell
+                   , haskell-src-exts
+                   , monad-loops
+                   , mtl
+                   , bytestring
+                   , utf8-string
+                   , hspec
+                   , directory
+                   , text
+                   , deepseq
+                   , exceptions
+
+benchmark floskell-bench
+  type: exitcode-stdio-1.0
+  hs-source-dirs:    src/main
+  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N
+  main-is:           Benchmark.hs
+  other-modules:     Markdone
+  build-depends:     base >= 4 && < 5
+                   , floskell
+                   , bytestring
+                   , utf8-string
+                   , haskell-src-exts
+                   , ghc-prim
+                   , directory
+                   , criterion
+                   , deepseq
+                   , exceptions
+                   , text
diff --git a/src/Floskell.hs b/src/Floskell.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell.hs
@@ -0,0 +1,222 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+
+-- | Haskell indenter.
+module Floskell
+    ( -- * Formatting functions.
+      reformat
+    , prettyPrint
+      -- * Style
+    , Style(..)
+    , styles
+      -- * Testing
+    , defaultExtensions
+    ) where
+
+import           Data.ByteString            ( ByteString )
+import qualified Data.ByteString            as S
+import qualified Data.ByteString.Char8      as S8
+import qualified Data.ByteString.Internal   as S
+import qualified Data.ByteString.Lazy       as L
+import qualified Data.ByteString.Lazy.Char8 as L8
+import qualified Data.ByteString.UTF8       as UTF8
+import qualified Data.ByteString.Unsafe     as S
+import           Data.Function              ( on )
+import           Data.List
+import qualified Data.Map.Strict            as Map
+import           Data.Maybe
+import           Data.Monoid
+
+import qualified Floskell.Buffer            as Buffer
+import           Floskell.Comments
+import           Floskell.Pretty            ( pretty, printComment )
+import           Floskell.Styles            ( styles )
+import           Floskell.Types
+
+import           Language.Haskell.Exts
+                 hiding ( Pretty, Style, parse, prettyPrint, style )
+import qualified Language.Haskell.Exts      as Exts
+
+data CodeBlock = HaskellSource Int ByteString | CPPDirectives ByteString
+    deriving ( Show, Eq )
+
+-- | Format the given source.
+reformat :: Style
+         -> Language
+         -> [Extension]
+         -> Maybe FilePath
+         -> ByteString
+         -> Either String L.ByteString
+reformat style language langextensions mfilepath x = preserveTrailingNewline x
+    . mconcat . intersperse "\n" <$> mapM processBlock (cppSplitBlocks x)
+  where
+    processBlock :: CodeBlock -> Either String L.ByteString
+    processBlock (CPPDirectives text) = Right $ L.fromStrict text
+    processBlock (HaskellSource offset text) =
+        let ls = S8.lines text
+            prefix = findPrefix ls
+            code = unlines' (map (stripPrefix prefix) ls)
+            exts = readExtensions (UTF8.toString code)
+            mode'' = case exts of
+                Nothing -> mode'
+                Just (Nothing, exts') ->
+                    mode' { extensions = exts' ++ extensions mode' }
+                Just (Just lang, exts') ->
+                    mode' { baseLanguage = lang
+                          , extensions   = exts' ++ extensions mode'
+                          }
+        in
+            case parseModuleWithComments mode'' (UTF8.toString code) of
+                ParseOk (m, comments) ->
+                    fmap (addPrefix prefix) (prettyPrint style m comments)
+                ParseFailed loc e -> Left $
+                    Exts.prettyPrint (loc { srcLine = srcLine loc + offset })
+                    ++ ": " ++ e
+
+    unlines' = S.concat . intersperse "\n"
+
+    unlines'' = L.concat . intersperse "\n"
+
+    addPrefix :: ByteString -> L8.ByteString -> L8.ByteString
+    addPrefix prefix = unlines'' . map (L8.fromStrict prefix <>) . L8.lines
+
+    stripPrefix :: ByteString -> ByteString -> ByteString
+    stripPrefix prefix line = if S.null (S8.dropWhile (== '\n') line)
+                              then line
+                              else fromMaybe (error "Missing expected prefix")
+                                  . s8_stripPrefix prefix $ line
+
+    findPrefix :: [ByteString] -> ByteString
+    findPrefix = takePrefix False . findSmallestPrefix . dropNewlines
+
+    dropNewlines :: [ByteString] -> [ByteString]
+    dropNewlines = filter (not . S.null . S8.dropWhile (== '\n'))
+
+    takePrefix :: Bool -> ByteString -> ByteString
+    takePrefix bracketUsed txt = case S8.uncons txt of
+        Nothing -> ""
+        Just ('>', txt') ->
+            if not bracketUsed then S8.cons '>' (takePrefix True txt') else ""
+        Just (c, txt') -> if c == ' ' || c == '\t'
+                          then S8.cons c (takePrefix bracketUsed txt')
+                          else ""
+
+    findSmallestPrefix :: [ByteString] -> ByteString
+    findSmallestPrefix [] = ""
+    findSmallestPrefix ("" : _) = ""
+    findSmallestPrefix (p : ps) =
+        let first = S8.head p
+            startsWithChar c x = S8.length x > 0 && S8.head x == c
+        in
+            if all (startsWithChar first) ps
+            then S8.cons first (findSmallestPrefix (S.tail p : map S.tail ps))
+            else ""
+
+    mode' = defaultParseMode { parseFilename = fromMaybe "<stdin>" mfilepath
+                             , baseLanguage  = language
+                             , extensions    = langextensions
+                             }
+
+    preserveTrailingNewline x x' = if not (S8.null x) && S8.last x == '\n'
+                                       && not (L8.null x') && L8.last x' /= '\n'
+                                   then x' <> "\n"
+                                   else x'
+
+-- | Break a Haskell code string into chunks, using CPP as a delimiter.
+-- Lines that start with '#if', '#end', or '#else' are their own chunks, and
+-- also act as chunk separators. For example, the code
+--
+-- > #ifdef X
+-- > x = X
+-- > y = Y
+-- > #else
+-- > x = Y
+-- > y = X
+-- > #endif
+--
+-- will become five blocks, one for each CPP line and one for each pair of declarations.
+cppSplitBlocks :: ByteString -> [CodeBlock]
+cppSplitBlocks = map (classify . unlines') . groupBy ((==) `on` (cppLine . snd))
+    . zip [ 0 .. ] . S8.lines
+  where
+    cppLine :: ByteString -> Bool
+    cppLine src =
+        any (`S8.isPrefixOf` src)
+            [ "#if"
+            , "#end"
+            , "#else"
+            , "#define"
+            , "#undef"
+            , "#elif"
+            , "#include"
+            , "#error"
+            , "#warning"
+            ]
+
+    unlines' :: [(Int, ByteString)] -> (Int, ByteString)
+    unlines' [] = (0, "")
+    unlines' xs@((line, _) : _) = (line, S8.unlines $ map snd xs)
+
+    classify :: (Int, ByteString) -> CodeBlock
+    classify (ofs, text) =
+        if cppLine text then CPPDirectives text else HaskellSource ofs text
+
+-- | Print the module.
+prettyPrint :: Style
+            -> Module SrcSpanInfo
+            -> [Comment]
+            -> Either a L.ByteString
+prettyPrint style m comments =
+    let (cs, ast) =
+            annotateComments (fromMaybe m $ applyFixities baseFixities m)
+                             comments
+        csComments = map comInfoComment cs
+    in
+        Right (runPrinterStyle style
+                               -- For the time being, assume that all "free-floating" comments come at the beginning.
+                               -- If they were not at the beginning, they would be after some ast node.
+                               -- Thus, print them before going for the ast.
+                               (do
+                                    mapM_ (printComment Nothing)
+                                          (reverse csComments)
+                                    pretty ast))
+
+-- | Pretty print the given printable thing.
+runPrinterStyle :: Style -> Printer () -> L.ByteString
+runPrinterStyle (Style _name _author _desc st) m =
+    maybe (error "Printer failed with mzero call.")
+          (Buffer.toLazyByteString . psBuffer)
+          (snd <$> execPrinter m
+                               (PrintState Buffer.empty
+                                           0
+                                           0
+                                           Map.empty
+                                           st
+                                           False
+                                           Anything))
+
+-- | Default extensions.
+defaultExtensions :: [Extension]
+defaultExtensions = [ e | e@EnableExtension{} <- knownExtensions ]
+    \\ map EnableExtension badExtensions
+
+-- | Extensions which steal too much syntax.
+badExtensions :: [KnownExtension]
+badExtensions =
+    [ Arrows -- steals proc
+    , TransformListComp -- steals the group keyword
+    , XmlSyntax
+    , RegularPatterns -- steals a-b
+    , UnboxedTuples -- breaks (#) lens operator
+    , PatternSynonyms -- steals the pattern keyword
+    , RecursiveDo -- steals the rec keyword
+    , DoRec -- same
+    , TypeApplications -- since GHC 8 and haskell-src-exts-1.19
+    ]
+
+-- ,QuasiQuotes -- breaks [x| ...], making whitespace free list comps break
+s8_stripPrefix :: ByteString -> ByteString -> Maybe ByteString
+s8_stripPrefix bs1@(S.PS _ _ l1) bs2
+    | bs1 `S.isPrefixOf` bs2 = Just (S.unsafeDrop l1 bs2)
+    | otherwise = Nothing
diff --git a/src/Floskell/Buffer.hs b/src/Floskell/Buffer.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell/Buffer.hs
@@ -0,0 +1,67 @@
+-- | An outout buffer for ByteStrings that keeps track of line and
+-- column numbers.
+module Floskell.Buffer
+    ( Buffer
+    , empty
+    , newline
+    , write
+    , line
+    , column
+    , toLazyByteString
+    ) where
+
+import qualified Data.ByteString         as BS
+import           Data.ByteString.Builder ( Builder )
+import qualified Data.ByteString.Builder as BB
+import qualified Data.ByteString.Lazy    as BL
+
+import           Data.Int                ( Int64 )
+
+data Buffer =
+    Buffer { bufferData        :: !Builder -- ^ The current output.
+           , bufferDataNoSpace :: !Builder -- ^ The current output without trailing spaces.
+           , bufferLine        :: !Int64   -- ^ Current line number.
+           , bufferColumn      :: !Int64   -- ^ Current column number.
+           }
+
+-- | An empty output buffer.
+empty :: Buffer
+empty = Buffer { bufferData        = mempty
+               , bufferDataNoSpace = mempty
+               , bufferLine        = 0
+               , bufferColumn      = 0
+               }
+
+-- | Append a ByteString to the output buffer.  It is an error for the
+-- string to contain newlines.
+write :: BS.ByteString -> Buffer -> Buffer
+write str buf =
+    buf { bufferData        = newBufferData
+        , bufferDataNoSpace =
+              if BS.all (== 32) str then bufferData buf else newBufferData
+        , bufferColumn      = bufferColumn buf + fromIntegral (BS.length str)
+        }
+  where
+    newBufferData = bufferData buf `mappend` BB.byteString str
+
+-- | Append a newline to the output buffer.
+newline :: Buffer -> Buffer
+newline buf = buf { bufferData        = newBufferData
+                  , bufferDataNoSpace = newBufferData
+                  , bufferLine        = bufferLine buf + 1
+                  , bufferColumn      = 0
+                  }
+  where
+    newBufferData = bufferDataNoSpace buf `mappend` BB.char7 '\n'
+
+-- | Return the current line number, counting from 0.
+line :: Buffer -> Int64
+line = bufferLine
+
+-- | Return the column number, counting from 0.
+column :: Buffer -> Int64
+column = bufferColumn
+
+-- | Return the contents of the output buffer as a lazy ByteString.
+toLazyByteString :: Buffer -> BL.ByteString
+toLazyByteString = BB.toLazyByteString . bufferData
diff --git a/src/Floskell/Comments.hs b/src/Floskell/Comments.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell/Comments.hs
@@ -0,0 +1,121 @@
+-- | Comment handling.
+module Floskell.Comments where
+
+import           Control.Arrow              ( first, second )
+import           Control.Monad.State.Strict
+
+import           Data.Foldable              ( traverse_ )
+import qualified Data.Map.Strict            as M
+
+import           Floskell.Types
+
+import           Language.Haskell.Exts
+                 hiding ( Pretty, Style, parse, prettyPrint, style )
+
+-- Order by start of span, larger spans before smaller spans.
+newtype OrderByStart = OrderByStart SrcSpan
+    deriving ( Eq )
+
+instance Ord OrderByStart where
+    compare (OrderByStart l) (OrderByStart r) =
+        compare (srcSpanStartLine l) (srcSpanStartLine r)
+        `mappend` compare (srcSpanStartColumn l) (srcSpanStartColumn r)
+        `mappend` compare (srcSpanEndLine r) (srcSpanEndLine l)
+        `mappend` compare (srcSpanEndColumn r) (srcSpanEndColumn l)
+
+-- Order by end of span, smaller spans before larger spans.
+newtype OrderByEnd = OrderByEnd SrcSpan
+    deriving ( Eq )
+
+instance Ord OrderByEnd where
+    compare (OrderByEnd l) (OrderByEnd r) =
+        compare (srcSpanEndLine l) (srcSpanEndLine r)
+        `mappend` compare (srcSpanEndColumn l) (srcSpanEndColumn r)
+        `mappend` compare (srcSpanStartLine r) (srcSpanStartLine l)
+        `mappend` compare (srcSpanStartColumn r) (srcSpanStartColumn l)
+
+-- | Annotate the AST with comments.
+annotateComments :: Traversable ast
+                 => ast SrcSpanInfo
+                 -> [Comment]
+                 -> ([ComInfo], ast NodeInfo)
+annotateComments src comments =
+    evalState (do
+                   traverse_ assignComment comments
+                   cis <- gets fst
+                   ast <- traverse transferComments src
+                   return (cis, ast))
+              ([], nodeinfos)
+  where
+    nodeinfos :: M.Map SrcSpanInfo NodeInfo
+    nodeinfos = foldr (\ssi -> M.insert ssi (NodeInfo ssi [])) M.empty src
+
+    -- Assign a single comment to the right AST node
+    assignComment :: Comment
+                  -> State ([ComInfo], M.Map SrcSpanInfo NodeInfo) ()
+    assignComment comment@(Comment _ cspan _) =
+        -- Find the biggest AST node directly in front of this comment.
+        case nodeBefore comment of
+            -- Comments before any AST node are handled separately.
+            Nothing -> modify $ first $ (:) (ComInfo comment Nothing)
+
+            Just ssi ->
+                -- Comments on the same line as the AST node belong to this node.
+                if sameline (srcInfoSpan ssi) cspan
+                then insertComment After ssi
+                else do
+                    nodeinfo <- gets ((M.! ssi) . snd)
+                    case nodeinfo of
+                        -- We've already collected comments for this
+                        -- node and this comment is a continuation.
+                        NodeInfo _ (ComInfo c' _ : _)
+                            | aligned c' comment -> insertComment After ssi
+
+                        -- The comment does not belong to this node.
+                        -- If there is a node following this comment,
+                        -- assign it to that node, else keep it here,
+                        -- anyway.
+                        _ -> case nodeAfter comment of
+                            Nothing -> insertComment After ssi
+                            Just ssi' -> insertComment Before ssi'
+      where
+        sameline :: SrcSpan -> SrcSpan -> Bool
+        sameline before after = srcSpanEndLine before == srcSpanStartLine after
+
+        aligned :: Comment -> Comment -> Bool
+        aligned (Comment _ before _) (Comment _ after _) =
+            srcSpanEndLine before == srcSpanStartLine after - 1
+            && srcSpanStartColumn before == srcSpanStartColumn after
+
+        insertComment :: Location
+                      -> SrcSpanInfo
+                      -> State ([ComInfo], M.Map SrcSpanInfo NodeInfo) ()
+        insertComment l ssi = modify $ second $
+            M.adjust (addComment (ComInfo comment (Just l))) ssi
+
+        addComment :: ComInfo -> NodeInfo -> NodeInfo
+        addComment x (NodeInfo s xs) = NodeInfo s (x : xs)
+
+    -- Transfer collected comments into the AST.
+    transferComments :: SrcSpanInfo
+                     -> State ([ComInfo], M.Map SrcSpanInfo NodeInfo) NodeInfo
+    transferComments ssi = do
+        ni <- gets ((M.! ssi) . snd)
+        -- Sometimes, there are multiple AST nodes with the same
+        -- SrcSpan.  Make sure we assign comments to only one of
+        -- them.
+        modify $ second $ M.adjust (\(NodeInfo s _) -> NodeInfo s []) ssi
+        return ni { nodeInfoComments = reverse $ nodeInfoComments ni }
+
+    nodeBefore (Comment _ ss _) =
+        fmap snd $ OrderByEnd ss `M.lookupLT` spansByEnd
+
+    nodeAfter (Comment _ ss _) =
+        fmap snd $ OrderByStart ss `M.lookupGT` spansByStart
+
+    spansByStart = foldr (\ssi -> M.insert (OrderByStart $ srcInfoSpan ssi) ssi)
+                         M.empty
+                         src
+
+    spansByEnd =
+        foldr (\ssi -> M.insert (OrderByEnd $ srcInfoSpan ssi) ssi) M.empty src
diff --git a/src/Floskell/Config.hs b/src/Floskell/Config.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell/Config.hs
@@ -0,0 +1,458 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Floskell.Config
+    ( Indent(..)
+    , LayoutContext(..)
+    , Location(..)
+    , WsLoc(..)
+    , Whitespace(..)
+    , Layout(..)
+    , ConfigMapKey(..)
+    , ConfigMap(..)
+    , PenaltyConfig(..)
+    , AlignConfig(..)
+    , IndentConfig(..)
+    , LayoutConfig(..)
+    , OpConfig(..)
+    , GroupConfig(..)
+    , OptionConfig(..)
+    , FlexConfig(..)
+    , defaultFlexConfig
+    , safeFlexConfig
+    , cfgMapFind
+    , cfgOpWs
+    , cfgGroupWs
+    , inWs
+    , wsSpace
+    , wsLinebreak
+    ) where
+
+import           Data.Aeson
+                 ( FromJSON(..), ToJSON(..), genericParseJSON, genericToJSON )
+import qualified Data.Aeson         as JSON
+import           Data.Aeson.Types   as JSON
+                 ( Options(..), camelTo2, typeMismatch )
+import           Data.ByteString    ( ByteString )
+import           Data.Default       ( Default(..) )
+import qualified Data.HashMap.Lazy  as HashMap
+import           Data.Map.Strict    ( Map )
+import qualified Data.Map.Strict    as Map
+import qualified Data.Text          as T
+import qualified Data.Text.Encoding as T ( decodeUtf8, encodeUtf8 )
+
+import           GHC.Generics
+
+data Indent = Align | IndentBy !Int | AlignOrIndentBy !Int
+    deriving ( Eq, Ord, Show, Generic )
+
+data LayoutContext = Declaration | Type | Pattern | Expression | Other
+    deriving ( Eq, Ord, Bounded, Enum, Show, Generic )
+
+data Location = Before | After
+    deriving ( Eq, Ord, Bounded, Enum, Show, Generic )
+
+data WsLoc = WsNone | WsBefore | WsAfter | WsBoth
+    deriving ( Eq, Ord, Bounded, Enum, Show, Generic )
+
+data Whitespace = Whitespace { wsSpaces         :: !WsLoc
+                             , wsLinebreaks     :: !WsLoc
+                             , wsForceLinebreak :: !Bool
+                             }
+    deriving ( Show, Generic )
+
+data Layout = Flex | Vertical | TryOneline
+    deriving ( Eq, Ord, Bounded, Enum, Show, Generic )
+
+data ConfigMapKey = ConfigMapKey !(Maybe ByteString) !(Maybe LayoutContext)
+    deriving ( Eq, Ord, Show )
+
+data ConfigMap a =
+    ConfigMap { cfgMapDefault :: !a, cfgMapOverrides :: !(Map ConfigMapKey a) }
+    deriving ( Generic )
+
+data PenaltyConfig = PenaltyConfig { penaltyMaxLineLength :: !Int
+                                   , penaltyLinebreak     :: !Int
+                                   , penaltyIndent        :: !Int
+                                   , penaltyOverfull      :: !Int
+                                   , penaltyOverfullOnce  :: !Int
+                                   }
+    deriving ( Generic )
+
+instance Default PenaltyConfig where
+    def = PenaltyConfig { penaltyMaxLineLength = 80
+                        , penaltyLinebreak     = 100
+                        , penaltyIndent        = 1
+                        , penaltyOverfull      = 10
+                        , penaltyOverfullOnce  = 200
+                        }
+
+data AlignConfig =
+    AlignConfig { cfgAlignLimits       :: !(Int, Int)
+                , cfgAlignCase         :: !Bool
+                , cfgAlignClass        :: !Bool
+                , cfgAlignImportModule :: !Bool
+                , cfgAlignImportSpec   :: !Bool
+                , cfgAlignLetBinds     :: !Bool
+                , cfgAlignRecordFields :: !Bool
+                , cfgAlignWhere        :: !Bool
+                }
+    deriving ( Generic )
+
+instance Default AlignConfig where
+    def = AlignConfig { cfgAlignLimits       = (10, 25)
+                      , cfgAlignCase         = False
+                      , cfgAlignClass        = False
+                      , cfgAlignImportModule = False
+                      , cfgAlignImportSpec   = False
+                      , cfgAlignLetBinds     = False
+                      , cfgAlignRecordFields = False
+                      , cfgAlignWhere        = False
+                      }
+
+data IndentConfig =
+    IndentConfig { cfgIndentOnside :: !Int
+                 , cfgIndentDeriving :: !Int
+                 , cfgIndentWhere :: !Int
+                 , cfgIndentApp :: !Indent
+                 , cfgIndentCase :: !Indent
+                 , cfgIndentClass :: !Indent
+                 , cfgIndentDo :: !Indent
+                 , cfgIndentExportSpecList :: !Indent
+                 , cfgIndentIf :: !Indent
+                 , cfgIndentImportSpecList :: !Indent
+                 , cfgIndentLet :: !Indent
+                 , cfgIndentLetBinds :: !Indent
+                 , cfgIndentLetIn :: !Indent
+                 , cfgIndentMultiIf :: !Indent
+                 , cfgIndentWhereBinds :: !Indent
+                 }
+    deriving ( Generic )
+
+instance Default IndentConfig where
+    def = IndentConfig { cfgIndentOnside = 4
+                       , cfgIndentDeriving = 4
+                       , cfgIndentWhere = 2
+                       , cfgIndentApp = IndentBy 4
+                       , cfgIndentCase = IndentBy 4
+                       , cfgIndentClass = IndentBy 4
+                       , cfgIndentDo = IndentBy 4
+                       , cfgIndentExportSpecList = IndentBy 4
+                       , cfgIndentIf = IndentBy 4
+                       , cfgIndentImportSpecList = IndentBy 4
+                       , cfgIndentLet = IndentBy 4
+                       , cfgIndentLetBinds = IndentBy 4
+                       , cfgIndentLetIn = IndentBy 4
+                       , cfgIndentMultiIf = IndentBy 4
+                       , cfgIndentWhereBinds = IndentBy 2
+                       }
+
+data LayoutConfig =
+    LayoutConfig { cfgLayoutApp :: !Layout
+                 , cfgLayoutConDecls :: !Layout
+                 , cfgLayoutDeclaration :: !Layout
+                 , cfgLayoutExportSpecList :: !Layout
+                 , cfgLayoutIf :: !Layout
+                 , cfgLayoutImportSpecList :: !Layout
+                 , cfgLayoutInfixApp :: !Layout
+                 , cfgLayoutLet :: !Layout
+                 , cfgLayoutListComp :: !Layout
+                 , cfgLayoutRecord :: !Layout
+                 , cfgLayoutTypesig :: !Layout
+                 }
+    deriving ( Generic )
+
+instance Default LayoutConfig where
+    def = LayoutConfig { cfgLayoutApp = Flex
+                       , cfgLayoutConDecls = Flex
+                       , cfgLayoutDeclaration = Flex
+                       , cfgLayoutExportSpecList = Flex
+                       , cfgLayoutIf = Flex
+                       , cfgLayoutImportSpecList = Flex
+                       , cfgLayoutInfixApp = Flex
+                       , cfgLayoutLet = Flex
+                       , cfgLayoutListComp = Flex
+                       , cfgLayoutRecord = Flex
+                       , cfgLayoutTypesig = Flex
+                       }
+
+newtype OpConfig = OpConfig { unOpConfig :: ConfigMap Whitespace }
+    deriving ( Generic )
+
+instance Default OpConfig where
+    def =
+        OpConfig ConfigMap { cfgMapDefault   = Whitespace WsBoth WsBefore False
+                           , cfgMapOverrides = Map.empty
+                           }
+
+newtype GroupConfig = GroupConfig { unGroupConfig :: ConfigMap Whitespace }
+    deriving ( Generic )
+
+instance Default GroupConfig where
+    def = GroupConfig ConfigMap { cfgMapDefault   =
+                                      Whitespace WsBoth WsAfter False
+                                , cfgMapOverrides = Map.empty
+                                }
+
+data OptionConfig = OptionConfig { cfgOptionSortPragmas           :: !Bool
+                                 , cfgOptionSplitLanguagePragmas  :: !Bool
+                                 , cfgOptionSortImports           :: !Bool
+                                 , cfgOptionSortImportLists       :: !Bool
+                                 , cfgOptionPreserveVerticalSpace :: !Bool
+                                 }
+    deriving ( Generic )
+
+instance Default OptionConfig where
+    def = OptionConfig { cfgOptionSortPragmas           = False
+                       , cfgOptionSplitLanguagePragmas  = False
+                       , cfgOptionSortImports           = False
+                       , cfgOptionSortImportLists       = False
+                       , cfgOptionPreserveVerticalSpace = False
+                       }
+
+data FlexConfig = FlexConfig { cfgPenalty :: !PenaltyConfig
+                             , cfgAlign   :: !AlignConfig
+                             , cfgIndent  :: !IndentConfig
+                             , cfgLayout  :: !LayoutConfig
+                             , cfgOp      :: !OpConfig
+                             , cfgGroup   :: !GroupConfig
+                             , cfgOptions :: !OptionConfig
+                             }
+    deriving ( Generic )
+
+instance Default FlexConfig where
+    def = FlexConfig { cfgPenalty = def
+                     , cfgAlign   = def
+                     , cfgIndent  = def
+                     , cfgLayout  = def
+                     , cfgOp      = def
+                     , cfgGroup   = def
+                     , cfgOptions = def
+                     }
+
+defaultFlexConfig :: FlexConfig
+defaultFlexConfig =
+    def { cfgOp = OpConfig ((unOpConfig def) { cfgMapOverrides =
+                                                   Map.fromList opWsOverrides
+                                             })
+        }
+  where
+    opWsOverrides =
+        [ (ConfigMapKey (Just ",") Nothing, Whitespace WsAfter WsBefore False)
+        , ( ConfigMapKey (Just "record") Nothing
+          , Whitespace WsAfter WsAfter False
+          )
+        , ( ConfigMapKey (Just ".") (Just Type)
+          , Whitespace WsAfter WsAfter False
+          )
+        ]
+
+safeFlexConfig :: FlexConfig -> FlexConfig
+safeFlexConfig cfg = cfg { cfgGroup = group, cfgOp = op }
+  where
+    group = GroupConfig $
+        updateOverrides (unGroupConfig $ cfgGroup cfg)
+                        [ ("(#", Expression), ("(#", Pattern) ]
+
+    op = OpConfig $
+        updateOverrides (unOpConfig $ cfgOp cfg) [ (".", Expression) ]
+
+    updateOverrides config overrides =
+        config { cfgMapOverrides =
+                     foldl (updateWs config) (cfgMapOverrides config) overrides
+               }
+
+    updateWs config m (key, ctx) =
+        Map.insert (ConfigMapKey (Just key) (Just ctx))
+                   (cfgMapFind ctx key config) { wsSpaces = WsBoth }
+                   m
+
+cfgMapFind :: LayoutContext -> ByteString -> ConfigMap a -> a
+cfgMapFind ctx key ConfigMap{..} =
+    let value = cfgMapDefault
+        value' = Map.findWithDefault value
+                                     (ConfigMapKey Nothing (Just ctx))
+                                     cfgMapOverrides
+        value'' = Map.findWithDefault value'
+                                      (ConfigMapKey (Just key) Nothing)
+                                      cfgMapOverrides
+        value''' = Map.findWithDefault value''
+                                       (ConfigMapKey (Just key) (Just ctx))
+                                       cfgMapOverrides
+    in
+        value'''
+
+cfgOpWs :: LayoutContext -> ByteString -> OpConfig -> Whitespace
+cfgOpWs ctx op = cfgMapFind ctx op . unOpConfig
+
+cfgGroupWs :: LayoutContext -> ByteString -> GroupConfig -> Whitespace
+cfgGroupWs ctx op = cfgMapFind ctx op . unGroupConfig
+
+inWs :: Location -> WsLoc -> Bool
+inWs _ WsBoth = True
+inWs Before WsBefore = True
+inWs After WsAfter = True
+inWs _ _ = False
+
+wsSpace :: Location -> Whitespace -> Bool
+wsSpace loc ws = loc `inWs` wsSpaces ws
+
+wsLinebreak :: Location -> Whitespace -> Bool
+wsLinebreak loc ws = loc `inWs` wsLinebreaks ws
+
+------------------------------------------------------------------------
+readMaybe :: Read a => String -> Maybe a
+readMaybe str = case reads str of
+    [ (x, "") ] -> Just x
+    _ -> Nothing
+
+enumOptions :: Int -> Options
+enumOptions n =
+    JSON.defaultOptions { constructorTagModifier = JSON.camelTo2 '-' . drop n }
+
+recordOptions :: Int -> Options
+recordOptions n =
+    JSON.defaultOptions { fieldLabelModifier = JSON.camelTo2 '-' . drop n
+                        , unwrapUnaryRecords = True
+                        }
+
+instance ToJSON Indent where
+    toJSON i = JSON.String $ case i of
+        Align -> "align"
+        IndentBy x -> "indent-by " `T.append` T.pack (show x)
+        AlignOrIndentBy x -> "align-or-indent-by " `T.append` T.pack (show x)
+
+instance FromJSON Indent where
+    parseJSON v@(JSON.String t) = maybe (JSON.typeMismatch "Indent" v) return $
+        if t == "align"
+        then Just Align
+        else if "indent-by " `T.isPrefixOf` t
+             then IndentBy <$> readMaybe (T.unpack $ T.drop 10 t)
+             else if "align-or-indent-by " `T.isPrefixOf` t
+                  then AlignOrIndentBy <$> readMaybe (T.unpack $ T.drop 19 t)
+                  else Nothing
+
+    parseJSON v = JSON.typeMismatch "Indent" v
+
+instance ToJSON LayoutContext where
+    toJSON = genericToJSON (enumOptions 0)
+
+instance FromJSON LayoutContext where
+    parseJSON = genericParseJSON (enumOptions 0)
+
+instance ToJSON WsLoc where
+    toJSON = genericToJSON (enumOptions 2)
+
+instance FromJSON WsLoc where
+    parseJSON = genericParseJSON (enumOptions 2)
+
+instance ToJSON Whitespace where
+    toJSON = genericToJSON (recordOptions 2)
+
+instance FromJSON Whitespace where
+    parseJSON = genericParseJSON (recordOptions 2)
+
+instance ToJSON Layout where
+    toJSON = genericToJSON (enumOptions 0)
+
+instance FromJSON Layout where
+    parseJSON = genericParseJSON (enumOptions 0)
+
+layoutToText :: LayoutContext -> T.Text
+layoutToText Declaration = "declaration"
+layoutToText Type = "type"
+layoutToText Pattern = "pattern"
+layoutToText Expression = "expression"
+layoutToText Other = "other"
+
+textToLayout :: T.Text -> Maybe LayoutContext
+textToLayout "declaration" = Just Declaration
+textToLayout "type" = Just Type
+textToLayout "pattern" = Just Pattern
+textToLayout "expression" = Just Expression
+textToLayout "other" = Just Other
+textToLayout _ = Nothing
+
+keyToText :: ConfigMapKey -> T.Text
+keyToText (ConfigMapKey Nothing Nothing) = "default"
+keyToText (ConfigMapKey (Just n) Nothing) = T.decodeUtf8 n
+keyToText (ConfigMapKey Nothing (Just l)) = "* in " `T.append` layoutToText l
+keyToText (ConfigMapKey (Just n) (Just l)) =
+    T.decodeUtf8 n `T.append` " in " `T.append` layoutToText l
+
+textToKey :: T.Text -> Maybe ConfigMapKey
+textToKey t = case T.splitOn " in " t of
+    [ "default" ] -> Just (ConfigMapKey Nothing Nothing)
+    [ "*", "*" ] -> Just (ConfigMapKey Nothing Nothing)
+    [ name ] -> Just (ConfigMapKey (Just (T.encodeUtf8 name)) Nothing)
+    [ name, "*" ] -> Just (ConfigMapKey (Just (T.encodeUtf8 name)) Nothing)
+    [ "*", layout ] -> ConfigMapKey Nothing . Just <$> textToLayout layout
+    [ name, layout ] -> ConfigMapKey (Just (T.encodeUtf8 name)) . Just
+        <$> textToLayout layout
+    _ -> Nothing
+
+instance ToJSON a => ToJSON (ConfigMap a) where
+    toJSON ConfigMap{..} = toJSON $ Map.insert "default" cfgMapDefault $
+        Map.mapKeys keyToText cfgMapOverrides
+
+instance FromJSON a => FromJSON (ConfigMap a) where
+    parseJSON value = do
+        o <- parseJSON value
+        cfgMapDefault <- maybe (fail "Missing key: default") return $
+            HashMap.lookup "default" o
+        cfgMapOverrides <- either fail (return . Map.fromList) $ mapM toKey $
+            HashMap.toList $ HashMap.delete "default" o
+        return ConfigMap { .. }
+      where
+        toKey (k, v) = case textToKey k of
+            Just k' -> Right (k', v)
+            Nothing -> Left ("Invalid key: " ++ T.unpack k)
+
+instance ToJSON PenaltyConfig where
+    toJSON = genericToJSON (recordOptions 7)
+
+instance FromJSON PenaltyConfig where
+    parseJSON = genericParseJSON (recordOptions 7)
+
+instance ToJSON AlignConfig where
+    toJSON = genericToJSON (recordOptions 8)
+
+instance FromJSON AlignConfig where
+    parseJSON = genericParseJSON (recordOptions 8)
+
+instance ToJSON IndentConfig where
+    toJSON = genericToJSON (recordOptions 9)
+
+instance FromJSON IndentConfig where
+    parseJSON = genericParseJSON (recordOptions 9)
+
+instance ToJSON LayoutConfig where
+    toJSON = genericToJSON (recordOptions 9)
+
+instance FromJSON LayoutConfig where
+    parseJSON = genericParseJSON (recordOptions 9)
+
+instance ToJSON OpConfig where
+    toJSON = genericToJSON (recordOptions 0)
+
+instance FromJSON OpConfig where
+    parseJSON = genericParseJSON (recordOptions 0)
+
+instance ToJSON GroupConfig where
+    toJSON = genericToJSON (recordOptions 0)
+
+instance FromJSON GroupConfig where
+    parseJSON = genericParseJSON (recordOptions 0)
+
+instance ToJSON OptionConfig where
+    toJSON = genericToJSON (recordOptions 9)
+
+instance FromJSON OptionConfig where
+    parseJSON = genericParseJSON (recordOptions 9)
+
+instance ToJSON FlexConfig where
+    toJSON = genericToJSON (recordOptions 3)
+
+instance FromJSON FlexConfig where
+    parseJSON = genericParseJSON (recordOptions 3)
diff --git a/src/Floskell/Pretty.hs b/src/Floskell/Pretty.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell/Pretty.hs
@@ -0,0 +1,2143 @@
+{-# LANGUAGE DefaultSignatures #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Floskell.Pretty where
+
+import           Control.Applicative            ( (<|>) )
+import           Control.Monad
+                 ( forM_, guard, replicateM_, unless, void, when )
+import           Control.Monad.State.Strict     ( get, gets, modify )
+
+import           Data.ByteString                ( ByteString )
+import qualified Data.ByteString                as BS
+import qualified Data.ByteString.Char8          as BS8
+import qualified Data.ByteString.Lazy           as BL
+
+import           Data.List                      ( groupBy, sortBy, sortOn )
+import           Data.Maybe                     ( catMaybes, fromMaybe )
+
+import qualified Floskell.Buffer                as Buffer
+import           Floskell.Config
+import           Floskell.Printers
+
+import           Floskell.Types
+
+import           Language.Haskell.Exts.Comments ( Comment(..) )
+import qualified Language.Haskell.Exts.Pretty   as HSE
+
+import           Language.Haskell.Exts.SrcLoc
+                 ( SrcSpan(..), noSrcSpan, srcInfoSpan )
+import           Language.Haskell.Exts.Syntax
+
+-- | Like `span`, but comparing adjacent items.
+run :: (a -> a -> Bool) -> [a] -> ([a], [a])
+run _ [] = ([], [])
+run _ [ x ] = ([ x ], [])
+run eq (x : y : xs)
+    | eq x y = let (ys, zs) = run eq (y : xs) in (x : ys, zs)
+    | otherwise = ([ x ], y : xs)
+
+-- | Like `groupBy`, but comparing adjacent items.
+runs :: (a -> a -> Bool) -> [a] -> [[a]]
+runs _ [] = []
+runs eq xs = let (ys, zs) = run eq xs in ys : runs eq zs
+
+stopImportModule :: TabStop
+stopImportModule = TabStop "import-module"
+
+stopImportSpec :: TabStop
+stopImportSpec = TabStop "import-spec"
+
+stopRecordField :: TabStop
+stopRecordField = TabStop "record"
+
+stopRhs :: TabStop
+stopRhs = TabStop "rhs"
+
+flattenApp :: Annotated ast
+           => (ast NodeInfo -> Maybe (ast NodeInfo, ast NodeInfo))
+           -> ast NodeInfo
+           -> [ast NodeInfo]
+flattenApp fn = go . amap (\info -> info { nodeInfoComments = [] })
+  where
+    go x = case fn x of
+        Just (lhs, rhs) -> let lhs' = go $ copyComments Before x lhs
+                               rhs' = go $ copyComments After x rhs
+                           in
+                               lhs' ++ rhs'
+        Nothing -> [ x ]
+
+flattenInfix
+    :: (Annotated ast1, Annotated ast2)
+    => (ast1 NodeInfo -> Maybe (ast1 NodeInfo, ast2 NodeInfo, ast1 NodeInfo))
+    -> ast1 NodeInfo
+    -> (ast1 NodeInfo, [(ast2 NodeInfo, ast1 NodeInfo)])
+flattenInfix fn = go . amap (\info -> info { nodeInfoComments = [] })
+  where
+    go x = case fn x of
+        Just (lhs, op, rhs) ->
+            let (lhs', ops) = go $ copyComments Before x lhs
+                (lhs'', ops') = go $ copyComments After x rhs
+            in
+                (lhs', ops ++ (op, lhs'') : ops')
+        Nothing -> (x, [])
+
+-- | Syntax shortcut for Pretty Printers.
+type PrettyPrinter f = f NodeInfo -> Printer ()
+
+-- | Pretty printing prettyHSE using haskell-src-exts pretty printer
+prettyHSE :: HSE.Pretty (ast NodeInfo) => PrettyPrinter ast
+prettyHSE ast = string $ HSE.prettyPrint ast
+
+-- | Type class for pretty-printable types.
+class Pretty ast where
+    prettyPrint :: PrettyPrinter ast
+    default prettyPrint :: HSE.Pretty (ast NodeInfo) => PrettyPrinter ast
+    prettyPrint = prettyHSE
+
+-- | Pretty print a syntax tree with annotated comments
+pretty :: (Annotated ast, Pretty ast) => PrettyPrinter ast
+pretty ast = do
+    printComments Before ast
+    prettyPrint ast
+    printComments After ast
+
+prettyOnside :: (Annotated ast, Pretty ast) => PrettyPrinter ast
+prettyOnside ast = do
+    eol <- gets psEolComment
+    when eol newline
+    nl <- gets psNewline
+    if nl
+        then do
+            printComments Before ast
+            onside $ prettyPrint ast
+            printComments After ast
+        else onside $ pretty ast
+
+-- | Empty NodeInfo
+noNodeInfo :: NodeInfo
+noNodeInfo = NodeInfo noSrcSpan []
+
+-- | Compare two AST nodes ignoring the annotation
+compareAST :: (Functor ast, Ord (ast ()))
+           => ast NodeInfo
+           -> ast NodeInfo
+           -> Ordering
+compareAST a b = void a `compare` void b
+
+-- | Return comments with matching location.
+filterComments :: Annotated a
+               => (Maybe Location -> Bool)
+               -> a NodeInfo
+               -> [ComInfo]
+filterComments f = filter (f . comInfoLocation) . nodeInfoComments . ann
+
+-- | Copy comments from one AST node to another.
+copyComments :: (Annotated ast1, Annotated ast2)
+             => Location
+             -> ast1 NodeInfo
+             -> ast2 NodeInfo
+             -> ast2 NodeInfo
+copyComments loc from to = amap updateComments to
+  where
+    updateComments info =
+        info { nodeInfoComments = oldComments ++ newComments }
+
+    oldComments = filterComments (/= Just loc) to
+
+    newComments = filterComments (== Just loc) from
+
+-- | Pretty print a comment.
+printComment :: Maybe SrcSpan -> Comment -> Printer ()
+printComment mayNodespan (Comment inline cspan str) = do
+    -- Insert proper amount of space before comment.
+    -- This maintains alignment. This cannot force comments
+    -- to go before the left-most possible indent (specified by depends).
+    case mayNodespan of
+        Just nodespan -> do
+            let neededSpaces = srcSpanStartColumn cspan
+                    - max 1 (srcSpanEndColumn nodespan)
+            replicateM_ neededSpaces space
+        Nothing -> return ()
+
+    if inline
+        then do
+            write "{-"
+            string str
+            write "-}"
+            when (1 == srcSpanStartColumn cspan) $
+                modify (\s -> s { psEolComment = True })
+        else do
+            write "--"
+            string str
+            modify (\s -> s { psEolComment = True })
+
+-- | Print comments of a node.
+printComments :: Annotated ast => Location -> ast NodeInfo -> Printer ()
+printComments loc' ast = do
+    let correctLocation comment = comInfoLocation comment == Just loc'
+        commentsWithLocation = filter correctLocation (nodeInfoComments info)
+        comments = map comInfoComment commentsWithLocation
+
+    unless (null comments) $ do
+        -- Preceeding comments must have a newline before them, but not break onside indent.
+        nl <- gets psNewline
+        onside' <- gets psOnside
+        when nl $ modify $ \s -> s { psOnside = 0 }
+        when (loc' == Before && not nl) newline
+
+        forM_ comments $ printComment (Just $ srcInfoSpan $ nodeInfoSpan info)
+
+        -- Write newline before restoring onside indent.
+        eol <- gets psEolComment
+        when (loc' == Before && eol && onside' > 0) newline
+        when nl $ modify $ \s -> s { psOnside = onside' }
+  where
+    info = ann ast
+
+-- | Return the configuration name of an operator
+opName :: QOp a -> ByteString
+opName op = case op of
+    (QVarOp _ qname) -> opName' qname
+    (QConOp _ qname) -> opName' qname
+
+-- | Return the configuration name of an operator
+opName' :: QName a -> ByteString
+opName' (Qual _ _ (Ident _ _)) = "``"
+opName' (Qual _ _ (Symbol _ _)) = ""
+opName' (UnQual _ (Ident _ _)) = "``"
+opName' (UnQual _ (Symbol _ str)) = BS8.pack str
+opName' (Special _ (FunCon _)) = "->"
+opName' (Special _ (Cons _)) = ":"
+opName' (Special _ _) = ""
+
+lineDelta :: Annotated ast => ast NodeInfo -> ast NodeInfo -> Int
+lineDelta prev next = nextLine - prevLine
+  where
+    prevLine = maximum (prevNodeLine : prevCommentLines)
+
+    nextLine = minimum (nextNodeLine : nextCommentLines)
+
+    prevNodeLine = srcSpanEndLine $ annSrcSpan prev
+
+    nextNodeLine = srcSpanStartLine $ annSrcSpan next
+
+    annSrcSpan = srcInfoSpan . nodeInfoSpan . ann
+
+    prevCommentLines = map (srcSpanEndLine . commentSrcSpan) $
+        filterComments (== Just After) prev
+
+    nextCommentLines = map (srcSpanStartLine . commentSrcSpan) $
+        filterComments (== Just Before) next
+
+    commentSrcSpan = (\(Comment _ sp _) -> sp) . comInfoComment
+
+linedFn :: Annotated ast
+        => (ast NodeInfo -> Printer ())
+        -> [ast NodeInfo]
+        -> Printer ()
+linedFn fn xs = do
+    preserveP <- getOption cfgOptionPreserveVerticalSpace
+    if preserveP
+        then case xs of
+            x : xs' -> do
+                cut $ fn x
+                forM_ (zip xs xs') $ \(prev, cur) -> do
+                    replicateM_ (min 2 (max 1 $ lineDelta prev cur)) newline
+                    cut $ fn cur
+            [] -> return ()
+        else inter newline $ map (cut . fn) xs
+
+lined :: (Annotated ast, Pretty ast) => [ast NodeInfo] -> Printer ()
+lined = linedFn pretty
+
+linedOnside :: (Annotated ast, Pretty ast) => [ast NodeInfo] -> Printer ()
+linedOnside = linedFn prettyOnside
+
+listVinternal :: (Annotated ast, Pretty ast)
+              => LayoutContext
+              -> ByteString
+              -> [ast NodeInfo]
+              -> Printer ()
+listVinternal ctx sep xs = aligned $ do
+    ws <- getConfig (cfgOpWs ctx sep . cfgOp)
+    nl <- gets psNewline
+    col <- getNextColumn
+    let correction = if wsLinebreak After ws || length xs < 2
+                     then 0
+                     else BS.length sep + if wsSpace After ws then 1 else 0
+        extraIndent = if nl then correction else 0
+        itemCol = col + fromIntegral extraIndent
+        sepCol = itemCol - fromIntegral correction
+    case xs of
+        [] -> newline
+        (x : xs') -> column itemCol $ do
+            cut $ do
+                printCommentsSimple Before x
+                cut . onside $ prettyPrint x
+                printCommentsSimple After x
+            forM_ xs' $ \x' -> do
+                printComments Before x'
+                column sepCol $ operatorV ctx sep
+                cut . onside $ prettyPrint x'
+                printComments After x'
+  where
+    printCommentsSimple loc ast =
+        let comments = map comInfoComment $ filterComments (== Just loc) ast
+        in
+            forM_ comments $
+            printComment (Just . srcInfoSpan . nodeInfoSpan $ ann ast)
+
+listH :: (Annotated ast, Pretty ast)
+      => LayoutContext
+      -> ByteString
+      -> ByteString
+      -> ByteString
+      -> [ast NodeInfo]
+      -> Printer ()
+listH _ open close _ [] = do
+    write open
+    write close
+
+listH ctx open close sep xs =
+    groupH ctx open close . inter (operatorH ctx sep) $ map pretty xs
+
+listV :: (Annotated ast, Pretty ast)
+      => LayoutContext
+      -> ByteString
+      -> ByteString
+      -> ByteString
+      -> [ast NodeInfo]
+      -> Printer ()
+listV ctx open close sep xs = groupV ctx open close $ do
+    ws <- getConfig (cfgOpWs ctx sep . cfgOp)
+    ws' <- getConfig (cfgGroupWs ctx open . cfgGroup)
+    unless (wsLinebreak Before ws' || wsSpace After ws' || wsLinebreak After ws
+            || not (wsSpace After ws))
+           space
+    listVinternal ctx sep xs
+
+list :: (Annotated ast, Pretty ast)
+     => LayoutContext
+     -> ByteString
+     -> ByteString
+     -> ByteString
+     -> [ast NodeInfo]
+     -> Printer ()
+list ctx open close sep xs = oneline hor <|> ver
+  where
+    hor = listH ctx open close sep xs
+
+    ver = listV ctx open close sep xs
+
+listH' :: (Annotated ast, Pretty ast)
+       => LayoutContext
+       -> ByteString
+       -> [ast NodeInfo]
+       -> Printer ()
+listH' ctx sep = inter (operatorH ctx sep) . map pretty
+
+listV' :: (Annotated ast, Pretty ast)
+       => LayoutContext
+       -> ByteString
+       -> [ast NodeInfo]
+       -> Printer ()
+listV' = listVinternal
+
+list' :: (Annotated ast, Pretty ast)
+      => LayoutContext
+      -> ByteString
+      -> [ast NodeInfo]
+      -> Printer ()
+list' ctx sep xs = oneline hor <|> ver
+  where
+    hor = listH' ctx sep xs
+
+    ver = listV' ctx sep xs
+
+listAutoWrap :: (Annotated ast, Pretty ast)
+             => LayoutContext
+             -> ByteString
+             -> ByteString
+             -> ByteString
+             -> [ast NodeInfo]
+             -> Printer ()
+listAutoWrap _ open close _ [] = do
+    write open
+    write close
+
+listAutoWrap ctx open close sep (x : xs) =
+    aligned . groupH ctx open close . aligned $ do
+        ws <- getConfig (cfgOpWs ctx sep . cfgOp)
+        let correction = if wsLinebreak After ws
+                         then 0
+                         else BS.length sep + if wsSpace After ws then 1 else 0
+        col <- getNextColumn
+        pretty x
+        forM_ xs $ \x' -> do
+            printComments Before x'
+            cut $ do
+                column (col - fromIntegral correction) $ operator ctx sep
+                prettyPrint x'
+                printComments After x'
+
+measure :: Printer a -> Printer (Maybe Int)
+measure p = do
+    s <- get
+    let s' = s { psBuffer = Buffer.empty, psEolComment = False }
+    return $ case execPrinter (oneline p) s' of
+        Nothing -> Nothing
+        Just (_, s'') -> Just . fromIntegral . (\x -> x - psIndentLevel s)
+            . BL.length . Buffer.toLazyByteString $ psBuffer s''
+
+measureDecl :: Decl NodeInfo -> Printer (Maybe [Int])
+measureDecl (PatBind _ pat _ Nothing) = fmap (: []) <$> measure (pretty pat)
+measureDecl (FunBind _ matches) = sequence <$> traverse measureMatch matches
+  where
+    measureMatch (Match _ name pats _ Nothing) = measure $ do
+        pretty name
+        space
+        inter space $ map pretty pats
+    measureMatch (InfixMatch _ pat name pats _ Nothing) = measure $ do
+        pretty pat
+        pretty $ VarOp noNodeInfo name
+        inter space $ map pretty pats
+    measureMatch _ = return Nothing
+measureDecl _ = return Nothing
+
+measureClassDecl :: ClassDecl NodeInfo -> Printer (Maybe [Int])
+measureClassDecl (ClsDecl _ decl) = measureDecl decl
+measureClassDecl _ = return Nothing
+
+measureInstDecl :: InstDecl NodeInfo -> Printer (Maybe [Int])
+measureInstDecl (InsDecl _ decl) = measureDecl decl
+measureInstDecl _ = return Nothing
+
+measureAlt :: Alt NodeInfo -> Printer (Maybe [Int])
+measureAlt (Alt _ pat _ Nothing) = fmap (: []) <$> measure (pretty pat)
+measureAlt _ = return Nothing
+
+withComputedTabStop :: TabStop
+                    -> (AlignConfig -> Bool)
+                    -> (a -> Printer (Maybe [Int]))
+                    -> [a]
+                    -> Printer b
+                    -> Printer b
+withComputedTabStop name predicate fn xs p = do
+    enabled <- getConfig (predicate . cfgAlign)
+    (limAbs, limRel) <- getConfig (cfgAlignLimits . cfgAlign)
+    mtabss <- sequence <$> traverse fn xs
+    let tab = do
+            tabss <- mtabss
+            let tabs = concat tabss
+                maxtab = maximum tabs
+                mintab = minimum tabs
+                delta = maxtab - mintab
+                diff = delta * 100 `div` maxtab
+            guard enabled
+            guard $ delta <= limAbs || diff <= limRel
+            return maxtab
+    withTabStops [ (name, tab) ] p
+
+------------------------------------------------------------------------
+-- Module
+-- | Extract the name as a String from a ModuleName
+moduleName :: ModuleName a -> String
+moduleName (ModuleName _ s) = s
+
+nameLength :: Name a -> Int
+nameLength (Ident _ i) = length i
+nameLength (Symbol _ s) = 2 + length s
+
+qnameLength :: QName a -> Int
+qnameLength (Qual _ mname name) = length (moduleName mname) + nameLength name
+    + 1
+qnameLength (UnQual _ name) = nameLength name
+qnameLength (Special _ con) = case con of
+    UnitCon _ -> 2
+    ListCon _ -> 2
+    FunCon _ -> 2
+    TupleCon _ boxed n -> 2 + n + case boxed of
+        Boxed -> 2
+        Unboxed -> 0
+    Cons _ -> 1
+    UnboxedSingleCon _ -> 5
+    ExprHole _ -> 1
+
+prettyPragmas :: [ModulePragma NodeInfo] -> Printer ()
+prettyPragmas ps = do
+    splitP <- getOption cfgOptionSplitLanguagePragmas
+    sortP <- getOption cfgOptionSortPragmas
+    let ps' = if splitP then concatMap splitPragma ps else ps
+    let ps'' = if sortP then sortBy compareAST ps' else ps'
+    inter blankline . map lined $ groupBy sameType ps''
+  where
+    splitPragma (LanguagePragma anno langs) =
+        map (LanguagePragma anno . (: [])) langs
+    splitPragma p = [ p ]
+
+    sameType LanguagePragma{} LanguagePragma{} = True
+    sameType OptionsPragma{} OptionsPragma{} = True
+    sameType AnnModulePragma{} AnnModulePragma{} = True
+    sameType _ _ = False
+
+prettyImports :: [ImportDecl NodeInfo] -> Printer ()
+prettyImports is = do
+    sortP <- getOption cfgOptionSortImports
+    alignModuleP <- getConfig (cfgAlignImportModule . cfgAlign)
+    alignSpecP <- getConfig (cfgAlignImportSpec . cfgAlign)
+    let maxNameLength = maximum $ map (length . moduleName . importModule) is
+        alignModule = if alignModuleP then Just 16 else Nothing
+        alignSpec = if alignSpecP
+                    then Just (fromMaybe 0 alignModule + 1 + maxNameLength)
+                    else Nothing
+    withTabStops [ (stopImportModule, alignModule)
+                 , (stopImportSpec, alignSpec)
+                 ] $ if sortP
+                     then inter blankline . map lined . groupBy samePrefix $
+                         sortOn (moduleName . importModule) is
+                     else lined is
+  where
+    samePrefix left right = prefix left == prefix right
+
+    prefix = takeWhile (/= '.') . moduleName . importModule
+
+skipBlank :: Annotated ast
+          => (ast NodeInfo -> ast NodeInfo -> Bool)
+          -> ast NodeInfo
+          -> ast NodeInfo
+          -> Bool
+skipBlank skip a b = skip a b && null (comments After a)
+    && null (comments Before b)
+  where
+    comments loc = filterComments (== Just loc)
+
+skipBlankAfterDecl :: Decl a -> Bool
+skipBlankAfterDecl a = case a of
+    TypeSig{} -> True
+    DeprPragmaDecl{} -> True
+    WarnPragmaDecl{} -> True
+    AnnPragma{} -> True
+    MinimalPragma{} -> True
+    InlineSig{} -> True
+    InlineConlikeSig{} -> True
+    SpecSig{} -> True
+    SpecInlineSig{} -> True
+    InstSig{} -> True
+    PatSynSig{} -> True
+    _ -> False
+
+skipBlankDecl :: Decl NodeInfo -> Decl NodeInfo -> Bool
+skipBlankDecl = skipBlank $ \a _ -> skipBlankAfterDecl a
+
+skipBlankClassDecl :: ClassDecl NodeInfo -> ClassDecl NodeInfo -> Bool
+skipBlankClassDecl = skipBlank $ \a _ -> case a of
+    (ClsDecl _ decl) -> skipBlankAfterDecl decl
+    ClsTyDef{} -> True
+    ClsDefSig{} -> True
+    _ -> False
+
+skipBlankInstDecl :: InstDecl NodeInfo -> InstDecl NodeInfo -> Bool
+skipBlankInstDecl = skipBlank $ \a _ -> case a of
+    (InsDecl _ decl) -> skipBlankAfterDecl decl
+    _ -> False
+
+prettyDecls :: (Annotated ast, Pretty ast)
+            => (ast NodeInfo -> ast NodeInfo -> Bool)
+            -> [ast NodeInfo]
+            -> Printer ()
+prettyDecls fn = inter blankline . map lined . runs fn
+
+prettySimpleDecl :: (Annotated ast1, Pretty ast1, Annotated ast2, Pretty ast2)
+                 => ast1 NodeInfo
+                 -> ByteString
+                 -> ast2 NodeInfo
+                 -> Printer ()
+prettySimpleDecl lhs op rhs = withLayout cfgLayoutDeclaration flex vertical
+  where
+    flex = do
+        pretty lhs
+        operator Declaration op
+        pretty rhs
+
+    vertical = do
+        pretty lhs
+        operatorV Declaration op
+        pretty rhs
+
+prettyConDecls :: (Annotated ast, Pretty ast) => [ast NodeInfo] -> Printer ()
+prettyConDecls condecls = withLayout cfgLayoutConDecls flex vertical
+  where
+    flex = listH' Declaration "|" condecls
+
+    vertical = listV' Declaration "|" condecls
+
+prettyForall :: (Annotated ast, Pretty ast) => [ast NodeInfo] -> Printer ()
+prettyForall vars = do
+    write "forall "
+    inter space $ map pretty vars
+    operator Type "."
+
+prettyTypesig :: (Annotated ast, Pretty ast)
+              => LayoutContext
+              -> [ast NodeInfo]
+              -> Type NodeInfo
+              -> Printer ()
+prettyTypesig ctx names ty = withLayout cfgLayoutTypesig flex vertical
+  where
+    flex = do
+        inter comma $ map pretty names
+        atTabStop stopRecordField
+        operator ctx "::"
+        pretty ty
+
+    vertical = do
+        inter comma $ map pretty names
+        atTabStop stopRecordField
+        alignOnOperator ctx "::" $ pretty' ty
+
+    pretty' (TyForall _ mtyvarbinds mcontext ty') = do
+        forM_ mtyvarbinds $ \tyvarbinds -> do
+            write "forall "
+            inter space $ map pretty tyvarbinds
+            withOperatorFormattingV Type "." (write "." >> space) id
+        forM_ mcontext $ \context -> do
+            case context of
+                (CxSingle _ asst) -> pretty asst
+                (CxTuple _ assts) -> list Type "(" ")" "," assts
+                (CxEmpty _) -> write "()"
+            operatorV Type "=>"
+        pretty' ty'
+    pretty' (TyFun _ ty' ty'') = do
+        pretty ty'
+        operatorV Type "->"
+        pretty' ty''
+    pretty' ty' = pretty ty'
+
+prettyApp :: (Annotated ast1, Annotated ast2, Pretty ast1, Pretty ast2)
+          => ast1 NodeInfo
+          -> [ast2 NodeInfo]
+          -> Printer ()
+prettyApp fn args = withLayout cfgLayoutApp flex vertical
+  where
+    flex = do
+        pretty fn
+        forM_ args $ \arg -> cut $ do
+            spaceOrNewline
+            pretty arg
+
+    vertical = do
+        pretty fn
+        withIndent cfgIndentApp $ linedOnside args
+
+prettyInfixApp :: (Annotated ast, Pretty ast, HSE.Pretty (op NodeInfo))
+               => (op NodeInfo -> ByteString)
+               -> LayoutContext
+               -> (ast NodeInfo, [(op NodeInfo, ast NodeInfo)])
+               -> Printer ()
+prettyInfixApp nameFn ctx (lhs, args) =
+    withLayout cfgLayoutInfixApp flex vertical
+  where
+    flex = do
+        pretty lhs
+        forM_ args $ \(op, arg) -> cut $ do
+            withOperatorFormatting ctx (nameFn op) (prettyHSE op) id
+            pretty arg
+
+    vertical = do
+        pretty lhs
+        forM_ args $ \(op, arg) -> do
+            withOperatorFormattingV ctx (nameFn op) (prettyHSE op) id
+            pretty arg
+
+prettyRecord :: (Annotated ast1, Pretty ast1, Annotated ast2, Pretty ast2)
+             => (ast2 NodeInfo -> Printer (Maybe Int))
+             -> LayoutContext
+             -> ast1 NodeInfo
+             -> [ast2 NodeInfo]
+             -> Printer ()
+prettyRecord len ctx name fields = withLayout cfgLayoutRecord flex vertical
+  where
+    flex = do
+        withOperatorFormattingH ctx "record" (pretty name) id
+        groupH ctx "{" "}" $ inter (operatorH ctx ",") $
+            map prettyOnside fields
+
+    vertical = do
+        withOperatorFormatting ctx "record" (pretty name) id
+        groupV ctx "{" "}" $ withComputedTabStop stopRecordField
+                                                 cfgAlignRecordFields
+                                                 (fmap (fmap pure) . len)
+                                                 fields $
+            listVinternal ctx "," fields
+
+prettyRecordFields :: (Annotated ast, Pretty ast)
+                   => (ast NodeInfo -> Printer (Maybe Int))
+                   -> LayoutContext
+                   -> [ast NodeInfo]
+                   -> Printer ()
+prettyRecordFields len ctx fields = withLayout cfgLayoutRecord flex vertical
+  where
+    flex = groupH ctx "{" "}" $ inter (operatorH ctx ",") $
+        map prettyOnside fields
+
+    vertical = groupV ctx "{" "}" $
+        withComputedTabStop stopRecordField
+                            cfgAlignRecordFields
+                            (fmap (fmap pure) . len)
+                            fields $ listVinternal ctx "," fields
+
+prettyPragma :: ByteString -> Printer () -> Printer ()
+prettyPragma name = prettyPragma' name . Just
+
+prettyPragma' :: ByteString -> Maybe (Printer ()) -> Printer ()
+prettyPragma' name mp = do
+    write "{-# "
+    write name
+    mayM_ mp $ withPrefix space aligned
+    write " #-}"
+
+instance Pretty Module where
+    prettyPrint (Module _ mhead pragmas imports decls) = inter blankline $
+        catMaybes [ ifNotEmpty prettyPragmas pragmas
+                  , pretty <$> mhead
+                  , ifNotEmpty prettyImports imports
+                  , ifNotEmpty (prettyDecls skipBlankDecl) decls
+                  ]
+      where
+        ifNotEmpty f xs = if null xs then Nothing else Just (f xs)
+
+    prettyPrint ast@XmlPage{} = prettyHSE ast
+    prettyPrint ast@XmlHybrid{} = prettyHSE ast
+
+instance Pretty ModuleHead where
+    prettyPrint (ModuleHead _ name mwarning mexports) = do
+        depend "module" $ do
+            pretty name
+            mayM_ mwarning $ withPrefix spaceOrNewline pretty
+        mayM_ mexports pretty
+        write " where"
+
+instance Pretty WarningText where
+    prettyPrint (DeprText _ s) = write "{-# DEPRECATED " >> string (show s)
+        >> write " #-}"
+    prettyPrint (WarnText _ s) = write "{-# WARNING " >> string (show s)
+        >> write " #-}"
+
+instance Pretty ExportSpecList where
+    prettyPrint (ExportSpecList _ exports) =
+        withLayout cfgLayoutExportSpecList flex vertical
+      where
+        flex = do
+            space
+            listAutoWrap Other "(" ")" "," exports
+
+        vertical = withIndent cfgIndentExportSpecList $
+            listV Other "(" ")" "," exports
+
+instance Pretty ExportSpec
+
+instance Pretty ImportDecl where
+    prettyPrint ImportDecl{..} = do
+        inter space . map write $
+            filter (not . BS.null)
+                   [ "import"
+                   , if importSrc then "{-# SOURCE #-}" else ""
+                   , if importSafe then "safe" else ""
+                   , if importQualified then "qualified" else ""
+                   ]
+        atTabStop stopImportModule
+        space
+        string $ moduleName importModule
+        mayM_ importAs $ \name -> do
+            atTabStop stopImportSpec
+            write " as "
+            pretty name
+        mayM_ importSpecs pretty
+
+instance Pretty ImportSpecList where
+    prettyPrint (ImportSpecList _ hiding specs) = do
+        sortP <- getOption cfgOptionSortImportLists
+        let specs' = if sortP then sortOn HSE.prettyPrint specs else specs
+        atTabStop stopImportSpec
+        withLayout cfgLayoutImportSpecList (flex specs') (vertical specs')
+      where
+        flex imports = do
+            when hiding $ write " hiding"
+            space
+            listAutoWrap Other "(" ")" "," imports
+
+        vertical imports = withIndent cfgIndentImportSpecList $ do
+            when hiding $ write "hiding "
+            listAutoWrap Other "(" ")" "," imports
+
+instance Pretty ImportSpec
+
+instance Pretty Assoc
+
+instance Pretty Decl where
+    prettyPrint (TypeDecl _ declhead ty) =
+        depend "type" $ prettySimpleDecl declhead "=" ty
+
+    prettyPrint (TypeFamDecl _ declhead mresultsig minjectivityinfo) =
+        depend "type family" $ do
+            pretty declhead
+            mayM_ mresultsig pretty
+            mayM_ minjectivityinfo pretty
+
+    prettyPrint (ClosedTypeFamDecl _
+                                   declhead
+                                   mresultsig
+                                   minjectivityinfo
+                                   typeeqns) = depend "type family" $ do
+        pretty declhead
+        mayM_ mresultsig pretty
+        mayM_ minjectivityinfo pretty
+        write " where"
+        newline
+        linedOnside typeeqns
+
+    prettyPrint (DataDecl _ dataornew mcontext declhead qualcondecls derivings) = do
+        depend' (pretty dataornew) $ do
+            mapM_ pretty mcontext
+            pretty declhead
+            unless (null qualcondecls) $
+                withLayout cfgLayoutDeclaration flex vertical
+        mapM_ pretty derivings
+      where
+        flex = do
+            operator Declaration "="
+            prettyConDecls qualcondecls
+
+        vertical = do
+            operatorV Declaration "="
+            prettyConDecls qualcondecls
+
+    prettyPrint (GDataDecl _
+                           dataornew
+                           mcontext
+                           declhead
+                           mkind
+                           gadtdecls
+                           derivings) = do
+        depend' (pretty dataornew) $ do
+            mapM_ pretty mcontext
+            pretty declhead
+            mayM_ mkind $ \kind -> do
+                operator Declaration "::"
+                pretty kind
+            write " where"
+            newline
+            linedOnside gadtdecls
+        mapM_ pretty derivings
+
+    prettyPrint (DataFamDecl _ mcontext declhead mresultsig) =
+        depend "data family" $ do
+            mapM_ pretty mcontext
+            pretty declhead
+            mapM_ pretty mresultsig
+
+    prettyPrint (TypeInsDecl _ ty ty') =
+        depend "type instance" $ prettySimpleDecl ty "=" ty'
+
+    prettyPrint (DataInsDecl _ dataornew ty qualcondecls derivings) = do
+        depend' (pretty dataornew >> write " instance") $ do
+            pretty ty
+            withLayout cfgLayoutDeclaration flex vertical
+        mapM_ pretty derivings
+      where
+        flex = do
+            operator Declaration "="
+            prettyConDecls qualcondecls
+
+        vertical = do
+            operatorV Declaration "="
+            prettyConDecls qualcondecls
+
+    prettyPrint (GDataInsDecl _ dataornew ty mkind gadtdecls derivings) = do
+        depend' (pretty dataornew >> write " instance") $ do
+            pretty ty
+            mayM_ mkind $ \kind -> do
+                operator Declaration "::"
+                pretty kind
+            write " where"
+            newline
+            linedOnside gadtdecls
+        mapM_ pretty derivings
+
+    prettyPrint (ClassDecl _ mcontext declhead fundeps mclassdecls) = do
+        depend "class" $ do
+            mapM_ pretty mcontext
+            pretty declhead
+            unless (null fundeps) $ do
+                operator Declaration "|"
+                list' Declaration "," fundeps
+        mayM_ mclassdecls $ \decls -> do
+            write " where"
+            withIndent cfgIndentClass $ withComputedTabStop stopRhs
+                                                            cfgAlignClass
+                                                            measureClassDecl
+                                                            decls $
+                prettyDecls skipBlankClassDecl decls
+
+    prettyPrint (InstDecl _ moverlap instrule minstdecls) = do
+        depend "instance" $ do
+            mapM_ pretty moverlap
+            pretty instrule
+        mayM_ minstdecls $ \decls -> do
+            write " where"
+            withIndent cfgIndentClass $
+                withComputedTabStop stopRhs cfgAlignClass measureInstDecl decls $
+                prettyDecls skipBlankInstDecl decls
+
+    prettyPrint (DerivDecl _ mderivstrategy moverlap instrule) =
+        depend "deriving" $ do
+            mayM_ mderivstrategy $ withPostfix space pretty
+            write "instance "
+            mayM_ moverlap $ withPostfix space pretty
+            pretty instrule
+
+    prettyPrint (InfixDecl _ assoc mint ops) = onside $ do
+        pretty assoc
+        mayM_ mint $ withPrefix space (int . fromIntegral)
+        space
+        inter comma $ map prettyHSE ops
+
+    prettyPrint (DefaultDecl _ types) = do
+        write "default "
+        listAutoWrap Other "(" ")" "," types
+
+    prettyPrint (SpliceDecl _ expr) = pretty expr
+
+    prettyPrint (TypeSig _ names ty) =
+        onside $ prettyTypesig Declaration names ty
+
+    prettyPrint (PatSynSig _ names mtyvarbinds mcontext mcontext' ty) =
+        depend "pattern" $ do
+            inter comma $ map pretty names
+            operator Declaration "::"
+            mapM_ prettyForall mtyvarbinds
+            mayM_ mcontext pretty
+            mayM_ mcontext' pretty
+            pretty ty
+
+    prettyPrint (FunBind _ matches) = linedOnside matches
+
+    prettyPrint (PatBind _ pat rhs mbinds) = do
+        onside $ do
+            pretty pat
+            atTabStop stopRhs
+            pretty rhs
+        mapM_ pretty mbinds
+
+    prettyPrint (PatSyn _ pat pat' patternsyndirection) = do
+        depend "pattern" $ prettySimpleDecl pat sep pat'
+        case patternsyndirection of
+            ExplicitBidirectional _ decls -> pretty (BDecls noNodeInfo decls)
+            _ -> return ()
+      where
+        sep = case patternsyndirection of
+            ImplicitBidirectional -> "="
+            ExplicitBidirectional _ _ -> "<-"
+            Unidirectional -> "<-"
+
+    prettyPrint (ForImp _ callconv msafety mstring name ty) =
+        depend "foreign import" $ do
+            pretty callconv
+            mayM_ msafety $ withPrefix space pretty
+            mayM_ mstring $ withPrefix space (string . show)
+            space
+            prettyTypesig Declaration [ name ] ty
+
+    prettyPrint (ForExp _ callconv mstring name ty) =
+        depend "foreign export" $ do
+            pretty callconv
+            mayM_ mstring $ withPrefix space (string . show)
+            space
+            prettyTypesig Declaration [ name ] ty
+
+    prettyPrint (RulePragmaDecl _ rules) =
+        if null rules
+        then prettyPragma' "RULES" Nothing
+        else prettyPragma "RULES" $ mapM_ pretty rules
+
+    prettyPrint (DeprPragmaDecl _ deprecations) =
+        if null deprecations
+        then prettyPragma' "DEPRECATED" Nothing
+        else prettyPragma "DEPRECATED" $ forM_ deprecations $
+            \(names, str) -> do
+                unless (null names) $ do
+                    inter comma $ map pretty names
+                    space
+                string (show str)
+
+    prettyPrint (WarnPragmaDecl _ warnings) =
+        if null warnings
+        then prettyPragma' "WARNING" Nothing
+        else prettyPragma "WARNING" $ forM_ warnings $ \(names, str) -> do
+            unless (null names) $ do
+                inter comma $ map pretty names
+                space
+            string (show str)
+
+    prettyPrint (InlineSig _ inline mactivation qname) = prettyPragma name $ do
+        mayM_ mactivation $ withPostfix space pretty
+        pretty qname
+      where
+        name = if inline then "INLINE" else "NOINLINE"
+
+    prettyPrint (InlineConlikeSig _ mactivation qname) =
+        prettyPragma "INLINE CONLIKE" $ do
+            mayM_ mactivation $ withPostfix space pretty
+            pretty qname
+
+    prettyPrint (SpecSig _ mactivation qname types) =
+        prettyPragma "SPECIALISE" $ do
+            mayM_ mactivation $ withPostfix space pretty
+            pretty qname
+            operator Declaration "::"
+            inter comma $ map pretty types
+
+    prettyPrint (SpecInlineSig _ inline mactivation qname types) =
+        prettyPragma name $ do
+            mayM_ mactivation $ withPostfix space pretty
+            pretty qname
+            operator Declaration "::"
+            inter comma $ map pretty types
+      where
+        name = if inline then "SPECIALISE INLINE" else "SPECIALISE NOINLINE"
+
+    prettyPrint (InstSig _ instrule) =
+        prettyPragma "SPECIALISE instance" $ pretty instrule
+
+    prettyPrint (AnnPragma _ annotation) =
+        prettyPragma "ANN" $ pretty annotation
+
+    prettyPrint (MinimalPragma _ mbooleanformula) =
+        prettyPragma "MINIMAL" $ mapM_ pretty mbooleanformula
+
+    -- prettyPrint (RoleAnnotDecl _ qname roles) = undefined
+    prettyPrint decl = prettyHSE decl
+
+instance Pretty DeclHead where
+    prettyPrint (DHead _ name) = pretty name
+
+    prettyPrint (DHInfix _ tyvarbind name) = do
+        pretty tyvarbind
+        pretty $ VarOp noNodeInfo name
+
+    prettyPrint (DHParen _ declhead) = parens $ pretty declhead
+
+    prettyPrint (DHApp _ declhead tyvarbind) = depend' (pretty declhead) $
+        pretty tyvarbind
+
+instance Pretty InstRule where
+    prettyPrint (IRule _ mtyvarbinds mcontext insthead) = do
+        mapM_ prettyForall mtyvarbinds
+        mapM_ pretty mcontext
+        pretty insthead
+
+    prettyPrint (IParen _ instrule) = parens $ pretty instrule
+
+instance Pretty InstHead where
+    prettyPrint (IHCon _ qname) = pretty qname
+
+    prettyPrint (IHInfix _ ty qname) = do
+        pretty ty
+        space
+        pretty qname
+
+    prettyPrint (IHParen _ insthead) = parens $ pretty insthead
+
+    prettyPrint (IHApp _ insthead ty) = depend' (pretty insthead) $ pretty ty
+
+instance Pretty Binds where
+    prettyPrint (BDecls _ decls) = withIndentBy cfgIndentWhere $ do
+        write "where"
+        withIndent cfgIndentWhereBinds $
+            withComputedTabStop stopRhs cfgAlignWhere measureDecl decls $
+            prettyDecls skipBlankDecl decls
+
+    prettyPrint (IPBinds _ ipbinds) = withIndentBy cfgIndentWhere $ do
+        write "where"
+        withIndent cfgIndentWhereBinds $ linedOnside ipbinds
+
+instance Pretty IPBind where
+    prettyPrint (IPBind _ ipname expr) = prettySimpleDecl ipname "=" expr
+
+instance Pretty InjectivityInfo where
+    prettyPrint (InjectivityInfo _ name names) = do
+        operator Declaration "|"
+        pretty name
+        operator Declaration "->"
+        inter space $ map pretty names
+
+instance Pretty ResultSig where
+    prettyPrint (KindSig _ kind) =
+        withLayout cfgLayoutDeclaration flex vertical
+      where
+        flex = do
+            operator Declaration "::"
+            pretty kind
+
+        vertical = do
+            operatorV Declaration "::"
+            pretty kind
+
+    prettyPrint (TyVarSig _ tyvarbind) =
+        withLayout cfgLayoutDeclaration flex vertical
+      where
+        flex = do
+            operator Declaration "="
+            pretty tyvarbind
+
+        vertical = do
+            operatorV Declaration "="
+            pretty tyvarbind
+
+instance Pretty ClassDecl where
+    prettyPrint (ClsDecl _ decl) = pretty decl
+
+    prettyPrint (ClsDataFam _ mcontext declhead mresultsig) = depend "data" $ do
+        mapM_ pretty mcontext
+        pretty declhead
+        mayM_ mresultsig pretty
+
+    prettyPrint (ClsTyFam _ declhead mresultsig minjectivityinfo) =
+        depend "type" $ do
+            pretty declhead
+            mayM_ mresultsig pretty
+            mapM_ pretty minjectivityinfo
+
+    prettyPrint (ClsTyDef _ typeeqn) = depend "type" $ pretty typeeqn
+
+    prettyPrint (ClsDefSig _ name ty) =
+        depend "default" $ prettyTypesig Declaration [ name ] ty
+
+instance Pretty InstDecl where
+    prettyPrint (InsDecl _ decl) = pretty decl
+
+    prettyPrint (InsType _ ty ty') =
+        depend "type" $ prettySimpleDecl ty "=" ty'
+
+    prettyPrint (InsData _ dataornew ty qualcondecls derivings) =
+        depend' (pretty dataornew) $ do
+            pretty ty
+            unless (null qualcondecls) $
+                withLayout cfgLayoutDeclaration flex vertical
+            mapM_ pretty derivings
+      where
+        flex = do
+            operator Declaration "="
+            prettyConDecls qualcondecls
+
+        vertical = do
+            operatorV Declaration "="
+            prettyConDecls qualcondecls
+
+    prettyPrint (InsGData _ dataornew ty mkind gadtdecls derivings) = do
+        depend' (pretty dataornew) $ do
+            pretty ty
+            mayM_ mkind $ \kind -> do
+                operator Declaration "::"
+                pretty kind
+            write " where"
+            newline
+            lined gadtdecls
+        mapM_ pretty derivings
+
+instance Pretty Deriving where
+    prettyPrint (Deriving _ mderivstrategy instrules) =
+        withIndentBy cfgIndentDeriving $ do
+            write "deriving "
+            mayM_ mderivstrategy $ withPostfix space pretty
+            case instrules of
+                [ i@IRule{} ] -> pretty i
+                [ IParen _ i ] -> listAutoWrap Other "(" ")" "," [ i ]
+                _ -> listAutoWrap Other "(" ")" "," instrules
+
+instance Pretty ConDecl where
+    prettyPrint (ConDecl _ name types) = do
+        pretty name
+        unless (null types) $ do
+            space
+            oneline hor <|> ver
+      where
+        hor = inter space $ map pretty types
+
+        ver = aligned $ linedOnside types
+
+    prettyPrint (InfixConDecl _ ty name ty') = do
+        pretty ty
+        pretty $ ConOp noNodeInfo name
+        pretty ty'
+
+    prettyPrint (RecDecl _ name fielddecls) =
+        prettyRecord len Declaration name fielddecls
+      where
+        len (FieldDecl _ names _) = measure $ inter comma $ map pretty names
+
+instance Pretty FieldDecl where
+    prettyPrint (FieldDecl _ names ty) = prettyTypesig Declaration names ty
+
+instance Pretty QualConDecl where
+    prettyPrint (QualConDecl _ mtyvarbinds mcontext condecl) = do
+        mapM_ prettyForall mtyvarbinds
+        mapM_ pretty mcontext
+        pretty condecl
+
+instance Pretty GadtDecl where
+    prettyPrint (GadtDecl _ name mfielddecls ty) = do
+        pretty name
+        operator Declaration "::"
+        mayM_ mfielddecls $ \decls -> do
+            prettyRecordFields len Declaration decls
+            operator Type "->"
+        pretty ty
+      where
+        len (FieldDecl _ names _) = measure $ inter comma $ map pretty names
+
+instance Pretty Match where
+    prettyPrint (Match _ name pats rhs mbinds) = do
+        onside $ do
+            pretty name
+            unless (null pats) $ do
+                space
+                inter space $ map pretty pats
+            atTabStop stopRhs
+            pretty rhs
+        mapM_ pretty mbinds
+
+    prettyPrint (InfixMatch _ pat name pats rhs mbinds) = do
+        onside $ do
+            pretty pat
+            pretty $ VarOp noNodeInfo name
+            inter space $ map pretty pats
+            atTabStop stopRhs
+            pretty rhs
+        mapM_ pretty mbinds
+
+instance Pretty Rhs where
+    prettyPrint (UnGuardedRhs _ expr) =
+        cut $ withLayout cfgLayoutDeclaration flex vertical
+      where
+        flex = do
+            operator Declaration "="
+            pretty expr
+
+        vertical = do
+            operatorV Declaration "="
+            pretty expr
+
+    prettyPrint (GuardedRhss _ guardedrhss) =
+        withIndent cfgIndentMultiIf $ linedOnside guardedrhss
+
+instance Pretty GuardedRhs where
+    prettyPrint (GuardedRhs _ stmts expr) =
+        withLayout cfgLayoutDeclaration flex vertical
+      where
+        flex = do
+            operatorSectionR Pattern "|" $ write "|"
+            inter comma $ map pretty stmts
+            operator Declaration "="
+            pretty expr
+
+        vertical = do
+            operatorSectionR Pattern "|" $ write "|"
+            inter comma $ map pretty stmts
+            operatorV Declaration "="
+            pretty expr
+
+instance Pretty Context where
+    prettyPrint (CxSingle _ asst) = do
+        pretty asst
+        operator Type "=>"
+
+    prettyPrint (CxTuple _ assts) = do
+        list Type "(" ")" "," assts
+        operator Type "=>"
+
+    prettyPrint (CxEmpty _) = do
+        write "()"
+        operator Type "=>"
+
+instance Pretty FunDep where
+    prettyPrint (FunDep _ names names') = do
+        inter space $ map pretty names
+        operator Declaration "->"
+        inter space $ map pretty names'
+
+instance Pretty Asst where
+    prettyPrint (ClassA _ qname types) = do
+        pretty qname
+        space
+        inter space $ map pretty types
+
+    prettyPrint (AppA _ name types) = do
+        pretty name
+        space
+        inter space $ map pretty types
+
+    prettyPrint (InfixA _ ty qname ty') = do
+        pretty ty
+        pretty $ QVarOp noNodeInfo qname
+        pretty ty'
+
+    prettyPrint (IParam _ ipname ty) = prettyTypesig Declaration [ ipname ] ty
+
+    prettyPrint (EqualP _ ty ty') = do
+        pretty ty
+        operator Type "~"
+        pretty ty'
+
+    prettyPrint (ParenA _ asst) = parens $ pretty asst
+
+    prettyPrint (WildCardA _ mname) = do
+        write "_"
+        mapM_ pretty mname
+
+instance Pretty Type where
+    prettyPrint (TyForall _ mtyvarbinds mcontext ty) = do
+        mapM_ prettyForall mtyvarbinds
+        mapM_ pretty mcontext
+        pretty ty
+
+    prettyPrint (TyFun _ ty ty') = do
+        pretty ty
+        operator Type "->"
+        pretty ty'
+
+    prettyPrint (TyTuple _ boxed tys) = case boxed of
+        Unboxed -> list Type "(#" "#)" "," tys
+        Boxed -> list Type "(" ")" "," tys
+
+    prettyPrint (TyUnboxedSum _ tys) = list Type "(#" "#)" "|" tys
+
+    prettyPrint (TyList _ ty) = group Type "[" "]" $ pretty ty
+
+    prettyPrint (TyParArray _ ty) = group Type "[:" ":]" $ pretty ty
+
+    prettyPrint (TyApp _ ty ty') = do
+        pretty ty
+        space
+        pretty ty'
+
+    prettyPrint (TyVar _ name) = pretty name
+
+    prettyPrint (TyCon _ qname) = pretty qname
+
+    prettyPrint (TyParen _ ty) = parens $ pretty ty
+
+    prettyPrint (TyInfix _ ty op ty') = do
+        pretty ty
+        withOperatorFormatting Type opname (prettyHSE op) id
+        pretty ty'
+      where
+        opname = opName' $ case op of
+            PromotedName _ qname -> qname
+            UnpromotedName _ qname -> qname
+
+    prettyPrint (TyKind _ ty kind) = do
+        pretty ty
+        operator Type "::"
+        pretty kind
+
+    prettyPrint t@(TyPromoted _ _promoted) = prettyHSE t
+
+    prettyPrint (TyEquals _ ty ty') = do
+        pretty ty
+        operator Type "~"
+        pretty ty'
+
+    prettyPrint (TySplice _ splice) = pretty splice
+
+    prettyPrint (TyBang _ bangtype unpackedness ty) = do
+        pretty unpackedness
+        pretty bangtype
+        pretty ty
+
+    prettyPrint t@(TyWildCard _ _mname) = prettyHSE t
+
+    prettyPrint (TyQuasiQuote _ str str') = do
+        write "["
+        string str
+        write "|"
+        string str'
+        write "|]"
+
+instance Pretty Kind where
+    prettyPrint (KindStar _) = write "*"
+
+    prettyPrint (KindFn _ kind kind') = do
+        pretty kind
+        operator Type "->"
+        pretty kind'
+
+    prettyPrint (KindParen _ kind) = parens $ pretty kind
+
+    prettyPrint (KindVar _ qname) = pretty qname
+
+    prettyPrint (KindApp _ kind kind') = do
+        pretty kind
+        space
+        pretty kind'
+
+    prettyPrint (KindTuple _ kinds) = list Type "'(" ")" "," kinds
+
+    prettyPrint (KindList _ kind) = group Type "'[" "]" $ pretty kind
+
+instance Pretty TyVarBind where
+    prettyPrint (KindedVar _ name kind) = parens $ do
+        pretty name
+        operator Type "::"
+        pretty kind
+
+    prettyPrint (UnkindedVar _ name) = pretty name
+
+instance Pretty TypeEqn where
+    prettyPrint (TypeEqn _ ty ty') = do
+        pretty ty
+        operator Type "="
+        pretty ty'
+
+instance Pretty Exp where
+    prettyPrint (Var _ qname) = pretty qname
+
+    prettyPrint (OverloadedLabel _ str) = do
+        write "#"
+        string str
+
+    prettyPrint (IPVar _ ipname) = pretty ipname
+
+    prettyPrint (Con _ qname) = pretty qname
+
+    prettyPrint (Lit _ literal) = pretty literal
+
+    prettyPrint e@(InfixApp _ _ qop _) =
+        prettyInfixApp opName Expression $ flattenInfix flattenInfixApp e
+      where
+        flattenInfixApp (InfixApp _ lhs qop' rhs) =
+            if compareAST qop qop' == EQ
+            then Just (lhs, qop', rhs)
+            else Nothing
+        flattenInfixApp _ = Nothing
+
+    prettyPrint e@App{} = case flattenApp flatten e of
+        fn : args -> prettyApp fn args
+        [] -> error "impossible"
+      where
+        flatten (App _ fn arg) = Just (fn, arg)
+        flatten _ = Nothing
+
+    prettyPrint (NegApp _ expr) = do
+        write "-"
+        pretty expr
+
+    prettyPrint (Lambda _ pats expr) = do
+        write "\\"
+        maybeSpace
+        inter space $ map pretty pats
+        operator Expression "->"
+        pretty expr
+      where
+        maybeSpace = case pats of
+            PIrrPat{} : _ -> space
+            PBangPat{} : _ -> space
+            _ -> return ()
+
+    prettyPrint (Let _ binds expr) = withLayout cfgLayoutLet flex vertical
+      where
+        flex = do
+            write "let "
+            prettyOnside (CompactBinds binds)
+            spaceOrNewline
+            write "in "
+            prettyOnside expr
+
+        vertical = withIndentFlat cfgIndentLet "let" $ do
+            withIndent cfgIndentLetBinds $ pretty (CompactBinds binds)
+            newline
+            write "in"
+            withIndent cfgIndentLetIn $ pretty expr
+
+    prettyPrint (If _ expr expr' expr'') = withLayout cfgLayoutIf flex vertical
+      where
+        flex = do
+            write "if "
+            prettyOnside expr
+            spaceOrNewline
+            write "then "
+            prettyOnside expr'
+            spaceOrNewline
+            write "else "
+            prettyOnside expr''
+
+        vertical = withIndentFlat cfgIndentIf "if " $ do
+            prettyOnside expr
+            newline
+            write "then "
+            prettyOnside expr'
+            newline
+            write "else "
+            prettyOnside expr''
+
+    prettyPrint (MultiIf _ guardedrhss) = do
+        write "if"
+        withIndent cfgIndentMultiIf . linedOnside $ map GuardedAlt guardedrhss
+
+    prettyPrint (Case _ expr alts) = do
+        write "case "
+        pretty expr
+        write " of"
+        if null alts
+            then write " { }"
+            else withIndent cfgIndentCase $
+                withComputedTabStop stopRhs cfgAlignCase measureAlt alts $
+                lined alts
+
+    prettyPrint (Do _ stmts) = do
+        write "do"
+        withIndent cfgIndentDo $ linedOnside stmts
+
+    prettyPrint (MDo _ stmts) = do
+        write "mdo"
+        withIndent cfgIndentDo $ linedOnside stmts
+
+    prettyPrint (Tuple _ boxed exprs) = case boxed of
+        Boxed -> list Expression "(" ")" "," exprs
+        Unboxed -> list Expression "(#" "#)" "," exprs
+
+    prettyPrint (UnboxedSum _ before after expr) = group Expression "(#" "#)"
+        . inter space $ replicate before (write "|") ++ [ pretty expr ]
+        ++ replicate after (write "|")
+
+    prettyPrint (TupleSection _ boxed mexprs) = case boxed of
+        Boxed -> list Expression "(" ")" "," $ map MayAst mexprs
+        Unboxed -> list Expression "(#" "#)" "," $ map MayAst mexprs
+
+    prettyPrint (List _ exprs) = list Expression "[" "]" "," exprs
+
+    prettyPrint (ParArray _ exprs) = list Expression "[:" ":]" "," exprs
+
+    prettyPrint (Paren _ expr) = parens $ pretty expr
+
+    prettyPrint (LeftSection _ expr qop) = parens $ do
+        pretty expr
+        operatorSectionL Expression (opName qop) $ prettyHSE qop
+
+    prettyPrint (RightSection _ qop expr) = parens $ do
+        operatorSectionR Expression (opName qop) $ prettyHSE qop
+        pretty expr
+
+    prettyPrint (RecConstr _ qname fieldupdates) =
+        prettyRecord len Expression qname fieldupdates
+      where
+        len (FieldUpdate _ n _) = measure $ pretty n
+        len (FieldPun _ n) = measure $ pretty n
+        len (FieldWildcard _) = measure $ write ".."
+
+    prettyPrint (RecUpdate _ expr fieldupdates) =
+        prettyRecord len Expression expr fieldupdates
+      where
+        len (FieldUpdate _ n _) = measure $ pretty n
+        len (FieldPun _ n) = measure $ pretty n
+        len (FieldWildcard _) = measure $ write ".."
+
+    prettyPrint (EnumFrom _ expr) = group Expression "[" "]" $ do
+        pretty expr
+        operatorSectionL Expression ".." $ write ".."
+
+    prettyPrint (EnumFromTo _ expr expr') = group Expression "[" "]" $ do
+        pretty expr
+        operator Expression ".."
+        pretty expr'
+
+    prettyPrint (EnumFromThen _ expr expr') = group Expression "[" "]" $ do
+        pretty expr
+        comma
+        pretty expr'
+        operatorSectionL Expression ".." $ write ".."
+
+    prettyPrint (EnumFromThenTo _ expr expr' expr'') =
+        group Expression "[" "]" $ do
+            pretty expr
+            comma
+            pretty expr'
+            operator Expression ".."
+            pretty expr''
+
+    prettyPrint (ParArrayFromTo _ expr expr') = group Expression "[:" ":]" $ do
+        pretty expr
+        operator Expression ".."
+        pretty expr'
+
+    prettyPrint (ParArrayFromThenTo _ expr expr' expr'') =
+        group Expression "[:" ":]" $ do
+            pretty expr
+            comma
+            pretty expr'
+            operator Expression ".."
+            pretty expr''
+
+    prettyPrint (ListComp _ expr qualstmts) =
+        withLayout cfgLayoutListComp flex vertical
+      where
+        flex = group Expression "[" "]" $ do
+            prettyOnside expr
+            operator Expression "|"
+            list' Expression "," qualstmts
+
+        vertical = groupV Expression "[" "]" $ do
+            prettyOnside expr
+            operatorV Expression "|"
+            listV' Expression "," qualstmts
+
+    prettyPrint (ParComp _ expr qualstmtss) =
+        withLayout cfgLayoutListComp flex vertical
+      where
+        flex = group Expression "[" "]" $ do
+            prettyOnside expr
+            forM_ qualstmtss $ \qualstmts -> cut $ do
+                operator Expression "|"
+                list' Expression "," qualstmts
+
+        vertical = groupV Expression "[" "]" $ do
+            prettyOnside expr
+            forM_ qualstmtss $ \qualstmts -> cut $ do
+                operatorV Expression "|"
+                listV' Expression "," qualstmts
+
+    prettyPrint (ParArrayComp _ expr qualstmtss) =
+        withLayout cfgLayoutListComp flex vertical
+      where
+        flex = group Expression "[:" ":]" $ do
+            prettyOnside expr
+            forM_ qualstmtss $ \qualstmts -> cut $ do
+                operator Expression "|"
+                list' Expression "," qualstmts
+
+        vertical = groupV Expression "[:" ":]" $ do
+            prettyOnside expr
+            forM_ qualstmtss $ \qualstmts -> cut $ do
+                operatorV Expression "|"
+                listV' Expression "," qualstmts
+
+    prettyPrint (ExpTypeSig _ expr typ) = prettyTypesig Expression [ expr ] typ
+
+    prettyPrint (VarQuote _ qname) = do
+        write "'"
+        pretty qname
+
+    prettyPrint (TypQuote _ qname) = do
+        write "''"
+        pretty qname
+
+    prettyPrint (BracketExp _ bracket) = pretty bracket
+
+    prettyPrint (SpliceExp _ splice) = pretty splice
+
+    prettyPrint (QuasiQuote _ str str') = do
+        write "["
+        string str
+        write "|"
+        string str'
+        write "|]"
+
+    prettyPrint (TypeApp _ typ) = do
+        write "@"
+        pretty typ
+
+    prettyPrint (XTag _ xname xattrs mexpr exprs) = do
+        write "<"
+        pretty xname
+        forM_ xattrs $ withPrefix space pretty
+        mayM_ mexpr $ withPrefix space pretty
+        write ">"
+        mapM_ pretty exprs
+        write "</"
+        pretty xname
+        write ">"
+
+    prettyPrint (XETag _ xname xattrs mexpr) = do
+        write "<"
+        pretty xname
+        forM_ xattrs $ withPrefix space pretty
+        mayM_ mexpr $ withPrefix space pretty
+        write "/>"
+
+    prettyPrint (XPcdata _ str) = string str
+
+    prettyPrint (XExpTag _ expr) = do
+        write "<% "
+        pretty expr
+        write " %>"
+
+    prettyPrint (XChildTag _ exprs) = do
+        write "<%>"
+        inter space $ map pretty exprs
+        write "</%>"
+
+    prettyPrint (CorePragma _ str expr) = do
+        prettyPragma "CORE" . string $ show str
+        space
+        pretty expr
+
+    prettyPrint (SCCPragma _ str expr) = do
+        prettyPragma "SCC" . string $ show str
+        space
+        pretty expr
+
+    prettyPrint (GenPragma _ str (a, b) (c, d) expr) = do
+        prettyPragma "GENERATED" $
+            inter space
+                  [ string $ show str
+                  , int $ fromIntegral a
+                  , write ":"
+                  , int $ fromIntegral b
+                  , write "-"
+                  , int $ fromIntegral c
+                  , write ":"
+                  , int $ fromIntegral d
+                  ]
+        space
+        pretty expr
+
+    prettyPrint (Proc _ pat expr) = do
+        write "proc "
+        pretty pat
+        operator Expression "->"
+        pretty expr
+
+    prettyPrint (LeftArrApp _ expr expr') = do
+        pretty expr
+        operator Expression "-<"
+        pretty expr'
+
+    prettyPrint (RightArrApp _ expr expr') = do
+        pretty expr
+        operator Expression ">-"
+        pretty expr'
+
+    prettyPrint (LeftArrHighApp _ expr expr') = do
+        pretty expr
+        operator Expression "-<<"
+        pretty expr'
+
+    prettyPrint (RightArrHighApp _ expr expr') = do
+        pretty expr
+        operator Expression ">>-"
+        pretty expr'
+
+    prettyPrint (LCase _ alts) = do
+        write "\\case"
+        if null alts
+            then write " { }"
+            else withIndent cfgIndentCase $
+                withComputedTabStop stopRhs cfgAlignCase measureAlt alts $
+                lined alts
+
+instance Pretty Alt where
+    prettyPrint (Alt _ pat rhs mbinds) = do
+        onside $ do
+            pretty pat
+            atTabStop stopRhs
+            pretty $ GuardedAlts rhs
+        mapM_ pretty mbinds
+
+instance Pretty XAttr where
+    prettyPrint (XAttr _ xname expr) = do
+        pretty xname
+        operator Expression "="
+        pretty expr
+
+instance Pretty Pat where
+    prettyPrint (PVar _ name) = pretty name
+
+    prettyPrint (PLit _ sign literal) = do
+        case sign of
+            Signless _ -> return ()
+            Negative _ -> write "-"
+        pretty literal
+
+    prettyPrint (PNPlusK _ name integer) = do
+        pretty name
+        operator Pattern "+"
+        int integer
+
+    prettyPrint p@(PInfixApp _ _ qname _) =
+        prettyInfixApp opName Pattern $ flattenInfix flattenPInfixApp p
+      where
+        flattenPInfixApp (PInfixApp _ lhs qname' rhs) =
+            if compareAST qname qname' == EQ
+            then Just (lhs, QConOp noNodeInfo qname', rhs)
+            else Nothing
+        flattenPInfixApp _ = Nothing
+
+    prettyPrint (PApp _ qname pats) = prettyApp qname pats
+
+    prettyPrint (PTuple _ boxed pats) = case boxed of
+        Boxed -> list Pattern "(" ")" "," pats
+        Unboxed -> list Pattern "(#" "#)" "," pats
+
+    prettyPrint (PUnboxedSum _ before after pat) = group Pattern "(#" "#)"
+        . inter space $ replicate before (write "|") ++ [ pretty pat ]
+        ++ replicate after (write "|")
+
+    prettyPrint (PList _ pats) = list Pattern "[" "]" "," pats
+
+    prettyPrint (PParen _ pat) = parens $ pretty pat
+
+    prettyPrint (PRec _ qname patfields) = do
+        withOperatorFormatting Pattern "record" (pretty qname) id
+        list Pattern "{" "}" "," patfields
+
+    prettyPrint (PAsPat _ name pat) = do
+        pretty name
+        operator Pattern "@"
+        pretty pat
+
+    prettyPrint (PWildCard _) = write "_"
+
+    prettyPrint (PIrrPat _ pat) = do
+        write "~"
+        pretty pat
+
+    prettyPrint (PatTypeSig _ pat ty) = prettyTypesig Pattern [ pat ] ty
+
+    prettyPrint (PViewPat _ expr pat) = do
+        pretty expr
+        operator Pattern "->"
+        pretty pat
+
+    prettyPrint (PRPat _ rpats) = list Pattern "[" "]" "," rpats
+
+    prettyPrint (PXTag _ xname pxattrs mpat pats) = do
+        write "<"
+        pretty xname
+        forM_ pxattrs $ withPrefix space pretty
+        mayM_ mpat $ withPrefix space pretty
+        write ">"
+        mapM_ pretty pats
+        write "<"
+        pretty xname
+        write ">"
+
+    prettyPrint (PXETag _ xname pxattrs mpat) = do
+        write "<"
+        pretty xname
+        forM_ pxattrs $ withPrefix space pretty
+        mayM_ mpat $ withPrefix space pretty
+        write "/>"
+
+    prettyPrint (PXPcdata _ str) = string str
+
+    prettyPrint (PXPatTag _ pat) = do
+        write "<%"
+        pretty pat
+        write "%>"
+
+    prettyPrint (PXRPats _ rpats) = do
+        write "<["
+        inter space $ map pretty rpats
+        write "%>"
+
+    prettyPrint (PSplice _ splice) = pretty splice
+
+    prettyPrint (PQuasiQuote _ str str') = do
+        write "[$"
+        string str
+        write "|"
+        string str'
+        write "|]"
+
+    prettyPrint (PBangPat _ pat) = do
+        write "!"
+        pretty pat
+
+instance Pretty PatField where
+    prettyPrint (PFieldPat _ qname pat) = do
+        pretty qname
+        operator Pattern "="
+        pretty pat
+
+    prettyPrint (PFieldPun _ qname) = pretty qname
+
+    prettyPrint (PFieldWildcard _) = write ".."
+
+instance Pretty PXAttr where
+    prettyPrint (PXAttr _ xname pat) = do
+        pretty xname
+        operator Pattern "="
+        pretty pat
+
+instance Pretty Literal where
+    prettyPrint (Char _ _ str) = do
+        write "'"
+        string str
+        write "'"
+
+    prettyPrint (String _ _ str) = do
+        write "\""
+        string str
+        write "\""
+
+    prettyPrint (Int _ _ str) = string str
+
+    prettyPrint (Frac _ _ str) = string str
+
+    prettyPrint (PrimInt _ _ str) = do
+        string str
+        write "#"
+
+    prettyPrint (PrimWord _ _ str) = do
+        string str
+        write "##"
+
+    prettyPrint (PrimFloat _ _ str) = do
+        string str
+        write "#"
+
+    prettyPrint (PrimDouble _ _ str) = do
+        string str
+        write "##"
+
+    prettyPrint (PrimChar _ _ str) = do
+        write "'"
+        string str
+        write "'#"
+
+    prettyPrint (PrimString _ _ str) = do
+        write "\""
+        string str
+        write "\"#"
+
+instance Pretty QualStmt where
+    prettyPrint (QualStmt _ stmt) = pretty stmt
+
+    prettyPrint (ThenTrans _ expr) = do
+        write "then "
+        pretty expr
+
+    prettyPrint (ThenBy _ expr expr') = do
+        write "then "
+        pretty expr
+        write " by "
+        pretty expr'
+
+    prettyPrint (GroupBy _ expr) = do
+        write "then group by "
+        pretty expr
+
+    prettyPrint (GroupUsing _ expr) = do
+        write "then group using "
+        pretty expr
+
+    prettyPrint (GroupByUsing _ expr expr') = do
+        write "then group by "
+        pretty expr
+        write " using "
+        pretty expr'
+
+instance Pretty Stmt where
+    prettyPrint (Generator _ pat expr) = do
+        pretty pat
+        operator Expression "<-"
+        pretty expr
+
+    -- Special case for If in Do,
+    prettyPrint (Qualifier _ expr@If{}) = do
+        cfg <- getConfig (cfgIndentIf . cfgIndent)
+        case cfg of
+            Align -> do
+                write ""
+                indented $ pretty expr
+            _ -> pretty expr
+
+    prettyPrint (Qualifier _ expr) = pretty expr
+
+    prettyPrint (LetStmt _ binds) = do
+        write "let "
+        pretty $ CompactBinds binds
+
+    prettyPrint (RecStmt _ stmts) = do
+        write "rec "
+        aligned $ linedOnside stmts
+
+instance Pretty FieldUpdate where
+    prettyPrint (FieldUpdate _ qname expr) = do
+        pretty qname
+        atTabStop stopRecordField
+        operator Expression "="
+        pretty expr
+
+    prettyPrint (FieldPun _ qname) = pretty qname
+
+    prettyPrint (FieldWildcard _) = write ".."
+
+instance Pretty QOp where
+    prettyPrint qop =
+        withOperatorFormatting Expression (opName qop) (prettyHSE qop) id
+
+instance Pretty Op where
+    prettyPrint (VarOp l name) = prettyPrint (QVarOp l (UnQual noNodeInfo name))
+    prettyPrint (ConOp l name) = prettyPrint (QConOp l (UnQual noNodeInfo name))
+
+instance Pretty Bracket where
+    prettyPrint (ExpBracket _ expr) = group Expression "[|" "|]" $ pretty expr
+
+    prettyPrint (PatBracket _ pat) = group Expression "[p|" "|]" $ pretty pat
+
+    prettyPrint (TypeBracket _ ty) = group Expression "[t|" "|]" $ pretty ty
+
+    prettyPrint (DeclBracket _ decls) =
+        group Expression "[d|" "|]" . aligned $ lined decls
+
+instance Pretty Splice where
+    prettyPrint (IdSplice _ str) = do
+        write "$"
+        string str
+
+    prettyPrint (ParenSplice _ expr) = group Expression "$(" ")" $ pretty expr
+
+instance Pretty ModulePragma where
+    prettyPrint (LanguagePragma _ names) =
+        prettyPragma "LANGUAGE" . inter comma $ map pretty names
+
+    prettyPrint (OptionsPragma _ mtool str) = prettyPragma name $
+        string (trim str)
+      where
+        name = case mtool of
+            Just tool -> "OPTIONS_" `mappend` BS8.pack (HSE.prettyPrint tool)
+            Nothing -> "OPTIONS"
+
+        trim = reverse . dropWhile (== ' ') . reverse . dropWhile (== ' ')
+
+    prettyPrint (AnnModulePragma _ annotation) =
+        prettyPragma "ANN" $ pretty annotation
+
+instance Pretty Rule where
+    prettyPrint (Rule _ str mactivation mrulevars expr expr') = do
+        string (show str)
+        space
+        mayM_ mactivation $ withPostfix space pretty
+        mapM_ prettyForall mrulevars
+        pretty expr
+        operator Expression "="
+        pretty expr'
+
+instance Pretty RuleVar where
+    prettyPrint (RuleVar _ name) = pretty name
+
+    prettyPrint (TypedRuleVar _ name ty) =
+        parens $ prettyTypesig Declaration [ name ] ty
+
+instance Pretty Activation where
+    prettyPrint (ActiveFrom _ pass) = brackets . int $ fromIntegral pass
+
+    prettyPrint (ActiveUntil _ pass) = brackets $ do
+        write "~"
+        int $ fromIntegral pass
+
+instance Pretty Annotation where
+    prettyPrint (Ann _ name expr) = do
+        pretty name
+        space
+        pretty expr
+
+    prettyPrint (TypeAnn _ name expr) = do
+        write "type "
+        pretty name
+        space
+        pretty expr
+
+    prettyPrint (ModuleAnn _ expr) = do
+        write "module "
+        pretty expr
+
+instance Pretty BooleanFormula where
+    prettyPrint (VarFormula _ name) = pretty name
+
+    prettyPrint (AndFormula _ booleanformulas) =
+        inter comma $ map pretty booleanformulas
+
+    prettyPrint (OrFormula _ booleanformulas) =
+        inter (operator Expression "|") $ map pretty booleanformulas
+
+    prettyPrint (ParenFormula _ booleanformula) = parens $ pretty booleanformula
+
+-- Stick with HSE
+instance Pretty DerivStrategy
+
+instance Pretty DataOrNew
+
+instance Pretty BangType
+
+instance Pretty Unpackedness
+
+instance Pretty RPat
+
+instance Pretty ModuleName
+
+instance Pretty QName
+
+instance Pretty Name
+
+instance Pretty IPName
+
+instance Pretty XName
+
+instance Pretty Safety
+
+instance Pretty CallConv
+
+instance Pretty Overlap
+
+-- Helpers
+newtype GuardedAlt l = GuardedAlt (GuardedRhs l)
+    deriving ( Functor, Annotated )
+
+instance Pretty GuardedAlt where
+    prettyPrint (GuardedAlt (GuardedRhs _ stmts expr)) = cut $ do
+        operatorSectionR Pattern "|" $ write "|"
+        inter comma $ map pretty stmts
+        operator Expression "->"
+        pretty expr
+
+newtype GuardedAlts l = GuardedAlts (Rhs l)
+    deriving ( Functor, Annotated )
+
+instance Pretty GuardedAlts where
+    prettyPrint (GuardedAlts (UnGuardedRhs _ expr)) = cut $ do
+        operator Expression "->"
+        pretty expr
+
+    prettyPrint (GuardedAlts (GuardedRhss _ guardedrhss)) =
+        withIndent cfgIndentMultiIf $ linedOnside $ map GuardedAlt guardedrhss
+
+newtype CompactBinds l = CompactBinds (Binds l)
+    deriving ( Functor, Annotated )
+
+instance Pretty CompactBinds where
+    prettyPrint (CompactBinds (BDecls _ decls)) = aligned $
+        withComputedTabStop stopRhs cfgAlignLetBinds measureDecl decls $
+        lined decls
+    prettyPrint (CompactBinds (IPBinds _ ipbinds)) =
+        aligned $ linedOnside ipbinds
+
+newtype MayAst a l = MayAst (Maybe (a l))
+
+instance Functor a => Functor (MayAst a) where
+    fmap _ (MayAst Nothing) = MayAst Nothing
+    fmap f (MayAst (Just x)) = MayAst . Just $ fmap f x
+
+instance Annotated a => Annotated (MayAst a) where
+    ann (MayAst Nothing) = undefined
+    ann (MayAst (Just x)) = ann x
+
+    amap _ (MayAst Nothing) = MayAst Nothing
+    amap f (MayAst (Just x)) = MayAst . Just $ amap f x
+
+instance (Annotated a, Pretty a) => Pretty (MayAst a) where
+    prettyPrint (MayAst x) = mapM_ pretty x
diff --git a/src/Floskell/Printers.hs b/src/Floskell/Printers.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell/Printers.hs
@@ -0,0 +1,446 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Floskell.Printers
+    ( getConfig
+    , getOption
+    , cut
+    , oneline
+      -- * Basic printing
+    , write
+    , string
+    , int
+    , space
+    , newline
+    , linebreak
+    , blankline
+    , spaceOrNewline
+      -- * Tab stops
+    , withTabStops
+    , atTabStop
+      -- * Combinators
+    , mayM_
+    , withPrefix
+    , withPostfix
+    , withIndent
+    , withIndentFlat
+    , withIndentBy
+    , withLayout
+    , inter
+      -- * Indentation
+    , getNextColumn
+    , column
+    , aligned
+    , indented
+    , onside
+    , depend
+    , depend'
+    , parens
+    , brackets
+      -- * Wrapping
+    , group
+    , groupH
+    , groupV
+      -- * Operators
+    , operator
+    , operatorH
+    , operatorV
+    , alignOnOperator
+    , alignOnOperatorH
+    , alignOnOperatorV
+    , withOperatorFormatting
+    , withOperatorFormattingH
+    , withOperatorFormattingV
+    , operatorSectionL
+    , operatorSectionR
+    , comma
+    ) where
+
+import           Control.Applicative        ( (<|>) )
+
+import           Control.Monad              ( guard, unless, when )
+import           Control.Monad.Search       ( cost, winner )
+
+import           Control.Monad.State.Strict ( get, gets, modify )
+
+import           Data.ByteString            ( ByteString )
+import qualified Data.ByteString            as BS
+import qualified Data.ByteString.Builder    as BB
+import qualified Data.ByteString.Lazy       as BL
+import           Data.Int                   ( Int64 )
+import           Data.List                  ( intersperse )
+import qualified Data.Map.Strict            as Map
+import           Data.Monoid                ( (<>) )
+
+import qualified Floskell.Buffer            as Buffer
+import           Floskell.Config
+import           Floskell.Types
+
+-- | Query part of the pretty printer config
+getConfig :: (FlexConfig -> b) -> Printer b
+getConfig f = f <$> gets psUserState
+
+-- | Query pretty printer options
+getOption :: (OptionConfig -> Bool) -> Printer Bool
+getOption f = getConfig (f . cfgOptions)
+
+-- | Line penalty calculation
+linePenalty :: Bool -> Int64 -> Printer Penalty
+linePenalty eol col = do
+    indentLevel <- gets psIndentLevel
+    config <- getConfig cfgPenalty
+    let maxcol = penaltyMaxLineLength config
+    let pLinebreak = onlyIf eol $ penaltyLinebreak config
+    let pIndent = fromIntegral indentLevel * (penaltyIndent config)
+    let pOverfull = onlyIf (col > fromIntegral maxcol) $ penaltyOverfull config
+            * fromIntegral (col - fromIntegral maxcol)
+            + penaltyOverfullOnce config
+    return . fromIntegral $ pLinebreak + pIndent + pOverfull
+  where
+    onlyIf cond penalty = if cond then penalty else 0
+
+-- | Try only the first (i.e. locally best) solution to the given
+-- pretty printer.  Use this function to improve performance whenever
+-- the formatting of an AST node has no effect on the penalty of any
+-- following AST node, such as top-level declarations or case
+-- branches.
+cut :: Printer a -> Printer a
+cut = winner
+
+withOutputRestriction :: OutputRestriction -> Printer a -> Printer a
+withOutputRestriction r p = do
+    orig <- gets psOutputRestriction
+    modify $ \s -> s { psOutputRestriction = max orig r }
+    result <- p
+    modify $ \s -> s { psOutputRestriction = orig }
+    return result
+
+oneline :: Printer a -> Printer a
+oneline p = do
+    eol <- gets psEolComment
+    when eol $ newline
+    withOutputRestriction NoOverflowOrLinebreak p
+
+-- | Write out a string, updating the current position information.
+write :: ByteString -> Printer ()
+write x = do
+    eol <- gets psEolComment
+    when eol newline
+    write' x
+  where
+    write' x' = do
+        state <- get
+        let indentLevel = fromIntegral (psIndentLevel state)
+            out = if psNewline state
+                  then BS.replicate indentLevel 32 <> x'
+                  else x'
+            buffer = psBuffer state
+            newCol = Buffer.column buffer + fromIntegral (BS.length out)
+        guard $ psOutputRestriction state == Anything || newCol
+            < fromIntegral (penaltyMaxLineLength (cfgPenalty (psUserState state)))
+        penalty <- linePenalty False newCol
+        when (penalty /= mempty) $ cost mempty penalty
+        modify (\s ->
+                s { psBuffer = Buffer.write out buffer, psEolComment = False })
+
+-- | Write a string.
+string :: String -> Printer ()
+string = write . BL.toStrict . BB.toLazyByteString . BB.stringUtf8
+
+-- | Write an integral.
+int :: Integer -> Printer ()
+int = string . show
+
+-- | Write a space.
+space :: Printer ()
+space = do
+    comment <- gets psEolComment
+    unless comment $ write " "
+
+-- | Output a newline.
+newline :: Printer ()
+newline = do
+    modify (\s ->
+            s { psIndentLevel = psIndentLevel s + psOnside s, psOnside = 0 })
+    state <- get
+    guard $ psOutputRestriction state /= NoOverflowOrLinebreak
+    penalty <- linePenalty True (psColumn state)
+    when (penalty /= mempty) $ cost penalty mempty
+    modify (\s -> s { psBuffer     = Buffer.newline (psBuffer state)
+                    , psEolComment = False
+                    })
+
+linebreak :: Printer ()
+linebreak = return () <|> newline
+
+blankline :: Printer ()
+blankline = newline >> newline
+
+spaceOrNewline :: Printer ()
+spaceOrNewline = space <|> newline
+
+withTabStops :: [(TabStop, Maybe Int)] -> Printer a -> Printer a
+withTabStops stops p = do
+    col <- getNextColumn
+    oldstops <- gets psTabStops
+    modify $ \s ->
+        s { psTabStops =
+                foldr (\(k, v) ->
+                       Map.alter (const $ fmap (\x -> col + fromIntegral x) v)
+                                 k)
+                      (psTabStops s)
+                      stops
+          }
+    res <- p
+    modify $ \s -> s { psTabStops = oldstops }
+    return res
+
+atTabStop :: TabStop -> Printer ()
+atTabStop tabstop = do
+    mstop <- gets (Map.lookup tabstop . psTabStops)
+    mayM_ mstop $ \stop -> do
+        col <- getNextColumn
+        let padding = max 0 $ fromIntegral (stop - col)
+        write (BS.replicate padding 32)
+
+mayM_ :: Maybe a -> (a -> Printer ()) -> Printer ()
+mayM_ Nothing _ = return ()
+mayM_ (Just x) p = p x
+
+withPrefix :: Applicative f => f a -> (x -> f b) -> x -> f b
+withPrefix pre f x = pre *> f x
+
+withPostfix :: Applicative f => f a -> (x -> f b) -> x -> f b
+withPostfix post f x = f x <* post
+
+withIndent :: (IndentConfig -> Indent) -> Printer a -> Printer a
+withIndent fn p = do
+    cfg <- getConfig (fn . cfgIndent)
+    case cfg of
+        Align -> align
+        IndentBy i -> indentby i
+        AlignOrIndentBy i -> align <|> indentby i
+  where
+    align = do
+        space
+        aligned p
+
+    indentby i = indent i $ do
+        newline
+        p
+
+withIndentFlat :: (IndentConfig -> Indent)
+               -> ByteString
+               -> Printer a
+               -> Printer a
+withIndentFlat fn kw p = do
+    cfg <- getConfig (fn . cfgIndent)
+    case cfg of
+        Align -> align
+        IndentBy i -> indentby i
+        AlignOrIndentBy i -> align <|> indentby i
+  where
+    align = aligned $ do
+        write kw
+        p
+
+    indentby i = do
+        write kw
+        indent i p
+
+withIndentBy :: (IndentConfig -> Int) -> Printer a -> Printer a
+withIndentBy fn = withIndent (IndentBy . fn)
+
+withLayout :: (LayoutConfig -> Layout) -> Printer a -> Printer a -> Printer a
+withLayout fn flex vertical = do
+    cfg <- getConfig (fn . cfgLayout)
+    case cfg of
+        Flex -> flex
+        Vertical -> vertical
+        TryOneline -> oneline flex <|> vertical
+
+inter :: Printer () -> [Printer ()] -> Printer ()
+inter x = sequence_ . intersperse x
+
+-- | Get the column for the next printed character.
+getNextColumn :: Printer Int64
+getNextColumn = do
+    st <- get
+    return $ if psEolComment st
+             then psIndentLevel st + psOnside st
+             else max (psColumn st) (psIndentLevel st)
+
+-- | 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
+    onside' <- gets psOnside
+    modify (\s -> s { psIndentLevel = i
+                    , psOnside      = if i > level then 0 else onside'
+                    })
+    m <- p
+    modify (\s -> s { psIndentLevel = level, psOnside = onside' })
+    return m
+
+-- | Increase indentation level by n spaces for the given printer.
+indent :: Int -> Printer a -> Printer a
+indent i p = do
+    level <- gets psIndentLevel
+    column (level + fromIntegral i) p
+
+aligned :: Printer a -> Printer a
+aligned p = do
+    col <- getNextColumn
+    column col $ do
+        modify $ \s -> s { psOnside = 0 }
+        p
+
+indented :: Printer a -> Printer a
+indented p = do
+    i <- getConfig (cfgIndentOnside . cfgIndent)
+    indent i p
+
+-- | Increase indentation level b n spaces for the given printer, but
+-- ignore increase when computing further indentations.
+onside :: Printer a -> Printer a
+onside p = do
+    eol <- gets psEolComment
+    when eol newline
+    onsideIndent <- getConfig (cfgIndentOnside . cfgIndent)
+    level <- gets psIndentLevel
+    onside' <- gets psOnside
+    modify (\s -> s { psOnside = fromIntegral onsideIndent })
+    m <- p
+    modify (\s -> s { psIndentLevel = level, psOnside = onside' })
+    return m
+
+depend :: ByteString -> Printer a -> Printer a
+depend kw = depend' (write kw)
+
+depend' :: Printer () -> Printer a -> Printer a
+depend' kw p = do
+    kw
+    space
+    indented p
+
+-- | Wrap in parens.
+parens :: Printer () -> Printer ()
+parens p = do
+    write "("
+    aligned $ do
+        p
+        write ")"
+
+-- | Wrap in brackets.
+brackets :: Printer () -> Printer ()
+brackets p = do
+    write "["
+    aligned $ do
+        p
+        write "]"
+
+group :: LayoutContext -> ByteString -> ByteString -> Printer () -> Printer ()
+group ctx open close p = do
+    force <- getConfig (wsForceLinebreak . cfgGroupWs ctx open . cfgGroup)
+    if force then vert else oneline hor <|> vert
+  where
+    hor = groupH ctx open close p
+
+    vert = groupV ctx open close p
+
+groupH :: LayoutContext -> ByteString -> ByteString -> Printer () -> Printer ()
+groupH ctx open close p = do
+    ws <- getConfig (cfgGroupWs ctx open . cfgGroup)
+    write open
+    when (wsSpace Before ws) space
+    p
+    when (wsSpace After ws) space
+    write close
+
+groupV :: LayoutContext -> ByteString -> ByteString -> Printer () -> Printer ()
+groupV ctx open close p = aligned $ do
+    ws <- getConfig (cfgGroupWs ctx open . cfgGroup)
+    write open
+    if wsLinebreak Before ws then newline else when (wsSpace Before ws) space
+    p
+    if wsLinebreak After ws then newline else when (wsSpace After ws) space
+    write close
+
+operator :: LayoutContext -> ByteString -> Printer ()
+operator ctx op = withOperatorFormatting ctx op (write op) id
+
+operatorH :: LayoutContext -> ByteString -> Printer ()
+operatorH ctx op = withOperatorFormattingH ctx op (write op) id
+
+operatorV :: LayoutContext -> ByteString -> Printer ()
+operatorV ctx op = withOperatorFormattingV ctx op (write op) id
+
+alignOnOperator :: LayoutContext -> ByteString -> Printer a -> Printer a
+alignOnOperator ctx op p =
+    withOperatorFormatting ctx op (write op) (aligned . (*> p))
+
+alignOnOperatorH :: LayoutContext -> ByteString -> Printer a -> Printer a
+alignOnOperatorH ctx op p =
+    withOperatorFormattingH ctx op (write op) (aligned . (*> p))
+
+alignOnOperatorV :: LayoutContext -> ByteString -> Printer a -> Printer a
+alignOnOperatorV ctx op p =
+    withOperatorFormattingV ctx op (write op) (aligned . (*> p))
+
+withOperatorFormatting :: LayoutContext
+                       -> ByteString
+                       -> Printer ()
+                       -> (Printer () -> Printer a)
+                       -> Printer a
+withOperatorFormatting ctx op opp fn = do
+    force <- getConfig (wsForceLinebreak . cfgOpWs ctx op . cfgOp)
+    if force then vert else hor <|> vert
+  where
+    hor = withOperatorFormattingH ctx op opp fn
+
+    vert = withOperatorFormattingV ctx op opp fn
+
+withOperatorFormattingH :: LayoutContext
+                        -> ByteString
+                        -> Printer ()
+                        -> (Printer () -> Printer a)
+                        -> Printer a
+withOperatorFormattingH ctx op opp fn = do
+    ws <- getConfig (cfgOpWs ctx op . cfgOp)
+    nl <- gets psNewline
+    eol <- gets psEolComment
+    when (wsSpace Before ws && not nl && not eol) space
+    fn $ do
+        opp
+        when (wsSpace After ws) space
+
+withOperatorFormattingV :: LayoutContext
+                        -> ByteString
+                        -> Printer ()
+                        -> (Printer () -> Printer a)
+                        -> Printer a
+withOperatorFormattingV ctx op opp fn = do
+    ws <- getConfig (cfgOpWs ctx op . cfgOp)
+    nl <- gets psNewline
+    eol <- gets psEolComment
+    if wsLinebreak Before ws
+        then newline
+        else when (wsSpace Before ws && not nl && not eol) space
+    fn $ do
+        opp
+        if wsLinebreak After ws then newline else when (wsSpace After ws) space
+
+operatorSectionL :: LayoutContext -> ByteString -> Printer () -> Printer ()
+operatorSectionL ctx op opp = do
+    ws <- getConfig (cfgOpWs ctx op . cfgOp)
+    when (wsSpace Before ws) space
+    opp
+
+operatorSectionR :: LayoutContext -> ByteString -> Printer () -> Printer ()
+operatorSectionR ctx op opp = do
+    ws <- getConfig (cfgOpWs ctx op . cfgOp)
+    opp
+    when (wsSpace After ws) space
+
+comma :: Printer ()
+comma = operator Expression ","
diff --git a/src/Floskell/Styles.hs b/src/Floskell/Styles.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell/Styles.hs
@@ -0,0 +1,391 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Floskell.Styles ( styles ) where
+
+import qualified Data.Map        as Map
+
+import           Floskell.Config
+import           Floskell.Types
+
+chrisDoneCfg :: FlexConfig
+chrisDoneCfg = safeFlexConfig $
+    defaultFlexConfig { cfgIndent, cfgLayout, cfgOp, cfgGroup, cfgOptions }
+  where
+    cfgIndent =
+        IndentConfig { cfgIndentOnside = 2
+                     , cfgIndentDeriving = 2
+                     , cfgIndentWhere = 2
+                     , cfgIndentApp = Align
+                     , cfgIndentCase = IndentBy 2
+                     , cfgIndentClass = IndentBy 2
+                     , cfgIndentDo = Align
+                     , cfgIndentIf = IndentBy 3
+                     , cfgIndentLet = Align
+                     , cfgIndentLetBinds = Align
+                     , cfgIndentLetIn = Align
+                     , cfgIndentMultiIf = IndentBy 2
+                     , cfgIndentWhereBinds = Align
+                     , cfgIndentExportSpecList = IndentBy 2
+                     , cfgIndentImportSpecList = IndentBy 7
+                     }
+
+    cfgLayout = LayoutConfig { cfgLayoutApp = TryOneline
+                             , cfgLayoutConDecls = Vertical
+                             , cfgLayoutDeclaration = TryOneline
+                             , cfgLayoutExportSpecList = TryOneline
+                             , cfgLayoutIf = Vertical
+                             , cfgLayoutImportSpecList = TryOneline
+                             , cfgLayoutInfixApp = TryOneline
+                             , cfgLayoutLet = Vertical
+                             , cfgLayoutListComp = Flex
+                             , cfgLayoutRecord = Vertical
+                             , cfgLayoutTypesig = TryOneline
+                             }
+
+    cfgOp =
+        OpConfig ConfigMap { cfgMapDefault   = Whitespace WsBoth WsBefore False
+                           , cfgMapOverrides = Map.fromList opWsOverrides
+                           }
+
+    opWsOverrides =
+        [ (ConfigMapKey (Just ",") Nothing, Whitespace WsNone WsBefore False)
+        , ( ConfigMapKey (Just "record") Nothing
+          , Whitespace WsAfter WsAfter False
+          )
+        , ( ConfigMapKey (Just ".") (Just Type)
+          , Whitespace WsAfter WsAfter False
+          )
+        , (ConfigMapKey (Just "=") Nothing, Whitespace WsBoth WsAfter False)
+        , (ConfigMapKey (Just "<-") Nothing, Whitespace WsBoth WsAfter False)
+        , (ConfigMapKey (Just ":") Nothing, Whitespace WsNone WsBefore False)
+        ]
+
+    cfgGroup =
+        GroupConfig ConfigMap { cfgMapDefault   =
+                                    Whitespace WsNone WsNone False
+                              , cfgMapOverrides = Map.fromList groupWsOverrides
+                              }
+
+    groupWsOverrides = []
+
+    cfgOptions = OptionConfig { cfgOptionSortPragmas           = False
+                              , cfgOptionSplitLanguagePragmas  = False
+                              , cfgOptionSortImports           = False
+                              , cfgOptionSortImportLists       = False
+                              , cfgOptionPreserveVerticalSpace = False
+                              }
+
+cramerCfg :: FlexConfig
+cramerCfg = safeFlexConfig $
+    defaultFlexConfig { cfgAlign
+                      , cfgIndent
+                      , cfgLayout
+                      , cfgOp
+                      , cfgGroup
+                      , cfgOptions
+                      }
+  where
+    cfgAlign = AlignConfig { cfgAlignLimits       = (10, 25)
+                           , cfgAlignCase         = False
+                           , cfgAlignClass        = False
+                           , cfgAlignImportModule = True
+                           , cfgAlignImportSpec   = True
+                           , cfgAlignLetBinds     = False
+                           , cfgAlignRecordFields = True
+                           , cfgAlignWhere        = False
+                           }
+
+    cfgIndent =
+        IndentConfig { cfgIndentOnside = 4
+                     , cfgIndentDeriving = 4
+                     , cfgIndentWhere = 2
+                     , cfgIndentApp = Align
+                     , cfgIndentCase = IndentBy 4
+                     , cfgIndentClass = IndentBy 4
+                     , cfgIndentDo = IndentBy 4
+                     , cfgIndentIf = Align
+                     , cfgIndentLet = Align
+                     , cfgIndentLetBinds = Align
+                     , cfgIndentLetIn = IndentBy 4
+                     , cfgIndentMultiIf = IndentBy 4
+                     , cfgIndentWhereBinds = IndentBy 2
+                     , cfgIndentExportSpecList = IndentBy 4
+                     , cfgIndentImportSpecList = AlignOrIndentBy 17
+                     }
+
+    cfgLayout = LayoutConfig { cfgLayoutApp = TryOneline
+                             , cfgLayoutConDecls = TryOneline
+                             , cfgLayoutDeclaration = Flex
+                             , cfgLayoutExportSpecList = TryOneline
+                             , cfgLayoutIf = TryOneline
+                             , cfgLayoutImportSpecList = TryOneline
+                             , cfgLayoutInfixApp = Flex
+                             , cfgLayoutLet = TryOneline
+                             , cfgLayoutListComp = TryOneline
+                             , cfgLayoutRecord = TryOneline
+                             , cfgLayoutTypesig = TryOneline
+                             }
+
+    cfgOp =
+        OpConfig ConfigMap { cfgMapDefault   = Whitespace WsBoth WsBefore False
+                           , cfgMapOverrides = Map.fromList opWsOverrides
+                           }
+
+    opWsOverrides =
+        [ (ConfigMapKey (Just ",") Nothing, Whitespace WsAfter WsBefore False)
+        , ( ConfigMapKey (Just "record") Nothing
+          , Whitespace WsAfter WsNone False
+          )
+        , ( ConfigMapKey (Just ".") (Just Type)
+          , Whitespace WsAfter WsAfter False
+          )
+        , (ConfigMapKey (Just "=") Nothing, Whitespace WsBoth WsAfter False)
+        , (ConfigMapKey (Just "$") Nothing, Whitespace WsBoth WsAfter False)
+        , (ConfigMapKey (Just "@") Nothing, Whitespace WsNone WsNone False)
+        , ( ConfigMapKey (Just "->") (Just Expression)
+          , Whitespace WsBoth WsAfter False
+          )
+        , ( ConfigMapKey (Just "record") (Just Pattern)
+          , Whitespace WsNone WsNone False
+          )
+        ]
+
+    cfgGroup =
+        GroupConfig ConfigMap { cfgMapDefault   =
+                                    Whitespace WsBoth WsAfter False
+                              , cfgMapOverrides = Map.fromList groupWsOverrides
+                              }
+
+    groupWsOverrides =
+        [ (ConfigMapKey Nothing (Just Type), Whitespace WsNone WsAfter False)
+        , ( ConfigMapKey Nothing (Just Pattern)
+          , Whitespace WsNone WsAfter False
+          )
+        , (ConfigMapKey (Just "$(") Nothing, Whitespace WsNone WsNone False)
+        , (ConfigMapKey (Just "[|") Nothing, Whitespace WsNone WsNone False)
+        , (ConfigMapKey (Just "[d|") Nothing, Whitespace WsNone WsNone False)
+        , (ConfigMapKey (Just "[p|") Nothing, Whitespace WsNone WsNone False)
+        , (ConfigMapKey (Just "[t|") Nothing, Whitespace WsNone WsNone False)
+        , (ConfigMapKey (Just "(") Nothing, Whitespace WsNone WsAfter False)
+        , ( ConfigMapKey (Just "(") (Just Other)
+          , Whitespace WsBoth WsAfter False
+          )
+        , ( ConfigMapKey (Just "[") (Just Pattern)
+          , Whitespace WsBoth WsAfter False
+          )
+        ]
+
+    cfgOptions = OptionConfig { cfgOptionSortPragmas           = True
+                              , cfgOptionSplitLanguagePragmas  = True
+                              , cfgOptionSortImports           = True
+                              , cfgOptionSortImportLists       = True
+                              , cfgOptionPreserveVerticalSpace = True
+                              }
+
+gibianskyCfg :: FlexConfig
+gibianskyCfg = safeFlexConfig $
+    defaultFlexConfig { cfgAlign
+                      , cfgIndent
+                      , cfgLayout
+                      , cfgOp
+                      , cfgGroup
+                      , cfgOptions
+                      }
+  where
+    cfgAlign = AlignConfig { cfgAlignLimits       = (10, 25)
+                           , cfgAlignCase         = True
+                           , cfgAlignClass        = False
+                           , cfgAlignImportModule = True
+                           , cfgAlignImportSpec   = False
+                           , cfgAlignLetBinds     = False
+                           , cfgAlignRecordFields = False
+                           , cfgAlignWhere        = False
+                           }
+
+    cfgIndent =
+        IndentConfig { cfgIndentOnside = 2
+                     , cfgIndentDeriving = 2
+                     , cfgIndentWhere = 2
+                     , cfgIndentApp = IndentBy 2
+                     , cfgIndentCase = IndentBy 2
+                     , cfgIndentClass = IndentBy 2
+                     , cfgIndentDo = IndentBy 2
+                     , cfgIndentIf = Align
+                     , cfgIndentLet = Align
+                     , cfgIndentLetBinds = Align
+                     , cfgIndentLetIn = Align
+                     , cfgIndentMultiIf = IndentBy 2
+                     , cfgIndentWhereBinds = IndentBy 2
+                     , cfgIndentExportSpecList = IndentBy 4
+                     , cfgIndentImportSpecList = IndentBy 4
+                     }
+
+    cfgLayout = LayoutConfig { cfgLayoutApp = TryOneline
+                             , cfgLayoutConDecls = Vertical
+                             , cfgLayoutDeclaration = Flex
+                             , cfgLayoutExportSpecList = TryOneline
+                             , cfgLayoutIf = Vertical
+                             , cfgLayoutImportSpecList = Flex
+                             , cfgLayoutInfixApp = TryOneline
+                             , cfgLayoutLet = Vertical
+                             , cfgLayoutListComp = TryOneline
+                             , cfgLayoutRecord = TryOneline
+                             , cfgLayoutTypesig = TryOneline
+                             }
+
+    cfgOp =
+        OpConfig ConfigMap { cfgMapDefault   = Whitespace WsBoth WsBefore False
+                           , cfgMapOverrides = Map.fromList opWsOverrides
+                           }
+
+    opWsOverrides =
+        [ (ConfigMapKey (Just ",") Nothing, Whitespace WsAfter WsBefore False)
+        , ( ConfigMapKey (Just "record") Nothing
+          , Whitespace WsAfter WsAfter False
+          )
+        , ( ConfigMapKey (Just ".") (Just Type)
+          , Whitespace WsAfter WsAfter False
+          )
+        , (ConfigMapKey (Just "=") Nothing, Whitespace WsBoth WsAfter False)
+        , (ConfigMapKey (Just ":") Nothing, Whitespace WsNone WsBefore False)
+        ]
+
+    cfgGroup =
+        GroupConfig ConfigMap { cfgMapDefault   =
+                                    Whitespace WsNone WsNone False
+                              , cfgMapOverrides = Map.fromList groupWsOverrides
+                              }
+
+    groupWsOverrides =
+        [ (ConfigMapKey (Just "{") Nothing, Whitespace WsBoth WsAfter False) ]
+
+    cfgOptions = OptionConfig { cfgOptionSortPragmas           = False
+                              , cfgOptionSplitLanguagePragmas  = False
+                              , cfgOptionSortImports           = False
+                              , cfgOptionSortImportLists       = False
+                              , cfgOptionPreserveVerticalSpace = False
+                              }
+
+johanTibellCfg :: FlexConfig
+johanTibellCfg = safeFlexConfig $
+    defaultFlexConfig { cfgIndent, cfgLayout, cfgOp, cfgGroup, cfgOptions }
+  where
+    cfgIndent =
+        IndentConfig { cfgIndentOnside = 4
+                     , cfgIndentDeriving = 4
+                     , cfgIndentWhere = 2
+                     , cfgIndentApp = IndentBy 4
+                     , cfgIndentCase = IndentBy 4
+                     , cfgIndentClass = IndentBy 4
+                     , cfgIndentDo = IndentBy 4
+                     , cfgIndentIf = IndentBy 4
+                     , cfgIndentLet = Align
+                     , cfgIndentLetBinds = Align
+                     , cfgIndentLetIn = Align
+                     , cfgIndentMultiIf = IndentBy 2
+                     , cfgIndentWhereBinds = IndentBy 2
+                     , cfgIndentExportSpecList = IndentBy 2
+                     , cfgIndentImportSpecList = IndentBy 7
+                     }
+
+    cfgLayout = LayoutConfig { cfgLayoutApp = TryOneline
+                             , cfgLayoutConDecls = Vertical
+                             , cfgLayoutDeclaration = TryOneline
+                             , cfgLayoutExportSpecList = TryOneline
+                             , cfgLayoutIf = Vertical
+                             , cfgLayoutImportSpecList = TryOneline
+                             , cfgLayoutInfixApp = TryOneline
+                             , cfgLayoutLet = Vertical
+                             , cfgLayoutListComp = Flex
+                             , cfgLayoutRecord = Vertical
+                             , cfgLayoutTypesig = TryOneline
+                             }
+
+    cfgOp =
+        OpConfig ConfigMap { cfgMapDefault   = Whitespace WsBoth WsBefore False
+                           , cfgMapOverrides = Map.fromList opWsOverrides
+                           }
+
+    opWsOverrides =
+        [ (ConfigMapKey (Just ",") Nothing, Whitespace WsAfter WsBefore False)
+        , ( ConfigMapKey (Just "record") Nothing
+          , Whitespace WsAfter WsAfter True
+          )
+        , ( ConfigMapKey (Just ".") (Just Type)
+          , Whitespace WsAfter WsAfter False
+          )
+        , (ConfigMapKey (Just "=") Nothing, Whitespace WsBoth WsAfter False)
+        , ( ConfigMapKey (Just ":") (Just Pattern)
+          , Whitespace WsNone WsBefore False
+          )
+        , ( ConfigMapKey (Just ",") (Just Pattern)
+          , Whitespace WsNone WsBefore False
+          )
+        , ( ConfigMapKey (Just ",") (Just Other)
+          , Whitespace WsNone WsBefore False
+          )
+        , ( ConfigMapKey (Just "record") (Just Pattern)
+          , Whitespace WsAfter WsAfter False
+          )
+        ]
+
+    cfgGroup =
+        GroupConfig ConfigMap { cfgMapDefault   =
+                                    Whitespace WsNone WsNone False
+                              , cfgMapOverrides = Map.fromList groupWsOverrides
+                              }
+
+    groupWsOverrides =
+        [ (ConfigMapKey (Just "{") Nothing, Whitespace WsBoth WsAfter False)
+        , ( ConfigMapKey (Just "{") (Just Pattern)
+          , Whitespace WsNone WsNone False
+          )
+        ]
+
+    cfgOptions = OptionConfig { cfgOptionSortPragmas           = False
+                              , cfgOptionSplitLanguagePragmas  = False
+                              , cfgOptionSortImports           = False
+                              , cfgOptionSortImportLists       = False
+                              , cfgOptionPreserveVerticalSpace = False
+                              }
+
+-- | Base style definition.
+base :: Style
+base = Style { styleName         = "base"
+             , styleAuthor       = "Enno Cramer"
+             , styleDescription  = "Configurable formatting style"
+             , styleInitialState = safeFlexConfig defaultFlexConfig
+             }
+
+chrisDone :: Style
+chrisDone = Style { styleName         = "chris-done"
+                  , styleAuthor       = "Chris Done"
+                  , styleDescription  = "Chris Done's style"
+                  , styleInitialState = chrisDoneCfg
+                  }
+
+cramer :: Style
+cramer = Style { styleName         = "cramer"
+               , styleAuthor       = "Enno Cramer"
+               , styleDescription  = "Enno Cramer's style"
+               , styleInitialState = cramerCfg
+               }
+
+gibiansky :: Style
+gibiansky = Style { styleName         = "gibiansky"
+                  , styleAuthor       = "Andrew Gibiansky"
+                  , styleDescription  = "Andrew Gibiansky's style"
+                  , styleInitialState = gibianskyCfg
+                  }
+
+johanTibell :: Style
+johanTibell = Style { styleName         = "johan-tibell"
+                    , styleAuthor       = "Johan Tibell"
+                    , styleDescription  = "Johan Tibell's style"
+                    , styleInitialState = johanTibellCfg
+                    }
+
+-- | Styles list, useful for programmatically choosing.
+styles :: [Style]
+styles = [ base, chrisDone, johanTibell, gibiansky, cramer ]
diff --git a/src/Floskell/Types.hs b/src/Floskell/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Floskell/Types.hs
@@ -0,0 +1,115 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+-- | All types.
+module Floskell.Types
+    ( OutputRestriction(..)
+    , Penalty(..)
+    , TabStop(..)
+    , Printer(..)
+    , execPrinter
+    , runPrinter
+    , PrintState(..)
+    , psLine
+    , psColumn
+    , psNewline
+    , Style(..)
+    , FlexConfig(..)
+    , NodeInfo(..)
+    , ComInfo(..)
+    , Location(..)
+    ) where
+
+import           Control.Applicative
+import           Control.Monad
+
+import           Control.Monad.Search
+                 ( MonadSearch, Search, runSearchBest )
+import           Control.Monad.State.Strict
+                 ( MonadState(..), StateT, execStateT, runStateT )
+
+import           Data.Int                       ( Int64 )
+import           Data.Map.Strict                ( Map )
+
+import           Data.Semigroup                 as Sem
+import           Data.Text                      ( Text )
+
+import           Floskell.Buffer                ( Buffer )
+import qualified Floskell.Buffer                as Buffer
+import           Floskell.Config                ( FlexConfig(..), Location(..) )
+
+import           Language.Haskell.Exts.Comments
+import           Language.Haskell.Exts.SrcLoc
+
+data OutputRestriction = Anything | NoOverflow | NoOverflowOrLinebreak
+    deriving ( Eq, Ord, Show )
+
+newtype Penalty = Penalty Int
+    deriving ( Eq, Ord, Num, Show )
+
+newtype TabStop = TabStop String
+    deriving ( Eq, Ord, Show )
+
+instance Sem.Semigroup Penalty where
+    (<>) = (+)
+
+instance Monoid Penalty where
+    mempty = 0
+#if !(MIN_VERSION_base(4,11,0))
+
+    mappend = (<>)
+#endif
+
+-- | A pretty printing monad.
+newtype Printer a =
+    Printer { unPrinter :: StateT PrintState (Search Penalty) a }
+    deriving ( Applicative, Monad, Functor, MonadState PrintState
+             , MonadSearch Penalty, MonadPlus, Alternative )
+
+execPrinter :: Printer a -> PrintState -> Maybe (Penalty, PrintState)
+execPrinter m s = runSearchBest $ execStateT (unPrinter m) s
+
+runPrinter :: Printer a -> PrintState -> Maybe (Penalty, (a, PrintState))
+runPrinter m s = runSearchBest $ runStateT (unPrinter m) s
+
+-- | The state of the pretty printer.
+data PrintState =
+    PrintState { psBuffer :: !Buffer -- ^ Output buffer
+               , psIndentLevel :: !Int64 -- ^ Current indentation level.
+               , psOnside :: !Int64 -- ^ Extra indentation is necessary with next line break.
+               , psTabStops :: !(Map TabStop Int64) -- ^ Tab stops for alignment.
+               , psUserState :: !FlexConfig -- ^ User state.
+               , psEolComment :: !Bool -- ^ An end of line comment has just been outputted.
+               , psOutputRestriction :: OutputRestriction
+               }
+
+psLine :: PrintState -> Int64
+psLine = Buffer.line . psBuffer
+
+psColumn :: PrintState -> Int64
+psColumn = Buffer.column . psBuffer
+
+psNewline :: PrintState -> Bool
+psNewline = (== 0) . Buffer.column . psBuffer
+
+-- | A printer style.
+data Style =
+    Style { styleName         :: !Text -- ^ Name of the style, used in the commandline interface.
+          , styleAuthor       :: !Text -- ^ Author of the printer (as opposed to the author of the style).
+          , styleDescription  :: !Text -- ^ Description of the style.
+          , styleInitialState :: !FlexConfig -- ^ User state, if needed.
+          }
+
+-- | Information for each node in the AST.
+data NodeInfo =
+    NodeInfo { nodeInfoSpan     :: !SrcSpanInfo -- ^ Location info from the parser.
+             , nodeInfoComments :: ![ComInfo] -- ^ Comments which are attached to this node.
+             }
+    deriving ( Show )
+
+-- | Comment with some more info.
+data ComInfo =
+    ComInfo { comInfoComment  :: !Comment          -- ^ The normal comment type.
+            , comInfoLocation :: !(Maybe Location) -- ^ Where the comment lies relative to the node.
+            }
+    deriving ( Show )
diff --git a/src/main/Benchmark.hs b/src/main/Benchmark.hs
new file mode 100644
--- /dev/null
+++ b/src/main/Benchmark.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Benchmark the pretty printer.
+module Main where
+
+import           Control.DeepSeq
+
+import           Criterion
+import           Criterion.Main
+
+import qualified Data.ByteString       as S
+import qualified Data.ByteString.Char8 as S8
+import qualified Data.ByteString.UTF8  as UTF8
+import qualified Data.Text             as T
+
+import           Floskell
+
+import           Language.Haskell.Exts ( Language(Haskell2010) )
+
+import           Markdone
+
+-- | Main benchmarks.
+main :: IO ()
+main = do
+    bytes <- S.readFile "BENCHMARK.md"
+    !forest <- fmap force (parse (tokenize bytes))
+    defaultMain [ bgroup (T.unpack $ styleName style) $
+                    toCriterion style forest
+                | style <- styles
+                ]
+
+-- | Convert the Markdone document to Criterion benchmarks.
+toCriterion :: Style -> [Markdone] -> [Benchmark]
+toCriterion style = go
+  where
+    go (Section name children : next) = bgroup (S8.unpack name) (go children)
+        : go next
+    go (PlainText desc : CodeFence lang code : next) =
+        if lang == "haskell"
+        then bench (UTF8.toString desc)
+                   (nf (either error id . reformat style
+                                                   Haskell2010
+                                                   defaultExtensions
+                                                   (Just "BENCHMARK.md"))
+                       code) : go next
+        else go next
+    go (PlainText{} : next) = go next
+    go (CodeFence{} : next) = go next
+    go [] = []
diff --git a/src/main/Main.hs b/src/main/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/main/Main.hs
@@ -0,0 +1,251 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Main entry point to floskell.
+module Main ( main ) where
+
+import           Control.Applicative             ( (<|>), many, optional )
+import           Control.Exception               ( catch, throw )
+
+import           Data.Aeson
+                 ( (.:?), (.=), FromJSON(..), ToJSON(..) )
+import qualified Data.Aeson                      as JSON
+import qualified Data.Aeson.Encode.Pretty        as JSON ( encodePretty )
+import qualified Data.Aeson.Types                as JSON ( typeMismatch )
+import qualified Data.ByteString                 as BS
+import qualified Data.ByteString.Lazy            as BL
+import qualified Data.HashMap.Lazy               as HashMap
+import           Data.List                       ( inits, sort )
+import           Data.Maybe                      ( isJust )
+import           Data.Monoid                     ( (<>) )
+
+import qualified Data.Text                       as T
+import           Data.Version                    ( showVersion )
+
+import           Floskell                        ( reformat, styles )
+import           Floskell.Types
+                 ( Style(styleName, styleInitialState) )
+
+import           Foreign.C.Error                 ( Errno(..), eXDEV )
+
+import           GHC.IO.Exception                ( ioe_errno )
+
+import           Language.Haskell.Exts
+                 ( Extension(..), Language(..), classifyExtension
+                 , classifyLanguage, knownExtensions, knownLanguages )
+
+import           Options.Applicative
+                 ( ParseError(..), abortOption, argument, execParser, footerDoc
+                 , fullDesc, header, help, helper, hidden, info, long, metavar
+                 , option, progDesc, short, str, switch )
+import qualified Options.Applicative.Help.Pretty as PP
+
+import           Paths_floskell                  ( version )
+
+import           System.Directory
+                 ( XdgDirectory(..), copyFile, copyPermissions, doesFileExist
+                 , findFileWith, getAppUserDataDirectory, getCurrentDirectory
+                 , getHomeDirectory, getTemporaryDirectory, getXdgDirectory
+                 , removeFile, renameFile )
+import           System.FilePath                 ( joinPath, splitDirectories )
+import           System.IO
+                 ( FilePath, hClose, hFlush, openTempFile )
+
+-- | Program options.
+data Options = Options { optStyle       :: Maybe String
+                       , optLanguage    :: Maybe String
+                       , optExtensions  :: [String]
+                       , optConfig      :: Maybe FilePath
+                       , optPrintConfig :: Bool
+                       , optFiles       :: [FilePath]
+                       }
+
+-- | Program configuration.
+data Config = Config { cfgStyle      :: Style
+                     , cfgLanguage   :: Language
+                     , cfgExtensions :: [Extension]
+                     }
+
+instance ToJSON Config where
+    toJSON Config{..} = JSON.object [ "style" .= styleName cfgStyle
+                                    , "language" .= show cfgLanguage
+                                    , "extensions" .= map showExt cfgExtensions
+                                    , "formatting" .= styleInitialState cfgStyle
+                                    ]
+      where
+        showExt (EnableExtension x) = show x
+        showExt (DisableExtension x) = "No" ++ show x
+        showExt (UnknownExtension x) = x
+
+instance FromJSON Config where
+    parseJSON (JSON.Object o) = do
+        style <- maybe (cfgStyle defaultConfig) lookupStyle <$> o .:? "style"
+        language <- maybe (cfgLanguage defaultConfig) lookupLanguage
+            <$> o .:? "language"
+        extensions <- maybe (cfgExtensions defaultConfig) (map lookupExtension)
+            <$> o .:? "extensions"
+        let flex = styleInitialState style
+        flex' <- maybe flex (updateFlexConfig flex) <$> o .:? "formatting"
+        let style' = style { styleInitialState = flex' }
+        return $ Config style' language extensions
+      where
+        updateFlexConfig cfg v =
+            case JSON.fromJSON $ mergeJSON (toJSON cfg) v of
+                JSON.Error e -> error e
+                JSON.Success x -> x
+
+        mergeJSON JSON.Null r = r
+        mergeJSON l JSON.Null = l
+        mergeJSON (JSON.Object l) (JSON.Object r) =
+            JSON.Object (HashMap.unionWith mergeJSON l r)
+        mergeJSON _ r = r
+
+    parseJSON v = JSON.typeMismatch "Config" v
+
+-- | Default program configuration.
+defaultConfig :: Config
+defaultConfig = Config (head styles) Haskell2010 []
+
+-- | Main entry point.
+main :: IO ()
+main = do
+    opts <- execParser parser
+    mconfig <- case optConfig opts of
+        Just c -> return $ Just c
+        Nothing ->
+            if isJust (optStyle opts) then return Nothing else findConfig
+    baseConfig <- case mconfig of
+        Just path -> readConfig path
+        Nothing -> return defaultConfig
+    let config = mergeConfig baseConfig opts
+    if optPrintConfig opts
+        then BL.putStr $ JSON.encodePretty config
+        else run config (optFiles opts)
+  where
+    parser = info (helper <*> versioner <*> options)
+                  (fullDesc
+                   <> progDesc "Floskell reformats one or more Haskell modules."
+                   <> header "floskell - A Haskell Source Code Pretty Printer"
+                   <> footerDoc (Just (footerStyles PP.<$$> footerLanguages
+                                       PP.<$$> footerExtensions)))
+
+    versioner = abortOption (InfoMsg $ "floskell " ++ showVersion version)
+                            (long "version"
+                             <> help "Display program version number" <> hidden)
+
+    options = Options
+        <$> optional (option str
+                             (long "style" <> short 's' <> metavar "STYLE"
+                              <> help "Formatting style"))
+        <*> optional (option str
+                             (long "language" <> short 'L' <> metavar "LANGUAGE"
+                              <> help "Select base language"))
+        <*> many (option str
+                         (long "extension" <> short 'X' <> metavar "EXTENSION"
+                          <> help "Enable or disable language extensions"))
+        <*> optional (option str
+                             (long "config" <> short 'c' <> metavar "FILE"
+                              <> help "Configuration file"))
+        <*> switch (long "print-config" <> help "Print configuration")
+        <*> many (argument str
+                           (metavar "FILES"
+                            <> help "Input files (will be replaced)"))
+
+    footerStyles =
+        makeFooter "Supported styles:" (map (T.unpack . styleName) styles)
+
+    footerLanguages =
+        makeFooter "Supported languages:" (map show knownLanguages)
+
+    footerExtensions =
+        makeFooter "Supported extensions:"
+                   [ show e | EnableExtension e <- knownExtensions ]
+
+    makeFooter hdr xs =
+        PP.empty PP.<$$> PP.text hdr PP.<$$> (PP.indent 2 . PP.fillSep
+                                              . PP.punctuate PP.comma
+                                              . map PP.text $ sort xs)
+
+-- | Reformat files or stdin based on provided configuration.
+run :: Config -> [FilePath] -> IO ()
+run Config{..} files = case files of
+    [] -> reformatStdin cfgStyle cfgLanguage cfgExtensions
+    _ -> mapM_ (reformatFile cfgStyle cfgLanguage cfgExtensions) files
+
+-- | Reformat stdin according to Style, Language, and Extensions.
+reformatStdin :: Style -> Language -> [Extension] -> IO ()
+reformatStdin style language extensions = BL.interact $
+    reformatByteString style language extensions Nothing . BL.toStrict
+
+-- | Reformat a file according to Style, Language, and Extensions.
+reformatFile :: Style -> Language -> [Extension] -> FilePath -> IO ()
+reformatFile style language extensions file = do
+    text <- BS.readFile file
+    tmpDir <- getTemporaryDirectory
+    (fp, h) <- openTempFile tmpDir "floskell.hs"
+    BL.hPutStr h $
+        reformatByteString style language extensions (Just file) text
+    hFlush h
+    hClose h
+    let exdev e = if ioe_errno e == Just ((\(Errno a) -> a) eXDEV)
+                  then copyFile fp file >> removeFile fp
+                  else throw e
+    copyPermissions file fp
+    renameFile fp file `catch` exdev
+
+-- | Reformat a ByteString according to Style, Language, and Extensions.
+reformatByteString :: Style
+                   -> Language
+                   -> [Extension]
+                   -> Maybe FilePath
+                   -> BS.ByteString
+                   -> BL.ByteString
+reformatByteString style language extensions mpath text =
+    either error id $ reformat style language extensions mpath text
+
+-- | Try to find a configuration file based on current working
+-- directory, or in one of the application configuration directories.
+findConfig :: IO (Maybe FilePath)
+findConfig = do
+    dotfilePaths <- sequence [ getHomeDirectory, getXdgDirectory XdgConfig "" ]
+    dotfileConfig <- findFileWith doesFileExist dotfilePaths ".floskell.json"
+    userPaths <- sequence [ getAppUserDataDirectory "floskell"
+                          , getXdgDirectory XdgConfig "floskell"
+                          ]
+    userConfig <- findFileWith doesFileExist userPaths "config.json"
+    localPaths <- map joinPath . reverse . drop 1 . inits . splitDirectories
+        <$> getCurrentDirectory
+    localConfig <- findFileWith doesFileExist localPaths "floskell.json"
+    return $ localConfig <|> userConfig <|> dotfileConfig
+
+-- | Load a configuration file.
+readConfig :: FilePath -> IO Config
+readConfig file = do
+    text <- BS.readFile file
+    either (error . (++) (file ++ ": ")) return $ JSON.eitherDecodeStrict text
+
+-- | Update the program configuration from the program options.
+mergeConfig :: Config -> Options -> Config
+mergeConfig cfg@Config{..} Options{..} =
+    cfg { cfgStyle      = maybe cfgStyle lookupStyle optStyle
+        , cfgLanguage   = maybe cfgLanguage lookupLanguage optLanguage
+        , cfgExtensions = cfgExtensions ++ map lookupExtension optExtensions
+        }
+
+-- | Lookup a style by name.
+lookupStyle :: String -> Style
+lookupStyle name = case filter ((== T.pack name) . styleName) styles of
+    [] -> error $ "Unknown style: " ++ name
+    x : _ -> x
+
+-- | Lookup a language by name.
+lookupLanguage :: String -> Language
+lookupLanguage name = case classifyLanguage name of
+    UnknownLanguage _ -> error $ "Unknown language: " ++ name
+    x -> x
+
+-- | Lookup an extension by name.
+lookupExtension :: String -> Extension
+lookupExtension name = case classifyExtension name of
+    UnknownExtension _ -> error $ "Unkown extension: " ++ name
+    x -> x
diff --git a/src/main/Markdone.hs b/src/main/Markdone.hs
new file mode 100644
--- /dev/null
+++ b/src/main/Markdone.hs
@@ -0,0 +1,118 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | A subset of markdown that only supports @#headings@ and code
+-- fences.
+--
+-- All content must be in section headings with proper hierarchy,
+-- anything else is rejected.
+module Markdone where
+
+import           Control.DeepSeq
+import           Control.Monad.Catch
+
+import           Data.ByteString         ( ByteString )
+import           Data.ByteString.Builder as B
+import qualified Data.ByteString.Char8   as S8
+import           Data.Char
+import           Data.Monoid             ( (<>) )
+import           Data.Typeable
+
+import           GHC.Generics
+
+-- | A markdone token.
+data Token = Heading !Int !ByteString
+           | PlainLine !ByteString
+           | BeginFence !ByteString
+           | EndFence
+    deriving ( Show )
+
+-- | A markdone document.
+data Markdone = Section !ByteString ![Markdone]
+              | CodeFence !ByteString !ByteString
+              | PlainText !ByteString
+    deriving ( Show, Generic )
+
+instance NFData Markdone
+
+-- | Parse error.
+data MarkdownError = NoFenceEnd | ExpectedSection
+    deriving ( Typeable, Show )
+
+instance Exception MarkdownError
+
+-- | Tokenize the bytestring.
+tokenize :: ByteString -> [Token]
+tokenize = map token . S8.lines
+  where
+    token line
+        | S8.isPrefixOf "#" line =
+            let (hashes, title) = S8.span (== '#') line
+            in
+                Heading (S8.length hashes) (S8.dropWhile isSpace title)
+        | S8.isPrefixOf "```" line =
+            if line == "```"
+            then EndFence
+            else BeginFence (S8.dropWhile (\c -> c == '`' || c == ' ') line)
+        | otherwise = PlainLine line
+
+-- | Parse into a forest.
+parse :: MonadThrow m => [Token] -> m [Markdone]
+parse = go (0 :: Int)
+  where
+    go level = \case
+        (Heading n label : rest) ->
+            let (children, rest') = span (\case
+                                              Heading nextN _ -> nextN > n
+                                              _ -> True)
+                                         rest
+            in
+                do
+                    childs <- go (level + 1) children
+                    siblings <- go level rest'
+                    return (Section label childs : siblings)
+        (BeginFence label : rest)
+            | level > 0 ->
+                let (content, rest') = span (\case
+                                                 PlainLine{} -> True
+                                                 _ -> False)
+                                            rest
+                in
+                    case rest' of
+                        (EndFence : rest'') ->
+                            fmap (CodeFence label
+                                            (S8.intercalate "\n"
+                                                            (map getPlain
+                                                                 content)) :)
+                                 (go level rest'')
+                        _ -> throwM NoFenceEnd
+        PlainLine p : rest
+            | level > 0 ->
+                let (content, rest') = span (\case
+                                                 PlainLine{} -> True
+                                                 _ -> False)
+                                            (PlainLine p : rest)
+                in
+                    fmap (PlainText (S8.intercalate "\n" (map getPlain content)) :)
+                         (go level rest')
+        [] -> return []
+        _ -> throwM ExpectedSection
+
+    getPlain (PlainLine x) = x
+    getPlain _ = ""
+
+print :: [Markdone] -> B.Builder
+print = mconcat . map (go (0 :: Int))
+  where
+    go level = \case
+        (Section heading children) ->
+            let level' = level + 1
+            in
+                B.byteString (S8.replicate level' '#') <> B.char7 ' '
+                <> B.byteString heading <> B.byteString "\n"
+                <> mconcat (map (go level') children)
+        (CodeFence lang code) -> B.byteString "``` " <> B.byteString lang
+            <> B.char7 '\n' <> B.byteString code <> B.byteString "\n```\n"
+        (PlainText text) -> B.byteString text <> B.byteString "\n"
diff --git a/src/main/Test.hs b/src/main/Test.hs
new file mode 100644
--- /dev/null
+++ b/src/main/Test.hs
@@ -0,0 +1,146 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Test the pretty printer.
+module Main where
+
+import           Control.Monad                ( forM_, guard )
+
+import           Data.ByteString              ( ByteString )
+import qualified Data.ByteString              as S
+import qualified Data.ByteString.Lazy         as L
+import qualified Data.ByteString.Lazy.Builder as L
+import qualified Data.ByteString.UTF8         as UTF8
+import           Data.Maybe                   ( mapMaybe )
+import qualified Data.Text                    as T
+
+import           Floskell
+
+import           Language.Haskell.Exts        ( Language(Haskell2010) )
+
+import           Markdone                     ( Markdone(..) )
+import qualified Markdone                     as MD
+
+import           System.Environment           ( getArgs )
+
+import           Test.Hspec
+
+data TestTree =
+    TestSection String [TestTree] | TestSnippet ByteString | TestMismatchMarker
+
+-- | Prints a string without quoting and escaping.
+newtype Readable = Readable ByteString
+    deriving ( Eq )
+
+instance Show Readable where
+    show (Readable x) = "\n" ++ UTF8.toString x
+
+-- | Version of 'shouldBe' that prints strings in a readable way,
+-- better for our use-case.
+shouldBeReadable :: ByteString -> ByteString -> Expectation
+shouldBeReadable x y = Readable x `shouldBe` Readable y
+
+haskell :: ByteString
+haskell = "haskell"
+
+referenceFile :: Style -> String
+referenceFile style = "styles/" ++ name ++ ".md"
+  where
+    name = T.unpack $ styleName style
+
+loadMarkdone :: String -> IO [Markdone]
+loadMarkdone filename = do
+    bytes <- S.readFile filename
+    MD.parse (MD.tokenize bytes)
+
+saveMarkdone :: String -> [Markdone] -> IO ()
+saveMarkdone filename doc =
+    S.writeFile filename $ L.toStrict $ L.toLazyByteString $ MD.print doc
+
+-- | Extract code snippets from a Markdone document.
+extractSnippets :: ByteString -> [Markdone] -> [TestTree]
+extractSnippets lang = mapMaybe convert
+  where
+    convert (Section name children) =
+        return $ TestSection (UTF8.toString name) $
+        extractSnippets lang children
+    convert (CodeFence l c) = do
+        guard $ l == lang
+        return $ TestSnippet c
+    convert _ = Nothing
+
+-- | Load haskell code snippets from Markdone document.
+loadSnippets :: String -> IO [TestTree]
+loadSnippets filename = do
+    doc <- loadMarkdone filename
+    return $ extractSnippets haskell doc
+
+-- | Some styles are broken and will fail the idempotency test.
+expectedFailures :: [(T.Text, [Int])]
+expectedFailures = []
+
+-- | Convert the Markdone document to Spec benchmarks.
+toSpec :: Style -> [Int] -> [TestTree] -> [TestTree] -> Spec
+toSpec style path inp ref =
+    forM_ (zip3 [ 1 :: Int .. ] inp (ref ++ repeat TestMismatchMarker)) $ \case
+        (n, TestSection title children, TestSection _ children') ->
+            describe title $ toSpec style (path ++ [ n ]) children children'
+        (n, TestSnippet code, TestSnippet code') -> do
+            let path' = (styleName style, path ++ [ n ])
+            it (name n "formats as expected") $
+                case reformatSnippet style code of
+                    Left e -> error e
+                    Right b -> b `shouldBeReadable` code'
+            it (name n "formatting is idempotent") $
+                if path' `elem` expectedFailures
+                then pending
+                else case reformatSnippet style
+                                          code >>= reformatSnippet style of
+                    Left e -> error e
+                    Right b -> b `shouldBeReadable` code'
+        (n, _, _) -> error $ name n "structure mismatch in reference file"
+  where
+    name n desc = "Snippet " ++ show n ++ " - " ++ desc
+
+-- | Main tests.
+testAll :: IO ()
+testAll = do
+    input <- loadSnippets "TEST.md"
+    refs <- mapM loadRef styles
+    hspec $ forM_ refs $
+        \(name, style, ref) -> context name $ toSpec style [] input ref
+  where
+    loadRef style = do
+        let name = T.unpack $ styleName style
+        tree <- loadSnippets $ referenceFile style
+        return (name, style, tree)
+
+reformatSnippet :: Style -> ByteString -> Either String ByteString
+reformatSnippet style code = L.toStrict
+    <$> reformat style Haskell2010 defaultExtensions (Just "TEST.md") code
+
+regenerate :: Style -> [Markdone] -> [Markdone]
+regenerate style = map fmt
+  where
+    fmt (CodeFence lang code) =
+        if lang == haskell
+        then CodeFence lang $ either (UTF8.fromString . ("-- " ++) . show) id $
+            reformatSnippet style code
+        else CodeFence lang code
+    fmt (Section heading children) =
+        Section heading $ regenerate style children
+    fmt x = x
+
+-- | Regenerate style reference files.
+regenerateAll :: IO ()
+regenerateAll = do
+    doc <- loadMarkdone "TEST.md"
+    forM_ styles $ \style -> saveMarkdone (referenceFile style) $
+        regenerate style doc
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case args of
+        [ "regenerate" ] -> regenerateAll
+        _ -> testAll
