diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@
 
 # Demo
 
-![Eval Demo](https://raw.githubusercontent.com/haskell/haskell-language-server/master/plugins/hls-eval-plugin/demo.gif)
+![Eval](demo.gif)
 
 # Test Structure
 
@@ -72,7 +72,7 @@
 -- 22
 ```
 
-Both plain Haskell and Literate Haskell (Bird-style only) source files are supported.
+Modules that use CPP and Literate Haskell (Bird-style only) modules are also supported (for GHC >= 8.8).
 
 # Test Components
 
diff --git a/demo.gif b/demo.gif
deleted file mode 100644
Binary files a/demo.gif and /dev/null differ
diff --git a/hls-eval-plugin.cabal b/hls-eval-plugin.cabal
--- a/hls-eval-plugin.cabal
+++ b/hls-eval-plugin.cabal
@@ -1,7 +1,10 @@
 cabal-version:      2.2
 name:               hls-eval-plugin
-version:            0.1.0.5
+version:            0.2.0.0
 synopsis:           Eval plugin for Haskell Language Server
+description:
+  Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>
+
 category:           Development
 bug-reports:        https://github.com/haskell/haskell-language-server/issues
 license:            Apache-2.0
@@ -14,8 +17,8 @@
 
 build-type:         Simple
 extra-source-files:
+  LICENSE
   README.md
-  demo.gif
 
 flag pedantic
   description: Enable -Werror
@@ -33,30 +36,32 @@
     Ide.Plugin.Eval.Code
     Ide.Plugin.Eval.CodeLens
     Ide.Plugin.Eval.GHC
+    Ide.Plugin.Eval.Parse.Comments
     Ide.Plugin.Eval.Parse.Option
-    Ide.Plugin.Eval.Parse.Parser
-    Ide.Plugin.Eval.Parse.Section
-    Ide.Plugin.Eval.Parse.Token
     Ide.Plugin.Eval.Types
     Ide.Plugin.Eval.Util
 
   build-depends:
     , aeson
-    , base                  >=4.12 && <5
+    , base                  >=4.12    && <5
     , containers
     , deepseq
     , Diff
     , directory
+    , dlist
     , extra
     , filepath
     , ghc
     , ghc-boot-th
     , ghc-paths
-    , ghcide
+    , ghcide                >=0.7.3.0
     , hashable
     , haskell-lsp
     , haskell-lsp-types
-    , hls-plugin-api
+    , hls-plugin-api        >=0.7
+    , lens
+    , megaparsec            >=0.9
+    , mtl
     , parser-combinators
     , pretty-simple
     , QuickCheck
diff --git a/src/Ide/Plugin/Eval/Code.hs b/src/Ide/Plugin/Eval/Code.hs
--- a/src/Ide/Plugin/Eval/Code.hs
+++ b/src/Ide/Plugin/Eval/Code.hs
@@ -15,20 +15,21 @@
 import Ide.Plugin.Eval.Types (
     Language (Plain),
     Loc,
-    Located (Located),
     Section (sectionLanguage),
-    Test (Example, Property, testOutput),
+    Test (..),
     Txt,
     locate,
-    locate0,
+    locate0, Located(..)
  )
 import InteractiveEval (runDecls)
 import Unsafe.Coerce (unsafeCoerce)
+import Control.Lens ((^.))
+import Language.Haskell.LSP.Types.Lens (start, line)
 
 -- | Return the ranges of the expression and result parts of the given test
-testRanges :: Loc Test -> (Range, Range)
-testRanges (Located line tst) =
-    let startLine = line
+testRanges :: Test -> (Range, Range)
+testRanges tst =
+    let startLine = testRange tst ^. start.line
         (exprLines, resultLines) = testLenghts tst
         resLine = startLine + exprLines
      in ( Range
@@ -44,7 +45,7 @@
 -}
 
 -- |The document range where the result of the test is defined
-resultRange :: Loc Test -> Range
+resultRange :: Test -> Range
 resultRange = snd . testRanges
 
 -- TODO: handle BLANKLINE
@@ -66,18 +67,18 @@
     | otherwise = showDiffs $ getDiff (map T.pack $ testOutput test) out
 
 testLenghts :: Test -> (Int, Int)
-testLenghts (Example e r) = (NE.length e, length r)
-testLenghts (Property _ r) = (1, length r)
+testLenghts (Example e r _) = (NE.length e, length r)
+testLenghts (Property _ r _) = (1, length r)
 
 -- |A one-line Haskell statement
 type Statement = Loc String
 
-asStatements :: Loc Test -> [Statement]
-asStatements lt = locate (asStmts <$> lt)
+asStatements :: Test -> [Statement]
+asStatements lt = locate $ Located (testRange lt ^. start.line) (asStmts lt)
 
 asStmts :: Test -> [Txt]
-asStmts (Example e _) = NE.toList e
-asStmts (Property t _) =
+asStmts (Example e _ _) = NE.toList e
+asStmts (Property t _ _) =
     ["prop11 = " ++ t, "(propEvaluation prop11 :: IO String)"]
 
 -- |Evaluate an expression (either a pure expression or an IO a)
diff --git a/src/Ide/Plugin/Eval/CodeLens.hs b/src/Ide/Plugin/Eval/CodeLens.hs
--- a/src/Ide/Plugin/Eval/CodeLens.hs
+++ b/src/Ide/Plugin/Eval/CodeLens.hs
@@ -1,10 +1,10 @@
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE DuplicateRecordFields #-}
 {-# LANGUAGE ExtendedDefaultRules #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE ImplicitParams #-}
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes #-}
@@ -26,257 +26,255 @@
 import Control.Applicative (Alternative ((<|>)))
 import Control.Arrow (second)
 import qualified Control.Exception as E
-import Control.Monad (
-    void,
-    when,
- )
+import Control.Monad
+    ( void,
+      when,
+    )
 import Control.Monad.IO.Class (MonadIO (liftIO))
-import Control.Monad.Trans.Except (
-    ExceptT (..),
-    runExceptT,
- )
-import Data.Aeson (
-    FromJSON,
-    ToJSON,
-    toJSON,
- )
+import Control.Monad.Trans.Except
+    ( ExceptT (..),
+    )
+import Data.Aeson
+    ( FromJSON,
+      ToJSON,
+      toJSON,
+    )
 import Data.Char (isSpace)
 import Data.Either (isRight)
-import qualified Data.HashMap.Strict as Map
-import Data.List (
-    dropWhileEnd,
-    find,
- )
-import Data.Maybe (
-    catMaybes,
-    fromMaybe,
- )
+import qualified Data.HashMap.Strict as HashMap
+import Data.List
+    (dropWhileEnd,
+      find
+    )
+import qualified Data.Map.Strict as Map
+import Data.Maybe
+    ( catMaybes,
+      fromMaybe,
+    )
 import Data.String (IsString)
 import Data.Text (Text)
 import qualified Data.Text as T
-import Data.Text.Encoding (decodeUtf8)
 import Data.Time (getCurrentTime)
 import Data.Typeable (Typeable)
-import Development.IDE (
-    GetModSummary (..),
-    GhcSession (..),
-    HscEnvEq (envImportPaths, hscEnv),
-    IdeState,
-    List (List),
-    NormalizedFilePath,
-    Range (Range),
-    Uri,
-    evalGhcEnv,
-    hscEnvWithImportPaths,
-    runAction,
-    stringBufferToByteString,
-    textToStringBuffer,
-    toNormalizedFilePath',
-    toNormalizedUri,
-    uriToFilePath',
-    use_,
- )
-import Development.IDE.Core.Preprocessor (
-    preprocessor,
- )
-import Development.IDE.GHC.Compat (HscEnv)
+import Development.IDE
+    (realSrcSpanToRange,  GetModSummary (..),
+      GetParsedModuleWithComments (..),
+      GhcSession (..),
+      HscEnvEq (envImportPaths),
+      IdeState,
+      List (List),
+      NormalizedFilePath,
+      Range (Range),
+      Uri,
+      evalGhcEnv,
+      fromNormalizedFilePath,
+      hscEnvWithImportPaths,
+      runAction,
+      textToStringBuffer,
+      toNormalizedFilePath',
+      toNormalizedUri,
+      uriToFilePath',
+      useWithStale_,
+      use_,
+    )
+import Development.IDE.GHC.Compat (AnnotationComment(AnnBlockComment, AnnLineComment), GenLocated (L), HscEnv, ParsedModule (..), SrcSpan (RealSrcSpan), srcSpanFile)
 import DynamicLoading (initializePlugins)
-import GHC (
-    ExecOptions (
-        execLineNumber,
-        execSourceFile
-    ),
-    ExecResult (..),
-    GeneralFlag (..),
-    Ghc,
-    GhcLink (LinkInMemory),
-    GhcMode (CompManager),
-    GhcMonad (getSession),
-    HscTarget (HscInterpreted),
-    LoadHowMuch (LoadAllTargets),
-    ModSummary (ms_hspp_opts),
-    Module (moduleName),
-    SuccessFlag (Failed, Succeeded),
-    TcRnExprMode (..),
-    execOptions,
-    execStmt,
-    exprType,
-    getInteractiveDynFlags,
-    getSessionDynFlags,
-    isImport,
-    isStmt,
-    load,
-    runDecls,
-    setContext,
-    setInteractiveDynFlags,
-    setLogAction,
-    setSessionDynFlags,
-    setTargets,
-    typeKind,
- )
+import FastString (unpackFS)
+import GHC
+    (ExecOptions
+        ( execLineNumber,
+          execSourceFile
+        ),
+      ExecResult (..),
+      GeneralFlag (..),
+      Ghc,
+      GhcLink (LinkInMemory),
+      GhcMode (CompManager),
+      GhcMonad (getSession),
+      HscTarget (HscInterpreted),
+      LoadHowMuch (LoadAllTargets),
+      ModSummary (ms_hspp_opts),
+      Module (moduleName),
+      SuccessFlag (Failed, Succeeded),
+      TcRnExprMode (..),
+      execOptions,
+      execStmt,
+      exprType,
+      getInteractiveDynFlags,
+      getSessionDynFlags,
+      isImport,
+      isStmt,
+      load,
+      runDecls,
+      setContext,
+      setInteractiveDynFlags,
+      setLogAction,
+      setSessionDynFlags,
+      setTargets,
+      typeKind,
+    )
 import GHC.Generics (Generic)
 import qualified GHC.LanguageExtensions.Type as LangExt
-import GhcPlugins (
-    DynFlags (..),
-    defaultLogActionHPutStrDoc,
-    gopt_set,
-    gopt_unset,
-    interpWays,
-    targetPlatform,
-    updateWays,
-    wayGeneralFlags,
-    wayUnsetGeneralFlags,
-    xopt_set,
- )
-import HscTypes (
-    InteractiveImport (IIModule),
-    ModSummary (ms_mod),
-    Target (Target),
-    TargetId (TargetFile),
- )
-import Ide.Plugin.Eval.Code (
-    Statement,
-    asStatements,
-    evalExpr,
-    evalExtensions,
-    evalSetup,
-    propSetup,
-    resultRange,
-    testCheck,
-    testRanges,
- )
-import Ide.Plugin.Eval.GHC (
-    addExtension,
-    addImport,
-    addPackages,
-    hasPackage,
-    isExpr,
-    showDynFlags,
- )
+import GhcPlugins
+    ( DynFlags (..),
+      defaultLogActionHPutStrDoc,
+      gopt_set,
+      gopt_unset,
+      interpWays,
+      targetPlatform,
+      updateWays,
+      wayGeneralFlags,
+      wayUnsetGeneralFlags,
+      xopt_set,
+    )
+import HscTypes
+    ( InteractiveImport (IIModule),
+      ModSummary (ms_mod),
+      Target (Target),
+      TargetId (TargetFile),
+    )
+import Ide.Plugin.Eval.Code
+    ( Statement,
+      asStatements,
+      evalExpr,
+      evalExtensions,
+      evalSetup,
+      propSetup,
+      resultRange,
+      testCheck,
+      testRanges,
+    )
+import Ide.Plugin.Eval.GHC
+    ( addExtension,
+      addImport,
+      addPackages,
+      hasPackage,
+      isExpr,
+      showDynFlags,
+    )
+import Ide.Plugin.Eval.Parse.Comments (commentsToSections)
 import Ide.Plugin.Eval.Parse.Option (langOptions)
-import Ide.Plugin.Eval.Parse.Section (
-    Section (
-        sectionFormat,
-        sectionTests
-    ),
-    allSections,
- )
-import Ide.Plugin.Eval.Parse.Token (tokensFrom)
-import Ide.Plugin.Eval.Types (
-    Format (SingleLine),
-    Loc,
-    Located (Located),
-    Test,
-    hasTests,
-    isProperty,
-    splitSections,
-    unLoc,
- )
-import Ide.Plugin.Eval.Util (
-    asS,
-    gStrictTry,
-    handleMaybe,
-    handleMaybeM,
-    isLiterate,
-    logWith,
-    response,
-    response',
-    timed,
- )
+import Ide.Plugin.Eval.Types
+import Ide.Plugin.Eval.Util
+    ( asS,
+      gStrictTry,
+      handleMaybe,
+      handleMaybeM,
+      isLiterate,
+      logWith,
+      response,
+      response',
+      timed,
+    )
 import Ide.PluginUtils (mkLspCommand)
-import Ide.Types (
-    CodeLensProvider,
-    CommandFunction,
-    CommandId,
-    PluginCommand (PluginCommand),
- )
-import Language.Haskell.LSP.Core (
-    LspFuncs (
-        getVirtualFileFunc,
-        withIndefiniteProgress
-    ),
-    ProgressCancellable (
-        Cancellable
-    ),
- )
-import Language.Haskell.LSP.Types (
-    ApplyWorkspaceEditParams (
-        ApplyWorkspaceEditParams
-    ),
-    CodeLens (CodeLens),
-    CodeLensParams (
-        CodeLensParams,
-        _textDocument
-    ),
-    Command (_arguments, _title),
-    ServerMethod (
-        WorkspaceApplyEdit
-    ),
-    TextDocumentIdentifier (..),
-    TextEdit (TextEdit),
-    WorkspaceEdit (WorkspaceEdit),
- )
+import Ide.Types
+    ( CodeLensProvider,
+      CommandFunction,
+      CommandId,
+      PluginCommand (PluginCommand),
+    )
+import Language.Haskell.LSP.Core
+    ( LspFuncs
+        ( getVirtualFileFunc,
+          withIndefiniteProgress
+        ),
+      ProgressCancellable
+        ( Cancellable
+        ),
+    )
+import Language.Haskell.LSP.Types
+    ( ApplyWorkspaceEditParams
+        ( ApplyWorkspaceEditParams
+        ),
+      CodeLens (CodeLens),
+      CodeLensParams
+        ( CodeLensParams,
+          _textDocument
+        ),
+      Command (_arguments, _title),
+      Position (..),
+      ServerMethod
+        ( WorkspaceApplyEdit
+        ),
+      TextDocumentIdentifier (..),
+      TextEdit (TextEdit),
+      WorkspaceEdit (WorkspaceEdit),
+    )
 import Language.Haskell.LSP.VFS (virtualFileText)
-import Outputable (
-    nest,
-    ppr,
-    showSDoc,
-    text,
-    ($$),
-    (<+>),
- )
+import Outputable
+    ( nest,
+      ppr,
+      showSDoc,
+      text,
+      ($$),
+      (<+>),
+    )
 import System.FilePath (takeFileName)
 import System.IO (hClose)
 import System.IO.Temp (withSystemTempFile)
 import Text.Read (readMaybe)
 import Util (OverridingBool (Never))
+import Development.IDE.Core.PositionMapping (toCurrentRange)
+import qualified Data.DList as DL
+import Control.Lens ((^.))
+import Language.Haskell.LSP.Types.Lens (line, end)
 
 {- | Code Lens provider
  NOTE: Invoked every time the document is modified, not just when the document is saved.
 -}
 codeLens :: CodeLensProvider IdeState
-codeLens lsp st plId CodeLensParams{_textDocument} =
+codeLens _lsp st plId CodeLensParams{_textDocument} =
     let dbg = logWith st
         perf = timed dbg
      in perf "codeLens" $
             response $ do
                 let TextDocumentIdentifier uri = _textDocument
                 fp <- handleMaybe "uri" $ uriToFilePath' uri
+                let nfp = toNormalizedFilePath' fp
+                    isLHS = isLiterate fp
                 dbg "fp" fp
-                mdlText <- moduleText lsp uri
-
-                {- Normalise CPP/LHS files/custom preprocessed files.
-                   Used to extract tests correctly from CPP and LHS (Bird-style).
-                -}
-                session :: HscEnvEq <-
-                    runGetSession st $ toNormalizedFilePath' fp
+                (ParsedModule{..}, posMap) <- liftIO $
+                    runAction "parsed" st $ useWithStale_ GetParsedModuleWithComments nfp
+                let comments = foldMap
+                        ( foldMap $ \case
+                            L (RealSrcSpan real) bdy
+                                | unpackFS (srcSpanFile real) ==
+                                    fromNormalizedFilePath nfp
+                                , let ran0 = realSrcSpanToRange real
+                                , Just curRan <- toCurrentRange posMap ran0
+                                ->
 
-                Right (ppContent, _dflags) <-
-                    perf "preprocessor" $
-                        liftIO $
-                            runExceptT $
-                                preprocessor (hscEnv session) fp (Just $ textToStringBuffer mdlText)
-                let text =
-                        cleanSource (isLiterate fp) . decodeUtf8 $
-                            stringBufferToByteString
-                                ppContent
-                -- dbg "PREPROCESSED CONTENT" text
+                                    -- since Haddock parsing is unset explicitly in 'getParsedModuleWithComments',
+                                    -- we can concentrate on these two
+                                    case bdy of
+                                        AnnLineComment cmt ->
+                                            mempty { lineComments = Map.singleton curRan (RawLineComment cmt) }
+                                        AnnBlockComment cmt ->
+                                            mempty { blockComments = Map.singleton curRan $ RawBlockComment cmt }
+                                        _ -> mempty
+                            _ -> mempty
+                        )
+                        $ snd pm_annotations
+                dbg "excluded comments" $ show $  DL.toList $
+                    foldMap
+                    (foldMap $ \(L a b) ->
+                        case b of
+                            AnnLineComment{} -> mempty
+                            AnnBlockComment{} -> mempty
+                            _ -> DL.singleton (a, b)
+                    )
+                    $ snd pm_annotations
+                dbg "comments" $ show comments
 
                 -- Extract tests from source code
-                let Right (setups, nonSetups) =
-                        (splitSections . filter hasTests <$>)
-                            . allSections
-                            . tokensFrom
-                            . T.unpack
-                            $ text
-                let tests = testsBySection nonSetups
-
+                let Sections{..} = commentsToSections isLHS comments
+                    tests = testsBySection nonSetupSections
                 cmd <- liftIO $ mkLspCommand plId evalCommandName "Evaluate=..." (Just [])
                 let lenses =
                         [ CodeLens testRange (Just cmd') Nothing
                         | (section, test) <- tests
                         , let (testRange, resultRange) = testRanges test
-                              args = EvalParams (setups ++ [section]) _textDocument
+                              args = EvalParams (setupSections ++ [section]) _textDocument
                               cmd' =
                                 (cmd :: Command)
                                     { _arguments = Just (List [toJSON args])
@@ -292,9 +290,9 @@
                         unwords
                             [ show (length tests)
                             , "tests in"
-                            , show (length nonSetups)
+                            , show (length nonSetupSections)
                             , "sections"
-                            , show (length setups)
+                            , show (length setupSections)
                             , "setups"
                             , show (length lenses)
                             , "lenses."
@@ -310,7 +308,7 @@
 evalCommand :: PluginCommand IdeState
 evalCommand = PluginCommand evalCommandName "evaluate" runEvalCmd
 
--- |Specify the test section to execute
+-- | Specify the test section to execute
 data EvalParams = EvalParams
     { sections :: [Section]
     , module_ :: !TextDocumentIdentifier
@@ -415,7 +413,7 @@
                                 (st, fp)
                                 tests
 
-            let workspaceEditsMap = Map.fromList [(_uri, List edits)]
+            let workspaceEditsMap = HashMap.fromList [(_uri, List $ addFinalReturn mdlText edits)]
             let workspaceEdits = WorkspaceEdit (Just workspaceEditsMap) Nothing
 
             return (WorkspaceApplyEdit, ApplyWorkspaceEditParams workspaceEdits)
@@ -423,6 +421,20 @@
             withIndefiniteProgress lsp "Evaluating" Cancellable $
                 response' cmd
 
+addFinalReturn :: Text -> [TextEdit] -> [TextEdit]
+addFinalReturn mdlText edits
+    | not (null edits) && not (T.null mdlText) && T.last mdlText /= '\n' =
+        finalReturn mdlText : edits
+    | otherwise = edits
+
+finalReturn :: Text -> TextEdit
+finalReturn txt =
+    let ls = T.lines txt
+        l = length ls -1
+        c = T.length . last $ ls
+        p = Position l c
+     in TextEdit (Range p p) "\n"
+
 moduleText :: (IsString e, MonadIO m) => LspFuncs c -> Uri -> ExceptT e m Text
 moduleText lsp uri =
     handleMaybeM "mdlText" $
@@ -432,13 +444,13 @@
                     lsp
                     (toNormalizedUri uri)
 
-testsBySection :: [Section] -> [(Section, Loc Test)]
+testsBySection :: [Section] -> [(Section, Test)]
 testsBySection sections =
     [(section, test) | section <- sections, test <- sectionTests section]
 
 type TEnv = (IdeState, String)
 
-runTests :: TEnv -> [(Section, Loc Test)] -> Ghc [TextEdit]
+runTests :: TEnv -> [(Section, Test)] -> Ghc [TextEdit]
 runTests e@(_st, _) tests = do
     df <- getInteractiveDynFlags
     evalSetup
@@ -446,7 +458,7 @@
 
     mapM (processTest e df) tests
   where
-    processTest :: TEnv -> DynFlags -> (Section, Loc Test) -> Ghc TextEdit
+    processTest :: TEnv -> DynFlags -> (Section, Test) -> Ghc TextEdit
     processTest e@(st, fp) df (section, test) = do
         let dbg = logWith st
         let pad = pad_ $ (if isLiterate fp then ("> " `T.append`) else id) $ padPrefix (sectionFormat section)
@@ -454,20 +466,34 @@
         rs <- runTest e df test
         dbg "TEST RESULTS" rs
 
-        let checkedResult = testCheck (section, unLoc test) rs
+        let checkedResult = testCheck (section, test) rs
 
-        let edit = TextEdit (resultRange test) (T.unlines . map pad $ checkedResult)
+        let edit = asEdit (sectionFormat section) test (map pad checkedResult)
         dbg "TEST EDIT" edit
         return edit
 
     -- runTest :: String -> DynFlags -> Loc Test -> Ghc [Text]
     runTest _ df test
-        | not (hasQuickCheck df) && (isProperty . unLoc $ test) =
+        | not (hasQuickCheck df) && isProperty test =
             return $
                 singleLine
                     "Add QuickCheck to your cabal dependencies to run this test."
     runTest e df test = evals e df (asStatements test)
 
+asEdit :: Format -> Test -> [Text] -> TextEdit
+asEdit (MultiLine commRange) test resultLines
+    -- A test in a block comment, ending with @-\}@ without newline in-between.
+    | testRange test ^. end.line == commRange ^. end . line
+    =
+    TextEdit
+        (Range
+            (testRange test ^. end)
+            (resultRange test ^. end)
+        )
+        ("\n" <> T.unlines (resultLines <> ["-}"]))
+asEdit _ test resultLines =
+    TextEdit (resultRange test) (T.unlines resultLines)
+
 {-
 The result of evaluating a test line can be:
 * a value
@@ -506,7 +532,7 @@
 Unknown extension: "NonExistent"
 
 >>> cls C
-Variable not in scope: cls :: t0 -> f0
+Variable not in scope: cls :: t0 -> ()
 Data constructor not in scope: C
 
 >>> "A
@@ -599,8 +625,8 @@
                 -- GhcSessionDeps
                 nfp
 
-needsQuickCheck :: [(Section, Loc Test)] -> Bool
-needsQuickCheck = any (isProperty . unLoc . snd)
+needsQuickCheck :: [(Section, Test)] -> Bool
+needsQuickCheck = any (isProperty . snd)
 
 hasQuickCheck :: DynFlags -> Bool
 hasQuickCheck df = hasPackage df "QuickCheck"
@@ -652,27 +678,6 @@
 padPrefix :: IsString p => Format -> p
 padPrefix SingleLine = "-- "
 padPrefix _ = ""
-
-{-
-Normalise preprocessed source code (from a CPP/LHS or other processed file) so that tests are on the same lines as in the original source.
-
->>> cleanSource True $ T.pack "#line 1 \nA comment\n> module X where"
-"comment\nmodule X where\n"
-
->>> cleanSource False $ T.pack "#1  \nmodule X where"
-"module X where\n"
--}
-cleanSource :: Bool -> Text -> Text
-cleanSource isLit =
-    T.unlines
-        . reverse
-        . (if isLit then map cleanBirdCode else id)
-        . takeWhile (\t -> T.null t || (T.head t /= '#'))
-        . reverse
-        . T.lines
-
-cleanBirdCode :: Text -> Text
-cleanBirdCode = T.drop 2
 
 {- | Resulting @Text@ MUST NOT prefix each line with @--@
    Such comment-related post-process will be taken place
diff --git a/src/Ide/Plugin/Eval/Parse/Comments.hs b/src/Ide/Plugin/Eval/Parse/Comments.hs
new file mode 100644
--- /dev/null
+++ b/src/Ide/Plugin/Eval/Parse/Comments.hs
@@ -0,0 +1,575 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+
+module Ide.Plugin.Eval.Parse.Comments where
+
+import qualified Control.Applicative.Combinators.NonEmpty as NE
+import Control.Arrow (first, (&&&), (>>>))
+import Control.Lens (lensField, lensRules, view, (.~), (^.))
+import Control.Lens.Extras (is)
+import Control.Lens.TH (makeLensesWith, makePrisms, mappingNamer)
+import Control.Monad (guard, void, when)
+import Control.Monad.Combinators ()
+import Control.Monad.Reader (ask)
+import Control.Monad.Trans.Reader (Reader, runReader)
+import qualified Data.Char as C
+import qualified Data.DList as DL
+import qualified Data.Foldable as F
+import Data.Function ((&))
+import Data.Functor.Identity
+import Data.List.NonEmpty (NonEmpty ((:|)))
+import qualified Data.List.NonEmpty as NE
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+import Data.Void (Void)
+import Development.IDE (Position, Range (Range))
+import Development.IDE.Types.Location (Position (..))
+import GHC.Generics
+import Ide.Plugin.Eval.Types
+import Language.Haskell.LSP.Types.Lens
+    ( character,
+      end,
+      line,
+      start,
+    )
+import Text.Megaparsec
+import qualified Text.Megaparsec as P
+import Text.Megaparsec.Char
+    ( alphaNumChar,
+      char,
+      eol,
+      hspace,
+      letterChar,
+    )
+import Data.Functor ((<&>))
+import qualified Data.Text as T
+
+{-
+We build parsers combining the following three kinds of them:
+
+    *   Line parser - paring a single line into an input,
+        works both for line- and block-comments.
+        A line should be a proper content of lines contained in comment:
+        doesn't include starting @--@ and @{\-@ and no ending @-\}@
+
+    *   Line comment group parser: parses a contiguous group of
+        tuples of position and line comment into sections of line comments.
+        Each input MUST start with @--@.
+
+    *   Block comment parser: Parsing entire block comment into sections.
+        Input must be surrounded by @{\-@ and @-\}@.
+-}
+
+-- | Line parser
+type LineParser a = forall m. Monad m => ParsecT Void String m a
+
+-- | Line comment group parser
+type LineGroupParser = Parsec Void [(Range, RawLineComment)]
+
+data BlockEnv = BlockEnv
+    { isLhs :: Bool
+    , blockRange :: Range
+    }
+    deriving (Read, Show, Eq, Ord)
+
+makeLensesWith
+    (lensRules & lensField .~ mappingNamer (pure . (++ "L")))
+    ''BlockEnv
+
+-- | Block comment parser
+type BlockCommentParser = ParsecT Void String (Reader BlockEnv)
+
+-- | Prop line, with "prop>" stripped off
+newtype PropLine = PropLine {getPropLine :: String}
+    deriving (Show)
+
+-- | Example line, with @>>>@ stripped off
+newtype ExampleLine = ExampleLine {getExampleLine :: String}
+    deriving (Show)
+
+data TestComment
+    = AProp
+        { testCommentRange :: Range
+        , lineProp :: PropLine
+        , propResults :: [String]
+        }
+    | AnExample
+        { testCommentRange :: Range
+        , lineExamples :: NonEmpty ExampleLine
+        , exampleResults :: [String]
+        }
+    deriving (Show)
+
+-- | Classification of comments
+data CommentFlavour = Vanilla | HaddockNext | HaddockPrev | Named String
+    deriving (Read, Show, Eq, Ord)
+
+-- | Single line or block comments?
+data CommentStyle = Line | Block Range
+    deriving (Read, Show, Eq, Ord, Generic)
+
+makePrisms ''CommentStyle
+
+commentsToSections ::
+    -- | True if it is literate Haskell
+    Bool ->
+    Comments ->
+    Sections
+commentsToSections isLHS Comments {..} =
+    let (lineSectionSeeds, lineSetupSeeds) =
+            foldMap
+                ( \lcs ->
+                    let theRan =
+                            Range
+                                (view start $ fst $ NE.head lcs)
+                                (view end $ fst $ NE.last lcs)
+                     in case parseMaybe lineGroupP $ NE.toList lcs of
+                            Nothing -> mempty
+                            Just (mls, rs) ->
+                                ( maybe mempty (uncurry Map.singleton) ((theRan,) <$> mls)
+                                , -- orders setup sections in ascending order
+                                  if null rs
+                                    then mempty
+                                    else
+                                        Map.singleton theRan $
+                                            DL.singleton (Line, rs)
+                                )
+                )
+                $ groupLineComments $
+                    Map.filterWithKey
+                        -- FIXME:
+                        -- To comply with the initial behaviour of
+                        -- Extended Eval Plugin;
+                        -- but it also rejects modules with
+                        -- non-zero base indentation level!
+                        ( \pos _ ->
+                            if isLHS
+                                then pos ^. start . character == 2
+                                else pos ^. start . character == 0
+                        )
+                        lineComments
+        (blockSeed, blockSetupSeeds) =
+            foldMap
+                ( \(ran, lcs) ->
+                    case parseBlockMaybe isLHS ran blockCommentBP $
+                        getRawBlockComment lcs of
+                        Nothing -> mempty
+                        Just (Named "setup", grp) ->
+                            -- orders setup sections in ascending order
+                            ( mempty
+                            , Map.singleton ran $
+                                DL.singleton (Block ran, grp)
+                            )
+                        Just grp ->
+                            ( Map.singleton ran grp
+                            , mempty
+                            )
+                )
+                -- It seems Extended Eval Plugin doesn't constraint
+                -- starting indentation level for block comments.
+                -- Rather, it constrains the indentation level /inside/
+                -- block comment body.
+                $ Map.toList blockComments
+        lineSections =
+            lineSectionSeeds <&> uncurry (testsToSection Line)
+        multilineSections =
+            Map.mapWithKey
+                (uncurry . testsToSection . Block)
+                blockSeed
+        setupSections =
+            -- Setups doesn't need Dummy position
+            map
+                ( \(style, tests) ->
+                    testsToSection
+                        style
+                        (Named "setup")
+                        tests
+                )
+                $ DL.toList $
+                    F.fold $
+                        Map.unionWith (<>) lineSetupSeeds blockSetupSeeds
+        nonSetupSections = F.toList $ lineSections `Map.union` multilineSections
+     in Sections {..}
+
+parseBlockMaybe :: Bool -> Range -> BlockCommentParser a -> String -> Maybe a
+parseBlockMaybe isLhs blockRange p i =
+    case runReader (runParserT p' "" i) BlockEnv {..} of
+        Left {} -> Nothing
+        Right a -> Just a
+    where
+        p' = do
+            updateParserState $ \st ->
+                st
+                    { statePosState =
+                        (statePosState st)
+                            { pstateSourcePos = positionToSourcePos $ blockRange ^. start
+                            }
+                    }
+            p
+
+type CommentRange = Range
+
+type SectionRange = Range
+
+testsToSection ::
+    CommentStyle ->
+    CommentFlavour ->
+    [TestComment] ->
+    Section
+testsToSection style flav tests =
+    let sectionName
+            | Named name <- flav = name
+            | otherwise = ""
+        sectionLanguage = case flav of
+            HaddockNext -> Haddock
+            HaddockPrev -> Haddock
+            _ -> Plain
+        sectionTests = map fromTestComment tests
+        sectionFormat =
+            case style of
+                Line -> SingleLine
+                Block ran -> MultiLine ran
+     in Section {..}
+
+fromTestComment :: TestComment -> Test
+fromTestComment AProp {..} =
+    Property
+        { testline = getPropLine lineProp
+        , testOutput = propResults
+        , testRange = testCommentRange
+        }
+fromTestComment AnExample {..} =
+    Example
+        { testLines = getExampleLine <$> lineExamples
+        , testOutput = exampleResults
+        , testRange = testCommentRange
+        }
+
+-- * Block comment parser
+
+{- $setup
+>>> dummyPos = Position 0 0
+>>> parseE p = either (error . errorBundlePretty) id . parse p ""
+-}
+
+-- >>> parseE (blockCommentBP True dummyPos) "{- |\n  >>> 5+5\n  11\n  -}"
+-- (HaddockNext,[AnExample {testCommentRange = Position {_line = 1, _character = 0}, lineExamples = ExampleLine {getExampleLine = " 5+5"} :| [], exampleResults = ["  11"]}])
+
+blockCommentBP ::
+    -- | True if Literate Haskell
+    BlockCommentParser (CommentFlavour, [TestComment])
+blockCommentBP = do
+    skipCount 2 anySingle -- "{-"
+    void $ optional $ char ' '
+    flav <- commentFlavourP
+    hit <- skipNormalCommentBlock
+    if hit
+        then do
+            body <-
+                many $
+                    (blockExamples <|> blockProp)
+                        <* skipNormalCommentBlock
+            void takeRest -- just consume the rest
+            pure (flav, body)
+        else pure (flav, [])
+
+skipNormalCommentBlock :: BlockCommentParser Bool
+skipNormalCommentBlock = do
+    BlockEnv {..} <- ask
+    skipManyTill (normalLineP isLhs $ Block blockRange) $
+        False <$ try (optional (chunk "-}") *> eof)
+            <|> True <$ lookAhead (try $ testSymbol isLhs $ Block blockRange)
+
+testSymbol :: Bool -> CommentStyle -> LineParser ()
+testSymbol isLHS style =
+    -- FIXME: To comply with existing Extended Eval Plugin Behaviour;
+    -- it must skip one space after a comment!
+    -- This prevents Eval Plugin from working on
+    -- modules with non-standard base indentation-level.
+    when (isLHS && is _Block style) (void $ count' 0 2 $ char ' ')
+        *> (exampleSymbol <|> propSymbol)
+
+eob :: LineParser ()
+eob = eof <|> try (optional (chunk "-}") *> eof) <|> void eol
+
+blockExamples
+    , blockProp ::
+        BlockCommentParser TestComment
+blockExamples = do
+    BlockEnv {..} <- ask
+    (ran, examples) <- withRange $ NE.some $ exampleLineStrP isLhs $ Block blockRange
+    AnExample ran examples <$> resultBlockP
+blockProp = do
+    BlockEnv {..} <- ask
+    (ran, Identity prop) <- withRange $ fmap Identity $ propLineStrP isLhs $ Block blockRange
+    AProp ran prop <$> resultBlockP
+
+withRange ::
+    (TraversableStream s, Stream s, Monad m, Ord v, Traversable t) =>
+    ParsecT v s m (t (a, Position)) ->
+    ParsecT v s m (Range, t a)
+withRange p = do
+    beg <- sourcePosToPosition <$> getSourcePos
+    as <- p
+    let fin
+            | null as = beg
+            | otherwise = snd $ last $ F.toList as
+    pure (Range beg fin, fst <$> as)
+
+resultBlockP :: BlockCommentParser [String]
+resultBlockP = do
+    BlockEnv {..} <- ask
+    many $
+        fmap fst . nonEmptyNormalLineP isLhs $
+            Block blockRange
+
+positionToSourcePos :: Position -> SourcePos
+positionToSourcePos pos =
+    P.SourcePos
+        { sourceName = "<block comment>"
+        , sourceLine = P.mkPos $ 1 + pos ^. line
+        , sourceColumn = P.mkPos $ 1 + pos ^. character
+        }
+
+sourcePosToPosition :: SourcePos -> Position
+sourcePosToPosition SourcePos {..} =
+    Position (unPos sourceLine - 1) (unPos sourceColumn - 1)
+
+-- * Line Group Parser
+
+{- |
+Result: a tuple of ordinary line tests and setting sections.
+
+TODO: Haddock comment can adjacent to vanilla comment:
+
+    @
+        -- Vanilla comment
+        -- Another vanilla
+        -- | This parses as Haddock comment as GHC
+    @
+
+This behaviour is not yet handled correctly in Eval Plugin;
+but for future extension for this, we use a tuple here instead of 'Either'.
+-}
+lineGroupP ::
+    LineGroupParser
+        (Maybe (CommentFlavour, [TestComment]), [TestComment])
+lineGroupP = do
+    (_, flav) <- lookAhead $ parseLine (commentFlavourP <* takeRest)
+    case flav of
+        Named "setup" -> (Nothing,) <$> lineCommentSectionsP
+        flav -> (,mempty) . Just . (flav,) <$> lineCommentSectionsP
+
+-- >>>  parse (lineGroupP <*eof) "" $ (dummyPosition, ) . RawLineComment <$> ["-- a", "-- b"]
+-- Variable not in scope: dummyPosition :: Position
+
+commentFlavourP :: LineParser CommentFlavour
+commentFlavourP =
+    P.option
+        Vanilla
+        ( HaddockNext <$ char '|'
+            <|> HaddockPrev <$ char '^'
+            <|> Named <$ char '$'
+                <* optional hspace
+                <*> ((:) <$> letterChar <*> P.many alphaNumChar)
+        )
+        <* optional (char ' ')
+
+lineCommentHeadP :: LineParser ()
+lineCommentHeadP = do
+    -- and no operator symbol character follows.
+    void $ chunk "--"
+    skipMany $ char '-'
+    void $ optional $ char ' '
+
+lineCommentSectionsP ::
+    LineGroupParser [TestComment]
+lineCommentSectionsP = do
+    skipMany normalLineCommentP
+    many $
+        exampleLinesGP
+            <|> uncurry AProp <$> propLineGP <*> resultLinesP
+                <* skipMany normalLineCommentP
+
+lexemeLine :: LineGroupParser a -> LineGroupParser a
+lexemeLine p = p <* skipMany normalLineCommentP
+
+resultLinesP :: LineGroupParser [String]
+resultLinesP = many nonEmptyLGP
+
+normalLineCommentP :: LineGroupParser (Range, String)
+normalLineCommentP =
+    parseLine (fst <$ commentFlavourP <*> normalLineP False Line)
+
+nonEmptyLGP :: LineGroupParser String
+nonEmptyLGP =
+    try $
+        fmap snd $
+            parseLine $
+                fst <$ commentFlavourP <*> nonEmptyNormalLineP False Line
+
+exampleLinesGP :: LineGroupParser TestComment
+exampleLinesGP =
+    lexemeLine $
+        uncurry AnExample . first convexHullRange . NE.unzip
+            <$> NE.some exampleLineGP
+            <*> resultLinesP
+
+convexHullRange :: NonEmpty Range -> Range
+convexHullRange nes =
+    Range (NE.head nes ^. start) (NE.last nes ^. end)
+
+exampleLineGP :: LineGroupParser (Range, ExampleLine)
+exampleLineGP =
+    -- In line-comments, indentation-level inside comment doesn't matter.
+    parseLine (fst <$ commentFlavourP <*> exampleLineStrP False Line)
+
+propLineGP :: LineGroupParser (Range, PropLine)
+propLineGP =
+    -- In line-comments, indentation-level inside comment doesn't matter.
+    parseLine (fst <$ commentFlavourP <*> propLineStrP False Line)
+
+{- |
+Turning a line parser into line group parser consuming a single line comment.
+Parses a sinlge line comment, skipping prefix "--[-*]" with optional one horizontal space.
+fails if the input does not start with "--".
+
+__N.B.__ We don't strip comment flavours.
+
+>>> pck = (:[]).(:[]) . RawLineComment
+
+>>> parseMaybe (parseLine $ takeRest) $ pck "-- >>> A"
+Just [">>> A"]
+
+>>> parseMaybe (parseLine $ takeRest) $ pck "---  >>> A"
+Just [" >>> A"]
+
+>>> parseMaybe (parseLine takeRest) $ pck ""
+Nothing
+-}
+parseLine ::
+    (Ord (f RawLineComment), Traversable f) =>
+    LineParser a ->
+    Parsec Void [f RawLineComment] (f a)
+parseLine p =
+    P.token
+        (mapM $ parseMaybe (lineCommentHeadP *> p) . getRawLineComment)
+        mempty
+
+-- * Line Parsers
+
+-- | Non-empty normal line.
+nonEmptyNormalLineP ::
+    -- | True if Literate Haskell
+    Bool ->
+    CommentStyle ->
+    LineParser (String, Position)
+nonEmptyNormalLineP isLHS style = try $ do
+    (ln, pos) <- normalLineP isLHS style
+    guard $
+        case style of
+            Block{} -> T.strip (T.pack ln) `notElem` ["{-", "-}", ""]
+            _ -> not $ all C.isSpace ln
+    pure (ln, pos)
+
+{- | Normal line is a line neither a example nor prop.
+ Empty line is normal.
+-}
+normalLineP ::
+    -- | True if Literate Haskell
+    Bool ->
+    CommentStyle ->
+    LineParser (String, Position)
+normalLineP isLHS style = do
+    notFollowedBy
+        (try $ testSymbol isLHS style)
+    when (isLHS && is _Block style) $
+        void $ count' 0 2 $ char ' '
+    consume style
+
+consume :: CommentStyle -> LineParser (String, Position)
+consume style =
+    case style of
+        Line -> (,) <$> takeRest <*> getPosition
+        Block {} -> manyTill_ anySingle (getPosition <* eob)
+
+getPosition :: (Ord v, TraversableStream s) => ParsecT v s m Position
+getPosition = sourcePosToPosition <$> getSourcePos
+
+-- | Parses example test line.
+exampleLineStrP ::
+    -- | True if Literate Haskell
+    Bool ->
+    CommentStyle ->
+    LineParser (ExampleLine, Position)
+exampleLineStrP isLHS style =
+    try $
+        -- FIXME: To comply with existing Extended Eval Plugin Behaviour;
+        -- it must skip one space after a comment!
+        -- This prevents Eval Plugin from working on
+        -- modules with non-standard base indentation-level.
+        when (isLHS && is _Block style) (void $ count' 0 2 $ char ' ')
+            *> exampleSymbol
+            *> (first ExampleLine <$> consume style)
+
+exampleSymbol :: LineParser ()
+exampleSymbol =
+    chunk ">>>" *> P.notFollowedBy (char '>')
+
+propSymbol :: LineParser ()
+propSymbol = chunk "prop>" *> P.notFollowedBy (char '>')
+
+-- | Parses prop test line.
+propLineStrP ::
+    -- | True if Literate HAskell
+    Bool ->
+    CommentStyle ->
+    LineParser (PropLine, Position)
+propLineStrP isLHS style =
+    -- FIXME: To comply with existing Extended Eval Plugin Behaviour;
+    -- it must skip one space after a comment!
+    -- This prevents Eval Plugin from working on
+    -- modules with non-standard base indentation-level.
+    when (isLHS && is _Block style) (void $ count' 0 2 $ char ' ')
+        *> chunk "prop>"
+        *> P.notFollowedBy (char '>')
+        *> (first PropLine <$> consume style)
+
+-- * Utilities
+
+{- |
+Given a sequence of tokens increasing in their starting position,
+groups them into sublists consisting of contiguous tokens;
+Two adjacent tokens are considered to be contiguous if
+
+    * line number increases by 1, and
+    * they have same starting column.
+
+>>> contiguousGroupOn id [(1,2),(2,2),(3,4),(4,4),(5,4),(7,0),(8,0)]
+[(1,2) :| [(2,2)],(3,4) :| [(4,4),(5,4)],(7,0) :| [(8,0)]]
+-}
+contiguousGroupOn :: (a -> (Int, Int)) -> [a] -> [NonEmpty a]
+contiguousGroupOn toLineCol = foldr step []
+    where
+        step a [] = [pure a]
+        step a bss0@((b :| bs) : bss)
+            | let (aLine, aCol) = toLineCol a
+              , let (bLine, bCol) = toLineCol b
+              , aLine + 1 == bLine && aCol == bCol =
+                (a :| b : bs) : bss
+            | otherwise = pure a : bss0
+
+{- | Given a map from positions, divides them into subgroup
+ with contiguous line and columns.
+-}
+groupLineComments ::
+    Map Range a -> [NonEmpty (Range, a)]
+groupLineComments =
+    contiguousGroupOn (fst >>> view start >>> view line &&& view character)
+        . Map.toList
diff --git a/src/Ide/Plugin/Eval/Parse/Option.hs b/src/Ide/Plugin/Eval/Parse/Option.hs
--- a/src/Ide/Plugin/Eval/Parse/Option.hs
+++ b/src/Ide/Plugin/Eval/Parse/Option.hs
@@ -6,13 +6,10 @@
 ) where
 
 import Control.Monad.Combinators (many)
-import Ide.Plugin.Eval.Parse.Parser (
-    Parser,
-    letterChar,
-    runParser,
-    space,
-    string,
- )
+import Text.Megaparsec.Char
+import Text.Megaparsec
+import Data.Void (Void)
+import Control.Arrow (left)
 
 {- |
 >>> langOptions ":set   -XBinaryLiterals  -XOverloadedStrings "
@@ -24,10 +21,13 @@
 >>> langOptions ""
 Left "No match"
 -}
-langOptions :: [Char] -> Either String [[Char]]
-langOptions = runParser (many space *> languageOpts <* many space)
+langOptions :: String -> Either String [String]
+langOptions =
+  left errorBundlePretty
+  . parse (space *> languageOpts <* eof) ""
 
--- >>> runParser languageOpts ":set -XBinaryLiterals -XOverloadedStrings"
--- Right ["BinaryLiterals","OverloadedStrings"]
-languageOpts :: Parser Char [[Char]]
-languageOpts = string ":set" *> many (many space *> string "-X" *> (many letterChar))
+-- >>> parseMaybe languageOpts ":set -XBinaryLiterals -XOverloadedStrings"
+-- Just ["BinaryLiterals","OverloadedStrings"]
+languageOpts :: Parsec Void String [String]
+languageOpts = string ":set" *> space1
+  *> many (string "-X" *> many letterChar <* space)
diff --git a/src/Ide/Plugin/Eval/Parse/Parser.hs b/src/Ide/Plugin/Eval/Parse/Parser.hs
deleted file mode 100644
--- a/src/Ide/Plugin/Eval/Parse/Parser.hs
+++ /dev/null
@@ -1,98 +0,0 @@
-{-# LANGUAGE DeriveFunctor #-}
-
--- |Simple List Parser, used for both line and test parsing.
-module Ide.Plugin.Eval.Parse.Parser (
-    Parser,
-    runParser,
-    satisfy,
-    alphaNumChar,
-    letterChar,
-    space,
-    string,
-    char,
-    tillEnd,
-) where
-
-import Control.Applicative (Alternative)
-import Control.Monad (MonadPlus, (>=>))
-import Control.Monad.Combinators (
-    empty,
-    (<|>),
- )
-import Data.Char (
-    isAlphaNum,
-    isLetter,
- )
-import Data.List (isPrefixOf)
-
-type CharParser = Parser Char
-
-{- $setup
- >>> import Control.Monad.Combinators
--}
-
-{- |
->>> runParser  (string "aa" <|> string "bb") "bb"
-Right "bb"
-
->>> runParser  (some (string "aa")) "aaaaaa"
-Right ["aa","aa","aa"]
--}
-string :: String -> CharParser String
-string t = Parser $
-    \s -> if t `isPrefixOf` s then Just (t, drop (length t) s) else Nothing
-
-letterChar :: Parser Char Char
-letterChar = satisfy isLetter
-
-alphaNumChar :: Parser Char Char
-alphaNumChar = satisfy isAlphaNum
-
-space :: Parser Char Char
-space = char ' '
-
-{- |
- >>> runParser (some $ char 'a') "aa"
- Right "aa"
--}
-char :: Char -> CharParser Char
-char ch = satisfy (== ch)
-
-{- |
->>> runParser tillEnd "abc\ndef"
-Right "abc\ndef"
--}
-tillEnd :: Parser t [t]
-tillEnd = Parser $ \s -> Just (s, [])
-
-satisfy :: (t -> Bool) -> Parser t t
-satisfy f = Parser sel
-  where
-    sel [] = Nothing
-    sel (t : ts)
-        | f t = Just (t, ts)
-        | otherwise = Nothing
-
-newtype Parser t a = Parser {parse :: [t] -> Maybe (a, [t])} deriving (Functor)
-
-instance Applicative (Parser t) where
-    pure a = Parser (\s -> Just (a, s))
-    (Parser p1) <*> (Parser p2) =
-        Parser (p1 >=> (\(f, s1) -> p2 s1 >>= \(a, s2) -> return (f a, s2)))
-
-instance Alternative (Parser t) where
-    empty = Parser (const Nothing)
-    p <|> q = Parser $ \s -> parse p s <|> parse q s
-
-instance Monad (Parser t) where
-    return = pure
-    (>>=) f g = Parser (parse f >=> (\(a, s') -> parse (g a) s'))
-
-instance MonadPlus (Parser t)
-
-runParser :: Show t => Parser t a -> [t] -> Either String a
-runParser m s = case parse m s of
-    Just (res, []) -> Right res
-    Just (_, ts) ->
-        Left $ "Parser did not consume entire stream, left: " ++ show ts
-    Nothing -> Left "No match"
diff --git a/src/Ide/Plugin/Eval/Parse/Section.hs b/src/Ide/Plugin/Eval/Parse/Section.hs
deleted file mode 100644
--- a/src/Ide/Plugin/Eval/Parse/Section.hs
+++ /dev/null
@@ -1,141 +0,0 @@
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE NoMonomorphismRestriction #-}
-{-# OPTIONS_GHC -Wwarn #-}
-{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
-
--- |Parse a Section, a group of zero or more tests defined in a multiline comment or a sequence of one line comments.
-module Ide.Plugin.Eval.Parse.Section (
-    allSections,
-    validSections,
-    Section (..),
-) where
-
-import qualified Control.Applicative.Combinators.NonEmpty as NE
-import Control.Monad.Combinators (
-    many,
-    optional,
-    some,
-    (<|>),
- )
-import qualified Data.List.NonEmpty as NE
-import Data.Maybe (catMaybes, fromMaybe)
-import Ide.Plugin.Eval.Parse.Parser (
-    Parser,
-    runParser,
-    satisfy,
- )
-import Ide.Plugin.Eval.Parse.Token (
-    Token (BlockOpen, blockFormat, blockLanguage, blockName),
-    TokenS,
-    isBlockClose,
-    isBlockOpen,
-    isCodeLine,
-    isPropLine,
-    isStatement,
-    isTextLine,
-    unsafeContent,
- )
-import Ide.Plugin.Eval.Types (
-    Format (SingleLine),
-    Loc,
-    Located (Located, located, location),
-    Section (..),
-    Test (Example, Property),
-    hasTests,
-    unLoc,
- )
-
-type Tk = Loc TokenS
-
-validSections :: [Tk] -> Either String [Section]
-validSections = (filter hasTests <$>) . allSections
-
-allSections :: [Tk] -> Either String [Section]
-allSections = runParser sections
-
-{-
->>> import Ide.Plugin.Eval.Parse.Token
->>> import  System.IO.Extra(readFileUTF8')
->>> testSource_ = runParser sections . tokensFrom
->>> testSource fp = testSource_ <$> readFileUTF8' fp
-
->>> testSource "plugins/default/src/Ide/Plugin/Eval/Test/TestGHC.hs"
-Right [Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [Located {location = 36, located = Property {testline = " \\(l::[Bool]) -> reverse (reverse l) == l", testOutput = []}}], sectionLanguage = Plain, sectionFormat = MultiLine},Section {sectionName = "", sectionTests = [Located {location = 40, located = Example {testLines = " :set -XScopedTypeVariables -XExplicitForAll" :| [" import qualified Test.QuickCheck as Q11"," runProp11 p = Q11.quickCheckWithResult Q11.stdArgs p >>= return . Q11.output"," prop11 = \\(l::[Int]) -> reverse (reverse l) == l"," runProp11 prop11"], testOutput = []}},Located {location = 46, located = Property {testline = " \\(l::[Int]) -> reverse (reverse l) == l", testOutput = []}}], sectionLanguage = Plain, sectionFormat = MultiLine},Section {sectionName = "", sectionTests = [Located {location = 50, located = Example {testLines = " t" :| [], testOutput = []}}], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [Located {location = 55, located = Example {testLines = " run $ runEval \"3+2\"" :| [], testOutput = []}}], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [Located {location = 125, located = Example {testLines = " isStmt \"\"" :| [], testOutput = ["stmt = let x =33;print x"]}}], sectionLanguage = Haddock, sectionFormat = MultiLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = MultiLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine},Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = SingleLine}]
-
->>> testSource "test/testdata/eval/T11.hs"
-Right [Section {sectionName = "", sectionTests = [Located {location = 2, located = Example {testLines = " :kind! a" :| [], testOutput = []}}], sectionLanguage = Plain, sectionFormat = SingleLine}]
-
->>> testSource "test/testdata/eval/T12.hs"
-Right [Section {sectionName = "", sectionTests = [Located {location = 6, located = Example {testLines = " type N = 1" :| [" type M = 40"," :kind N + M + 1"], testOutput = []}}], sectionLanguage = Plain, sectionFormat = SingleLine}]
-
->>> testSource_ $ "{"++"-\n       -" ++ "}"
-Right [Section {sectionName = "", sectionTests = [], sectionLanguage = Plain, sectionFormat = MultiLine}]
--}
-sections :: Parser Tk [Section]
-sections =
-    catMaybes <$> many (const Nothing <$> some code <|> Just <$> section)
-
-section :: Parser Tk Section
-section = sectionBody >>= sectionEnd
-
-sectionBody :: Parser Tk Section
-sectionBody =
-    ( \(unLoc -> BlockOpen{..}) ts ->
-        Section (fromMaybe "" blockName) (catMaybes ts) blockLanguage blockFormat
-    )
-        <$> open <*> many (Just <$> example <|> Just <$> property <|> const Nothing <$> doc)
-
-sectionEnd :: Section -> Parser Tk Section
-sectionEnd s
-    | sectionFormat s == SingleLine = optional code *> return s
-    | otherwise = close *> return s
-
--- section = do
---   s <-
---     maybe
---       (Section "" [] Plain SingleLine)
---       ( \(Located _ BlockOpen {..}) ->
---           Section (fromMaybe "" blockName) [] blockLanguage blockFormat
---       )
---       <$> optional open
---   ts <- many (Just <$> example <|> Just <$> property <|> const Nothing <$> doc)
---   optional close
---   return $ s {sectionTests = catMaybes ts}
-
--- singleSection :: Parser Tk Section
--- singleSection = (\ts -> Section "" (catMaybes ts) Plain SingleLine) <$> tests
-
--- tests :: Parser Tk [Maybe (Loc Test)]
--- tests = some (Just <$> example <|> Just <$> property <|> const Nothing <$> doc)
-
-doc :: Parser Tk [Tk]
-doc = some text
-
-example, property :: Parser Tk (Loc Test)
-property =
-    ( \(Located l p) rs ->
-        Located l (Property (unsafeContent p) (unsafeContent . located <$> rs))
-    )
-        <$> prop
-        <*> many nonEmptyText
-example =
-    ( \es rs ->
-        Located
-            (location (NE.head es))
-            (Example (unsafeContent . located <$> es) (unsafeContent . located <$> rs))
-    )
-        <$> NE.some statement
-        <*> many nonEmptyText
-
-open, close, statement, nonEmptyText, text, prop, code :: Parser Tk Tk
-statement = is isStatement
-text = is isTextLine
-prop = is isPropLine
-open = is isBlockOpen
-close = is isBlockClose
-code = is isCodeLine
-nonEmptyText = is (\l -> isTextLine l && not (null (unsafeContent l)))
-
-is :: (b -> Bool) -> Parser (Loc b) (Loc b)
-is p = satisfy (p . unLoc)
diff --git a/src/Ide/Plugin/Eval/Parse/Token.hs b/src/Ide/Plugin/Eval/Parse/Token.hs
deleted file mode 100644
--- a/src/Ide/Plugin/Eval/Parse/Token.hs
+++ /dev/null
@@ -1,298 +0,0 @@
-{-# OPTIONS_GHC -Wwarn #-}
-
--- | Parse source code into a list of line Tokens.
-module Ide.Plugin.Eval.Parse.Token (
-    Token (..),
-    TokenS,
-    tokensFrom,
-    unsafeContent,
-    isStatement,
-    isTextLine,
-    isPropLine,
-    isCodeLine,
-    isBlockOpen,
-    isBlockClose,
-) where
-
-import Control.Monad.Combinators (
-    many,
-    optional,
-    skipManyTill,
-    (<|>),
- )
-import Data.Functor (($>))
-import Data.List (foldl')
-import Ide.Plugin.Eval.Parse.Parser (
-    Parser,
-    alphaNumChar,
-    char,
-    letterChar,
-    runParser,
-    satisfy,
-    space,
-    string,
-    tillEnd,
- )
-import Ide.Plugin.Eval.Types (
-    Format (..),
-    Language (..),
-    Loc,
-    Located (Located),
- )
-import Maybes (fromJust, fromMaybe)
-
-type TParser = Parser Char (State, [TokenS])
-
-data State = InCode | InSingleComment | InMultiComment deriving (Eq, Show)
-
-commentState :: Bool -> State
-commentState True = InMultiComment
-commentState False = InSingleComment
-
-type TokenS = Token String
-
-data Token s
-    = -- | Text, without prefix "(--)? >>>"
-      Statement s
-    | -- | Text, without prefix "(--)? prop>"
-      PropLine s
-    | -- | Text inside a comment
-      TextLine s
-    | -- | Line of code (outside comments)
-      CodeLine
-    | -- | Open of comment
-      BlockOpen {blockName :: Maybe s, blockLanguage :: Language, blockFormat :: Format}
-    | -- | Close of multi-line comment
-      BlockClose
-    deriving (Eq, Show)
-
-isStatement :: Token s -> Bool
-isStatement (Statement _) = True
-isStatement _ = False
-
-isTextLine :: Token s -> Bool
-isTextLine (TextLine _) = True
-isTextLine _ = False
-
-isPropLine :: Token s -> Bool
-isPropLine (PropLine _) = True
-isPropLine _ = False
-
-isCodeLine :: Token s -> Bool
-isCodeLine CodeLine = True
-isCodeLine _ = False
-
-isBlockOpen :: Token s -> Bool
-isBlockOpen (BlockOpen _ _ _) = True
-isBlockOpen _ = False
-
-isBlockClose :: Token s -> Bool
-isBlockClose BlockClose = True
-isBlockClose _ = False
-
-unsafeContent :: Token a -> a
-unsafeContent = fromJust . contentOf
-
-contentOf :: Token a -> Maybe a
-contentOf (Statement c) = Just c
-contentOf (PropLine c) = Just c
-contentOf (TextLine c) = Just c
-contentOf _ = Nothing
-
-{- | Parse source code and return a list of located Tokens
->>> import           Ide.Plugin.Eval.Types        (unLoc)
->>> tks src = map unLoc . tokensFrom  <$> readFile src
-
->>> tks "test/testdata/eval/T1.hs"
-[CodeLine,CodeLine,CodeLine,CodeLine,BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = SingleLine},Statement " unwords example",CodeLine,CodeLine]
-
->>> tks "test/testdata/eval/TLanguageOptions.hs"
-[BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = SingleLine},TextLine "Support for language options",CodeLine,CodeLine,CodeLine,CodeLine,BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = SingleLine},TextLine "Language options set in the module source (ScopedTypeVariables)",TextLine "also apply to tests so this works fine",Statement " f = (\\(c::Char) -> [c])",CodeLine,BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = MultiLine},TextLine "Multiple options can be set with a single `:set`",TextLine "",Statement " :set -XMultiParamTypeClasses -XFlexibleInstances",Statement " class Z a b c",BlockClose,CodeLine,BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = MultiLine},TextLine "",TextLine "",TextLine "Options apply only in the section where they are defined (unless they are in the setup section), so this will fail:",TextLine "",Statement " class L a b c",BlockClose,CodeLine,CodeLine,BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = MultiLine},TextLine "",TextLine "Options apply to all tests in the same section after their declaration.",TextLine "",TextLine "Not set yet:",TextLine "",Statement " class D",TextLine "",TextLine "Now it works:",TextLine "",Statement ":set -XMultiParamTypeClasses",Statement " class C",TextLine "",TextLine "It still works",TextLine "",Statement " class F",BlockClose,CodeLine,BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = MultiLine},TextLine "Wrong option names are reported.",Statement " :set -XWrong",BlockClose]
--}
-tokensFrom :: String -> [Loc (Token String)]
-tokensFrom = tokens . lines . filter (/= '\r')
-
-{- |
->>> tokens ["-- |$setup >>> 4+7","x=11"]
-[Located {location = 0, located = BlockOpen {blockName = Just "setup", blockLanguage = Haddock, blockFormat = SingleLine}},Located {location = 0, located = Statement " 4+7"},Located {location = 1, located = CodeLine}]
-
->>> tokens ["-- $start"]
-[Located {location = 0, located = BlockOpen {blockName = Just "start", blockLanguage = Plain, blockFormat = SingleLine}},Located {location = 0, located = TextLine ""}]
-
->>> tokens ["--","-- >>> 4+7"]
-[Located {location = 0, located = BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = SingleLine}},Located {location = 0, located = TextLine ""},Located {location = 1, located = Statement " 4+7"}]
-
->>> tokens ["-- |$setup  44","-- >>> 4+7"]
-[Located {location = 0, located = BlockOpen {blockName = Just "setup", blockLanguage = Haddock, blockFormat = SingleLine}},Located {location = 0, located = TextLine "44"},Located {location = 1, located = Statement " 4+7"}]
-
->>> tokens ["{"++"- |$doc",">>> 2+2","4","prop> x-x==0","--minus","-"++"}"]
-[Located {location = 0, located = BlockOpen {blockName = Just "doc", blockLanguage = Haddock, blockFormat = MultiLine}},Located {location = 0, located = TextLine ""},Located {location = 1, located = Statement " 2+2"},Located {location = 2, located = TextLine "4"},Located {location = 3, located = PropLine " x-x==0"},Located {location = 4, located = TextLine "--minus"},Located {location = 5, located = BlockClose}]
-
-Multi lines, closed on following line:
-
->>> tokens ["{"++"-","-"++"}"]
-[Located {location = 0, located = BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = MultiLine}},Located {location = 0, located = TextLine ""},Located {location = 1, located = BlockClose}]
-
->>> tokens [" {"++"-","-"++"} "]
-[Located {location = 0, located = BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = MultiLine}},Located {location = 0, located = TextLine ""},Located {location = 1, located = BlockClose}]
-
->>> tokens ["{"++"- SOME TEXT "," MORE -"++"}"]
-[Located {location = 0, located = BlockOpen {blockName = Nothing, blockLanguage = Plain, blockFormat = MultiLine}},Located {location = 0, located = TextLine "SOME TEXT "},Located {location = 1, located = BlockClose}]
-
-Multi lines, closed on the same line:
-
->>> tokens $ ["{","--"++"}"]
-[Located {location = 0, located = CodeLine}]
-
->>> tokens $ ["  {","- IGNORED -","}  "]
-[Located {location = 0, located = CodeLine}]
-
->>> tokens ["{","-# LANGUAGE TupleSections","#-","}"]
-[Located {location = 0, located = CodeLine},Located {location = 1, located = CodeLine}]
-
->>> tokens []
-[]
--}
-tokens :: [String] -> [Loc TokenS]
-tokens = concatMap (\(l, vs) -> map (Located l) vs) . zip [0 ..] . reverse . snd . foldl' next (InCode, [])
-  where
-    next (st, tokens) ln = case runParser (aline st) ln of
-        Right (st', tokens') -> (st', tokens' : tokens)
-        Left err -> error $ unwords ["Tokens.next failed to parse", ln, err]
-
--- | Parse a line of input
-aline :: State -> TParser
-aline InCode = optionStart <|> multi <|> singleOpen <|> codeLine
-aline InSingleComment = optionStart <|> multi <|> commentLine False <|> codeLine
-aline InMultiComment = multiClose <|> commentLine True
-
-multi :: TParser
-multi = multiOpenClose <|> multiOpen
-
-codeLine :: TParser
-codeLine = (InCode, [CodeLine]) <$ tillEnd
-
-{- | A multi line comment that starts and ends on the same line.
-
->>> runParser multiOpenClose $ concat ["{","--","}"]
-Right (InCode,[CodeLine])
-
->>> runParser multiOpenClose $ concat [" {","-| >>> IGNORED -","} "]
-Right (InCode,[CodeLine])
--}
-multiOpenClose :: TParser
-multiOpenClose = (multiStart >> multiClose) $> (InCode, [CodeLine])
-
-{- | Parses the opening of a multi line comment.
->>> runParser multiOpen $ "{"++"- $longSection this is also parsed"
-Right (InMultiComment,[BlockOpen {blockName = Just "longSection", blockLanguage = Plain, blockFormat = MultiLine},TextLine "this is also parsed"])
-
->>> runParser multiOpen $ "{"++"- $longSection >>> 2+3"
-Right (InMultiComment,[BlockOpen {blockName = Just "longSection", blockLanguage = Plain, blockFormat = MultiLine},Statement " 2+3"])
--}
-multiOpen :: TParser
-multiOpen =
-    ( \() (maybeLanguage, maybeName) tk ->
-        (InMultiComment, [BlockOpen maybeName (defLang maybeLanguage) MultiLine, tk])
-    )
-        <$> multiStart
-        <*> languageAndName
-        <*> commentRest
-
-{- | Parse the first line of a sequence of single line comments
->>> runParser singleOpen "-- |$doc >>>11"
-Right (InSingleComment,[BlockOpen {blockName = Just "doc", blockLanguage = Haddock, blockFormat = SingleLine},Statement "11"])
--}
-singleOpen :: TParser
-singleOpen =
-    ( \() (maybeLanguage, maybeName) tk ->
-        (InSingleComment, [BlockOpen maybeName (defLang maybeLanguage) SingleLine, tk])
-    )
-        <$> singleStart
-        <*> languageAndName
-        <*> commentRest
-
-{- | Parse a line in a comment
->>> runParser (commentLine False) "x=11"
-Left "No match"
-
->>> runParser (commentLine False) "-- >>>11"
-Right (InSingleComment,[Statement "11"])
-
->>> runParser (commentLine True) "-- >>>11"
-Right (InMultiComment,[TextLine "-- >>>11"])
--}
-commentLine :: Bool -> TParser
-commentLine noPrefix =
-    (\tk -> (commentState noPrefix, [tk])) <$> (optLineStart noPrefix *> commentBody)
-
-commentRest :: Parser Char (Token [Char])
-commentRest = many space *> commentBody
-
-commentBody :: Parser Char (Token [Char])
-commentBody = stmt <|> prop <|> txt
-  where
-    txt = TextLine <$> tillEnd
-    stmt = Statement <$> (string ">>>" *> tillEnd)
-    prop = PropLine <$> (string "prop>" *> tillEnd)
-
--- | Remove comment line prefix, if needed
-optLineStart :: Bool -> Parser Char ()
-optLineStart noPrefix
-    | noPrefix = pure ()
-    | otherwise = singleStart
-
-singleStart :: Parser Char ()
-singleStart = (string "--" *> optional space) $> ()
-
-multiStart :: Parser Char ()
-multiStart = sstring "{-" $> ()
-
-{- Parse the close of a multi-line comment
->>> runParser multiClose $ "-"++"}"
-Right (InCode,[BlockClose])
-
->>> runParser multiClose $ "-"++"}  "
-Right (InCode,[BlockClose])
-
-As there is currently no way of handling tests in the final line of a multi line comment, it ignores anything that precedes the closing marker:
-
->>> runParser multiClose $ "IGNORED -"++"}  "
-Right (InCode,[BlockClose])
--}
-multiClose :: TParser
-multiClose = skipManyTill (satisfy (const True)) (string "-}" *> many space) >> return (InCode, [BlockClose])
-
-optionStart :: Parser Char (State, [Token s])
-optionStart = (string "{-#" *> tillEnd) $> (InCode, [CodeLine])
-
-name :: Parser Char [Char]
-name = (:) <$> letterChar <*> many (alphaNumChar <|> char '_')
-
-sstring :: String -> Parser Char [Char]
-sstring s = many space *> string s *> many space
-
-{- |
->>>runParser languageAndName "|$"
-Right (Just Haddock,Just "")
-
->>>runParser languageAndName "|$start"
-Right (Just Haddock,Just "start")
-
->>>runParser languageAndName "| $start"
-Right (Just Haddock,Just "start")
-
->>>runParser languageAndName "^"
-Right (Just Haddock,Nothing)
-
->>>runParser languageAndName "$start"
-Right (Nothing,Just "start")
--}
-languageAndName :: Parser Char (Maybe Language, Maybe String)
-languageAndName =
-    (,) <$> optional ((char '|' <|> char '^') >> pure Haddock)
-        <*> optional
-            (char '$' *> (fromMaybe "" <$> optional name))
-
-defLang :: Maybe Language -> Language
-defLang = fromMaybe Plain
diff --git a/src/Ide/Plugin/Eval/Types.hs b/src/Ide/Plugin/Eval/Types.hs
--- a/src/Ide/Plugin/Eval/Types.hs
+++ b/src/Ide/Plugin/Eval/Types.hs
@@ -1,31 +1,42 @@
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveFunctor #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE UndecidableInstances #-}
 {-# OPTIONS_GHC -Wwarn #-}
 
-module Ide.Plugin.Eval.Types (
-    locate,
-    locate0,
-    Test (..),
-    isProperty,
-    Format (..),
-    Language (..),
-    Section (..),
-    hasTests,
-    hasPropertyTest,
-    splitSections,
-    Loc,
-    Located (..),
-    unLoc,
-    Txt,
-) where
+module Ide.Plugin.Eval.Types
+    ( locate,
+      locate0,
+      Test (..),
+      isProperty,
+      Format (..),
+      Language (..),
+      Section (..),
+      Sections (..),
+      hasTests,
+      hasPropertyTest,
+      splitSections,
+      Loc,
+      Located (..),
+      Comments (..),
+      RawBlockComment (..),
+      RawLineComment (..),
+      unLoc,
+      Txt,
+    )
+where
 
 import Control.DeepSeq (NFData (rnf), deepseq)
 import Data.Aeson (FromJSON, ToJSON)
 import Data.List (partition)
 import Data.List.NonEmpty (NonEmpty)
+import Data.Map.Strict (Map)
 import Data.String (IsString (..))
+import Development.IDE (Range)
 import GHC.Generics (Generic)
+import qualified Text.Megaparsec as P
 
 -- | A thing with a location attached.
 data Located l a = Located {location :: l, located :: a}
@@ -50,9 +61,15 @@
 
 type Txt = String
 
+data Sections = Sections
+    { nonSetupSections :: [Section]
+    , setupSections :: [Section]
+    }
+    deriving (Show, Eq, Generic)
+
 data Section = Section
     { sectionName :: Txt
-    , sectionTests :: [Loc Test]
+    , sectionTests :: [Test]
     , sectionLanguage :: Language
     , sectionFormat :: Format
     }
@@ -62,22 +79,61 @@
 hasTests = not . null . sectionTests
 
 hasPropertyTest :: Section -> Bool
-hasPropertyTest = any (isProperty . unLoc) . sectionTests
+hasPropertyTest = any isProperty . sectionTests
 
 -- |Split setup and normal sections
 splitSections :: [Section] -> ([Section], [Section])
 splitSections = partition ((== "setup") . sectionName)
 
 data Test
-    = Example {testLines :: NonEmpty Txt, testOutput :: [Txt]}
-    | Property {testline :: Txt, testOutput :: [Txt]}
+    = Example {testLines :: NonEmpty Txt, testOutput :: [Txt], testRange :: Range}
+    | Property {testline :: Txt, testOutput :: [Txt], testRange :: Range}
     deriving (Eq, Show, Generic, FromJSON, ToJSON, NFData)
 
+data Comments = Comments
+    { lineComments :: Map Range RawLineComment
+    , blockComments :: Map Range RawBlockComment
+    }
+    deriving (Show, Eq, Ord, Generic)
+
+newtype RawBlockComment = RawBlockComment {getRawBlockComment :: String}
+    deriving (Show, Eq, Ord)
+    deriving newtype
+        ( IsString
+        , P.Stream
+        , P.TraversableStream
+        , P.VisualStream
+        , Semigroup
+        , Monoid
+        )
+
+newtype RawLineComment = RawLineComment {getRawLineComment :: String}
+    deriving (Show, Eq, Ord)
+    deriving newtype
+        ( IsString
+        , P.Stream
+        , P.TraversableStream
+        , P.VisualStream
+        , Semigroup
+        , Monoid
+        )
+
+instance Semigroup Comments where
+    Comments ls bs <> Comments ls' bs' = Comments (ls <> ls') (bs <> bs')
+
+instance Monoid Comments where
+    mempty = Comments mempty mempty
+
 isProperty :: Test -> Bool
-isProperty (Property _ _) = True
+isProperty Property {} = True
 isProperty _ = False
 
-data Format = SingleLine | MultiLine deriving (Eq, Show, Ord, Generic, FromJSON, ToJSON, NFData)
+data Format
+    = SingleLine
+    | -- | @Range@ is that of surrounding entire block comment, not section.
+      -- Used for detecting no-newline test commands.
+      MultiLine Range
+    deriving (Eq, Show, Ord, Generic, FromJSON, ToJSON, NFData)
 
 data Language = Plain | Haddock deriving (Eq, Show, Generic, Ord, FromJSON, ToJSON, NFData)
 
diff --git a/src/Ide/Plugin/Eval/Util.hs b/src/Ide/Plugin/Eval/Util.hs
--- a/src/Ide/Plugin/Eval/Util.hs
+++ b/src/Ide/Plugin/Eval/Util.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE ImplicitParams #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# OPTIONS_GHC -Wno-orphans #-}
