diff --git a/.stylish-haskell.yaml b/.stylish-haskell.yaml
new file mode 100644
--- /dev/null
+++ b/.stylish-haskell.yaml
@@ -0,0 +1,171 @@
+# stylish-haskell configuration file
+# ==================================
+
+# The stylish-haskell tool is mainly configured by specifying steps. These steps
+# are a list, so they have an order, and one specific step may appear more than
+# once (if needed). Each file is processed by these steps in the given order.
+steps:
+  # Convert some ASCII sequences to their Unicode equivalents. This is disabled
+  # by default.
+  # - unicode_syntax:
+  #     # In order to make this work, we also need to insert the UnicodeSyntax
+  #     # language pragma. If this flag is set to true, we insert it when it's
+  #     # not already present. You may want to disable it if you configure
+  #     # language extensions using some other method than pragmas. Default:
+  #     # true.
+  #     add_language_pragma: true
+
+  # Align the right hand side of some elements.  This is quite conservative
+  # and only applies to statements where each element occupies a single
+  # line.
+  - simple_align:
+      cases: true
+      top_level_patterns: true
+      records: true
+
+  # Import cleanup
+  - imports:
+      # There are different ways we can align names and lists.
+      #
+      # - global: Align the import names and import list throughout the entire
+      #   file.
+      #
+      # - file: Like global, but don't add padding when there are no qualified
+      #   imports in the file.
+      #
+      # - group: Only align the imports per group (a group is formed by adjacent
+      #   import lines).
+      #
+      # - none: Do not perform any alignment.
+      #
+      # Default: global.
+      align: file
+
+      # Folowing options affect only import list alignment.
+      #
+      # List align has following options:
+      #
+      # - after_alias: Import list is aligned with end of import including
+      #   'as' and 'hiding' keywords.
+      #
+      #   > import qualified Data.List      as List (concat, foldl, foldr, head,
+      #   >                                          init, last, length)
+      #
+      # - with_alias: Import list is aligned with start of alias or hiding.
+      #
+      #   > import qualified Data.List      as List (concat, foldl, foldr, head,
+      #   >                                 init, last, length)
+      #
+      # - new_line: Import list starts always on new line.
+      #
+      #   > import qualified Data.List      as List
+      #   >     (concat, foldl, foldr, head, init, last, length)
+      #
+      # Default: after_alias
+      list_align: after_alias
+
+      # Long list align style takes effect when import is too long. This is
+      # determined by 'columns' setting.
+      #
+      # - inline: This option will put as much specs on same line as possible.
+      #
+      # - new_line: Import list will start on new line.
+      #
+      # - new_line_multiline: Import list will start on new line when it's
+      #   short enough to fit to single line. Otherwise it'll be multiline.
+      #
+      # - multiline: One line per import list entry.
+      #   Type with contructor list acts like single import.
+      #
+      #   > import qualified Data.Map as M
+      #   >     ( empty
+      #   >     , singleton
+      #   >     , ...
+      #   >     , delete
+      #   >     )
+      #
+      # Default: inline
+      long_list_align: inline
+
+      # List padding determines indentation of import list on lines after import.
+      # This option affects 'list_align' and 'long_list_align'.
+      list_padding: 4
+
+      # Separate lists option affects formating of import list for type
+      # or class. The only difference is single space between type and list
+      # of constructors, selectors and class functions.
+      #
+      # - true: There is single space between Foldable type and list of it's
+      #   functions.
+      #
+      #   > import Data.Foldable (Foldable (fold, foldl, foldMap))
+      #
+      # - false: There is no space between Foldable type and list of it's
+      #   functions.
+      #
+      #   > import Data.Foldable (Foldable(fold, foldl, foldMap))
+      #
+      # Default: true
+      separate_lists: true
+
+  # Language pragmas
+  - language_pragmas:
+      # We can generate different styles of language pragma lists.
+      #
+      # - vertical: Vertical-spaced language pragmas, one per line.
+      #
+      # - compact: A more compact style.
+      #
+      # - compact_line: Similar to compact, but wrap each line with
+      #   `{-#LANGUAGE #-}'.
+      #
+      # Default: vertical.
+      style: vertical
+
+      # Align affects alignment of closing pragma brackets.
+      #
+      # - true: Brackets are aligned in same collumn.
+      #
+      # - false: Brackets are not aligned together. There is only one space
+      #   between actual import and closing bracket.
+      #
+      # Default: true
+      align: true
+
+      # stylish-haskell can detect redundancy of some language pragmas. If this
+      # is set to true, it will remove those redundant pragmas. Default: true.
+      remove_redundant: true
+
+  # Replace tabs by spaces. This is disabled by default.
+  # - tabs:
+  #     # Number of spaces to use for each tab. Default: 8, as specified by the
+  #     # Haskell report.
+  #     spaces: 2
+
+  # Remove trailing whitespace
+  - trailing_whitespace: {}
+
+# A common setting is the number of columns (parts of) code will be wrapped
+# to. Different steps take this into account. Default: 80.
+columns: 80
+
+# By default, line endings are converted according to the OS. You can override
+# preferred format here.
+#
+# - native: Native newline format. CRLF on Windows, LF on other OSes.
+#
+# - lf: Convert to LF ("\n").
+#
+# - crlf: Convert to CRLF ("\r\n").
+#
+# Default: native.
+newline: native
+
+# Sometimes, language extensions are specified in a cabal file or from the
+# command line instead of using language pragmas in the file. stylish-haskell
+# needs to be aware of these, so it can parse the file correctly.
+#
+# No language extensions are enabled by default.
+# language_extensions:
+  # - TemplateHaskell
+  # - QuasiQuotes
diff --git a/app/Actions.hs b/app/Actions.hs
--- a/app/Actions.hs
+++ b/app/Actions.hs
@@ -3,51 +3,39 @@
   ) where
 
 import Language.Haskell.Format
-import OptionsParser
+import Language.Haskell.Format.Utilities
+import Options
 import Types
 
 import Control.Monad
-import Data.Algorithm.Diff
-import Data.Algorithm.DiffContext
-import Data.Algorithm.DiffOutput
-import Text.PrettyPrint
 
-act :: Options -> ReformatResult -> IO ReformatResult
-act options r@(InvalidReformat input errorString) = do
-  putStrLn ("Error reformatting " ++ show input ++ ": " ++ errorString)
-  return r
-act options r@(Reformat input source result) = act' (optAction options)
+act :: Options -> Formatted -> IO Formatted
+act options r@(Formatted input source result) = act' (optAction options)
   where
     act' PrintDiffs =
-      when wasReformatted (printDiff input source result) >> return r
-    act' PrintSources = undefined
-    act' PrintFilePaths = when wasReformatted (print input) >> return r
+      when wasReformatted' (printDiff input source result) >> return r
+    act' PrintSources = do
+      when wasReformatted' (printSource $ reformattedSource result)
+      return (Formatted input (reformattedSource result) result)
+    act' PrintFilePaths = when wasReformatted' (print input) >> return r
     act' WriteSources = do
-      when wasReformatted (writeSource input (reformattedSource result))
-      return (Reformat input (reformattedSource result) result)
-    wasReformatted = sourceChangedOrHasSuggestions source result
-
-sourceChangedOrHasSuggestions :: HaskellSource -> Reformatted -> Bool
-sourceChangedOrHasSuggestions source reformatted =
-  not (null (suggestions reformatted)) ||
-  source /= reformattedSource reformatted
+      when wasReformatted' (writeSource input (reformattedSource result))
+      return (Formatted input (reformattedSource result) result)
+    wasReformatted' = wasReformatted source result
 
-printDiff :: InputFile -> HaskellSource -> Reformatted -> IO ()
-printDiff (InputFilePath path) source reformatted = do
+printDiff :: SourceFile -> HaskellSource -> Reformatted -> IO ()
+printDiff (SourceFilePath path) source reformatted = do
   putStrLn (path ++ ":")
   mapM_ (putStr . show) (suggestions reformatted)
   putStr (showDiff source (reformattedSource reformatted))
-printDiff InputFromStdIn source reformatted = do
+printDiff StdinSource source reformatted = do
   mapM_ (putStr . show) (suggestions reformatted)
   putStr (showDiff source (reformattedSource reformatted))
 
-showDiff :: HaskellSource -> HaskellSource -> String
-showDiff (HaskellSource a) (HaskellSource b) = render (toDoc diff)
-  where
-    toDoc = prettyContextDiff (text "Original") (text "Reformatted") text
-    diff = getContextDiff linesOfContext (lines a) (lines b)
-    linesOfContext = 1
+printSource :: HaskellSource -> IO ()
+printSource (HaskellSource _ source) = putStr source
 
-writeSource :: InputFile -> HaskellSource -> IO ()
-writeSource (InputFilePath path) (HaskellSource source) = writeFile path source
-writeSource InputFromStdIn (HaskellSource source) = putStr source
+writeSource :: SourceFile -> HaskellSource -> IO ()
+writeSource (SourceFilePath path) (HaskellSource _ source) =
+  writeFile path source
+writeSource StdinSource (HaskellSource _ source) = putStr source
diff --git a/app/ExitCode.hs b/app/ExitCode.hs
new file mode 100644
--- /dev/null
+++ b/app/ExitCode.hs
@@ -0,0 +1,44 @@
+module ExitCode
+  ( exitCode
+  , helpDoc
+  , operationalFailureCode
+  ) where
+
+import Types
+
+import System.Exit
+import Text.PrettyPrint.ANSI.Leijen
+
+-- | Decide which exit code to use
+--
+-- Assume if we are printing diffs that we *are* being run in a CI and so
+-- should exit with failure if any format differences were found. Otherwise
+-- assume we are being run in a context where non-zero exit indicates
+-- failure of the tool to operate properly.
+exitCode :: Action -> Bool -> ExitCode
+exitCode action formattedCodeDiffers =
+  if formattedCodeDiffers && failOnDifferences
+    then ExitFailure formattedCodeDiffersFailureCode
+    else ExitSuccess
+  where
+    failOnDifferences = action == PrintDiffs
+
+helpDoc :: Doc
+helpDoc =
+  vsep
+    [ text "Exit Codes:"
+    , text "  0 = No error"
+    , text "  1 = Encountered I/O or other operational error"
+    , text "  2 = Failed to parse a source code file"
+    , text "  3 = Source code was parsed but cannot be formatted properly"
+    , text
+        "  4 = Formatted code differs from existing source (--print-diffs only)"
+    ]
+
+-- | Encountered I/O or other operational error
+operationalFailureCode :: Int
+operationalFailureCode = 1
+
+-- | Formatted code differs from existing source code
+formattedCodeDiffersFailureCode :: Int
+formattedCodeDiffersFailureCode = 4
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,73 +1,81 @@
+{-# LANGUAGE Rank2Types #-}
+
 module Main
   ( main
   ) where
 
-import           Actions
-import           Language.Haskell.Format
-import           Language.Haskell.Format.Utilities  hiding (wasReformatted)
-import           Language.Haskell.Source.Enumerator
-import           OptionsParser                      as Options
-import           Types
+import Actions
+import ExitCode
+import Language.Haskell.Format
+import Language.Haskell.Format.Utilities
+import Language.Haskell.Source.Enumerator
+import Options
+import Types
 
-import           Control.Applicative
-import           Control.Monad
-import           Data.List
-import           Data.Maybe
-import           Data.Monoid
-import           Options.Applicative.Extra          as OptApp
-import           Pipes
-import qualified Pipes.Prelude                      as P
-import           System.Exit
+import Conduit
+import Options.Applicative.Extra          as OptApp
+import System.Directory
+import System.Exit
 
 main :: IO ()
 main = do
   options <- execParser Options.parser
-  formatter <- defaultFormatter
-  reformatNeeded <- runFormatter formatter options
-  if reformatNeeded
-    then exitFailure
-    else exitSuccess
+  formattedCodeDiffers <- run options
+  exitWith $ exitCode (optAction options) formattedCodeDiffers
 
-runFormatter :: Formatter -> Options -> IO Bool
-runFormatter formatter options =
-  anyStrict
-    sourceChangedOrHasSuggestions
-    (inputFiles >-> P.map reformat >-> P.mapM writeOutput)
+run :: Options -> IO Bool
+run options =
+  runConduit $
+  sources .| mapMC readSource .| mapMC formatSource .| handleFormatErrors .|
+  mapMC action .|
+  summarize
   where
-    anyStrict :: Monad m => (a -> Bool) -> Producer a m () -> m Bool
-    anyStrict f = P.fold (\acc x -> acc || f x) False id
-    inputFiles = readInputFiles options
-    reformat = applyFormatter formatter
-    writeOutput = Actions.act options
-
-readInputFiles :: Options -> Producer InputFileWithSource IO ()
-readInputFiles options =
-  determineInputFilePaths (optPaths options) >-> P.mapM readInputFile
+    paths = do
+      let explicitPaths = optPaths options
+      if null explicitPaths
+        then do
+          currentPath <- getCurrentDirectory
+          return [currentPath]
+        else return explicitPaths
+    sources :: Source IO SourceFile
+    sources = lift paths >>= mapM_ sourcesFromPath
+    formatSource source = do
+      formatter <- defaultFormatter
+      return $ applyFormatter formatter source
+    handleFormatErrors = concatMapMC handleFormatError
+    action = Actions.act options
+    summarize =
+      anyC' (\(Formatted _ source result) -> wasReformatted source result)
 
-determineInputFilePaths :: [FilePath] -> Producer InputFile IO ()
-determineInputFilePaths [] = enumeratePath "." >-> P.map InputFilePath
-determineInputFilePaths ["-"] = yield InputFromStdIn
-determineInputFilePaths paths =
-  for (each paths) enumeratePath >-> P.map InputFilePath
+sourcesFromPath :: FilePath -> Source IO SourceFile
+sourcesFromPath "-"  = yield StdinSource
+sourcesFromPath path = enumeratePath path .| mapC SourceFilePath
 
-readInputFile :: InputFile -> IO InputFileWithSource
-readInputFile (InputFilePath path) =
-  InputFileWithSource (InputFilePath path) <$> readSource path
-readInputFile InputFromStdIn = InputFileWithSource InputFromStdIn <$> readStdin
+readSource :: SourceFile -> IO SourceFileWithContents
+readSource s@(SourceFilePath path) =
+  SourceFileWithContents s . HaskellSource path <$> readFile path
+readSource s@StdinSource =
+  SourceFileWithContents s . HaskellSource "stdin" <$> getContents
 
-applyFormatter :: Formatter -> InputFileWithSource -> ReformatResult
-applyFormatter (Formatter format) (InputFileWithSource input source) =
-  case format source of
-    Left error        -> InvalidReformat input error
-    Right reformatted -> Reformat input source reformatted
+applyFormatter :: Formatter -> SourceFileWithContents -> FormatResult
+applyFormatter (Formatter doFormat) (SourceFileWithContents file contents) =
+  case doFormat contents of
+    Left err       -> Left (FormatError file err)
+    Right reformat -> Right (Formatted file contents reformat)
 
-readSource :: HaskellSourceFilePath -> IO HaskellSource
-readSource path = HaskellSource <$> readFile path
+handleFormatError :: FormatResult -> IO (Maybe Formatted)
+handleFormatError (Left err)        = printFormatError err >> return Nothing
+handleFormatError (Right formatted) = return $ Just formatted
 
-readStdin :: IO HaskellSource
-readStdin = HaskellSource <$> getContents
+printFormatError :: FormatError -> IO ()
+printFormatError (FormatError input errorString) =
+  putStrLn ("Error reformatting " ++ show input ++ ": " ++ errorString)
 
-sourceChangedOrHasSuggestions :: ReformatResult -> Bool
-sourceChangedOrHasSuggestions (Reformat input source reformatted) =
-  not (null (suggestions reformatted)) ||
-  source /= reformattedSource reformatted
+-- | Check that at least one value in the stream returns True.
+--
+-- Does not shortcut, entire stream is always consumed
+anyC' :: Monad m => (a -> Bool) -> Consumer a m Bool
+anyC' f = do
+  result <- anyC f -- Check for at least one value, may shortcut
+  sinkNull -- consume any remaining input skipped by a shortcut
+  return result
diff --git a/app/Options.hs b/app/Options.hs
new file mode 100644
--- /dev/null
+++ b/app/Options.hs
@@ -0,0 +1,83 @@
+module Options
+  ( Options
+  , optAction
+  , optPaths
+  , parser
+  ) where
+
+import qualified ExitCode
+import           Types
+
+import           Control.Applicative
+import           Data.Monoid
+import           Options.Applicative.Builder
+import           Options.Applicative.Common
+import           Options.Applicative.Extra
+import           Text.PrettyPrint.ANSI.Leijen as Leijen hiding ((<$>), (<>))
+
+data Options = Options
+  { optPrintDiffs     :: Bool
+  , optPrintSources   :: Bool
+  , optPrintFilePaths :: Bool
+  , optWriteSources   :: Bool
+  , optPaths          :: [FilePath]
+  }
+
+optAction :: Options -> Action
+optAction options
+  | optPrintDiffs options = PrintDiffs
+  | optPrintFilePaths options = PrintFilePaths
+  | optPrintSources options = PrintSources
+  | optWriteSources options = WriteSources
+  | optPaths options == ["-"] = PrintSources
+  | otherwise = PrintDiffs
+
+optionParser :: Parser Options
+optionParser =
+  Options <$>
+  switch
+    (long "print-diffs" <> short 'd' <>
+     help "If a file's formatting is different, print a diff.") <*>
+  switch
+    (long "print-sources" <> short 's' <> hidden <>
+     help "If a file's formatting is different, print its source.") <*>
+  switch
+    (long "print-paths" <> short 'l' <> hidden <>
+     help "If a file's formatting is different, print its path.") <*>
+  switch
+    (long "write-sources" <> short 'w' <> hidden <>
+     help "If a file's formatting is different, overwrite it.") <*>
+  many pathOption
+  where
+    pathOption = strArgument (metavar "FILE" <> pathOptionHelp)
+    pathOptionHelp =
+      helpDoc $
+      Just $
+      text "Explicit paths to process." <> line <>
+      unorderdedList
+        " - "
+        [ text "A single '-' will process standard input."
+        , text "Files will be processed directly."
+        , text
+            "Directories will be recursively searched for source files to process."
+        , text
+            ".cabal files will be parsed and all specified source directories and files processed."
+        , text
+            "If no paths are given, the current directory will be searched for .cabal files to process, if none are found the current directory will be recursively searched for source files to process."
+        ]
+
+unorderdedList :: String -> [Doc] -> Doc
+unorderdedList prefix = vsep . map ((text prefix <>) . align)
+
+optionParserInfo :: ParserInfo Options
+optionParserInfo =
+  info
+    (helper <*> optionParser)
+    (fullDesc <> header "hfmt - Format Haskell programs" <>
+     progDesc
+       "Reformats Haskell source files by applying HLint, hindent, and stylish-haskell." <>
+     footerDoc (Just ExitCode.helpDoc) <>
+     failureCode ExitCode.operationalFailureCode)
+
+parser :: ParserInfo Options
+parser = optionParserInfo
diff --git a/app/OptionsParser.hs b/app/OptionsParser.hs
deleted file mode 100644
--- a/app/OptionsParser.hs
+++ /dev/null
@@ -1,85 +0,0 @@
-module OptionsParser
-  ( Options
-  , optAction
-  , optPaths
-  , parser
-  ) where
-
-import Types
-
-import Control.Applicative
-import Data.List
-import Data.Monoid
-import Options.Applicative.Builder
-import Options.Applicative.Common
-import Options.Applicative.Extra
-import Text.PrettyPrint.ANSI.Leijen hiding (empty, (<$>), (<>))
-
-data Options = Options
-  { optPrintDiffs     :: Bool
-  , optPrintSources   :: Bool
-  , optPrintFilePaths :: Bool
-  , optWriteSources   :: Bool
-  , optPaths          :: [FilePath]
-  }
-
-optAction :: Options -> Action
-optAction options
-  | optPrintDiffs options = PrintDiffs
-  | optPrintFilePaths options = PrintFilePaths
-  | optPrintSources options = PrintSources
-  | optWriteSources options = WriteSources
-  | optPaths options == ["-"] = PrintSources
-  | otherwise = PrintDiffs
-
-optionParser :: Parser Options
-optionParser =
-  Options <$>
-  switch
-    (long "print-diffs" <> short 'd' <>
-     help "If a file's formatting is different, print a diff.") <*>
-  switch
-    (long "print-sources" <> short 's' <>
-     help "If a file's formatting is different, print its source.") <*>
-  switch
-    (long "print-paths" <> short 'l' <>
-     help "If a file's formatting is different, print its path.") <*>
-  switch
-    (long "write-sources" <> short 'w' <>
-     help "If a file's formatting is different, overwrite it.") <*>
-  many pathOption
-  where
-    pathOption = strArgument (metavar "PATH" <> pathOptionHelp)
-    pathOptionHelp =
-      helpDoc $
-      Just $
-      paragraph "Explicit paths to process." <> hardline <>
-      unorderdedList
-        " - "
-        [ paragraph "A single '-' will process standard input."
-        , paragraph "Files will be processed directly."
-        , paragraph
-            "Directories will be recursively searched for source files to process."
-        , paragraph
-            ".cabal files will be parsed and all specified source directories and files processed."
-        , paragraph
-            "If no paths are given, the current directory will be searched for .cabal files to process, if none are found the current directory will be recursively searched for source files to process."
-        ]
-
-paragraph :: String -> Doc
-paragraph = mconcat . intersperse softline . map text . words
-
-unorderdedList :: String -> [Doc] -> Doc
-unorderdedList prefix =
-  encloseSep (text prefix) mempty (text prefix) . map (hang 0)
-
-optionParserInfo :: ParserInfo Options
-optionParserInfo =
-  info
-    (helper <*> optionParser)
-    (fullDesc <> header "hfmt - format Haskell programs" <>
-     progDesc
-       "Operates on Haskell source files, reformatting them by applying suggestions from HLint, hindent, and stylish-haskell. Inspired by the gofmt utility.")
-
-parser :: ParserInfo Options
-parser = optionParserInfo
diff --git a/app/Types.hs b/app/Types.hs
--- a/app/Types.hs
+++ b/app/Types.hs
@@ -1,39 +1,41 @@
 module Types
   ( Action(..)
-  , InputFile(..)
-  , InputFileWithSource(..)
-  , ErrorString
-  , ReformatResult(..)
-  , HaskellSourceFilePath
+  , FormatResult
+  , FormatError(..)
+  , Formatted(..)
   , HaskellSource(..)
+  , SourceFile(..)
+  , SourceFileWithContents(..)
   ) where
 
 import Language.Haskell.Format
-import Language.Haskell.Source.Enumerator (HaskellSourceFilePath)
 
 data Action
   = PrintDiffs
   | PrintSources
   | PrintFilePaths
   | WriteSources
+  deriving (Eq)
 
-data InputFile
-  = InputFilePath HaskellSourceFilePath
-  | InputFromStdIn
+data SourceFile
+  = SourceFilePath FilePath
+  | StdinSource
 
-instance Show InputFile where
-  show (InputFilePath path) = path
-  show InputFromStdIn       = "-"
+instance Show SourceFile where
+  show (SourceFilePath path) = path
+  show StdinSource           = "-"
 
-data InputFileWithSource =
-  InputFileWithSource InputFile
-                      HaskellSource
+data SourceFileWithContents =
+  SourceFileWithContents SourceFile
+                         HaskellSource
 
-type ErrorString = String
+type FormatResult = Either FormatError Formatted
 
-data ReformatResult
-  = InvalidReformat InputFile
-                    ErrorString
-  | Reformat InputFile
-             HaskellSource
-             Reformatted
+data FormatError =
+  FormatError SourceFile
+              String
+
+data Formatted =
+  Formatted SourceFile
+            HaskellSource
+            Reformatted
diff --git a/hfmt.cabal b/hfmt.cabal
--- a/hfmt.cabal
+++ b/hfmt.cabal
@@ -1,5 +1,5 @@
 name:                hfmt
-version:             0.1.1
+version:             0.2.0
 synopsis:            Haskell source code formatter
 description:         Inspired by gofmt. Built using hlint, hindent, and stylish-haskell.
 license:             MIT
@@ -11,9 +11,9 @@
 bug-reports:         http://github.com/danstiner/hfmt/issues
 category:            Language
 build-type:          Simple
-extra-source-files:  README.md
+extra-source-files:  README.md, .stylish-haskell.yaml
 cabal-version:       >=1.9.2
-tested-with:         GHC >= 7.10
+tested-with:         GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.3
 
 library
   Hs-Source-Dirs:    src
@@ -21,26 +21,28 @@
                      , Language.Haskell.Format.Utilities
                      , Language.Haskell.Source.Enumerator
   Other-Modules:     Language.Haskell.Format.Internal
-                     , Language.Haskell.Format.Definitions
                      , Language.Haskell.Format.HIndent
                      , Language.Haskell.Format.HLint
                      , Language.Haskell.Format.Stylish
+                     , Language.Haskell.Format.Types
                      , Path.Find
   GHC-Options:       -Wall
   Build-Depends:     base >= 4.8 && < 5
                      , bytestring
                      , Cabal
+                     , conduit-combinators
+                     , Diff
                      , directory
                      , exceptions
                      , filepath
-                     , haskell-src-exts
-                     , hindent >= 5.2.3
-                     , hlint >= 1.9
+                     , haskell-src-exts < 1.20
+                     , hindent == 5.*
+                     , hlint == 2.*
                      , HUnit
                      , path
                      , path-io
-                     , pipes
-                     , stylish-haskell >= 0.7
+                     , pretty
+                     , stylish-haskell == 0.8.*
                      , text
                      , transformers
                      , yaml
@@ -49,16 +51,16 @@
   Hs-Source-Dirs:    app
   Main-Is:           Main.hs
   Other-Modules:     Actions
-                   , OptionsParser
+                   , ExitCode
+                   , Options
                    , Types
   GHC-Options:       -Wall
-  Build-Depends:     base == 4.*
+  Build-Depends:     base >= 4.8 && < 5
+                   , conduit-combinators
+                   , directory
                    , hfmt
                    , ansi-wl-pprint
-                   , Diff
                    , optparse-applicative
-                   , pipes
-                   , pretty
 
 test-suite self-formatting-test
   Type:              exitcode-stdio-1.0
diff --git a/src/Language/Haskell/Format.hs b/src/Language/Haskell/Format.hs
--- a/src/Language/Haskell/Format.hs
+++ b/src/Language/Haskell/Format.hs
@@ -12,13 +12,10 @@
   , Reformatted(..)
   ) where
 
-import           Language.Haskell.Format.Definitions
-import qualified Language.Haskell.Format.HIndent     as HIndent
-import qualified Language.Haskell.Format.HLint       as HLint
-import qualified Language.Haskell.Format.Stylish     as Stylish
-
-import           Control.Applicative
-import           Data.Maybe
+import qualified Language.Haskell.Format.HIndent as HIndent
+import qualified Language.Haskell.Format.HLint   as HLint
+import qualified Language.Haskell.Format.Stylish as Stylish
+import           Language.Haskell.Format.Types
 
 data Settings =
   Settings
diff --git a/src/Language/Haskell/Format/Definitions.hs b/src/Language/Haskell/Format/Definitions.hs
deleted file mode 100644
--- a/src/Language/Haskell/Format/Definitions.hs
+++ /dev/null
@@ -1,46 +0,0 @@
-module Language.Haskell.Format.Definitions
-  ( HaskellSource(..)
-  , Reformatted(..)
-  , Formatter(..)
-  , Suggestion(..)
-  ) where
-
-import Control.Applicative
-import Control.Monad
-import Data.Monoid
-
-type ErrorString = String
-
-newtype HaskellSource =
-  HaskellSource String
-  deriving (Eq)
-
-newtype Suggestion =
-  Suggestion String
-
-instance Show Suggestion where
-  show (Suggestion text) = text
-
-data Reformatted = Reformatted
-  { reformattedSource :: HaskellSource
-  , suggestions       :: [Suggestion]
-  }
-
-instance Monoid Reformatted where
-  mempty = Reformatted undefined []
-  (Reformatted _ suggestionsA) `mappend` (Reformatted sourceB suggestionsB) =
-    Reformatted sourceB (suggestionsA <> suggestionsB)
-
-newtype Formatter = Formatter
-  { unFormatter :: HaskellSource -> Either ErrorString Reformatted
-  }
-
-instance Monoid Formatter where
-  mempty = Formatter (\source -> Right (Reformatted source []))
-  (Formatter f) `mappend` (Formatter g) = Formatter (asReformatter g <=< f)
-
-asReformatter ::
-     (HaskellSource -> Either ErrorString Reformatted)
-  -> Reformatted
-  -> Either ErrorString Reformatted
-asReformatter formatter r = (r <>) <$> formatter (reformattedSource r)
diff --git a/src/Language/Haskell/Format/HIndent.hs b/src/Language/Haskell/Format/HIndent.hs
--- a/src/Language/Haskell/Format/HIndent.hs
+++ b/src/Language/Haskell/Format/HIndent.hs
@@ -4,24 +4,20 @@
   , defaultFormatter
   ) where
 
-import           Control.Applicative
 import           Data.ByteString.Builder
-import           Data.ByteString.Lazy                as L
-import           Data.Maybe
-import qualified Data.Text                           as Text
-import           Data.Text.Encoding                  as Encoding
-import           Data.Text.Lazy                      as TL
-import           Data.Text.Lazy.Builder
-import qualified Data.Yaml                           as Y
+import           Data.ByteString.Lazy             as L
+import qualified Data.Text                        as Text
+import           Data.Text.Encoding               as Encoding
+import qualified Data.Yaml                        as Y
 import           HIndent
 import           HIndent.Types
-import           Language.Haskell.Exts.Extension     (Extension)
+import           Language.Haskell.Exts.Extension  (Extension)
 import           Path
-import qualified Path.Find                           as Path
-import qualified Path.IO                             as Path
+import qualified Path.Find                        as Path
+import qualified Path.IO                          as Path
 
-import           Language.Haskell.Format.Definitions
 import           Language.Haskell.Format.Internal
+import           Language.Haskell.Format.Types
 
 data Settings =
   Settings Config
@@ -57,10 +53,10 @@
 formatter = mkFormatter . hindent
 
 hindent :: Settings -> HaskellSource -> Either String HaskellSource
-hindent (Settings config extensions) (HaskellSource source) =
-  toHaskellSource <$> reformat config extensions Nothing sourceText
+hindent (Settings config extensions) (HaskellSource filepath source) =
+  HaskellSource filepath . unpackBuilder <$>
+  reformat config extensions Nothing sourceText
   where
     sourceText = Encoding.encodeUtf8 . Text.pack $ source
-    toHaskellSource =
-      HaskellSource .
+    unpackBuilder =
       Text.unpack . Encoding.decodeUtf8 . L.toStrict . toLazyByteString
diff --git a/src/Language/Haskell/Format/HLint.hs b/src/Language/Haskell/Format/HLint.hs
--- a/src/Language/Haskell/Format/HLint.hs
+++ b/src/Language/Haskell/Format/HLint.hs
@@ -3,16 +3,14 @@
   , suggester
   ) where
 
-import           Language.Haskell.Format.Definitions
 import           Language.Haskell.Format.Internal
+import           Language.Haskell.Format.Types
 
-import           Control.Applicative
-import           Language.Haskell.HLint3             (Classify, Hint,
-                                                      ParseError (..),
-                                                      ParseFlags, applyHints,
-                                                      parseModuleEx)
-import qualified Language.Haskell.HLint3             as HLint3
-import           System.IO.Unsafe                    (unsafePerformIO)
+import           Language.Haskell.HLint3          (Classify, Hint,
+                                                   ParseError (..), ParseFlags,
+                                                   applyHints, parseModuleEx)
+import qualified Language.Haskell.HLint3          as HLint3
+import           System.IO.Unsafe                 (unsafePerformIO)
 
 suggester :: (ParseFlags, [Classify], Hint) -> Formatter
 suggester = mkSuggester . hlint
@@ -21,12 +19,11 @@
      (ParseFlags, [Classify], Hint)
   -> HaskellSource
   -> Either String [Suggestion]
-hlint (flags, classify, hint) (HaskellSource source) =
+hlint (flags, classify, hint) (HaskellSource filepath source) =
   case unsafePerformIO (parseModuleEx flags filepath (Just source)) of
     Right m         -> Right . map ideaToSuggestion . ideas $ m
     Left parseError -> Left . parseErrorMessage $ parseError
   where
-    filepath = ""
     ideas m = applyHints classify hint [m]
 
 autoSettings :: IO (ParseFlags, [Classify], Hint)
diff --git a/src/Language/Haskell/Format/Internal.hs b/src/Language/Haskell/Format/Internal.hs
--- a/src/Language/Haskell/Format/Internal.hs
+++ b/src/Language/Haskell/Format/Internal.hs
@@ -3,9 +3,7 @@
   , mkSuggester
   ) where
 
-import Language.Haskell.Format.Definitions
-
-import Control.Applicative
+import Language.Haskell.Format.Types
 
 mkFormatter :: (HaskellSource -> Either String HaskellSource) -> Formatter
 mkFormatter f = Formatter (fmap (\source -> Reformatted source []) . f)
diff --git a/src/Language/Haskell/Format/Stylish.hs b/src/Language/Haskell/Format/Stylish.hs
--- a/src/Language/Haskell/Format/Stylish.hs
+++ b/src/Language/Haskell/Format/Stylish.hs
@@ -3,26 +3,29 @@
   , formatter
   ) where
 
-import Control.Applicative
-import Language.Haskell.Stylish            as Stylish
+import Language.Haskell.Stylish         as Stylish
 
-import Language.Haskell.Format.Definitions
 import Language.Haskell.Format.Internal
+import Language.Haskell.Format.Types
 
 newtype Settings =
   Settings Stylish.Config
 
 autoSettings :: IO Settings
-autoSettings =
-  Settings <$> Stylish.loadConfig (Stylish.makeVerbose False) Nothing
+autoSettings = do
+  path <- Stylish.configFilePath verbose Nothing
+  config <- Stylish.loadConfig verbose (Just path)
+  return (Settings config)
+  where
+    verbose = Stylish.makeVerbose False
 
 formatter :: Settings -> Formatter
 formatter = mkFormatter . stylish
 
 stylish :: Settings -> HaskellSource -> Either String HaskellSource
-stylish (Settings config) (HaskellSource source) =
-  HaskellSource . unlines <$>
-  Stylish.runSteps extensions Nothing steps sourceLines
+stylish (Settings config) (HaskellSource filepath source) =
+  HaskellSource filepath . unlines <$>
+  Stylish.runSteps extensions (Just filepath) steps sourceLines
   where
     sourceLines = lines source
     extensions = Stylish.configLanguageExtensions config
diff --git a/src/Language/Haskell/Format/Types.hs b/src/Language/Haskell/Format/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Haskell/Format/Types.hs
@@ -0,0 +1,46 @@
+module Language.Haskell.Format.Types
+  ( HaskellSource(..)
+  , Reformatted(..)
+  , Formatter(..)
+  , Suggestion(..)
+  ) where
+
+import Control.Monad
+import Data.Monoid   ((<>))
+
+type ErrorString = String
+
+data HaskellSource =
+  HaskellSource FilePath
+                String
+  deriving (Eq)
+
+newtype Suggestion =
+  Suggestion String
+
+instance Show Suggestion where
+  show (Suggestion text) = text
+
+data Reformatted = Reformatted
+  { reformattedSource :: HaskellSource
+  , suggestions       :: [Suggestion]
+  }
+
+instance Monoid Reformatted where
+  mempty = Reformatted undefined []
+  (Reformatted _ suggestionsA) `mappend` (Reformatted sourceB suggestionsB) =
+    Reformatted sourceB (suggestionsA <> suggestionsB)
+
+newtype Formatter = Formatter
+  { unFormatter :: HaskellSource -> Either ErrorString Reformatted
+  }
+
+instance Monoid Formatter where
+  mempty = Formatter (\source -> Right (Reformatted source []))
+  (Formatter f) `mappend` (Formatter g) = Formatter (asReformatter g <=< f)
+
+asReformatter ::
+     (HaskellSource -> Either ErrorString Reformatted)
+  -> Reformatted
+  -> Either ErrorString Reformatted
+asReformatter formatter r = (r <>) <$> formatter (reformattedSource r)
diff --git a/src/Language/Haskell/Format/Utilities.hs b/src/Language/Haskell/Format/Utilities.hs
--- a/src/Language/Haskell/Format/Utilities.hs
+++ b/src/Language/Haskell/Format/Utilities.hs
@@ -1,69 +1,105 @@
 module Language.Haskell.Format.Utilities
-  ( wasReformatted
+  ( defaultFormatter
   , hunitTest
-  , defaultFormatter
+  , showDiff
+  , wasReformatted
   ) where
 
-import           Language.Haskell.Format
-import           Language.Haskell.Format.Definitions
-import           Language.Haskell.Source.Enumerator
+import System.IO.Unsafe
 
-import           Control.Applicative
-import           Control.Monad
-import           Data.Monoid
-import           Pipes
-import qualified Pipes.Prelude                       as P
-import           Test.HUnit
+import Language.Haskell.Format
+import Language.Haskell.Source.Enumerator
 
+import Conduit
+import Control.Monad
+import Data.Algorithm.DiffContext
+import Data.List
+import Data.Maybe
+import Test.HUnit
+import Text.PrettyPrint
+
 type ErrorString = String
 
 data CheckResult
-  = InvalidCheckResult ErrorString
+  = InvalidCheckResult HaskellSource
+                       ErrorString
   | CheckResult HaskellSource
                 Reformatted
 
+checkResultPath :: CheckResult -> FilePath
+checkResultPath (InvalidCheckResult (HaskellSource filepath _) _) = filepath
+checkResultPath (CheckResult (HaskellSource filepath _) _)        = filepath
+
 hunitTest :: FilePath -> Test
-hunitTest filepath = TestLabel filepath $ makeTestCase filepath
+hunitTest filepath = TestLabel filepath . unsafePerformIO . testPath $ filepath
 
-makeTestCase :: FilePath -> Test
-makeTestCase filepath =
-  TestCase $ do
-    formatter <- defaultFormatter
-    runEffect $ check formatter filepath >-> assertResults
+testPath :: FilePath -> IO Test
+testPath filepath = do
+  formatter <- defaultFormatter
+  TestList <$>
+    runConduit (check formatter filepath .| mapC makeTestCase .| sinkList)
 
-assertResults :: Consumer CheckResult IO ()
-assertResults =
-  forever $ do
-    result <- await
-    case result of
-      (InvalidCheckResult errorString) ->
-        lift $ assertFailure ("Error: " ++ errorString)
-      (CheckResult source reformatted) ->
-        when (wasReformatted source reformatted) $
-        lift $
-        assertFailure
-          ("Incorrect formatting: " ++ concatMap show (suggestions reformatted))
+makeTestCase :: CheckResult -> Test
+makeTestCase result =
+  TestLabel (checkResultPath result) . TestCase $ assertCheckResult result
 
-check :: Formatter -> FilePath -> Producer CheckResult IO ()
-check formatter path =
-  enumeratePath path >-> P.mapM readSource >-> P.map (checkFormatting formatter)
+assertCheckResult :: CheckResult -> IO ()
+assertCheckResult result =
+  case result of
+    (InvalidCheckResult _ errorString) ->
+      assertFailure ("Error: " ++ errorString)
+    (CheckResult source reformatted) ->
+      when (wasReformatted source reformatted) $
+      assertFailure (showReformatted source reformatted)
+  where
+    showReformatted :: HaskellSource -> Reformatted -> String
+    showReformatted source reformatted =
+      intercalate "\n" $
+      catMaybes
+        [ showSourceChanges source reformatted
+        , showSuggestions source reformatted
+        ]
+    showSourceChanges source reformatted =
+      whenMaybe
+        (sourceChanged source reformatted)
+        (showDiff source (reformattedSource reformatted))
+    showSuggestions _ reformatted =
+      whenMaybe
+        (hasSuggestions reformatted)
+        (concatMap show (suggestions reformatted))
+    whenMaybe :: Bool -> a -> Maybe a
+    whenMaybe cond val = const val <$> guard cond
 
-readSource :: HaskellSourceFilePath -> IO HaskellSource
-readSource path = HaskellSource <$> readFile path
+showDiff :: HaskellSource -> HaskellSource -> String
+showDiff (HaskellSource _ a) (HaskellSource _ b) = render (toDoc diff)
+  where
+    toDoc = prettyContextDiff (text "Original") (text "Reformatted") text
+    diff = getContextDiff linesOfContext (lines a) (lines b)
+    linesOfContext = 1
 
+check :: Formatter -> FilePath -> Source IO CheckResult
+check formatter filepath =
+  enumeratePath filepath .| mapMC readSourceFile .|
+  mapC (checkFormatting formatter)
+
+readSourceFile :: FilePath -> IO HaskellSource
+readSourceFile filepath = HaskellSource filepath <$> readFile filepath
+
 checkFormatting :: Formatter -> HaskellSource -> CheckResult
-checkFormatting (Formatter format) source =
-  case format source of
-    Left error        -> InvalidCheckResult error
+checkFormatting (Formatter doFormat) source =
+  case doFormat source of
+    Left err          -> InvalidCheckResult source err
     Right reformatted -> CheckResult source reformatted
 
-fix :: Formatter -> FilePath -> IO ()
-fix = undefined
-
 defaultFormatter :: IO Formatter
 defaultFormatter = mconcat <$> (autoSettings >>= formatters)
 
 wasReformatted :: HaskellSource -> Reformatted -> Bool
 wasReformatted source reformatted =
-  not (null (suggestions reformatted)) ||
-  source /= reformattedSource reformatted
+  hasSuggestions reformatted || sourceChanged source reformatted
+
+sourceChanged :: HaskellSource -> Reformatted -> Bool
+sourceChanged source reformatted = source /= reformattedSource reformatted
+
+hasSuggestions :: Reformatted -> Bool
+hasSuggestions reformatted = not (null (suggestions reformatted))
diff --git a/src/Language/Haskell/Source/Enumerator.hs b/src/Language/Haskell/Source/Enumerator.hs
--- a/src/Language/Haskell/Source/Enumerator.hs
+++ b/src/Language/Haskell/Source/Enumerator.hs
@@ -1,72 +1,49 @@
 module Language.Haskell.Source.Enumerator
   ( enumeratePath
-  , HaskellSourceFilePath
   ) where
 
+import           Conduit
 import           Control.Applicative
 import           Control.Monad
 import           Data.List
 import           Distribution.PackageDescription
 import           Distribution.PackageDescription.Parse
 import qualified Distribution.Verbosity                as Verbosity
-import           Pipes
 import           System.Directory
 import           System.FilePath
 
-type HaskellSourceFilePath = FilePath
-
-enumeratePath :: FilePath -> Producer HaskellSourceFilePath IO ()
-enumeratePath = filePath
-
-filePath :: FilePath -> Producer HaskellSourceFilePath IO ()
-filePath path = do
-  isCabal <- lift $ isCabalFile path
-  if isCabal
-    then package' path
-    else directoryOrFile path
-
-simpleFilePath :: FilePath -> Producer HaskellSourceFilePath IO ()
-simpleFilePath path = do
-  isHaskellSource <- lift $ isHaskellSourceFile path
-  when isHaskellSource $ yield path
-
-simpleDirectory :: FilePath -> Producer HaskellSourceFilePath IO ()
-simpleDirectory path = do
-  contents <- lift $ getDirectoryContentsFullPaths path
-  mapM_ simpleFileOrDirectory contents
+enumeratePath :: FilePath -> Source IO FilePath
+enumeratePath path = enumPath path .| mapC normalise
 
-simpleFileOrDirectory :: FilePath -> Producer HaskellSourceFilePath IO ()
-simpleFileOrDirectory filepath = do
-  dir <- lift $ doesDirectoryExist filepath
-  if dir
-    then simpleDirectory filepath
-    else simpleFilePath filepath
+enumPath :: FilePath -> Source IO FilePath
+enumPath path = do
+  isDirectory <- lift $ doesDirectoryExist path
+  case isDirectory of
+    True -> enumDirectory path
+    False
+      | hasCabalExtension path -> enumPackage path
+    False
+      | hasHaskellExtension path -> yield path
+    False -> return ()
 
-package' :: FilePath -> Producer HaskellSourceFilePath IO ()
-package' path = readPackage path >>= expandPaths
+enumPackage :: FilePath -> Source IO FilePath
+enumPackage cabalFile = readPackage cabalFile >>= expandPaths
   where
     readPackage = lift . readPackageDescription Verbosity.silent
-    expandPaths = mapM_ (simpleFileOrDirectory . mkFull) . sourcePaths
-    dir = dropFileName path
-    mkFull = (dir </>)
+    expandPaths = mapM_ (enumPath . mkFull) . sourcePaths
+    packageDir = dropFileName cabalFile
+    mkFull = (packageDir </>)
 
-directory :: FilePath -> Producer HaskellSourceFilePath IO ()
-directory path = do
-  contents <- lift $ getDirectoryContentsFullPaths path
+enumDirectory :: FilePath -> Source IO FilePath
+enumDirectory path = do
+  contents <- lift $ getDirectoryContentFullPaths path
   cabalFiles <- lift $ filterM isCabalFile contents
   if null cabalFiles
-    then mapM_ directoryOrFile contents
-    else mapM_ package' cabalFiles
-
-directoryOrFile :: FilePath -> Producer HaskellSourceFilePath IO ()
-directoryOrFile path = do
-  isFile <- lift $ doesFileExist path
-  if isFile
-    then simpleFilePath path
-    else directory path
+    then mapM_ enumPath contents
+    else mapM_ enumPackage cabalFiles
 
-getDirectoryContentsFullPaths :: FilePath -> IO [FilePath]
-getDirectoryContentsFullPaths path =
+getDirectoryContentFullPaths :: FilePath -> IO [FilePath]
+getDirectoryContentFullPaths path =
   mkFull . notHidden . notMeta <$> getDirectoryContents path
   where
     mkFull = map (path </>)
@@ -74,17 +51,14 @@
     notMeta = (\\ [".", ".."])
 
 isCabalFile :: FilePath -> IO Bool
-isCabalFile path = (hasCabalExtension &&) <$> isFile
-  where
-    isFile = doesFileExist path
-    hasCabalExtension = ".cabal" `isSuffixOf` path
+isCabalFile path = return (hasCabalExtension path) <&&> doesFileExist path
 
-isHaskellSourceFile :: FilePath -> IO Bool
-isHaskellSourceFile path = (hasHaskellExtension &&) <$> isFile
-  where
-    isFile = doesFileExist path
-    hasHaskellExtension = ".hs" `isSuffixOf` path || ".lhs" `isSuffixOf` path
+hasCabalExtension :: FilePath -> Bool
+hasCabalExtension path = ".cabal" `isSuffixOf` path
 
+hasHaskellExtension :: FilePath -> Bool
+hasHaskellExtension path = ".hs" `isSuffixOf` path || ".lhs" `isSuffixOf` path
+
 sourcePaths :: GenericPackageDescription -> [FilePath]
 sourcePaths pkg = nub $ concatMap ($ pkg) pathExtractors
   where
@@ -97,3 +71,8 @@
       , concatMap (hsSourceDirs . benchmarkBuildInfo . condTreeData . snd) .
         condBenchmarks
       ]
+
+(<&&>) :: Applicative f => f Bool -> f Bool -> f Bool
+(<&&>) = liftA2 (&&)
+
+infixr 3 <&&> -- same as (&&)
diff --git a/src/Path/Find.hs b/src/Path/Find.hs
--- a/src/Path/Find.hs
+++ b/src/Path/Find.hs
@@ -6,14 +6,11 @@
   ( findFileUp
   ) where
 
-import Control.Exception      (evaluate)
-import Control.Monad
 import Control.Monad.Catch
 import Control.Monad.IO.Class
 import Data.List
 import Path
-import Path.IO                hiding (findFiles)
-import System.IO.Error        (isPermissionError)
+import Path.IO
 
 -- | Find the location of a file matching the given predicate.
 findFileUp ::
@@ -23,15 +20,6 @@
   -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory.
   -> m (Maybe (Path Abs File)) -- ^ Absolute file path.
 findFileUp = findPathUp snd
-
--- | Find the location of a directory matching the given predicate.
-findDirUp ::
-     (MonadIO m, MonadThrow m)
-  => Path Abs Dir -- ^ Start here.
-  -> (Path Abs Dir -> Bool) -- ^ Predicate to match the directory.
-  -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory.
-  -> m (Maybe (Path Abs Dir)) -- ^ Absolute directory path.
-findDirUp = findPathUp fst
 
 -- | Find the location of a path matching the given predicate.
 findPathUp ::
