packages feed

ghcide 0.0.5 → 0.0.6

raw patch · 33 files changed

+2478/−281 lines, 33 filesdep +fuzzydep +gitrevdep +haddock-librarydep ~haskell-lspdep ~haskell-lsp-typesdep ~hie-biosnew-component:exe:ghcide-test-preprocessorPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: fuzzy, gitrev, haddock-library, regex-tdfa

Dependency ranges changed: haskell-lsp, haskell-lsp-types, hie-bios, shake

API changes (from Hackage documentation)

- Development.IDE.GHC.Util: hDuplicateTo :: Handle -> Handle -> IO ()
+ Development.IDE.Core.Completions: WithSnippets :: Bool -> WithSnippets
+ Development.IDE.Core.Completions: cacheDataProducer :: HscEnv -> DynFlags -> TypecheckedModule -> [TypecheckedModule] -> IO CachedCompletions
+ Development.IDE.Core.Completions: data CachedCompletions
+ Development.IDE.Core.Completions: getCompletions :: IdeOptions -> CachedCompletions -> TypecheckedModule -> PosPrefixInfo -> ClientCapabilities -> WithSnippets -> IO [CompletionItem]
+ Development.IDE.Core.Completions: instance GHC.Classes.Eq Development.IDE.Core.Completions.Context
+ Development.IDE.Core.Completions: instance GHC.Show.Show Development.IDE.Core.Completions.Context
+ Development.IDE.Core.Completions: newtype WithSnippets
+ Development.IDE.Core.CompletionsTypes: CC :: [Text] -> [CompItem] -> QualCompls -> [Text] -> CachedCompletions
+ Development.IDE.Core.CompletionsTypes: CI :: Name -> Text -> Maybe Type -> Text -> Maybe Backtick -> [Text] -> CompItem
+ Development.IDE.Core.CompletionsTypes: LeftSide :: Backtick
+ Development.IDE.Core.CompletionsTypes: QualCompls :: Map Text [CompItem] -> QualCompls
+ Development.IDE.Core.CompletionsTypes: Surrounded :: Backtick
+ Development.IDE.Core.CompletionsTypes: [allModNamesAsNS] :: CachedCompletions -> [Text]
+ Development.IDE.Core.CompletionsTypes: [docs] :: CompItem -> [Text]
+ Development.IDE.Core.CompletionsTypes: [getQualCompls] :: QualCompls -> Map Text [CompItem]
+ Development.IDE.Core.CompletionsTypes: [importableModules] :: CachedCompletions -> [Text]
+ Development.IDE.Core.CompletionsTypes: [importedFrom] :: CompItem -> Text
+ Development.IDE.Core.CompletionsTypes: [isInfix] :: CompItem -> Maybe Backtick
+ Development.IDE.Core.CompletionsTypes: [label] :: CompItem -> Text
+ Development.IDE.Core.CompletionsTypes: [origName] :: CompItem -> Name
+ Development.IDE.Core.CompletionsTypes: [qualCompls] :: CachedCompletions -> QualCompls
+ Development.IDE.Core.CompletionsTypes: [thingType] :: CompItem -> Maybe Type
+ Development.IDE.Core.CompletionsTypes: [unqualCompls] :: CachedCompletions -> [CompItem]
+ Development.IDE.Core.CompletionsTypes: data Backtick
+ Development.IDE.Core.CompletionsTypes: data CachedCompletions
+ Development.IDE.Core.CompletionsTypes: data CompItem
+ Development.IDE.Core.CompletionsTypes: instance Control.DeepSeq.NFData Development.IDE.Core.CompletionsTypes.CachedCompletions
+ Development.IDE.Core.CompletionsTypes: instance GHC.Base.Monoid Development.IDE.Core.CompletionsTypes.QualCompls
+ Development.IDE.Core.CompletionsTypes: instance GHC.Base.Semigroup Development.IDE.Core.CompletionsTypes.QualCompls
+ Development.IDE.Core.CompletionsTypes: instance GHC.Classes.Eq Development.IDE.Core.CompletionsTypes.CompItem
+ Development.IDE.Core.CompletionsTypes: instance GHC.Classes.Ord Development.IDE.Core.CompletionsTypes.CompItem
+ Development.IDE.Core.CompletionsTypes: instance GHC.Show.Show Development.IDE.Core.CompletionsTypes.Backtick
+ Development.IDE.Core.CompletionsTypes: instance GHC.Show.Show Development.IDE.Core.CompletionsTypes.CachedCompletions
+ Development.IDE.Core.CompletionsTypes: instance GHC.Show.Show Development.IDE.Core.CompletionsTypes.CompItem
+ Development.IDE.Core.CompletionsTypes: instance GHC.Show.Show Development.IDE.Core.CompletionsTypes.QualCompls
+ Development.IDE.Core.CompletionsTypes: newtype QualCompls
+ Development.IDE.Core.CompletionsTypes: showGhc :: Outputable a => a -> String
+ Development.IDE.Core.RuleTypes: ProduceCompletions :: ProduceCompletions
+ Development.IDE.Core.RuleTypes: data ProduceCompletions
+ Development.IDE.Core.RuleTypes: instance Control.DeepSeq.NFData Development.IDE.Core.RuleTypes.ProduceCompletions
+ Development.IDE.Core.RuleTypes: instance Data.Binary.Class.Binary Development.IDE.Core.RuleTypes.ProduceCompletions
+ Development.IDE.Core.RuleTypes: instance Data.Hashable.Class.Hashable Development.IDE.Core.RuleTypes.ProduceCompletions
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Eq Development.IDE.Core.RuleTypes.ProduceCompletions
+ Development.IDE.Core.RuleTypes: instance GHC.Generics.Generic Development.IDE.Core.RuleTypes.ProduceCompletions
+ Development.IDE.Core.RuleTypes: instance GHC.Show.Show Development.IDE.Core.RuleTypes.ProduceCompletions
+ Development.IDE.Core.Shake: getHiddenDiagnostics :: IdeState -> IO [FileDiagnostic]
+ Development.IDE.GHC.Util: hDuplicateTo' :: Handle -> Handle -> IO ()
+ Development.IDE.Types.Diagnostics: HideDiag :: ShowDiagnostic
+ Development.IDE.Types.Diagnostics: ShowDiag :: ShowDiagnostic
+ Development.IDE.Types.Diagnostics: data ShowDiagnostic
+ Development.IDE.Types.Diagnostics: instance Control.DeepSeq.NFData Development.IDE.Types.Diagnostics.ShowDiagnostic
+ Development.IDE.Types.Diagnostics: instance GHC.Classes.Eq Development.IDE.Types.Diagnostics.ShowDiagnostic
+ Development.IDE.Types.Diagnostics: instance GHC.Classes.Ord Development.IDE.Types.Diagnostics.ShowDiagnostic
+ Development.IDE.Types.Diagnostics: instance GHC.Show.Show Development.IDE.Types.Diagnostics.ShowDiagnostic
+ Development.IDE.Types.Location: readSrcSpan :: ReadS SrcSpan
- Development.IDE.Types.Diagnostics: type FileDiagnostic = (NormalizedFilePath, Diagnostic)
+ Development.IDE.Types.Diagnostics: type FileDiagnostic = (NormalizedFilePath, ShowDiagnostic, Diagnostic)

Files

CHANGELOG.md view
@@ -1,5 +1,27 @@ ### unreleased +### 0.0.6 (2020-01-10)++* Fix type in hover information for do-notation and list+  comprehensions (see #243).+* Fix hover and goto-definition for multi-clause definitions (see #252).+* Upgrade to `hie-bios-0.3` (see #257)+* Upgrade to `haskell-lsp-0.19` (see #254)+* Code lenses for missing signatures are displayed even if the warning+  has not been enabled. The warning itself will not be shown if it is+  not enabled. (see #232)+* Define `__GHCIDE__` when running CPP to allow for `ghcide`-specific+  workarounds. (see #264)+* Fix some filepath normalization issues. (see #266)+* Fix build with `shake-0.18.4` (see #272)+* Fix hover for type constructors and type classes. (see #267)+* Support custom preprocessors (see #282)+* Add support for code completions (see #227)+* Code action for removing redundant symbols from imports (see #290)+* Support document symbol requests (see #293)+* Show CPP errors as diagnostics (see #296)+* Code action for adding suggested imports (see #295)+ ### 0.0.5 (2019-12-12)  * Support for GHC plugins (see #192)
README.md view
@@ -69,6 +69,30 @@ You can install the VSCode extension from the [VSCode marketplace](https://marketplace.visualstudio.com/items?itemName=DigitalAssetHoldingsLLC.ghcide). +### Using with Atom++You can follow the [instructions](https://github.com/moodmosaic/ide-haskell-ghcide#readme) to install with `apm`.++### Using with Sublime Text++* Install [LSP](https://packagecontrol.io/packages/LSP)+* Press Ctrl+Shift+P or Cmd+Shift+P in Sublime Text and search for *Preferences: LSP Settings*, then paste these settings+```+{+  "clients":+  {+    "ghcide":+    {+      "enabled"   : true,+      "languageId": "haskell",+      "command"   : ["ghcide", "--lsp"],+      "scopes"    : ["source.haskell"],+      "syntaxes"  : ["Packages/Haskell/Haskell.sublime-syntax"]+    }+  }+}+```+ ### Using with Emacs  If you don't already have [MELPA](https://melpa.org/#/) package installation configured, visit MELPA [getting started](https://melpa.org/#/getting-started) page to get set up. Then, install [`use-package`](https://melpa.org/#/use-package).
exe/Arguments.hs view
@@ -11,6 +11,7 @@     ,argsCwd :: Maybe FilePath     ,argFiles :: [FilePath]     ,argsVersion :: Bool+    ,argsShakeProfiling :: Maybe FilePath     }  getArguments :: IO Arguments@@ -27,3 +28,4 @@       <*> optional (strOption $ long "cwd" <> metavar "DIR" <> help "Change to this directory")       <*> many (argument str (metavar "FILES/DIRS..."))       <*> switch (long "version" <> help "Show ghcide and GHC versions")+      <*> optional (strOption $ long "shake-profiling" <> metavar "DIR" <> help "Dump profiling reports to this directory")
exe/Main.hs view
@@ -2,6 +2,7 @@ -- SPDX-License-Identifier: Apache-2.0 {-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above {-# LANGUAGE CPP #-} -- To get precise GHC version+{-# LANGUAGE TemplateHaskell #-}  module Main(main) where @@ -12,6 +13,7 @@ import Control.Concurrent.Extra import Control.Exception import Control.Monad.Extra+import Control.Monad.IO.Class import Data.Default import System.Time.Extra import Development.IDE.Core.FileStore@@ -38,7 +40,8 @@ import System.IO import System.Exit import Paths_ghcide-import Development.Shake hiding (Env)+import Development.GitRev+import Development.Shake (Action, action) import qualified Data.Set as Set import qualified Data.Map.Strict as Map @@ -51,9 +54,16 @@ getLibdir :: IO FilePath getLibdir = fromMaybe GHC.Paths.libdir <$> lookupEnv "NIX_GHC_LIBDIR" -ghcideVersion :: String-ghcideVersion = "ghcide version: " <> showVersion version-             <> " (GHC: "          <> VERSION_ghc <> ")"+ghcideVersion :: IO String+ghcideVersion = do+  path <- getExecutablePath+  let gitHashSection = case $(gitHash) of+        x | x == "UNKNOWN" -> ""+        x -> " (GIT hash: " <> x <> ")"+  return $ "ghcide version: " <> showVersion version+             <> " (GHC: " <> VERSION_ghc+             <> ") (PATH: " <> path <> ")"+             <> gitHashSection  main :: IO () main = do@@ -61,8 +71,8 @@     --          then the language server will not work     Arguments{..} <- getArguments -    if argsVersion then putStrLn ghcideVersion >> exitSuccess-    else hPutStrLn stderr {- see WARNING above -} ghcideVersion+    if argsVersion then ghcideVersion >>= putStrLn >> exitSuccess+    else hPutStrLn stderr {- see WARNING above -} =<< ghcideVersion      -- lock to avoid overlapping output on stdout     lock <- newLock@@ -83,7 +93,9 @@             -- very important we only call loadSession once, and it's fast, so just do it before starting             session <- loadSession dir             let options = (defaultIdeOptions $ return session)-                    { optReportProgress = clientSupportsProgress caps }+                    { optReportProgress = clientSupportsProgress caps +                    , optShakeProfiling = argsShakeProfiling+                    }             initialise (mainRule >> action kick) getLspId event (logger minBound) options vfs     else do         putStrLn $ "Ghcide setup tester in " ++ dir ++ "."@@ -150,13 +162,19 @@ showEvent :: Lock -> FromServerMessage -> IO () showEvent _ (EventFileDiagnostics _ []) = return () showEvent lock (EventFileDiagnostics (toNormalizedFilePath -> file) diags) =-    withLock lock $ T.putStrLn $ showDiagnosticsColored $ map (file,) diags+    withLock lock $ T.putStrLn $ showDiagnosticsColored $ map (file,ShowDiag,) diags showEvent lock e = withLock lock $ print e   cradleToSession :: Cradle -> IO HscEnvEq cradleToSession cradle = do-    opts <- either throwIO return =<< getCompilerOptions "" cradle+    cradleRes <- getCompilerOptions "" cradle+    opts <- case cradleRes of+        CradleSuccess r -> pure r+        CradleFail err -> throwIO err+        -- TODO Rather than failing here, we should ignore any files that use this cradle.+        -- That will require some more changes.+        CradleNone -> fail "'none' cradle is not yet supported"     libdir <- getLibdir     env <- runGhc (Just libdir) $ do         _targets <- initSession opts
ghcide.cabal view
@@ -2,7 +2,7 @@ build-type:         Simple category:           Development name:               ghcide-version:            0.0.5+version:            0.0.6 license:            Apache-2.0 license-file:       LICENSE author:             Digital Asset@@ -39,15 +39,18 @@         deepseq,         directory,         extra,+        fuzzy,         filepath,+        haddock-library,         hashable,-        haskell-lsp-types >= 0.18,-        haskell-lsp >= 0.18,+        haskell-lsp-types == 0.19.*,+        haskell-lsp == 0.19.*,         mtl,         network-uri,         prettyprinter-ansi-terminal,         prettyprinter-ansi-terminal,         prettyprinter,+        regex-tdfa >= 1.3.1.0,         rope-utf16-splay,         safe-exceptions,         shake >= 0.17.5,@@ -96,6 +99,8 @@     include-dirs:         include     exposed-modules:+        Development.IDE.Core.Completions+        Development.IDE.Core.CompletionsTypes         Development.IDE.Core.FileStore         Development.IDE.Core.OfInterest         Development.IDE.Core.PositionMapping@@ -123,15 +128,24 @@         Development.IDE.GHC.Warnings         Development.IDE.Import.FindImports         Development.IDE.LSP.CodeAction-        Development.IDE.LSP.Definition-        Development.IDE.LSP.Hover+        Development.IDE.LSP.Completions+        Development.IDE.LSP.HoverDefinition         Development.IDE.LSP.Notifications+        Development.IDE.LSP.Outline         Development.IDE.Spans.AtPoint         Development.IDE.Spans.Calculate         Development.IDE.Spans.Documentation         Development.IDE.Spans.Type     ghc-options: -Wall -Wno-name-shadowing +executable ghcide-test-preprocessor+    default-language: Haskell2010+    hs-source-dirs: test/preprocessor+    ghc-options: -Wall+    main-is: Main.hs+    build-depends:+        base == 4.*+ executable ghcide     if flag(ghc-lib)       buildable: False@@ -149,8 +163,9 @@         filepath,         ghc-paths,         ghc,+        gitrev,         haskell-lsp,-        hie-bios >= 0.2 && < 0.3,+        hie-bios >= 0.3.2 && < 0.4,         ghcide,         optparse-applicative,         shake,@@ -170,8 +185,10 @@     type: exitcode-stdio-1.0     default-language: Haskell2010     build-tool-depends:-        ghcide:ghcide+        ghcide:ghcide,+        ghcide:ghcide-test-preprocessor     build-depends:+        aeson,         base,         bytestring,         containers,
src/Development/IDE/Core/Compile.hs view
@@ -102,11 +102,14 @@         catchSrcErrors "typecheck" $ do             setupEnv deps             let modSummary = pm_mod_summary pm+                dflags = ms_hspp_opts modSummary             modSummary' <- initPlugins modSummary             (warnings, tcm) <- withWarnings "typecheck" $ \tweak ->-                GHC.typecheckModule $ demoteIfDefer pm{pm_mod_summary = tweak modSummary'}+                GHC.typecheckModule $ enableTopLevelWarnings+                                    $ demoteIfDefer pm{pm_mod_summary = tweak modSummary'}             tcm2 <- mkTcModuleResult tcm-            return (map unDefer warnings, tcm2)+            let errorPipeline = unDefer . hideDiag dflags+            return (map errorPipeline warnings, tcm2)  initPlugins :: GhcMonad m => ModSummary -> m ModSummary initPlugins modSummary = do@@ -170,13 +173,20 @@                    . (`gopt_set` Opt_DeferTypedHoles)                    . (`gopt_set` Opt_DeferOutOfScopeVariables) -  update_hspp_opts :: (DynFlags -> DynFlags) -> ModSummary -> ModSummary-  update_hspp_opts up ms = ms{ms_hspp_opts = up $ ms_hspp_opts ms}+enableTopLevelWarnings :: ParsedModule -> ParsedModule+enableTopLevelWarnings =+  (update_pm_mod_summary . update_hspp_opts)+  (`wopt_set` Opt_WarnMissingSignatures)+  -- the line below would show also warnings for let bindings without signature+  -- ((`wopt_set` Opt_WarnMissingSignatures) . (`wopt_set` Opt_WarnMissingLocalSignatures)) -  update_pm_mod_summary :: (ModSummary -> ModSummary) -> ParsedModule -> ParsedModule-  update_pm_mod_summary up pm =-    pm{pm_mod_summary = up $ pm_mod_summary pm}+update_hspp_opts :: (DynFlags -> DynFlags) -> ModSummary -> ModSummary+update_hspp_opts up ms = ms{ms_hspp_opts = up $ ms_hspp_opts ms} +update_pm_mod_summary :: (ModSummary -> ModSummary) -> ParsedModule -> ParsedModule+update_pm_mod_summary up pm =+  pm{pm_mod_summary = up $ pm_mod_summary pm}+ unDefer :: (WarnReason, FileDiagnostic) -> FileDiagnostic unDefer (Reason Opt_WarnDeferredTypeErrors         , fd) = upgradeWarningToError fd unDefer (Reason Opt_WarnTypedHoles                 , fd) = upgradeWarningToError fd@@ -184,10 +194,15 @@ unDefer ( _                                        , fd) = fd  upgradeWarningToError :: FileDiagnostic -> FileDiagnostic-upgradeWarningToError (nfp, fd) =-  (nfp, fd{_severity = Just DsError, _message = warn2err $ _message fd}) where+upgradeWarningToError (nfp, sh, fd) =+  (nfp, sh, fd{_severity = Just DsError, _message = warn2err $ _message fd}) where   warn2err :: T.Text -> T.Text   warn2err = T.intercalate ": error:" . T.splitOn ": warning:"++hideDiag :: DynFlags -> (WarnReason, FileDiagnostic) -> (WarnReason, FileDiagnostic)+hideDiag originalFlags (Reason warning, (nfp, _sh, fd))+  | not (wopt warning originalFlags) = (Reason warning, (nfp, HideDiag, fd))+hideDiag _originalFlags t = t   addRelativeImport :: NormalizedFilePath -> ParsedModule -> DynFlags -> DynFlags addRelativeImport fp modu dflags = dflags
+ src/Development/IDE/Core/Completions.hs view
@@ -0,0 +1,539 @@+-- Mostly taken from "haskell-ide-engine"+module Development.IDE.Core.Completions (+  CachedCompletions+, cacheDataProducer+, WithSnippets(..)+,getCompletions+) where++import Control.Applicative+import Data.Char (isSpace, isUpper)+import Data.Generics+import Data.List as List hiding (stripPrefix)+import qualified Data.Map  as Map+import Data.Maybe (fromMaybe, mapMaybe)+import qualified Data.Text as T+import qualified Text.Fuzzy as Fuzzy++import GHC+import HscTypes+import Name+import RdrName+import TcRnTypes+import Type+import Var+import Packages+import DynFlags+import ConLike+import DataCon+import SrcLoc as GHC++import Language.Haskell.LSP.Types+import Language.Haskell.LSP.Types.Capabilities+import qualified Language.Haskell.LSP.VFS as VFS+import Development.IDE.Core.CompletionsTypes+import Development.IDE.Spans.Documentation+import Development.IDE.GHC.Util+import Development.IDE.GHC.Error+import Development.IDE.Types.Options++-- From haskell-ide-engine/src/Haskell/Ide/Engine/Support/HieExtras.hs++safeTyThingId :: TyThing -> Maybe Id+safeTyThingId (AnId i)                    = Just i+safeTyThingId (AConLike (RealDataCon dc)) = Just $ dataConWrapId dc+safeTyThingId _                           = Nothing++safeTyThingType :: TyThing -> Maybe Type+safeTyThingType thing+  | Just i <- safeTyThingId thing = Just (varType i)+safeTyThingType (ATyCon tycon)    = Just (tyConKind tycon)+safeTyThingType _                 = Nothing++-- From haskell-ide-engine/hie-plugin-api/Haskell/Ide/Engine/Context.hs++-- | A context of a declaration in the program+-- e.g. is the declaration a type declaration or a value declaration+-- Used for determining which code completions to show+-- TODO: expand this with more contexts like classes or instances for+-- smarter code completion+data Context = TypeContext+             | ValueContext+             | ModuleContext String -- ^ module context with module name+             | ImportContext String -- ^ import context with module name+             | ImportListContext String -- ^ import list context with module name+             | ImportHidingContext String -- ^ import hiding context with module name+             | ExportContext -- ^ List of exported identifiers from the current module+  deriving (Show, Eq)++-- | Generates a map of where the context is a type and where the context is a value+-- i.e. where are the value decls and the type decls+getCContext :: Position -> ParsedModule -> Maybe Context+getCContext pos pm+  | Just (L (RealSrcSpan r) modName) <- moduleHeader+  , pos `isInsideRange` r+  = Just (ModuleContext (moduleNameString modName))++  | Just (L (RealSrcSpan r) _) <- exportList+  , pos `isInsideRange` r+  = Just ExportContext++  | Just ctx <- something (Nothing `mkQ` go `extQ` goInline) decl+  = Just ctx++  | Just ctx <- something (Nothing `mkQ` importGo) imports+  = Just ctx++  | otherwise+  = Nothing++  where decl = hsmodDecls $ unLoc $ pm_parsed_source pm+        moduleHeader = hsmodName $ unLoc $ pm_parsed_source pm+        exportList = hsmodExports $ unLoc $ pm_parsed_source pm+        imports = hsmodImports $ unLoc $ pm_parsed_source pm++        go :: LHsDecl GhcPs -> Maybe Context+        go (L (RealSrcSpan r) SigD {})+          | pos `isInsideRange` r = Just TypeContext+          | otherwise = Nothing+        go (L (GHC.RealSrcSpan r) GHC.ValD {})+          | pos `isInsideRange` r = Just ValueContext+          | otherwise = Nothing+        go _ = Nothing++        goInline :: GHC.LHsType GhcPs -> Maybe Context+        goInline (GHC.L (GHC.RealSrcSpan r) _)+          | pos `isInsideRange` r = Just TypeContext+          | otherwise = Nothing+        goInline _ = Nothing++        p `isInsideRange` r = sp <= p && p <= ep+          where (sp, ep) = unpackRealSrcSpan r++        -- | Converts from one based tuple+        toPos :: (Int,Int) -> Position+        toPos (l,c) = Position (l-1) (c-1)+          +        unpackRealSrcSpan :: GHC.RealSrcSpan -> (Position, Position)+        unpackRealSrcSpan rspan =+          (toPos (l1,c1),toPos (l2,c2))+          where s  = GHC.realSrcSpanStart rspan+                l1 = GHC.srcLocLine s+                c1 = GHC.srcLocCol s+                e  = GHC.realSrcSpanEnd rspan+                l2 = GHC.srcLocLine e+                c2 = GHC.srcLocCol e++        importGo :: GHC.LImportDecl GhcPs -> Maybe Context+        importGo (L (RealSrcSpan r) impDecl)+          | pos `isInsideRange` r+          = importInline importModuleName (ideclHiding impDecl)+          <|> Just (ImportContext importModuleName)++          | otherwise = Nothing+          where importModuleName = moduleNameString $ unLoc $ ideclName impDecl++        importGo _ = Nothing++        importInline :: String -> Maybe (Bool,  GHC.Located [LIE GhcPs]) -> Maybe Context+        importInline modName (Just (True, L (RealSrcSpan r) _))+          | pos `isInsideRange` r = Just $ ImportHidingContext modName+          | otherwise = Nothing+        importInline modName (Just (False, L (RealSrcSpan r) _))+          | pos `isInsideRange` r = Just $ ImportListContext modName+          | otherwise = Nothing+        importInline _ _ = Nothing++occNameToComKind :: Maybe T.Text -> OccName -> CompletionItemKind+occNameToComKind ty oc+  | isVarOcc  oc = case occNameString oc of+                     i:_ | isUpper i -> CiConstructor+                     _               -> CiFunction+  | isTcOcc   oc = case ty of+                     Just t+                       | "Constraint" `T.isSuffixOf` t +                       -> CiClass+                     _ -> CiStruct+  | isDataOcc oc = CiConstructor+  | otherwise    = CiVariable++mkCompl :: IdeOptions -> CompItem -> CompletionItem+mkCompl IdeOptions{..} CI{origName,importedFrom,thingType,label,isInfix,docs} =+  CompletionItem label kind ((colon <>) <$> typeText)+    (Just $ CompletionDocMarkup $ MarkupContent MkMarkdown $ T.intercalate sectionSeparator docs')+    Nothing Nothing Nothing Nothing (Just insertText) (Just Snippet)+    Nothing Nothing Nothing Nothing Nothing+  where kind = Just $ occNameToComKind typeText $ occName origName+        insertText = case isInfix of+            Nothing -> case getArgText <$> thingType of+                            Nothing -> label+                            Just argText -> label <> " " <> argText+            Just LeftSide -> label <> "`"++            Just Surrounded -> label+        typeText+          | Just t <- thingType = Just . stripForall $ T.pack (showGhc t)+          | otherwise = Nothing+        docs' = ("*Defined in '" <> importedFrom <> "'*\n") : docs+        colon = if optNewColonConvention then ": " else ":: "++stripForall :: T.Text -> T.Text+stripForall t+  | T.isPrefixOf "forall" t =+    -- We drop 2 to remove the '.' and the space after it+    T.drop 2 (T.dropWhile (/= '.') t)+  | otherwise               = t++getArgText :: Type -> T.Text+getArgText typ = argText+  where+    argTypes = getArgs typ+    argText :: T.Text+    argText =  mconcat $ List.intersperse " " $ zipWith snippet [1..] argTypes+    snippet :: Int -> Type -> T.Text+    snippet i t = T.pack $ "${" <> show i <> ":" <> showGhc t <> "}"+    getArgs :: Type -> [Type]+    getArgs t+      | isPredTy t = []+      | isDictTy t = []+      | isForAllTy t = getArgs $ snd (splitForAllTys t)+      | isFunTy t =+        let (args, ret) = splitFunTys t+          in if isForAllTy ret+              then getArgs ret+              else Prelude.filter (not . isDictTy) args+      | isPiTy t = getArgs $ snd (splitPiTys t)+      | isCoercionTy t = maybe [] (getArgs . snd) (splitCoercionType_maybe t)+      | otherwise = []++mkModCompl :: T.Text -> CompletionItem+mkModCompl label =+  CompletionItem label (Just CiModule) Nothing+    Nothing Nothing Nothing Nothing Nothing Nothing Nothing+    Nothing Nothing Nothing Nothing Nothing++mkImportCompl :: T.Text -> T.Text -> CompletionItem+mkImportCompl enteredQual label =+  CompletionItem m (Just CiModule) (Just label)+    Nothing Nothing Nothing Nothing Nothing Nothing Nothing+    Nothing Nothing Nothing Nothing Nothing+  where +    m = fromMaybe "" (T.stripPrefix enteredQual label)++mkExtCompl :: T.Text -> CompletionItem+mkExtCompl label =+  CompletionItem label (Just CiKeyword) Nothing+    Nothing Nothing Nothing Nothing Nothing Nothing Nothing+    Nothing Nothing Nothing Nothing Nothing++mkPragmaCompl :: T.Text -> T.Text -> CompletionItem+mkPragmaCompl label insertText =+  CompletionItem label (Just CiKeyword) Nothing+    Nothing Nothing Nothing Nothing Nothing (Just insertText) (Just Snippet)+    Nothing Nothing Nothing Nothing Nothing++cacheDataProducer :: HscEnv -> DynFlags -> TypecheckedModule -> [TypecheckedModule] -> IO CachedCompletions+cacheDataProducer packageState dflags tm tcs = do+  let parsedMod = tm_parsed_module tm+      curMod = moduleName $ ms_mod $ pm_mod_summary parsedMod+      Just (_,limports,_,_) = tm_renamed_source tm++      iDeclToModName :: ImportDecl name -> ModuleName+      iDeclToModName = unLoc . ideclName++      showModName :: ModuleName -> T.Text+      showModName = T.pack . moduleNameString++      asNamespace :: ImportDecl name -> ModuleName+      asNamespace imp = maybe (iDeclToModName imp) GHC.unLoc (ideclAs imp)+      -- Full canonical names of imported modules+      importDeclerations = map unLoc limports++      -- The list of all importable Modules from all packages+      moduleNames = map showModName (listVisibleModuleNames dflags)++      -- The given namespaces for the imported modules (ie. full name, or alias if used)+      allModNamesAsNS = map (showModName . asNamespace) importDeclerations++      typeEnv = tcg_type_env $ fst $ tm_internals_ tm+      rdrEnv = tcg_rdr_env $ fst $ tm_internals_ tm+      rdrElts = globalRdrEnvElts rdrEnv++      foldMapM :: (Foldable f, Monad m, Monoid b) => (a -> m b) -> f a -> m b+      foldMapM f xs = foldr step return xs mempty where+        step x r z = f x >>= \y -> r $! z `mappend` y++      getCompls :: [GlobalRdrElt] -> IO ([CompItem],QualCompls)+      getCompls = foldMapM getComplsForOne++      getComplsForOne :: GlobalRdrElt -> IO ([CompItem],QualCompls)+      getComplsForOne (GRE n _ True _) =+        case lookupTypeEnv typeEnv n of+          Just tt -> case safeTyThingId tt of+            Just var -> (\x -> ([x],mempty)) <$> varToCompl var+            Nothing -> (\x -> ([x],mempty)) <$> toCompItem curMod n+          Nothing -> (\x -> ([x],mempty)) <$> toCompItem curMod n+      getComplsForOne (GRE n _ False prov) =+        flip foldMapM (map is_decl prov) $ \spec -> do+          compItem <- toCompItem (is_mod spec) n+          let unqual+                | is_qual spec = []+                | otherwise = [compItem]+              qual+                | is_qual spec = Map.singleton asMod [compItem]+                | otherwise = Map.fromList [(asMod,[compItem]),(origMod,[compItem])]+              asMod = showModName (is_as spec)+              origMod = showModName (is_mod spec)+          return (unqual,QualCompls qual)++      varToCompl :: Var -> IO CompItem+      varToCompl var = do+        let typ = Just $ varType var+            name = Var.varName var+            label = T.pack $ showGhc name+        docs <- getDocumentationTryGhc packageState (tm:tcs) name+        return $ CI name (showModName curMod) typ label Nothing docs++      toCompItem :: ModuleName -> Name -> IO CompItem+      toCompItem mn n = do+        docs <- getDocumentationTryGhc packageState (tm:tcs) n+        ty <- runGhcEnv packageState $ catchSrcErrors "completion" $ do+                name' <- lookupName n+                return $ name' >>= safeTyThingType+        return $ CI n (showModName mn) (either (const Nothing) id ty) (T.pack $ showGhc n) Nothing docs++  (unquals,quals) <- getCompls rdrElts++  return $ CC+    { allModNamesAsNS = allModNamesAsNS+    , unqualCompls = unquals+    , qualCompls = quals+    , importableModules = moduleNames+    }++newtype WithSnippets = WithSnippets Bool++toggleSnippets :: ClientCapabilities -> WithSnippets -> CompletionItem -> CompletionItem+toggleSnippets ClientCapabilities { _textDocument } (WithSnippets with) x+  | with && supported = x+  | otherwise = x { _insertTextFormat = Just PlainText+                  , _insertText       = Nothing+                  }+  where supported = fromMaybe False (_textDocument >>= _completion >>= _completionItem >>= _snippetSupport)++-- | Returns the cached completions for the given module and position.+getCompletions :: IdeOptions -> CachedCompletions -> TypecheckedModule -> VFS.PosPrefixInfo -> ClientCapabilities -> WithSnippets -> IO [CompletionItem]+getCompletions ideOpts CC { allModNamesAsNS, unqualCompls, qualCompls, importableModules }+               tm prefixInfo caps withSnippets = do+  let VFS.PosPrefixInfo { VFS.fullLine, VFS.prefixModule, VFS.prefixText } = prefixInfo+      enteredQual = if T.null prefixModule then "" else prefixModule <> "."+      fullPrefix  = enteredQual <> prefixText++      -- default to value context if no explicit context+      context = fromMaybe ValueContext $ getCContext pos (tm_parsed_module tm)++      {- correct the position by moving 'foo :: Int -> String ->    '+                                                                    ^+          to                             'foo :: Int -> String ->    '+                                                              ^+      -}+      pos =+        let Position l c = VFS.cursorPos prefixInfo+            typeStuff = [isSpace, (`elem` (">-." :: String))]+            stripTypeStuff = T.dropWhileEnd (\x -> any (\f -> f x) typeStuff)+            -- if oldPos points to+            -- foo -> bar -> baz+            --    ^+            -- Then only take the line up to there, discard '-> bar -> baz'+            partialLine = T.take c fullLine+            -- drop characters used when writing incomplete type sigs+            -- like '-> '+            d = T.length fullLine - T.length (stripTypeStuff partialLine)+        in Position l (c - d)++      filtModNameCompls =+        map mkModCompl+          $ mapMaybe (T.stripPrefix enteredQual)+          $ Fuzzy.simpleFilter fullPrefix allModNamesAsNS++      filtCompls = map Fuzzy.original $ Fuzzy.filter prefixText ctxCompls "" "" label False+        where+          isTypeCompl = isTcOcc . occName . origName+          -- completions specific to the current context+          ctxCompls' = case context of+                        TypeContext -> filter isTypeCompl compls+                        ValueContext -> filter (not . isTypeCompl) compls+                        _ -> filter (not . isTypeCompl) compls+          -- Add whether the text to insert has backticks+          ctxCompls = map (\comp -> comp { isInfix = infixCompls }) ctxCompls'++          infixCompls :: Maybe Backtick+          infixCompls = isUsedAsInfix fullLine prefixModule prefixText (VFS.cursorPos prefixInfo)++          compls = if T.null prefixModule+            then unqualCompls+            else Map.findWithDefault [] prefixModule $ getQualCompls qualCompls++      filtListWith f list =+        [ f label+        | label <- Fuzzy.simpleFilter fullPrefix list+        , enteredQual `T.isPrefixOf` label+        ]++      filtListWithSnippet f list suffix =+        [ toggleSnippets caps withSnippets (f label (snippet <> suffix))+        | (snippet, label) <- list+        , Fuzzy.test fullPrefix label+        ]++      filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules+      filtPragmaCompls = filtListWithSnippet mkPragmaCompl validPragmas+      filtOptsCompls   = filtListWith mkExtCompl++      stripLeading :: Char -> String -> String+      stripLeading _ [] = []+      stripLeading c (s:ss)+        | s == c = ss+        | otherwise = s:ss++      result+        | "import " `T.isPrefixOf` fullLine+        = filtImportCompls+        | "{-# language" `T.isPrefixOf` T.toLower fullLine+        = filtOptsCompls languagesAndExts+        | "{-# options_ghc" `T.isPrefixOf` T.toLower fullLine+        = filtOptsCompls (map (T.pack . stripLeading '-') $ flagsForCompletion False)+        | "{-# " `T.isPrefixOf` fullLine+        = filtPragmaCompls (pragmaSuffix fullLine)+        | otherwise+        = filtModNameCompls ++ map (toggleSnippets caps withSnippets+                                      . mkCompl ideOpts . stripAutoGenerated) filtCompls+  +  return result++-- The supported languages and extensions+languagesAndExts :: [T.Text]+languagesAndExts = map T.pack DynFlags.supportedLanguagesAndExtensions+  +-- ---------------------------------------------------------------------+-- helper functions for pragmas+-- ---------------------------------------------------------------------++validPragmas :: [(T.Text, T.Text)]+validPragmas =+  [ ("LANGUAGE ${1:extension}"        , "LANGUAGE")+  , ("OPTIONS_GHC -${1:option}"       , "OPTIONS_GHC")+  , ("INLINE ${1:function}"           , "INLINE")+  , ("NOINLINE ${1:function}"         , "NOINLINE")+  , ("INLINABLE ${1:function}"        , "INLINABLE")+  , ("WARNING ${1:message}"           , "WARNING")+  , ("DEPRECATED ${1:message}"        , "DEPRECATED")+  , ("ANN ${1:annotation}"            , "ANN")+  , ("RULES"                          , "RULES")+  , ("SPECIALIZE ${1:function}"       , "SPECIALIZE")+  , ("SPECIALIZE INLINE ${1:function}", "SPECIALIZE INLINE")+  ]++pragmaSuffix :: T.Text -> T.Text+pragmaSuffix fullLine+  |  "}" `T.isSuffixOf` fullLine = mempty+  | otherwise = " #-}"++-- ---------------------------------------------------------------------+-- helper functions for infix backticks+-- ---------------------------------------------------------------------++hasTrailingBacktick :: T.Text -> Position -> Bool+hasTrailingBacktick line Position { _character }+    | T.length line > _character = (line `T.index` _character) == '`'+    | otherwise = False++isUsedAsInfix :: T.Text -> T.Text -> T.Text -> Position -> Maybe Backtick+isUsedAsInfix line prefixMod prefixText pos+    | hasClosingBacktick && hasOpeningBacktick = Just Surrounded+    | hasOpeningBacktick = Just LeftSide+    | otherwise = Nothing+  where+    hasOpeningBacktick = openingBacktick line prefixMod prefixText pos+    hasClosingBacktick = hasTrailingBacktick line pos++openingBacktick :: T.Text -> T.Text -> T.Text -> Position -> Bool+openingBacktick line prefixModule prefixText Position { _character }+  | backtickIndex < 0 = False+  | otherwise = (line `T.index` backtickIndex) == '`'+    where+    backtickIndex :: Int+    backtickIndex =+      let+          prefixLength = T.length prefixText+          moduleLength = if prefixModule == ""+                    then 0+                    else T.length prefixModule + 1 {- Because of "." -}+      in+        -- Points to the first letter of either the module or prefix text+        _character - (prefixLength + moduleLength) - 1+++-- ---------------------------------------------------------------------++-- | Under certain circumstance GHC generates some extra stuff that we+-- don't want in the autocompleted symbols+stripAutoGenerated :: CompItem -> CompItem+stripAutoGenerated ci =+    ci {label = stripPrefix (label ci)}+    {- When e.g. DuplicateRecordFields is enabled, compiler generates+    names like "$sel:accessor:One" and "$sel:accessor:Two" to disambiguate record selectors+    https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields/DuplicateRecordFields#Implementation+    -}++-- TODO: Turn this into an alex lexer that discards prefixes as if they were whitespace.++stripPrefix :: T.Text -> T.Text+stripPrefix name = T.takeWhile (/=':') $ go prefixes+  where+    go [] = name+    go (p:ps)+      | T.isPrefixOf p name = T.drop (T.length p) name+      | otherwise = go ps++-- | Prefixes that can occur in a GHC OccName+prefixes :: [T.Text]+prefixes =+  [+    -- long ones+    "$con2tag_"+  , "$tag2con_"+  , "$maxtag_"++  -- four chars+  , "$sel:"+  , "$tc'"++  -- three chars+  , "$dm"+  , "$co"+  , "$tc"+  , "$cp"+  , "$fx"++  -- two chars+  , "$W"+  , "$w"+  , "$m"+  , "$b"+  , "$c"+  , "$d"+  , "$i"+  , "$s"+  , "$f"+  , "$r"+  , "C:"+  , "N:"+  , "D:"+  , "$p"+  , "$L"+  , "$f"+  , "$t"+  , "$c"+  , "$m"+  ]
+ src/Development/IDE/Core/CompletionsTypes.hs view
@@ -0,0 +1,62 @@+module Development.IDE.Core.CompletionsTypes (+  module Development.IDE.Core.CompletionsTypes+) where++import           Control.DeepSeq+import qualified Data.Map  as Map+import qualified Data.Text as T++import           GHC+import           Outputable+import           DynFlags++-- From haskell-ide-engine/src/Haskell/Ide/Engine/LSP/Completions.hs++showGhc :: Outputable a => a -> String+showGhc = showPpr unsafeGlobalDynFlags++data Backtick = Surrounded | LeftSide deriving Show+data CompItem = CI+  { origName     :: Name           -- ^ Original name, such as Maybe, //, or find.+  , importedFrom :: T.Text         -- ^ From where this item is imported from.+  , thingType    :: Maybe Type     -- ^ Available type information.+  , label        :: T.Text         -- ^ Label to display to the user.+  , isInfix      :: Maybe Backtick -- ^ Did the completion happen+                                   -- in the context of an infix notation.+  , docs         :: [T.Text]       -- ^ Available documentation.+  }+instance Show CompItem where+  show CI { .. } = "CompItem { origName = \"" ++ showGhc origName ++ "\""+                   ++ ", importedFrom = " ++ show importedFrom+                   ++ ", thingType = " ++ show (fmap showGhc thingType)+                   ++ ", label = " ++ show label+                   ++ ", isInfix = " ++ show isInfix +                   ++ ", docs = " ++ show docs+                   ++ " } "+instance Eq CompItem where+  ci1 == ci2 = origName ci1 == origName ci2+instance Ord CompItem where+  compare ci1 ci2 = origName ci1 `compare` origName ci2++-- Associates a module's qualifier with its members+newtype QualCompls+  = QualCompls { getQualCompls :: Map.Map T.Text [CompItem] }+  deriving Show+instance Semigroup QualCompls where+  (QualCompls a) <> (QualCompls b) = QualCompls $ Map.unionWith (++) a b+instance Monoid QualCompls where+  mempty = QualCompls Map.empty+  mappend = (Prelude.<>)++-- | End result of the completions+data CachedCompletions = CC+  { allModNamesAsNS :: [T.Text] -- ^ All module names in scope.+                                -- Prelude is a single module+  , unqualCompls :: [CompItem]  -- ^ All Possible completion items+  , qualCompls :: QualCompls    -- ^ Completion items associated to+                                -- to a specific module name.+  , importableModules :: [T.Text] -- ^ All modules that may be imported.+  } deriving Show++instance NFData CachedCompletions where+    rnf = rwhnf
src/Development/IDE/Core/FileStore.hs view
@@ -76,7 +76,8 @@               modifyVar_ vfsVar $ \(nextVersion, vfs) -> pure $ (nextVersion + 1, ) $                   case content of                     Nothing -> Map.delete uri vfs-                    Just content -> Map.insert uri (VirtualFile nextVersion (Rope.fromText content)) vfs+                    -- The second version number is only used in persistFileVFS which we do not use so we set it to 0.+                    Just content -> Map.insert uri (VirtualFile nextVersion 0 (Rope.fromText content)) vfs         }  makeLSPVFSHandle :: LspFuncs c -> VFSHandle@@ -139,7 +140,7 @@         alwaysRerun         mbVirtual <- liftIO $ getVirtualFile vfs $ filePathToUri' file         case mbVirtual of-            Just (VirtualFile ver _) -> pure (Just $ BS.pack $ show ver, ([], Just $ VFSVersion ver))+            Just (virtualFileVersion -> ver) -> pure (Just $ BS.pack $ show ver, ([], Just $ VFSVersion ver))             Nothing -> liftIO $ fmap wrap (getModTime file')               `catch` \(e :: IOException) -> do                 let err | isDoesNotExistError e = "File does not exist: " ++ file'
src/Development/IDE/Core/Preprocessor.hs view
@@ -8,7 +8,6 @@ import Development.IDE.GHC.CPP import Development.IDE.GHC.Orphans() import Development.IDE.GHC.Compat-import GHC import GhcMonad import StringBuffer as SB @@ -19,11 +18,17 @@ import DynFlags import qualified HeaderInfo as Hdr import Development.IDE.Types.Diagnostics+import Development.IDE.Types.Location import Development.IDE.GHC.Error-import SysTools (Option (..), runUnlit)+import SysTools (Option (..), runUnlit, runPp) import Control.Monad.Trans.Except import qualified GHC.LanguageExtensions as LangExt import Data.Maybe+import Control.Exception.Safe (catch, throw)+import Data.IORef (IORef, modifyIORef, newIORef, readIORef)+import Data.Text (Text)+import qualified Data.Text as T+import Outputable (showSDoc)   -- | Given a file and some contents, apply any necessary preprocessors,@@ -43,14 +48,80 @@      -- Perform cpp     dflags  <- ExceptT $ parsePragmasIntoDynFlags filename contents-    if not $ xopt LangExt.Cpp dflags then+    (isOnDisk, contents, dflags) <-+        if not $ xopt LangExt.Cpp dflags then+            return (isOnDisk, contents, dflags)+        else do+            cppLogs <- liftIO $ newIORef []+            contents <- ExceptT+                        $ liftIO+                        $ (Right <$> (runCpp dflags {log_action = logAction cppLogs} filename+                                       $ if isOnDisk then Nothing else Just contents))+                            `catch`+                            ( \(e :: GhcException) -> do+                                logs <- readIORef cppLogs+                                case diagsFromCPPLogs filename (reverse logs) of+                                  [] -> throw e+                                  diags -> return $ Left diags+                            )+            dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents+            return (False, contents, dflags)++    -- Perform preprocessor+    if not $ gopt Opt_Pp dflags then         return (contents, dflags)     else do-        contents <- liftIO $ runCpp dflags filename $ if isOnDisk then Nothing else Just contents+        contents <- liftIO $ runPreprocessor dflags filename $ if isOnDisk then Nothing else Just contents         dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents         return (contents, dflags)+  where+    logAction :: IORef [CPPLog] -> LogAction+    logAction cppLogs dflags _reason severity srcSpan _style msg = do+      let log = CPPLog severity srcSpan $ T.pack $ showSDoc dflags msg+      modifyIORef cppLogs (log :)  +data CPPLog = CPPLog Severity SrcSpan Text+  deriving (Show)+++data CPPDiag+  = CPPDiag+      { cdRange :: Range,+        cdSeverity :: Maybe DiagnosticSeverity,+        cdMessage :: [Text]+      }+  deriving (Show)+++diagsFromCPPLogs :: FilePath -> [CPPLog] -> [FileDiagnostic]+diagsFromCPPLogs filename logs =+  map (\d -> (toNormalizedFilePath filename, ShowDiag, cppDiagToDiagnostic d)) $+    go [] logs+  where+    -- On errors, CPP calls logAction with a real span for the initial log and+    -- then additional informational logs with `UnhelpfulSpan`. Collect those+    -- informational log messages and attaches them to the initial log message.+    go :: [CPPDiag] -> [CPPLog] -> [CPPDiag]+    go acc [] = reverse $ map (\d -> d {cdMessage = reverse $ cdMessage d}) acc+    go acc (CPPLog sev span@(RealSrcSpan _) msg : logs) =+      let diag = CPPDiag (srcSpanToRange span) (toDSeverity sev) [msg]+       in go (diag : acc) logs+    go (diag : diags) (CPPLog _sev (UnhelpfulSpan _) msg : logs) =+      go (diag {cdMessage = msg : cdMessage diag} : diags) logs+    go [] (CPPLog _sev (UnhelpfulSpan _) _msg : logs) = go [] logs+    cppDiagToDiagnostic :: CPPDiag -> Diagnostic+    cppDiagToDiagnostic d =+      Diagnostic+        { _range = cdRange d,+          _severity = cdSeverity d,+          _code = Nothing,+          _source = Just "CPP",+          _message = T.unlines $ cdMessage d,+          _relatedInformation = Nothing+        }++ isLiterate :: FilePath -> Bool isLiterate x = takeExtension x `elem` [".lhs",".lhs-boot"] @@ -132,3 +203,18 @@                         = "# " <> num <> " \"" <> map (\x -> if isPathSeparator x then '/' else x) filename <> "\""                     | otherwise = x             stringToStringBuffer . unlines . map tweak . lines <$> readFileUTF8' out+++-- | Run a preprocessor on a file+runPreprocessor :: DynFlags -> FilePath -> Maybe SB.StringBuffer -> IO SB.StringBuffer+runPreprocessor dflags filename contents = withTempDir $ \dir -> do+    let out = dir </> takeFileName filename <.> "out"+    inp <- case contents of+        Nothing -> return filename+        Just contents -> do+            let inp = dir </> takeFileName filename <.> "hs"+            withBinaryFile inp WriteMode $ \h ->+                hPutStringBuffer h contents+            return inp+    runPp dflags [SysTools.Option filename, SysTools.Option inp, SysTools.FileOption "" out]+    SB.hGetStringBuffer out
src/Development/IDE/Core/RuleTypes.hs view
@@ -27,6 +27,7 @@ import HscTypes (CgGuts, Linkable, HomeModInfo, ModDetails) import Development.IDE.GHC.Compat +import           Development.IDE.Core.CompletionsTypes import           Development.IDE.Spans.Type  @@ -85,7 +86,10 @@ -- | Read the given HIE file. type instance RuleResult GetHieFile = HieFile +-- | Produce completions info for a file+type instance RuleResult ProduceCompletions = (CachedCompletions, TcModuleResult) + data GetParsedModule = GetParsedModule     deriving (Eq, Show, Typeable, Generic) instance Hashable GetParsedModule@@ -153,3 +157,9 @@ instance Hashable GetHieFile instance NFData   GetHieFile instance Binary   GetHieFile++data ProduceCompletions = ProduceCompletions+    deriving (Eq, Show, Typeable, Generic)+instance Hashable ProduceCompletions+instance NFData   ProduceCompletions+instance Binary   ProduceCompletions
src/Development/IDE/Core/Rules.hs view
@@ -32,6 +32,7 @@ import Control.Monad.Trans.Class import Control.Monad.Trans.Maybe import Development.IDE.Core.Compile+import Development.IDE.Core.Completions import Development.IDE.Types.Options import Development.IDE.Spans.Calculate import Development.IDE.Import.DependencyInformation@@ -55,7 +56,7 @@  import           GHC hiding (parseModule, typecheckModule) import qualified GHC.LanguageExtensions as LangExt-import Development.IDE.GHC.Compat+import Development.IDE.GHC.Compat (hie_file_result, readHieFile) import           UniqSupply import NameCache import HscTypes@@ -102,17 +103,17 @@ getAtPoint :: NormalizedFilePath -> Position -> Action (Maybe (Maybe Range, [T.Text])) getAtPoint file pos = fmap join $ runMaybeT $ do   opts <- lift getIdeOptions+  spans <- useE GetSpanInfo file   files <- transitiveModuleDeps <$> useE GetDependencies file   tms   <- usesE TypeCheck (file : files)-  spans <- useE GetSpanInfo file   return $ AtPoint.atPoint opts (map tmrModule tms) spans pos  -- | Goto Definition. getDefinition :: NormalizedFilePath -> Position -> Action (Maybe Location) getDefinition file pos = fmap join $ runMaybeT $ do+    opts <- lift getIdeOptions     spans <- useE GetSpanInfo file     pkgState <- hscEnv <$> useE GhcSession file-    opts <- lift getIdeOptions     let getHieFile x = useNoFile (GetHieFile x)     lift $ AtPoint.gotoDefinition getHieFile opts pkgState spans pos @@ -229,7 +230,7 @@     where cycleErrorInFile f (PartOfCycle imp fs)             | f `elem` fs = Just (imp, fs)           cycleErrorInFile _ _ = Nothing-          toDiag imp mods = (fp ,) $ Diagnostic+          toDiag imp mods = (fp , ShowDiag , ) $ Diagnostic             { _range = (_range :: Location -> Range) loc             , _severity = Just DsError             , _source = Just "Import cycle detection"@@ -304,6 +305,20 @@ generateCoreRule =     define $ \GenerateCore -> generateCore +produceCompletions :: Rules ()+produceCompletions =+    define $ \ProduceCompletions file -> do+        deps <- maybe (TransitiveDependencies [] []) fst <$> useWithStale GetDependencies file+        tms <- mapMaybe (fmap fst) <$> usesWithStale TypeCheck (transitiveModuleDeps deps)+        tm <- fmap fst <$> useWithStale TypeCheck file+        packageState <- fmap (hscEnv . fst) <$> useWithStale GhcSession file+        case (tm, packageState) of+            (Just tm', Just packageState') -> do+                cdata <- liftIO $ cacheDataProducer packageState' (hsc_dflags packageState')+                                                    (tmrModule tm') (map tmrModule tms)+                return ([], Just (cdata, tm'))+            _ -> return ([], Nothing)+ generateByteCodeRule :: Rules () generateByteCodeRule =     define $ \GenerateByteCode file -> do@@ -361,6 +376,7 @@     generateByteCodeRule     loadGhcSession     getHieFileRule+    produceCompletions  ------------------------------------------------------------ 
src/Development/IDE/Core/Shake.hs view
@@ -28,6 +28,7 @@     use_, useNoFile_, uses_,     define, defineEarlyCutoff, defineOnDisk, needOnDisk, needOnDisks, fingerprintToBS,     getDiagnostics, unsafeClearDiagnostics,+    getHiddenDiagnostics,     IsIdeGlobal, addIdeGlobal, getIdeGlobalState, getIdeGlobalAction,     garbageCollect,     setPriority,@@ -52,9 +53,10 @@ import           Data.Dynamic import           Data.Maybe import Data.Map.Strict (Map)-import           Data.List.Extra+import           Data.List.Extra (foldl', partition, takeEnd) import qualified Data.Set as Set import qualified Data.Text as T+import Data.Traversable (for) import Data.Tuple.Extra import Data.Unique import Development.IDE.Core.Debouncer@@ -93,6 +95,7 @@     ,globals :: Var (HMap.HashMap TypeRep Dynamic)     ,state :: Var Values     ,diagnostics :: Var DiagnosticStore+    ,hiddenDiagnostics :: Var DiagnosticStore     ,publishedDiagnostics :: Var (Map NormalizedUri [Diagnostic])     -- ^ This represents the set of diagnostics that we have published.     -- Due to debouncing not every change might get published.@@ -225,14 +228,15 @@   -- This is debugging code that generates a series of profiles, if the Boolean is true-shakeRunDatabaseProfile :: Maybe FilePath -> ShakeDatabase -> [Action a] -> IO [a]+shakeRunDatabaseProfile :: Maybe FilePath -> ShakeDatabase -> [Action a] -> IO ([a], Maybe FilePath) shakeRunDatabaseProfile mbProfileDir shakeDb acts = do         (time, (res,_)) <- duration $ shakeRunDatabase shakeDb acts-        whenJust mbProfileDir $ \dir -> do-            count <- modifyVar profileCounter $ \x -> let !y = x+1 in return (y,y)-            let file = "ide-" ++ profileStartTime ++ "-" ++ takeEnd 5 ("0000" ++ show count) ++ "-" ++ showDP 2 time <.> "html"-            shakeProfileDatabase shakeDb $ dir </> file-        return res+        proFile <- for mbProfileDir $ \dir -> do+                count <- modifyVar profileCounter $ \x -> let !y = x+1 in return (y,y)+                let file = "ide-" ++ profileStartTime ++ "-" ++ takeEnd 5 ("0000" ++ show count) ++ "-" ++ showDP 2 time <.> "html"+                shakeProfileDatabase shakeDb $ dir </> file+                return (dir </> file)+        return (res, proFile)     where  {-# NOINLINE profileStartTime #-}@@ -289,6 +293,7 @@         globals <- newVar HMap.empty         state <- newVar HMap.empty         diagnostics <- newVar mempty+        hiddenDiagnostics <- newVar mempty         publishedDiagnostics <- newVar mempty         debouncer <- newDebouncer         positionMapping <- newVar Map.empty@@ -389,9 +394,15 @@                   let res' = case res of                           Left e -> "exception: " <> displayException e                           Right _ -> "completed"+                      profile = case res of+                          Right (_, Just fp) -> +                              let link = case filePathToUri' $ toNormalizedFilePath fp of+                                            NormalizedUri x -> x+                              in ", profile saved at " <> T.unpack link+                          _ -> ""                   logDebug logger $ T.pack $-                      "Finishing shakeRun (took " ++ showDuration runTime ++ ", " ++ res' ++ ")"-                  signalBarrier bar res+                      "Finishing shakeRun (took " ++ showDuration runTime ++ ", " ++ res' ++ profile ++ ")"+                  signalBarrier bar (fst <$> res)               -- important: we send an async exception to the thread, then wait for it to die, before continuing               pure (killThread thread >> void (waitBarrier bar), either throwIO return =<< waitBarrier bar)) @@ -400,6 +411,11 @@     val <- readVar diagnostics     return $ getAllDiagnostics val +getHiddenDiagnostics :: IdeState -> IO [FileDiagnostic]+getHiddenDiagnostics IdeState{shakeExtras = ShakeExtras{hiddenDiagnostics}} = do+    val <- readVar hiddenDiagnostics+    return $ getAllDiagnostics val+ -- | FIXME: This function is temporary! Only required because the files of interest doesn't work unsafeClearDiagnostics :: IdeState -> IO () unsafeClearDiagnostics IdeState{shakeExtras = ShakeExtras{diagnostics}} =@@ -408,12 +424,13 @@ -- | Clear the results for all files that do not match the given predicate. garbageCollect :: (NormalizedFilePath -> Bool) -> Action () garbageCollect keep = do-    ShakeExtras{state, diagnostics,publishedDiagnostics,positionMapping} <- getShakeExtras+    ShakeExtras{state, diagnostics,hiddenDiagnostics,publishedDiagnostics,positionMapping} <- getShakeExtras     liftIO $         do newState <- modifyVar state $ \values -> do                values <- evaluate $ HMap.filterWithKey (\(file, _) _ -> keep file) values                return $! dupe values            modifyVar_ diagnostics $ \diags -> return $! filterDiagnostics keep diags+           modifyVar_ hiddenDiagnostics $ \hdiags -> return $! filterDiagnostics keep hdiags            modifyVar_ publishedDiagnostics $ \diags -> return $! Map.filterWithKey (\uri _ -> keep (fromUri uri)) diags            let versionsForFile =                    Map.fromListWith Set.union $@@ -528,7 +545,7 @@                             Failed -> (toShakeValue ShakeResult bs, Failed)                 Just v -> pure $ (maybe ShakeNoCutoff ShakeResult bs, Succeeded (vfsVersion =<< modTime) v)             liftIO $ setValues state key file res-            updateFileDiagnostics file (Key key) extras $ map snd diags+            updateFileDiagnostics file (Key key) extras $ map (\(_,y,z) -> (y,z)) diags             let eq = case (bs, fmap decodeShakeValue old) of                     (ShakeResult a, Just (ShakeResult b)) -> a == b                     (ShakeStale a, Just (ShakeStale b)) -> a == b@@ -589,7 +606,7 @@       case mbOld of           Nothing -> do               (diags, mbHash) <- runAct-              updateFileDiagnostics file (Key key) extras $ map snd diags+              updateFileDiagnostics file (Key key) extras $ map (\(_,y,z) -> (y,z)) diags               pure $ RunResult ChangedRecomputeDiff (fromMaybe "" mbHash) (isJust mbHash)           Just old -> do               current <- validateHash <$> (actionCatch getHash $ \(_ :: SomeException) -> pure "")@@ -600,7 +617,7 @@                     pure $ RunResult ChangedNothing (fromMaybe "" current) (isJust current)                   else do                     (diags, mbHash) <- runAct-                    updateFileDiagnostics file (Key key) extras $ map snd diags+                    updateFileDiagnostics file (Key key) extras $ map (\(_,y,z) -> (y,z)) diags                     let change                           | mbHash == Just old = ChangedRecomputeSame                           | otherwise = ChangedRecomputeDiff@@ -656,21 +673,30 @@      NormalizedFilePath   -> Key   -> ShakeExtras-  -> [Diagnostic] -- ^ current results+  -> [(ShowDiagnostic,Diagnostic)] -- ^ current results   -> Action ()-updateFileDiagnostics fp k ShakeExtras{diagnostics, publishedDiagnostics, state, debouncer, eventer} current = liftIO $ do+updateFileDiagnostics fp k ShakeExtras{diagnostics, hiddenDiagnostics, publishedDiagnostics, state, debouncer, eventer} current = liftIO $ do     modTime <- join . fmap currentValue <$> getValues state GetModificationTime fp+    let (currentShown, currentHidden) = partition ((== ShowDiag) . fst) current     mask_ $ do         -- Mask async exceptions to ensure that updated diagnostics are always         -- published. Otherwise, we might never publish certain diagnostics if         -- an exception strikes between modifyVar but before         -- publishDiagnosticsNotification.         newDiags <- modifyVar diagnostics $ \old -> do-            let newDiagsStore = setStageDiagnostics fp (vfsVersion =<< modTime) (T.pack $ show k) current old+            let newDiagsStore = setStageDiagnostics fp (vfsVersion =<< modTime)+                                  (T.pack $ show k) (map snd currentShown) old             let newDiags = getFileDiagnostics fp newDiagsStore             _ <- evaluate newDiagsStore             _ <- evaluate newDiags             pure $! (newDiagsStore, newDiags)+        modifyVar_ hiddenDiagnostics $ \old -> do+            let newDiagsStore = setStageDiagnostics fp (vfsVersion =<< modTime)+                                  (T.pack $ show k) (map snd currentHidden) old+            let newDiags = getFileDiagnostics fp newDiagsStore+            _ <- evaluate newDiagsStore+            _ <- evaluate newDiags+            return newDiagsStore         let uri = filePathToUri' fp         let delay = if null newDiags then 0.1 else 0         registerEvent debouncer delay uri $ do@@ -751,7 +777,7 @@     DiagnosticStore ->     [FileDiagnostic] getAllDiagnostics =-    concatMap (\(k,v) -> map (fromUri k,) $ getDiagnosticsFromStore v) . Map.toList+    concatMap (\(k,v) -> map (fromUri k,ShowDiag,) $ getDiagnosticsFromStore v) . Map.toList  getFileDiagnostics ::     NormalizedFilePath ->
src/Development/IDE/GHC/CPP.hs view
@@ -114,6 +114,8 @@                     ++ map SysTools.Option sse_defs                     ++ map SysTools.Option avx_defs                     ++ mb_macro_include+        -- Define a special macro "__GHCIDE__"+                    ++ [ SysTools.Option "-D__GHCIDE__"]         -- Set the language mode to assembler-with-cpp when preprocessing. This         -- alleviates some of the C99 macro rules relating to whitespace and the hash         -- operator, which we tend to abuse. Clang in particular is not very happy
src/Development/IDE/GHC/Compat.hs view
@@ -2,6 +2,7 @@ -- SPDX-License-Identifier: Apache-2.0  {-# LANGUAGE CPP #-}+{-# LANGUAGE PatternSynonyms #-} #include "ghc-api-version.h"  -- | Attempt at hiding the GHC version differences we can.@@ -15,23 +16,35 @@     includePathsGlobal,     includePathsQuote,     addIncludePathsQuote,-    ghcEnumerateExtensions+    ghcEnumerateExtensions,+    pattern DerivD,+    pattern ForD,+    pattern InstD,+    pattern TyClD,+    pattern ValD,+    pattern ClassOpSig,+    pattern IEThingWith,++    module GHC     ) where  import StringBuffer import DynFlags+import FieldLabel import GHC.LanguageExtensions.Type  #if MIN_GHC_API_VERSION(8,8,0) import Data.List.Extra (enumerate) #endif +import qualified GHC+import GHC hiding (ClassOpSig, DerivD, ForD, IEThingWith, InstD, TyClD, ValD)+ #if MIN_GHC_API_VERSION(8,8,0) import HieAst import HieBin import HieTypes #else-import GHC import GhcPlugins import NameCache import Avail@@ -82,4 +95,60 @@ ghcEnumerateExtensions = [Cpp .. StarIsType] #else ghcEnumerateExtensions = [Cpp .. EmptyDataDeriving]+#endif++pattern DerivD :: DerivDecl p -> HsDecl p+pattern DerivD x <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.DerivD _ x+#else+    GHC.DerivD x+#endif++pattern ForD :: ForeignDecl p -> HsDecl p+pattern ForD x <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.ForD _ x+#else+    GHC.ForD x+#endif++pattern ValD :: HsBind p -> HsDecl p+pattern ValD x <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.ValD _ x+#else+    GHC.ValD x+#endif++pattern InstD :: InstDecl p -> HsDecl p+pattern InstD x <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.InstD _ x+#else+    GHC.InstD x+#endif++pattern TyClD :: TyClDecl p -> HsDecl p+pattern TyClD x <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.TyClD _ x+#else+    GHC.TyClD x+#endif++pattern ClassOpSig :: Bool -> [Located (IdP pass)] -> LHsSigType pass -> Sig pass+pattern ClassOpSig a b c <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.ClassOpSig _ a b c+#else+    GHC.ClassOpSig a b c+#endif++pattern IEThingWith :: LIEWrappedName (IdP pass) -> IEWildcard -> [LIEWrappedName (IdP pass)] -> [Located (FieldLbl (IdP pass))] -> IE pass+pattern IEThingWith a b c d <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.IEThingWith _ a b c d+#else+    GHC.IEThingWith a b c d #endif
src/Development/IDE/GHC/Error.hs view
@@ -12,9 +12,13 @@    -- * utilities working with spans   , srcSpanToLocation+  , srcSpanToRange   , srcSpanToFilename   , zeroSpan   , realSpan++  -- * utilities working with severities+  , toDSeverity   ) where  import                     Development.IDE.Types.Diagnostics as D@@ -34,7 +38,7 @@   diagFromText :: T.Text -> D.DiagnosticSeverity -> SrcSpan -> T.Text -> FileDiagnostic-diagFromText diagSource sev loc msg = (toNormalizedFilePath $ srcSpanToFilename loc,)+diagFromText diagSource sev loc msg = (toNormalizedFilePath $ srcSpanToFilename loc,ShowDiag,)     Diagnostic     { _range    = srcSpanToRange loc     , _severity = Just sev
src/Development/IDE/GHC/Util.hs view
@@ -20,7 +20,7 @@     moduleImportPath,     HscEnvEq, hscEnv, newHscEnvEq,     readFileUtf8,-    hDuplicateTo,+    hDuplicateTo',     cgGutsToCoreModule     ) where @@ -167,8 +167,8 @@  -- This is a slightly modified version of hDuplicateTo in GHC. -- See the inline comment for more details.-hDuplicateTo :: Handle -> Handle -> IO ()-hDuplicateTo h1@(FileHandle path m1) h2@(FileHandle _ m2)  = do+hDuplicateTo' :: Handle -> Handle -> IO ()+hDuplicateTo' h1@(FileHandle path m1) h2@(FileHandle _ m2)  = do  withHandle__' "hDuplicateTo" h2 m2 $ \h2_ -> do    -- The implementation in base has this call to hClose_help.    -- _ <- hClose_help h2_@@ -181,7 +181,7 @@    -- if it happens just in the right moment.    withHandle_' "hDuplicateTo" h1 m1 $ \h1_ -> do      dupHandleTo path h1 Nothing h2_ h1_ (Just handleFinalizer)-hDuplicateTo h1@(DuplexHandle path r1 w1) h2@(DuplexHandle _ r2 w2)  = do+hDuplicateTo' h1@(DuplexHandle path r1 w1) h2@(DuplexHandle _ r2 w2)  = do  withHandle__' "hDuplicateTo" h2 w2  $ \w2_ -> do    _ <- hClose_help w2_    withHandle_' "hDuplicateTo" h1 w1 $ \w1_ -> do@@ -190,7 +190,7 @@    _ <- hClose_help r2_    withHandle_' "hDuplicateTo" h1 r1 $ \r1_ -> do      dupHandleTo path h1 (Just w1) r2_ r1_ Nothing-hDuplicateTo h1 _ =+hDuplicateTo' h1 _ =   ioe_dupHandlesNotCompatible h1  -- | This is copied unmodified from GHC since it is not exposed.
src/Development/IDE/LSP/CodeAction.hs view
@@ -12,9 +12,12 @@     ) where  import           Language.Haskell.LSP.Types+import Control.Monad (join) import Development.IDE.GHC.Compat import Development.IDE.Core.Rules+import Development.IDE.Core.RuleTypes import Development.IDE.Core.Shake+import Development.IDE.GHC.Error import Development.IDE.LSP.Server import Development.IDE.Types.Location import qualified Data.HashMap.Strict as Map@@ -24,10 +27,14 @@ import Language.Haskell.LSP.Messages import qualified Data.Rope.UTF16 as Rope import Data.Aeson.Types (toJSON, fromJSON, Value(..), Result(..))+import Control.Monad.Trans.Maybe import Data.Char import Data.Maybe import Data.List.Extra import qualified Data.Text as T+import Text.Regex.TDFA ((=~), (=~~))+import Text.Regex.TDFA.Text()+import Outputable (ppr, showSDocUnsafe)  -- | Generate code actions. codeAction@@ -35,14 +42,15 @@     -> IdeState     -> CodeActionParams     -> IO (List CAResult)-codeAction lsp _ CodeActionParams{_textDocument=TextDocumentIdentifier uri,_context=CodeActionContext{_diagnostics=List xs}} = do+codeAction lsp state CodeActionParams{_textDocument=TextDocumentIdentifier uri,_context=CodeActionContext{_diagnostics=List xs}} = do     -- disable logging as its quite verbose     -- logInfo (ideLogger ide) $ T.pack $ "Code action req: " ++ show arg     contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri     let text = Rope.toText . (_text :: VirtualFile -> Rope.Rope) <$> contents+    parsedModule <- (runAction state . getParsedModule . toNormalizedFilePath) `traverse` uriToFilePath uri     pure $ List         [ CACodeAction $ CodeAction title (Just CodeActionQuickFix) (Just $ List [x]) (Just edit) Nothing-        | x <- xs, (title, tedit) <- suggestAction text x+        | x <- xs, (title, tedit) <- suggestAction ( join parsedModule ) text x         , let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing         ] @@ -53,19 +61,21 @@     -> CodeLensParams     -> IO (List CodeLens) codeLens _lsp ideState CodeLensParams{_textDocument=TextDocumentIdentifier uri} = do-    diag <- getDiagnostics ideState     case uriToFilePath' uri of       Just (toNormalizedFilePath -> filePath) -> do+        _ <- runAction ideState $ runMaybeT $ useE TypeCheck filePath+        diag <- getDiagnostics ideState+        hDiag <- getHiddenDiagnostics ideState         pure $ List           [ CodeLens _range (Just (Command title "typesignature.add" (Just $ List [toJSON edit]))) Nothing-          | (dFile, dDiag@Diagnostic{_range=_range@Range{..},..}) <- diag+          | (dFile, _, dDiag@Diagnostic{_range=_range@Range{..},..}) <- diag ++ hDiag           , dFile == filePath-          , (title, tedit) <- suggestTopLevelBinding False dDiag+          , (title, tedit) <- suggestSignature False dDiag           , let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing           ]       Nothing -> pure $ List [] --- | Generate code lenses.+-- | Execute the "typesignature.add" command. executeAddSignatureCommand     :: LSP.LspFuncs ()     -> IdeState@@ -79,18 +89,40 @@     | otherwise     = return (Null, Nothing) -suggestAction :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]-suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}+suggestAction  :: Maybe ParsedModule -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]+suggestAction parsedModule text diag = concat+    [ suggestAddExtension diag+    , suggestExtendImport text diag+    , suggestFillHole diag+    , suggestFillTypeWildcard diag+    , suggestFixConstructorImport text diag+    , suggestModuleTypo diag+    , suggestReplaceIdentifier text diag+    , suggestSignature True diag+    ] ++ concat+    [ suggestRemoveRedundantImport pm text diag | Just pm <- [parsedModule]] ++suggestRemoveRedundantImport :: ParsedModule -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]+suggestRemoveRedundantImport ParsedModule{pm_parsed_source = L _  HsModule{hsmodImports}} contents Diagnostic{_range=_range@Range{..},..}+--     The qualified import of ‘many’ from module ‘Control.Applicative’ is redundant+    | Just [_, bindings] <- matchRegex _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"+    , Just (L _ impDecl) <- find (\(L l _) -> srcSpanToRange l == _range ) hsmodImports+    , Just c <- contents+    , ranges <- map (rangesForBinding impDecl . T.unpack) (T.splitOn ", " bindings)+    , ranges' <- extendAllToIncludeCommaIfPossible (indexedByPosition $ T.unpack c) (concat ranges)+    = [( "Remove " <> bindings <> " from import" , [ TextEdit r "" | r <- ranges' ] )]+ -- File.hs:16:1: warning: --     The import of `Data.List' is redundant --       except perhaps to import instances from `Data.List' --     To import instances alone, use: import Data.List()-    | "The import of " `T.isInfixOf` _message-    || "The qualified import of " `T.isInfixOf` _message-    , " is redundant" `T.isInfixOf` _message+    | _message =~ ("The( qualified)? import of [^ ]* is redundant" :: String)         = [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]+    | otherwise = [] +suggestReplaceIdentifier :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]+suggestReplaceIdentifier contents Diagnostic{_range=_range@Range{..},..} -- File.hs:52:41: error: --     * Variable not in scope: --         suggestAcion :: Maybe T.Text -> Range -> Range@@ -104,7 +136,10 @@ --     Module ‘Data.Text’ does not export ‘isPrfixOf’.     | renameSuggestions@(_:_) <- extractRenamableTerms _message         = [ ("Replace with ‘" <> name <> "’", [mkRenameEdit contents _range name]) | name <- renameSuggestions ]+    | otherwise = [] +suggestFillTypeWildcard :: Diagnostic -> [(T.Text, [TextEdit])]+suggestFillTypeWildcard Diagnostic{_range=_range@Range{..},..} -- Foo.hs:3:8: error: --     * Found type wildcard `_' standing for `p -> p1 -> p' @@ -112,7 +147,10 @@     , " standing for " `T.isInfixOf` _message     , typeSignature <- extractWildCardTypeSignature _message         =  [("Use type signature: ‘" <> typeSignature <> "’", [TextEdit _range typeSignature])]+    | otherwise = [] +suggestAddExtension :: Diagnostic -> [(T.Text, [TextEdit])]+suggestAddExtension Diagnostic{_range=_range@Range{..},..} -- File.hs:22:8: error: --     Illegal lambda-case (use -XLambdaCase) -- File.hs:22:6: error:@@ -135,7 +173,10 @@ --       In the instance declaration for `Unit (m a)'     | exts@(_:_) <- filter (`Set.member` ghcExtensions) $ T.split (not . isAlpha) $ T.replace "-X" "" _message         = [("Add " <> x <> " extension", [TextEdit (Range (Position 0 0) (Position 0 0)) $ "{-# LANGUAGE " <> x <> " #-}\n"]) | x <- exts]+    | otherwise = [] +suggestModuleTypo :: Diagnostic -> [(T.Text, [TextEdit])]+suggestModuleTypo Diagnostic{_range=_range@Range{..},..} -- src/Development/IDE/Core/Compile.hs:58:1: error: --     Could not find module ‘Data.Cha’ --     Perhaps you meant Data.Char (from base-4.12.0.0)@@ -144,7 +185,10 @@       findSuggestedModules = map (head . T.words) . drop 2 . T.lines       proposeModule mod = ("replace with " <> mod, [TextEdit _range mod])       in map proposeModule $ nubOrd $ findSuggestedModules _message+    | otherwise = [] +suggestFillHole :: Diagnostic -> [(T.Text, [TextEdit])]+suggestFillHole Diagnostic{_range=_range@Range{..},..} --  ...Development/IDE/LSP/CodeAction.hs:103:9: warning: --   * Found hole: _ :: Int -> String --   * In the expression: _@@ -177,12 +221,39 @@       extractFitNames     = map (T.strip . head . T.splitOn " :: ")       in map proposeHoleFit $ nubOrd $ findSuggestedHoleFits _message -    | tlb@[_] <- suggestTopLevelBinding True diag = tlb+    | otherwise = [] -suggestAction _ _ = []+suggestExtendImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]+suggestExtendImport contents Diagnostic{_range=_range,..}+    | Just [binding, mod, srcspan] <-+      matchRegex _message+      "Perhaps you want to add ‘([^’]*)’ to the import list in the import of ‘([^’]*)’ *\\((.*)\\).$"+    , Just c <- contents+    = let range = case [ x | (x,"") <- readSrcSpan (T.unpack srcspan)] of+            [s] -> let x = srcSpanToRange s+                   in x{_end = (_end x){_character = succ (_character (_end x))}}+            _ -> error "bug in srcspan parser"+          importLine = textInRange range c+        in [("Add " <> binding <> " to the import list of " <> mod+        , [TextEdit range (addBindingToImportList binding importLine)])]+    | otherwise = [] -suggestTopLevelBinding :: Bool -> Diagnostic -> [(T.Text, [TextEdit])]-suggestTopLevelBinding isQuickFix Diagnostic{_range=_range@Range{..},..}+suggestFixConstructorImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]+suggestFixConstructorImport _ Diagnostic{_range=_range,..}+    -- ‘Success’ is a data constructor of ‘Result’+    -- To import it use+    -- import Data.Aeson.Types( Result( Success ) )+    -- or+    -- import Data.Aeson.Types( Result(..) ) (lsp-ui)+  | Just [constructor, typ] <-+    matchRegex _message+    "‘([^’]*)’ is a data constructor of ‘([^’]*)’ To import it use"+  = let fixedImport = typ <> "(" <> constructor <> ")"+    in [("Fix import of " <> fixedImport, [TextEdit _range fixedImport])]+  | otherwise = []++suggestSignature :: Bool -> Diagnostic -> [(T.Text, [TextEdit])]+suggestSignature isQuickFix Diagnostic{_range=_range@Range{..},..}     | "Top-level binding with no type signature" `T.isInfixOf` _message = let       filterNewlines = T.concat  . T.lines       unifySpaces    = T.unwords . T.words@@ -192,7 +263,23 @@       title          = if isQuickFix then "add signature: " <> signature else signature       action         = TextEdit beforeLine $ signature <> "\n"       in [(title, [action])]-suggestTopLevelBinding _ _ = []+suggestSignature isQuickFix Diagnostic{_range=_range@Range{..},..}+    | "Polymorphic local binding with no type signature" `T.isInfixOf` _message = let+      filterNewlines = T.concat  . T.lines+      unifySpaces    = T.unwords . T.words+      signature      = removeInitialForAll+                     $ T.takeWhile (\x -> x/='*' && x/='•')+                     $ T.strip $ unifySpaces $ last $ T.splitOn "type signature: " $ filterNewlines _message+      startOfLine    = Position (_line _start) (_character _start)+      beforeLine     = Range startOfLine startOfLine+      title          = if isQuickFix then "add signature: " <> signature else signature+      action         = TextEdit beforeLine $ signature <> "\n" <> T.replicate (_character _start) " "+      in [(title, [action])]+    where removeInitialForAll :: T.Text -> T.Text+          removeInitialForAll (T.breakOnEnd " :: " -> (nm, ty))+              | "forall" `T.isPrefixOf` ty = nm <> T.drop 2 (snd (T.breakOn "." ty))+              | otherwise                  = nm <> ty+suggestSignature _ _ = []  topOfHoleFitsMarker :: T.Text topOfHoleFitsMarker =@@ -256,6 +343,7 @@         = (T.intercalate "\n" $ preRow ++ [preCol], T.intercalate "\n" $ postCol : postRow)     | otherwise = (x, T.empty) +-- | Returns [start .. end[ textInRange :: Range -> T.Text -> T.Text textInRange (Range (Position startRow startCol) (Position endRow endCol)) text =     case compare startRow endRow of@@ -273,6 +361,52 @@     where       linesBeginningWithStartLine = drop startRow (T.splitOn "\n" text) +-- | Returns the ranges for a binding in an import declaration+rangesForBinding :: ImportDecl GhcPs -> String -> [Range]+rangesForBinding ImportDecl{ideclHiding = Just (False, L _ lies)} b =+    concatMap (map srcSpanToRange . rangesForBinding' b') lies+  where+    b' = wrapOperatorInParens (unqualify b)++    wrapOperatorInParens x = if isAlpha (head x) then x else "(" <> x <> ")"++    unqualify x = snd $ breakOnEnd "." x++rangesForBinding _ _ = []++rangesForBinding' :: String -> LIE GhcPs -> [SrcSpan]+rangesForBinding' b (L l x@IEVar{}) | showSDocUnsafe (ppr x) == b = [l]+rangesForBinding' b (L l x@IEThingAbs{}) | showSDocUnsafe (ppr x) == b = [l]+rangesForBinding' b (L l x@IEThingAll{}) | showSDocUnsafe (ppr x) == b = [l]+rangesForBinding' b (L l (IEThingWith thing _  inners labels))+    | showSDocUnsafe (ppr thing) == b = [l]+    | otherwise =+        [ l' | L l' x <- inners, showSDocUnsafe (ppr x) == b] +++        [ l' | L l' x <- labels, showSDocUnsafe (ppr x) == b]+rangesForBinding' _ _ = []++-- | Extends an import list with a new binding.+--   Assumes an import statement of the form:+--       import (qualified) A (..) ..+--   Places the new binding first, preserving whitespace.+--   Copes with multi-line import lists+addBindingToImportList :: T.Text -> T.Text -> T.Text+addBindingToImportList binding importLine = case T.breakOn "(" importLine of+    (pre, T.uncons -> Just (_, rest)) ->+      case T.uncons (T.dropWhile isSpace rest) of+        Just (')', _) -> T.concat [pre, "(", binding, rest]+        _             -> T.concat [pre, "(", binding, ", ", rest]+    _ ->+      error+        $  "importLine does not have the expected structure: "+        <> T.unpack importLine++-- | Returns Just (the submatches) for the first capture, or Nothing.+matchRegex :: T.Text -> T.Text -> Maybe [T.Text]+matchRegex message regex = case T.unwords (T.words message) =~~ regex of+    Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings+    Nothing -> Nothing+ setHandlersCodeAction :: PartialHandlers setHandlersCodeAction = PartialHandlers $ \WithMessage{..} x -> return x{     LSP.codeActionHandler = withResponse RspCodeAction codeAction@@ -283,3 +417,51 @@     LSP.codeLensHandler = withResponse RspCodeLens codeLens,     LSP.executeCommandHandler = withResponseAndRequest RspExecuteCommand ReqApplyWorkspaceEdit executeAddSignatureCommand     }++--------------------------------------------------------------------------------++type PositionIndexedString = [(Position, Char)]++indexedByPosition :: String -> PositionIndexedString+indexedByPosition = unfoldr f . (Position 0 0,) where+  f (_, []) = Nothing+  f (p@(Position l _), '\n' : rest) = Just ((p,'\n'), (Position (l+1) 0, rest))+  f (p@(Position l c),    x : rest) = Just ((p,   x), (Position l (c+1), rest))++-- | Returns a tuple (before, contents, after)+unconsRange :: Range -> PositionIndexedString -> (PositionIndexedString, PositionIndexedString, PositionIndexedString)+unconsRange Range {..} indexedString = (before, mid, after)+  where+    (before, rest) = span ((/= _start) . fst) indexedString+    (mid, after) = span ((/= _end) . fst) rest++stripRange :: Range -> PositionIndexedString -> PositionIndexedString+stripRange r s = case unconsRange r s of+  (b, _, a) -> b ++ a++extendAllToIncludeCommaIfPossible :: PositionIndexedString -> [Range] -> [Range]+extendAllToIncludeCommaIfPossible _             [] =  []+extendAllToIncludeCommaIfPossible indexedString (r : rr) = r' : extendAllToIncludeCommaIfPossible indexedString' rr+  where+    r' = case extendToIncludeCommaIfPossible indexedString r of+          [] -> r+          r' : _ -> r'+    indexedString' = stripRange r' indexedString++-- | Returns a sorted list of ranges with extended selections includindg preceding or trailing commas+extendToIncludeCommaIfPossible :: PositionIndexedString -> Range -> [Range]+extendToIncludeCommaIfPossible indexedString range =+    -- a, |b|, c ===> a|, b|, c+    [ range{_start = start'}+    | (start', ',') : _ <- [before']+    ]+    +++    -- a, |b|, c ===> a, |b, |c+    [ range{_end = end'}+    |  (_, ',') : rest <- [after']+    , let (end', _) : _ = dropWhile (isSpace . snd) rest+    ]+  where+    (before, _, after) = unconsRange range indexedString+    after' = dropWhile (isSpace . snd) after+    before' = dropWhile (isSpace . snd) (reverse before)
+ src/Development/IDE/LSP/Completions.hs view
@@ -0,0 +1,51 @@+module Development.IDE.LSP.Completions (+  setHandlersCompletion+) where++import Language.Haskell.LSP.Messages+import Language.Haskell.LSP.Types+import qualified Language.Haskell.LSP.Core as LSP+import qualified Language.Haskell.LSP.VFS as VFS+import Language.Haskell.LSP.Types.Capabilities++import Development.IDE.Core.Service+import Development.IDE.Core.Completions+import Development.IDE.Types.Location+import Development.IDE.Core.PositionMapping+import Development.IDE.Core.RuleTypes+import Development.IDE.Core.Shake+import Development.IDE.LSP.Server++-- | Generate code actions.+getCompletionsLSP+    :: LSP.LspFuncs ()+    -> IdeState+    -> CompletionParams+    -> IO CompletionResponseResult+getCompletionsLSP lsp ide+  CompletionParams{_textDocument=TextDocumentIdentifier uri+                  ,_position=position+                  ,_context=completionContext} = do+    contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri+    case (contents, uriToFilePath' uri) of+      (Just cnts, Just path) -> do+        let npath = toNormalizedFilePath path+        (ideOpts, compls) <- runAction ide ((,) <$> getIdeOptions <*> useWithStale ProduceCompletions npath)+        case compls of+          Just ((cci', tm'), mapping) -> do+            let position' = fromCurrentPosition mapping position+            pfix <- maybe (return Nothing) (flip VFS.getCompletionPrefix cnts) position'+            case (pfix, completionContext) of+              (Just (VFS.PosPrefixInfo _ "" _ _), Just CompletionContext { _triggerCharacter = Just "."})+                -> return (Completions $ List [])+              (Just pfix', _) -> do+                let fakeClientCapabilities = ClientCapabilities Nothing Nothing Nothing Nothing+                Completions . List <$> getCompletions ideOpts cci' (tmrModule tm') pfix' fakeClientCapabilities (WithSnippets True)+              _ -> return (Completions $ List [])+          _ -> return (Completions $ List [])+      _ -> return (Completions $ List [])++setHandlersCompletion :: PartialHandlers+setHandlersCompletion = PartialHandlers $ \WithMessage{..} x -> return x{+    LSP.completionHandler = withResponse RspCompletion getCompletionsLSP+    }
− src/Development/IDE/LSP/Definition.hs
@@ -1,43 +0,0 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.--- SPDX-License-Identifier: Apache-2.0----- | Go to the definition of a variable.-module Development.IDE.LSP.Definition-    ( setHandlersDefinition-    ) where--import           Language.Haskell.LSP.Types-import Development.IDE.Types.Location--import Development.IDE.Types.Logger-import Development.IDE.Core.Rules-import Development.IDE.Core.Service-import Development.IDE.LSP.Server-import qualified Language.Haskell.LSP.Core as LSP-import Language.Haskell.LSP.Messages--import qualified Data.Text as T---- | Go to the definition of a variable.-gotoDefinition-    :: IdeState-    -> TextDocumentPositionParams-    -> IO LocationResponseParams-gotoDefinition ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos _) = do-    mbResult <- case uriToFilePath' uri of-        Just path -> do-            logInfo (ideLogger ide) $-                "Definition request at position " <> T.pack (showPosition pos) <>-                " in file: " <> T.pack path-            runAction ide $ getDefinition (toNormalizedFilePath path) pos-        Nothing -> pure Nothing-    pure $ case mbResult of-        Nothing -> MultiLoc []-        Just loc -> SingleLoc loc---setHandlersDefinition :: PartialHandlers-setHandlersDefinition = PartialHandlers $ \WithMessage{..} x -> return x{-    LSP.definitionHandler = withResponse RspDefinition $ const gotoDefinition-    }
− src/Development/IDE/LSP/Hover.hs
@@ -1,47 +0,0 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.--- SPDX-License-Identifier: Apache-2.0----- | Display information on hover.-module Development.IDE.LSP.Hover-    ( setHandlersHover-    ) where--import Language.Haskell.LSP.Types-import Development.IDE.Types.Location-import Development.IDE.Core.Service-import Development.IDE.LSP.Server-import Development.IDE.Types.Logger-import qualified Language.Haskell.LSP.Core as LSP-import Language.Haskell.LSP.Messages--import qualified Data.Text as T--import Development.IDE.Core.Rules---- | Display information on hover.-onHover-    :: IdeState-    -> TextDocumentPositionParams-    -> IO (Maybe Hover)-onHover ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos _) = do-    mbResult <- case uriToFilePath' uri of-        Just (toNormalizedFilePath -> filePath) -> do-          logInfo (ideLogger ide) $-              "Hover request at position " <> T.pack (showPosition pos) <>-              " in file: " <> T.pack (fromNormalizedFilePath filePath)-          runAction ide $ getAtPoint filePath pos-        Nothing       -> pure Nothing--    case mbResult of-        Just (mbRange, contents) ->-            pure $ Just $ Hover-                        (HoverContents $ MarkupContent MkMarkdown $ T.intercalate sectionSeparator contents)-                        mbRange--        Nothing -> pure Nothing--setHandlersHover :: PartialHandlers-setHandlersHover = PartialHandlers $ \WithMessage{..} x -> return x{-    LSP.hoverHandler = withResponse RspHover $ const onHover-    }
+ src/Development/IDE/LSP/HoverDefinition.hs view
@@ -0,0 +1,59 @@+-- Copyright (c) 2019 The DAML Authors. All rights reserved.+-- SPDX-License-Identifier: Apache-2.0+++-- | Display information on hover.+module Development.IDE.LSP.HoverDefinition+    ( setHandlersHover+    , setHandlersDefinition+    ) where++import           Development.IDE.Core.Rules+import           Development.IDE.Core.Service+import           Development.IDE.LSP.Server+import           Development.IDE.Types.Location+import           Development.IDE.Types.Logger+import           Development.Shake+import qualified Language.Haskell.LSP.Core       as LSP+import           Language.Haskell.LSP.Messages+import           Language.Haskell.LSP.Types++import qualified Data.Text as T++gotoDefinition :: IdeState -> TextDocumentPositionParams -> IO LocationResponseParams+hover          :: IdeState -> TextDocumentPositionParams -> IO (Maybe Hover)+gotoDefinition = request "Definition" getDefinition (MultiLoc []) SingleLoc+hover          = request "Hover"      getAtPoint     Nothing      foundHover++foundHover :: (Maybe Range, [T.Text]) -> Maybe Hover+foundHover (mbRange, contents) =+  Just $ Hover (HoverContents $ MarkupContent MkMarkdown $ T.intercalate sectionSeparator contents) mbRange++setHandlersDefinition, setHandlersHover :: PartialHandlers+setHandlersDefinition = PartialHandlers $ \WithMessage{..} x ->+  return x{LSP.definitionHandler = withResponse RspDefinition $ const gotoDefinition}+setHandlersHover      = PartialHandlers $ \WithMessage{..} x ->+  return x{LSP.hoverHandler      = withResponse RspHover      $ const hover}++-- | Respond to and log a hover or go-to-definition request+request+  :: T.Text+  -> (NormalizedFilePath -> Position -> Action (Maybe a))+  -> b+  -> (a -> b)+  -> IdeState+  -> TextDocumentPositionParams+  -> IO b+request label getResults notFound found ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos _) = do+    mbResult <- case uriToFilePath' uri of+        Just path -> logAndRunRequest label getResults ide pos path+        Nothing   -> pure Nothing+    pure $ maybe notFound found mbResult++logAndRunRequest :: T.Text -> (NormalizedFilePath -> Position -> Action b) -> IdeState -> Position -> String -> IO b+logAndRunRequest label getResults ide pos path = do+  let filePath = toNormalizedFilePath path+  logInfo (ideLogger ide) $+    label <> " request at position " <> T.pack (showPosition pos) <>+    " in file: " <> T.pack path+  runAction ide $ getResults filePath pos
src/Development/IDE/LSP/LanguageServer.hs view
@@ -28,10 +28,11 @@ import System.IO import Control.Monad.Extra -import Development.IDE.LSP.Definition-import Development.IDE.LSP.Hover+import Development.IDE.LSP.HoverDefinition import Development.IDE.LSP.CodeAction+import Development.IDE.LSP.Completions import Development.IDE.LSP.Notifications+import Development.IDE.LSP.Outline import Development.IDE.Core.Service import Development.IDE.Types.Logger import Development.IDE.Core.FileStore@@ -48,7 +49,7 @@     -- to stdout. This guards against stray prints from corrupting the JSON-RPC     -- message stream.     newStdout <- hDuplicate stdout-    stderr `Ghcide.hDuplicateTo` stdout+    stderr `Ghcide.hDuplicateTo'` stdout     hSetBuffering stderr NoBuffering     hSetBuffering stdout NoBuffering @@ -98,6 +99,8 @@             setHandlersIgnore <> -- least important             setHandlersDefinition <> setHandlersHover <>             setHandlersCodeAction <> setHandlersCodeLens <> -- useful features someone may override+            setHandlersCompletion <>+            setHandlersOutline <>             userHandlers <>             setHandlersNotifications <> -- absolutely critical, join them with user notifications             cancelHandler cancelRequest@@ -203,6 +206,7 @@ modifyOptions :: LSP.Options -> LSP.Options modifyOptions x = x{ LSP.textDocumentSync   = Just $ tweakTDS origTDS                    , LSP.executeCommandCommands = Just ["typesignature.add"]+                   , LSP.completionTriggerCharacters = Just "."                    }     where         tweakTDS tds = tds{_openClose=Just True, _change=Just TdSyncIncremental, _save=Just $ SaveOptions Nothing}
+ src/Development/IDE/LSP/Outline.hs view
@@ -0,0 +1,195 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DuplicateRecordFields #-}+#include "ghc-api-version.h"++module Development.IDE.LSP.Outline+  ( setHandlersOutline+  )+where++import qualified Language.Haskell.LSP.Core     as LSP+import           Language.Haskell.LSP.Messages+import           Language.Haskell.LSP.Types+import           Data.Functor+import           Data.Generics+import           Data.Maybe+import           Data.Text                      ( Text+                                                , pack+                                                )+import qualified Data.Text                     as T+import           Development.IDE.Core.Rules+import           Development.IDE.Core.Shake+import           Development.IDE.GHC.Compat+import           Development.IDE.GHC.Error      ( srcSpanToRange )+import           Development.IDE.LSP.Server+import           Development.IDE.Types.Location+import           Outputable                     ( Outputable+                                                , ppr+                                                , showSDocUnsafe+                                                )++setHandlersOutline :: PartialHandlers+setHandlersOutline = PartialHandlers $ \WithMessage {..} x -> return x+  { LSP.documentSymbolHandler = withResponse RspDocumentSymbols moduleOutline+  }++moduleOutline+  :: LSP.LspFuncs () -> IdeState -> DocumentSymbolParams -> IO DSResult+moduleOutline _lsp ideState DocumentSymbolParams { _textDocument = TextDocumentIdentifier uri }+  = case uriToFilePath uri of+    Just (toNormalizedFilePath -> fp) -> do+      mb_decls <- runAction ideState $ use GetParsedModule fp+      pure $ case mb_decls of+        Nothing -> DSDocumentSymbols (List [])+        Just (ParsedModule { pm_parsed_source = L _ltop HsModule { hsmodName, hsmodDecls, hsmodImports } })+          -> let+               declSymbols  = mapMaybe documentSymbolForDecl hsmodDecls+               moduleSymbol = hsmodName <&> \(L l m) ->+                 (defDocumentSymbol l :: DocumentSymbol)+                   { _name  = pprText m+                   , _kind  = SkFile+                   , _range = Range (Position 0 0) (Position maxBound 0) -- _ltop is 0 0 0 0+                   }+               importSymbols = mapMaybe documentSymbolForImport hsmodImports+               allSymbols    = case moduleSymbol of+                 Nothing -> importSymbols <> declSymbols+                 Just x ->+                   [ x { _children = Just (List (importSymbols <> declSymbols))+                       }+                   ]+             in+               DSDocumentSymbols (List allSymbols)+++    Nothing -> pure $ DSDocumentSymbols (List [])++documentSymbolForDecl :: Located (HsDecl GhcPs) -> Maybe DocumentSymbol+documentSymbolForDecl (L l (TyClD FamDecl { tcdFam = FamilyDecl { fdLName = L _ n, fdInfo, fdTyVars } }))+  = Just (defDocumentSymbol l :: DocumentSymbol)+    { _name   = showRdrName n+                  <> (case pprText fdTyVars of+                       "" -> ""+                       t  -> " " <> t+                     )+    , _detail = Just $ pprText fdInfo+    , _kind   = SkClass+    }+documentSymbolForDecl (L l (TyClD ClassDecl { tcdLName = L _ name, tcdSigs, tcdTyVars }))+  = Just (defDocumentSymbol l :: DocumentSymbol)+    { _name     = showRdrName name+                    <> (case pprText tcdTyVars of+                         "" -> ""+                         t  -> " " <> t+                       )+    , _kind     = SkClass+    , _detail   = Just "class"+    , _children =+      Just $ List+        [ (defDocumentSymbol l :: DocumentSymbol)+            { _name           = showRdrName n+            , _kind           = SkMethod+            , _selectionRange = srcSpanToRange l'+            }+        | L l  (ClassOpSig False names _) <- tcdSigs+        , L l' n                            <- names+        ]+    }+documentSymbolForDecl (L l (TyClD DataDecl { tcdLName = L _ name, tcdDataDefn = HsDataDefn { dd_cons } }))+  = Just (defDocumentSymbol l :: DocumentSymbol)+    { _name     = showRdrName name+    , _kind     = SkStruct+    , _children =+      Just $ List+        [ (defDocumentSymbol l :: DocumentSymbol)+            { _name           = showRdrName n+            , _kind           = SkConstructor+            , _selectionRange = srcSpanToRange l'+            }+        | L l  x <- dd_cons+        , L l' n <- getConNames x+        ]+    }+documentSymbolForDecl (L l (TyClD SynDecl { tcdLName = L l' n })) = Just+  (defDocumentSymbol l :: DocumentSymbol) { _name           = showRdrName n+                                          , _kind           = SkTypeParameter+                                          , _selectionRange = srcSpanToRange l'+                                          }+documentSymbolForDecl (L l (InstD (ClsInstD { cid_inst = ClsInstDecl { cid_poly_ty } })))+  = Just (defDocumentSymbol l :: DocumentSymbol) { _name = pprText cid_poly_ty+                                                 , _kind = SkInterface+                                                 }+documentSymbolForDecl (L l (InstD DataFamInstD { dfid_inst = DataFamInstDecl (HsIB { hsib_body = FamEqn { feqn_tycon, feqn_pats } }) }))+  = Just (defDocumentSymbol l :: DocumentSymbol)+    { _name = showRdrName (unLoc feqn_tycon) <> " " <> T.unwords+                (map pprText feqn_pats)+    , _kind = SkInterface+    }+documentSymbolForDecl (L l (InstD TyFamInstD { tfid_inst = TyFamInstDecl (HsIB { hsib_body = FamEqn { feqn_tycon, feqn_pats } }) }))+  = Just (defDocumentSymbol l :: DocumentSymbol)+    { _name = showRdrName (unLoc feqn_tycon) <> " " <> T.unwords+                (map pprText feqn_pats)+    , _kind = SkInterface+    }+documentSymbolForDecl (L l (DerivD DerivDecl { deriv_type })) =+  gfindtype deriv_type <&> \(L (_ :: SrcSpan) name) ->+    (defDocumentSymbol l :: DocumentSymbol) { _name = pprText @(HsType GhcPs)+                                              name+                                            , _kind = SkInterface+                                            }+documentSymbolForDecl (L l (ValD FunBind{fun_id = L _ name})) = Just+    (defDocumentSymbol l :: DocumentSymbol)+      { _name   = showRdrName name+      , _kind   = SkFunction+      }+documentSymbolForDecl (L l (ValD PatBind{pat_lhs})) = Just+    (defDocumentSymbol l :: DocumentSymbol)+      { _name   = pprText pat_lhs+      , _kind   = SkFunction+      }++documentSymbolForDecl (L l (ForD x)) = Just+  (defDocumentSymbol l :: DocumentSymbol)+    { _name   = case x of+                  ForeignImport{} -> name+                  ForeignExport{} -> name+#if MIN_GHC_API_VERSION(8,6,0)+                  XForeignDecl{}  -> "?"+#endif+    , _kind   = SkObject+    , _detail = case x of+                  ForeignImport{} -> Just "import"+                  ForeignExport{} -> Just "export"+#if MIN_GHC_API_VERSION(8,6,0)+                  XForeignDecl{}  -> Nothing+#endif+    }+  where name = showRdrName $ unLoc $ fd_name x++documentSymbolForDecl _ = Nothing++documentSymbolForImport :: Located (ImportDecl GhcPs) -> Maybe DocumentSymbol+documentSymbolForImport (L l ImportDecl { ideclName, ideclQualified }) = Just+  (defDocumentSymbol l :: DocumentSymbol)+    { _name   = "import " <> pprText ideclName+    , _kind   = SkModule+    , _detail = if ideclQualified then Just "qualified" else Nothing+    }+#if MIN_GHC_API_VERSION(8,6,0)+documentSymbolForImport (L _ XImportDecl {}) = Nothing+#endif++defDocumentSymbol :: SrcSpan -> DocumentSymbol+defDocumentSymbol l = DocumentSymbol { .. } where+  _detail         = Nothing+  _deprecated     = Nothing+  _name           = ""+  _kind           = SkUnknown 0+  _range          = srcSpanToRange l+  _selectionRange = srcSpanToRange l+  _children       = Nothing++showRdrName :: RdrName -> Text+showRdrName = pprText++pprText :: Outputable a => a -> Text+pprText = pack . showSDocUnsafe . ppr
src/Development/IDE/Spans/AtPoint.hs view
@@ -22,7 +22,6 @@  -- GHC API imports import Avail-import GHC import DynFlags import FastString import Name@@ -56,32 +55,51 @@   -> Position   -> Maybe (Maybe Range, [T.Text]) atPoint IdeOptions{..} tcs srcSpans pos = do-    SpanInfo{..} <- listToMaybe $ orderSpans $ spansAtPoint pos srcSpans-    ty <- spaninfoType-    let mbName  = getNameM spaninfoSource-        mbDefinedAt = fmap (\name -> "**Defined " <> T.pack (showSDocUnsafe $ pprNameDefnLoc name) <> "**\n") mbName-        docInfo  = maybe [] (\name -> getDocumentation name tcs) mbName-        range = Range-                  (Position spaninfoStartLine spaninfoStartCol)-                  (Position spaninfoEndLine spaninfoEndCol)-        colon = if optNewColonConvention then ":" else "::"-        wrapLanguageSyntax x = T.unlines [ "```" <> T.pack optLanguageSyntax, x, "```"]-        typeSig = wrapLanguageSyntax $ case mbName of-          Nothing -> colon <> " " <> showName ty-          Just name ->-            let modulePrefix = maybe "" (<> ".") (getModuleNameAsText name)-            in  modulePrefix <> showName name <> "\n  " <> colon <> " " <> showName ty-        hoverInfo = docInfo <> [typeSig] <> maybeToList mbDefinedAt-    return (Just range, hoverInfo)+    firstSpan <- listToMaybe $ deEmpasizeGeneratedEqShow $ spansAtPoint pos srcSpans+    return (Just (range firstSpan), hoverInfo firstSpan)   where+    -- Hover info for types, classes, type variables+    hoverInfo SpanInfo{spaninfoType = Nothing , ..} =+       documentation <> (wrapLanguageSyntax <$> name <> kind) <> location+     where+       documentation = findDocumentation mbName+       name     = [maybe shouldNotHappen showName  mbName]+       location = [maybe shouldNotHappen definedAt mbName]+       kind     = [] -- TODO+       shouldNotHappen = "ghcide: did not expect a type level component without a name"+       mbName = getNameM spaninfoSource++    -- Hover info for values/data+    hoverInfo SpanInfo{spaninfoType = (Just typ), ..} =+       documentation <> (wrapLanguageSyntax <$> nameOrSource <> typeAnnotation) <> location+     where+       mbName = getNameM spaninfoSource+       documentation  = findDocumentation mbName+       typeAnnotation = [colon <> showName typ]+       nameOrSource   = [maybe literalSource qualifyNameIfPossible mbName]+       literalSource = "" -- TODO: literals: display (length-limited) source+       qualifyNameIfPossible name' = modulePrefix <> showName name'+         where modulePrefix = maybe "" (<> ".") (getModuleNameAsText name')+       location = [maybe "" definedAt mbName]++    findDocumentation = maybe [] (getDocumentation tcs)+    definedAt name = "**Defined " <> T.pack (showSDocUnsafe $ pprNameDefnLoc name) <> "**\n"++    range SpanInfo{..} = Range+      (Position spaninfoStartLine spaninfoStartCol)+      (Position spaninfoEndLine spaninfoEndCol)++    colon = if optNewColonConvention then ": " else ":: "+    wrapLanguageSyntax x = T.unlines [ "```" <> T.pack optLanguageSyntax, x, "```"]+     -- NOTE(RJR): This is a bit hacky.     -- We don't want to show the user type signatures generated from Eq and Show     -- instances, as they do not appear in the source program.     -- However the user could have written an `==` or `show` function directly,     -- in which case we still want to show information for that.     -- Hence we just move such information later in the list of spans.-    orderSpans :: [SpanInfo] -> [SpanInfo]-    orderSpans = uncurry (++) . partition (not . isTypeclassDeclSpan)+    deEmpasizeGeneratedEqShow :: [SpanInfo] -> [SpanInfo]+    deEmpasizeGeneratedEqShow = uncurry (++) . partition (not . isTypeclassDeclSpan)     isTypeclassDeclSpan :: SpanInfo -> Bool     isTypeclassDeclSpan spanInfo =       case getNameM (spaninfoSource spanInfo) of@@ -90,9 +108,7 @@  locationsAtPoint :: forall m . MonadIO m => (FilePath -> m (Maybe HieFile)) -> IdeOptions -> HscEnv -> Position -> [SpanInfo] -> m [Location] locationsAtPoint getHieFile IdeOptions{..} pkgState pos =-    fmap (map srcSpanToLocation) .-    mapMaybeM (getSpan . spaninfoSource) .-    spansAtPoint pos+    fmap (map srcSpanToLocation) . mapMaybeM (getSpan . spaninfoSource) . spansAtPoint pos   where getSpan :: SpanSource -> m (Maybe SrcSpan)         getSpan NoSource = pure Nothing         getSpan (SpanS sp) = pure $ Just sp@@ -121,16 +137,21 @@         setFileName f (RealSrcSpan span) = RealSrcSpan (span { srcSpanFile = mkFastString f })         setFileName _ span@(UnhelpfulSpan _) = span +-- | Filter out spans which do not enclose a given point spansAtPoint :: Position -> [SpanInfo] -> [SpanInfo] spansAtPoint pos = filter atp where   line = _line pos   cha = _character pos-  atp SpanInfo{..} =    spaninfoStartLine <= line-                     && spaninfoEndLine >= line-                     && spaninfoStartCol <= cha-                     -- The end col points to the column after the-                     -- last character so we use > instead of >=-                     && spaninfoEndCol > cha+  atp SpanInfo{..} =+      startsBeforePosition && endsAfterPosition+    where+      startLineCmp = compare spaninfoStartLine line+      endLineCmp   = compare spaninfoEndLine   line++      startsBeforePosition = startLineCmp == LT || (startLineCmp == EQ && spaninfoStartCol <= cha)+                                              -- The end col points to the column after the+                                              -- last character so we use > instead of >=+      endsAfterPosition = endLineCmp == GT || (endLineCmp == EQ && spaninfoEndCol > cha)  showName :: Outputable a => a -> T.Text showName = T.pack . prettyprint
src/Development/IDE/Spans/Calculate.hs view
@@ -23,6 +23,7 @@ import           GHC import           GhcMonad import           FastString (mkFastString)+import           OccName import           Development.IDE.Types.Location import           Development.IDE.Spans.Type import           Development.IDE.GHC.Error (zeroSpan)@@ -30,6 +31,7 @@ import           TcHsSyn import           Var import Development.IDE.Core.Compile+import qualified Development.IDE.GHC.Compat as Compat import Development.IDE.GHC.Util  @@ -63,7 +65,8 @@          es  = listifyAllSpans  tcs :: [LHsExpr GhcTc]          ps  = listifyAllSpans' tcs :: [Pat GhcTc]          ts  = listifyAllSpans $ tm_renamed_source tcm :: [LHsType GhcRn]-     bts <- mapM (getTypeLHsBind tcm) bs -- binds+     let funBinds = funBindMap $ tm_parsed_module tcm+     bts <- mapM (getTypeLHsBind funBinds) bs -- binds      ets <- mapM (getTypeLHsExpr tcm) es -- expressions      pts <- mapM (getTypeLPat tcm)    ps -- patterns      tts <- mapM (getLHsType tcm)     ts -- types@@ -74,8 +77,17 @@   where cmp (_,a,_) (_,b,_)           | a `isSubspanOf` b = LT           | b `isSubspanOf` a = GT-          | otherwise = EQ+          | otherwise         = compare (srcSpanStart a) (srcSpanStart b) +-- | The locations in the typechecked module are slightly messed up in some cases (e.g. HsMatchContext always+-- points to the first match) whereas the parsed module has the correct locations.+-- Therefore we build up a map from OccName to the corresponding definition in the parsed module+-- to lookup precise locations for things like multi-clause function definitions.+--+-- For now this only contains FunBinds.+funBindMap :: ParsedModule -> OccEnv (HsBind GhcPs)+funBindMap pm = mkOccEnv $ [ (occName $ unLoc f, bnd) | L _ (Compat.ValD bnd@FunBind{fun_id = f}) <- hsmodDecls $ unLoc $ pm_parsed_source pm ]+ getExports :: TypecheckedModule -> [(SpanSource, SrcSpan, Maybe Type)] getExports m     | Just (_, _, Just exports, _) <- renamedSource m =@@ -95,9 +107,13 @@  -- | Get the name and type of a binding. getTypeLHsBind :: (GhcMonad m)-               => TypecheckedModule+               => OccEnv (HsBind GhcPs)                -> LHsBind GhcTc                -> m [(SpanSource, SrcSpan, Maybe Type)]+getTypeLHsBind funBinds (L _spn FunBind{fun_id = pid})+  | Just FunBind {fun_matches = MG{mg_alts=L _ matches}} <- lookupOccEnv funBinds (occName $ unLoc pid) =+  return [(Named (getName (unLoc pid)), getLoc mc_fun, Just (varType (unLoc pid))) | match <- matches, FunRhs{mc_fun = mc_fun} <- [m_ctxt $ unLoc match] ]+-- In theory this shouldn’t ever fail but if it does, we can at least show the first clause. getTypeLHsBind _ (L _spn FunBind{fun_id = pid,fun_matches = MG{}}) =   return [(Named $ getName (unLoc pid), getLoc pid, Just (varType (unLoc pid)))] getTypeLHsBind _ _ = return []
src/Development/IDE/Spans/Documentation.hs view
@@ -1,8 +1,12 @@ -- Copyright (c) 2019 The DAML Authors. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 +{-# LANGUAGE CPP #-}+#include "ghc-api-version.h"+ module Development.IDE.Spans.Documentation (     getDocumentation+  , getDocumentationTryGhc   ) where  import           Control.Monad@@ -16,10 +20,32 @@ import           GHC import SrcLoc +#if MIN_GHC_API_VERSION(8,6,0)+import           Data.Char (isSpace)+import           Development.IDE.GHC.Util+import qualified Documentation.Haddock.Parser as H+import qualified Documentation.Haddock.Types as H+#endif +getDocumentationTryGhc+  :: HscEnv+  -> [TypecheckedModule]+  -> Name+  -> IO [T.Text]+#if MIN_GHC_API_VERSION(8,6,0)+getDocumentationTryGhc packageState tcs name = do+  res <- runGhcEnv packageState $ catchSrcErrors "docs" $ getDocs name+  case res of+    Right (Right (Just docs, _)) -> return [T.pack $ haddockToMarkdown $ H.toRegular $ H._doc $ H.parseParas Nothing $ unpackHDS docs]+    _ -> return $ getDocumentation tcs name+#else+getDocumentationTryGhc _packageState tcs name = do+  return $ getDocumentation tcs name+#endif+ getDocumentation- ::  Name -- ^ The name you want documentation for.- -> [TypecheckedModule] -- ^ All of the possible modules it could be defined in.+ :: [TypecheckedModule] -- ^ All of the possible modules it could be defined in.+ ->  Name -- ^ The name you want documentation for.  -> [T.Text] -- This finds any documentation between the name you want -- documentation for and the one before it. This is only an@@ -28,12 +54,11 @@ -- may be edge cases where it is very wrong). -- TODO : Build a version of GHC exactprint to extract this information -- more accurately.-getDocumentation targetName tcs = fromMaybe [] $ do+getDocumentation tcs targetName = fromMaybe [] $ do   -- Find the module the target is defined in.   targetNameSpan <- realSpan $ nameSrcSpan targetName   tc <--    listToMaybe-      $ filter ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)+    find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)       $ reverse tcs -- TODO : Is reversing the list here really neccessary?   -- Names bound by the module (we want to exclude non-"top-level"   -- bindings but unfortunately we get all here).@@ -90,3 +115,81 @@                             then Just $ T.pack s                             else Nothing     _ -> Nothing++#if MIN_GHC_API_VERSION(8,6,0)+-- Simple (and a bit hacky) conversion from Haddock markup to Markdown+haddockToMarkdown+  :: H.DocH String String -> String++haddockToMarkdown H.DocEmpty+  = ""+haddockToMarkdown (H.DocAppend d1 d2)+  = haddockToMarkdown d1 <> haddockToMarkdown d2+haddockToMarkdown (H.DocString s)+  = s+haddockToMarkdown (H.DocParagraph p)+  = "\n\n" ++ haddockToMarkdown p+haddockToMarkdown (H.DocIdentifier i)+  = "`" ++ i ++ "`"+haddockToMarkdown (H.DocIdentifierUnchecked i)+  = "`" ++ i ++ "`"+haddockToMarkdown (H.DocModule i)+  = "`" ++ i ++ "`"+haddockToMarkdown (H.DocWarning w)+  = haddockToMarkdown w+haddockToMarkdown (H.DocEmphasis d)+  = "*" ++ haddockToMarkdown d ++ "*"+haddockToMarkdown (H.DocBold d)+  = "**" ++ haddockToMarkdown d ++ "**"+haddockToMarkdown (H.DocMonospaced d)+  = "`" ++ escapeBackticks (haddockToMarkdown d) ++ "`"+  where+    escapeBackticks "" = ""+    escapeBackticks ('`':ss) = '\\':'`':escapeBackticks ss+    escapeBackticks (s  :ss) = s:escapeBackticks ss+haddockToMarkdown (H.DocCodeBlock d)+  = "\n```haskell\n" ++ haddockToMarkdown d ++ "\n```\n"+haddockToMarkdown (H.DocExamples es)+  = "\n```haskell\n" ++ unlines (map exampleToMarkdown es) ++ "\n```\n"+  where+    exampleToMarkdown (H.Example expr result)+      = ">>> " ++ expr ++ "\n" ++ unlines result+haddockToMarkdown (H.DocHyperlink (H.Hyperlink url Nothing))+  = "<" ++ url ++ ">"+#if MIN_VERSION_haddock_library(1,8,0)+haddockToMarkdown (H.DocHyperlink (H.Hyperlink url (Just label)))+  = "[" ++ haddockToMarkdown label ++ "](" ++ url ++ ")"+#else+haddockToMarkdown (H.DocHyperlink (H.Hyperlink url (Just label)))+  = "[" ++ label ++ "](" ++ url ++ ")"+#endif+haddockToMarkdown (H.DocPic (H.Picture url Nothing))+  = "![](" ++ url ++ ")"+haddockToMarkdown (H.DocPic (H.Picture url (Just label)))+  = "![" ++ label ++ "](" ++ url ++ ")"+haddockToMarkdown (H.DocAName aname)+  = "[" ++ aname ++ "]:"+haddockToMarkdown (H.DocHeader (H.Header level title))+  = replicate level '#' ++ " " ++ haddockToMarkdown title++haddockToMarkdown (H.DocUnorderedList things)+  = '\n' : (unlines $ map (\thing -> "+ " ++ dropWhile isSpace (haddockToMarkdown thing)) things)+haddockToMarkdown (H.DocOrderedList things)+  = '\n' : (unlines $ map (\thing -> "1. " ++ dropWhile isSpace (haddockToMarkdown thing)) things)+haddockToMarkdown (H.DocDefList things)+  = '\n' : (unlines $ map (\(term, defn) -> "+ **" ++ haddockToMarkdown term ++ "**: " ++ haddockToMarkdown defn) things)++-- we cannot render math by default+haddockToMarkdown (H.DocMathInline _)+  = "*cannot render inline math formula*"+haddockToMarkdown (H.DocMathDisplay _)+  = "\n\n*cannot render display math formula*\n\n"++-- TODO: render tables+haddockToMarkdown (H.DocTable _t)+  = "\n\n*tables are not yet supported*\n\n"++-- things I don't really know how to handle+haddockToMarkdown (H.DocProperty _)+  = ""  -- don't really know what to do+#endif
src/Development/IDE/Spans/Type.hs view
@@ -13,9 +13,8 @@  import GHC import Control.DeepSeq-import Data.Maybe import OccName-+import Development.IDE.GHC.Util  -- | Type of some span of source code. Most of these fields are -- unboxed but Haddock doesn't show that.@@ -29,7 +28,7 @@            ,spaninfoEndCol :: {-# UNPACK #-} !Int             -- ^ End column of the span (absolute), zero-based.            ,spaninfoType :: !(Maybe Type)-            -- ^ A pretty-printed representation fo the type.+            -- ^ A pretty-printed representation for the type.            ,spaninfoSource :: !SpanSource            -- ^ The actutal 'Name' associated with the span, if             -- any. This can be useful for accessing a variety of@@ -37,7 +36,9 @@             -- locality, definition location, etc.            } instance Show SpanInfo where-  show (SpanInfo sl sc el ec t n) = show [show sl, show sc, show el, show ec, show $ isJust t, show n]+  show (SpanInfo sl sc el ec t n) =+    unwords ["(SpanInfo", show sl, show sc, show el, show ec+            , show $ maybe "NoType" prettyPrint t, "(" <> show n <> "))"]  instance NFData SpanInfo where     rnf = rwhnf
src/Development/IDE/Types/Diagnostics.hs view
@@ -4,6 +4,7 @@  module Development.IDE.Types.Diagnostics (   LSP.Diagnostic(..),+  ShowDiagnostic(..),   FileDiagnostic,   LSP.DiagnosticSeverity(..),   DiagnosticStore,@@ -13,6 +14,7 @@   showDiagnosticsColored,   ) where +import Control.DeepSeq import Data.Maybe as Maybe import qualified Data.Text as T import Data.Text.Prettyprint.Doc@@ -30,7 +32,7 @@   ideErrorText :: NormalizedFilePath -> T.Text -> FileDiagnostic-ideErrorText fp msg = (fp, LSP.Diagnostic {+ideErrorText fp msg = (fp, ShowDiag, LSP.Diagnostic {     _range = noRange,     _severity = Just LSP.DsError,     _code = Nothing,@@ -39,14 +41,28 @@     _relatedInformation = Nothing     }) +-- | Defines whether a particular diagnostic should be reported+--   back to the user.+--+--   One important use case is "missing signature" code lenses,+--   for which we need to enable the corresponding warning during+--   type checking. However, we do not want to show the warning+--   unless the programmer asks for it (#261).+data ShowDiagnostic+    = ShowDiag  -- ^ Report back to the user+    | HideDiag  -- ^ Hide from user+    deriving (Eq, Ord, Show) +instance NFData ShowDiagnostic where+    rnf = rwhnf+ -- | Human readable diagnostics for a specific file. -- --   This type packages a pretty printed, human readable error message --   along with the related source location so that we can display the error --   on either the console or in the IDE at the right source location. ---type FileDiagnostic = (NormalizedFilePath, Diagnostic)+type FileDiagnostic = (NormalizedFilePath, ShowDiagnostic, Diagnostic)  prettyRange :: Range -> Doc Terminal.AnsiStyle prettyRange Range{..} = f _start <> "-" <> f _end@@ -66,9 +82,10 @@ prettyDiagnostics = vcat . map prettyDiagnostic  prettyDiagnostic :: FileDiagnostic -> Doc Terminal.AnsiStyle-prettyDiagnostic (fp, LSP.Diagnostic{..}) =+prettyDiagnostic (fp, sh, LSP.Diagnostic{..}) =     vcat         [ slabel_ "File:    " $ pretty (fromNormalizedFilePath fp)+        , slabel_ "Hidden:  " $ if sh == ShowDiag then "no" else "yes"         , slabel_ "Range:   " $ prettyRange _range         , slabel_ "Source:  " $ pretty _source         , slabel_ "Severity:" $ pretty $ show sev
src/Development/IDE/Types/Location.hs view
@@ -21,15 +21,23 @@     , filePathToUri     , filePathToUri'     , uriToFilePath'+    , readSrcSpan     ) where +import Control.Applicative import Language.Haskell.LSP.Types (Location(..), Range(..), Position(..)) import Control.DeepSeq+import Control.Monad import Data.Binary import Data.Maybe as Maybe import Data.Hashable import Data.String+import qualified Data.Text as T+import FastString+import Network.URI import System.FilePath+import qualified System.FilePath.Posix as FPP+import qualified System.FilePath.Windows as FPW import System.Info.Extra import qualified Language.Haskell.LSP.Types as LSP import Language.Haskell.LSP.Types as LSP (@@ -39,6 +47,8 @@   , toNormalizedUri   , fromNormalizedUri   )+import GHC+import Text.ParserCombinators.ReadP as ReadP   -- | Newtype wrapper around FilePath that always has normalized slashes.@@ -49,25 +59,9 @@     fromString = toNormalizedFilePath  toNormalizedFilePath :: FilePath -> NormalizedFilePath+-- We want to keep empty paths instead of normalising them to "." toNormalizedFilePath "" = NormalizedFilePath ""-toNormalizedFilePath fp = NormalizedFilePath $ normalise' fp-    where-        -- We do not use System.FilePath’s normalise here since that-        -- also normalises things like the case of the drive letter-        -- which NormalizedUri does not normalise so we get VFS lookup failures.-        normalise' :: FilePath -> FilePath-        normalise' = oneSlash . map (\c -> if isPathSeparator c then pathSeparator else c)--        -- Allow double slashes as the very first element of the path for UNC drives on Windows-        -- otherwise turn adjacent slashes into one. These slashes often arise from dodgy CPP-        oneSlash :: FilePath -> FilePath-        oneSlash (x:xs) | isWindows = x : f xs-        oneSlash xs = f xs--        f (x:y:xs) | isPathSeparator x, isPathSeparator y = f (x:xs)-        f (x:xs) = x : f xs-        f [] = []-+toNormalizedFilePath fp = NormalizedFilePath $ normalise fp  fromNormalizedFilePath :: NormalizedFilePath -> FilePath fromNormalizedFilePath (NormalizedFilePath fp) = fp@@ -78,13 +72,49 @@ -- So we have our own wrapper here that supports empty filepaths. uriToFilePath' :: Uri -> Maybe FilePath uriToFilePath' uri-    | uri == filePathToUri "" = Just ""+    | uri == fromNormalizedUri emptyPathUri = Just ""     | otherwise = LSP.uriToFilePath uri +emptyPathUri :: NormalizedUri+emptyPathUri = filePathToUri' ""+ filePathToUri' :: NormalizedFilePath -> NormalizedUri-filePathToUri' = toNormalizedUri . filePathToUri . fromNormalizedFilePath+filePathToUri' (NormalizedFilePath fp) = toNormalizedUri $ Uri $ T.pack $ LSP.fileScheme <> "//" <> platformAdjustToUriPath fp+  where+    -- The definitions below are variants of the corresponding functions in Language.Haskell.LSP.Types.Uri that assume that+    -- the filepath has already been normalised. This is necessary since normalising the filepath has a nontrivial cost. +    toNormalizedUri :: Uri -> NormalizedUri+    toNormalizedUri (Uri t) =+      NormalizedUri $ T.pack $ escapeURIString isUnescapedInURI $ unEscapeString $ T.unpack t +    platformAdjustToUriPath :: FilePath -> String+    platformAdjustToUriPath srcPath+      | isWindows = '/' : escapedPath+      | otherwise = escapedPath+      where+        (splitDirectories, splitDrive)+          | isWindows =+              (FPW.splitDirectories, FPW.splitDrive)+          | otherwise =+              (FPP.splitDirectories, FPP.splitDrive)+        escapedPath =+            case splitDrive srcPath of+                (drv, rest) ->+                    convertDrive drv `FPP.joinDrive`+                    FPP.joinPath (map (escapeURIString unescaped) $ splitDirectories rest)+        -- splitDirectories does not remove the path separator after the drive so+        -- we do a final replacement of \ to /+        convertDrive drv+          | isWindows && FPW.hasTrailingPathSeparator drv =+              FPP.addTrailingPathSeparator (init drv)+          | otherwise = drv+        unescaped c+          | isWindows = isUnreserved c || c `elem` [':', '\\', '/']+          | otherwise = isUnreserved c || c == '/'+++ fromUri :: LSP.NormalizedUri -> NormalizedFilePath fromUri = toNormalizedFilePath . fromMaybe noFilePath . uriToFilePath' . fromNormalizedUri @@ -96,6 +126,42 @@ noRange :: Range noRange =  Range (Position 0 0) (Position 100000 0) - showPosition :: Position -> String showPosition Position{..} = show (_line + 1) ++ ":" ++ show (_character + 1)++-- | Parser for the GHC output format+readSrcSpan :: ReadS SrcSpan+readSrcSpan = readP_to_S (singleLineSrcSpanP <|> multiLineSrcSpanP)+  where+    singleLineSrcSpanP, multiLineSrcSpanP :: ReadP SrcSpan+    singleLineSrcSpanP = do+      fp <- filePathP+      l  <- readS_to_P reads <* char ':'+      c0 <- readS_to_P reads+      c1 <- (char '-' *> readS_to_P reads) <|> pure c0+      let from = mkSrcLoc fp l c0+          to   = mkSrcLoc fp l c1+      return $ mkSrcSpan from to++    multiLineSrcSpanP = do+      fp <- filePathP+      s <- parensP (srcLocP fp)+      void $ char '-'+      e <- parensP (srcLocP fp)+      return $ mkSrcSpan s e++    parensP :: ReadP a -> ReadP a+    parensP = between (char '(') (char ')')++    filePathP :: ReadP FastString+    filePathP = fromString <$> (readFilePath <* char ':') <|> pure ""++    srcLocP :: FastString -> ReadP SrcLoc+    srcLocP fp = do+      l <- readS_to_P reads+      void $ char ','+      c <- readS_to_P reads+      return $ mkSrcLoc fp l c++    readFilePath :: ReadP FilePath+    readFilePath = some ReadP.get
test/data/GotoHover.hs view
@@ -1,16 +1,16 @@--module Testing ( module Testing )where+{- HLINT ignore -}+module Testing ( module Testing ) where import Data.Text (Text, pack) data TypeConstructor = DataConstructor   { fff :: Text   , ggg :: Int } aaa :: TypeConstructor aaa = DataConstructor-  { fff = ""-  , ggg = 0+  { fff = "dfgy"+  , ggg = 832   } bbb :: TypeConstructor-bbb = DataConstructor "" 0+bbb = DataConstructor "mjgp" 2994 ccc :: (Text, Int) ccc = (fff bbb, ggg aaa) ddd :: Num a => a -> a -> a@@ -19,9 +19,32 @@ hhh (Just a) (><) = a >< a iii a b = a `b` a jjj s = pack $ s <> s-class Class a where+class MyClass a where   method :: a -> Int-instance Class Int where+instance MyClass Int where   method = succ-kkk :: Class a => Int -> a -> Int+kkk :: MyClass a => Int -> a -> Int kkk n c = n + method c++doBind :: Maybe ()+doBind = do unwrapped <- Just ()+            return unwrapped++listCompBind :: [Char]+listCompBind = [ succ c | c <- "ptfx" ]++multipleClause :: Bool -> Char+multipleClause True  =    't'+multipleClause False = 'f'++-- | Recognizable docs: kpqz+documented :: Monad m => Either Int (m a)+documented = Left 7518++listOfInt = [ 8391 :: Int, 6268 ]++outer :: Bool+outer = undefined where++  inner :: Char+  inner = undefined
test/exe/Main.hs view
@@ -2,20 +2,24 @@ -- SPDX-License-Identifier: Apache-2.0  {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE CPP #-} #include "ghc-api-version.h"  module Main (main) where  import Control.Applicative.Combinators+import Control.Exception (catch) import Control.Monad import Control.Monad.IO.Class (liftIO) import Data.Char (toLower) import Data.Foldable+import Data.List import Development.IDE.GHC.Util import qualified Data.Text as T import Development.IDE.Test import Development.IDE.Test.Runfiles+import Development.IDE.Types.Location import Language.Haskell.LSP.Test import Language.Haskell.LSP.Types import Language.Haskell.LSP.Types.Capabilities@@ -37,11 +41,17 @@       closeDoc doc       void (message :: Session WorkDoneProgressEndNotification)   , initializeResponseTests+  , completionTests+  , cppTests   , diagnosticTests   , codeActionTests+  , codeLensesTests+  , outlineTests   , findDefinitionAndHoverTests   , pluginTests+  , preprocessorTests   , thTests+  , unitTests   ]  initializeResponseTests :: TestTree@@ -57,14 +67,14 @@     testGroup "initialize response capabilities"     [ chk "   text doc sync"             _textDocumentSync  tds     , chk "   hover"                         _hoverProvider (Just True)-    , chk "NO completion"               _completionProvider  Nothing+    , chk "   completion"               _completionProvider (Just $ CompletionOptions (Just False) (Just ["."]) Nothing)     , chk "NO signature help"        _signatureHelpProvider  Nothing     , chk "   goto definition"          _definitionProvider (Just True)     , chk "NO goto type definition" _typeDefinitionProvider (Just $ GotoOptionsStatic False)     , chk "NO goto implementation"  _implementationProvider (Just $ GotoOptionsStatic False)     , chk "NO find references"          _referencesProvider  Nothing     , chk "NO doc highlight"     _documentHighlightProvider  Nothing-    , chk "NO doc symbol"           _documentSymbolProvider  Nothing+    , chk "   doc symbol"           _documentSymbolProvider  (Just True)     , chk "NO workspace symbol"    _workspaceSymbolProvider  Nothing     , chk "   code action"             _codeActionProvider $ Just $ CodeActionOptionsStatic True     , chk "   code lens"                 _codeLensProvider $ Just $ CodeLensOptions Nothing@@ -299,6 +309,7 @@   , testSessionWait "package imports" $ do       let thisDataListContent = T.unlines             [ "module Data.List where"+            , "x :: Integer"             , "x = 123"             ]       let mainContent = T.unlines@@ -379,11 +390,18 @@   [ renameActionTests   , typeWildCardActionTests   , removeImportTests+  , extendImportTests+  , fixConstructorImportTests   , importRenameActionTests   , fillTypedHoleTests   , addSigActionTests   ] +codeLensesTests :: TestTree+codeLensesTests = testGroup "code lenses"+  [ addSigLensesTests+  ]+ renameActionTests :: TestTree renameActionTests = testGroup "rename actions"   [ testSession "change to local variable name" $ do@@ -541,6 +559,7 @@             [ "{-# OPTIONS_GHC -Wunused-imports #-}"             , "module ModuleB where"             , "import ModuleA"+            , "stuffB :: Integer"             , "stuffB = 123"             ]       docB <- openDoc' "ModuleB.hs" "haskell" contentB@@ -553,6 +572,7 @@       let expectedContentAfterAction = T.unlines             [ "{-# OPTIONS_GHC -Wunused-imports #-}"             , "module ModuleB where"+            , "stuffB :: Integer"             , "stuffB = 123"             ]       liftIO $ expectedContentAfterAction @=? contentAfterAction@@ -565,6 +585,7 @@             [ "{-# OPTIONS_GHC -Wunused-imports #-}"             , "module ModuleB where"             , "import qualified ModuleA"+            , "stuffB :: Integer"             , "stuffB = 123"             ]       docB <- openDoc' "ModuleB.hs" "haskell" contentB@@ -577,11 +598,248 @@       let expectedContentAfterAction = T.unlines             [ "{-# OPTIONS_GHC -Wunused-imports #-}"             , "module ModuleB where"+            , "stuffB :: Integer"             , "stuffB = 123"             ]       liftIO $ expectedContentAfterAction @=? contentAfterAction+  , testSession "redundant binding" $ do+      let contentA = T.unlines+            [ "module ModuleA where"+            , "stuffA = False"+            , "stuffB :: Integer"+            , "stuffB = 123"+            , "stuffC = ()"+            ]+      _docA <- openDoc' "ModuleA.hs" "haskell" contentA+      let contentB = T.unlines+            [ "{-# OPTIONS_GHC -Wunused-imports #-}"+            , "module ModuleB where"+            , "import ModuleA (stuffA, stuffB, stuffC, stuffA)"+            , "main = print stuffB"+            ]+      docB <- openDoc' "ModuleB.hs" "haskell" contentB+      _ <- waitForDiagnostics+      [CACodeAction action@CodeAction { _title = actionTitle }]+          <- getCodeActions docB (Range (Position 2 0) (Position 2 5))+      liftIO $ "Remove stuffA, stuffC from import" @=? actionTitle+      executeCodeAction action+      contentAfterAction <- documentContents docB+      let expectedContentAfterAction = T.unlines+            [ "{-# OPTIONS_GHC -Wunused-imports #-}"+            , "module ModuleB where"+            , "import ModuleA (stuffB)"+            , "main = print stuffB"+            ]+      liftIO $ expectedContentAfterAction @=? contentAfterAction+  , testSession "redundant symbol binding" $ do+      let contentA = T.unlines+            [ "module ModuleA where"+            , "a !! b = a"+            , "stuffB :: Integer"+            , "stuffB = 123"+            ]+      _docA <- openDoc' "ModuleA.hs" "haskell" contentA+      let contentB = T.unlines+            [ "{-# OPTIONS_GHC -Wunused-imports #-}"+            , "module ModuleB where"+            , "import qualified ModuleA as A ((!!), stuffB, (!!))"+            , "main = print A.stuffB"+            ]+      docB <- openDoc' "ModuleB.hs" "haskell" contentB+      _ <- waitForDiagnostics+      [CACodeAction action@CodeAction { _title = actionTitle }]+          <- getCodeActions docB (Range (Position 2 0) (Position 2 5))+#if MIN_GHC_API_VERSION(8,6,0)+      liftIO $ "Remove !! from import" @=? actionTitle+#else+      liftIO $ "Remove A.!! from import" @=? actionTitle+#endif+      executeCodeAction action+      contentAfterAction <- documentContents docB+      let expectedContentAfterAction = T.unlines+            [ "{-# OPTIONS_GHC -Wunused-imports #-}"+            , "module ModuleB where"+            , "import qualified ModuleA as A (stuffB)"+            , "main = print A.stuffB"+            ]+      liftIO $ expectedContentAfterAction @=? contentAfterAction+  , (`xfail` "known broken (#299)") $ testSession "redundant hierarchical import" $ do+      let contentA = T.unlines+            [ "module ModuleA where"+            , "data A = A"+            , "stuffB :: Integer"+            , "stuffB = 123"+            ]+      _docA <- openDoc' "ModuleA.hs" "haskell" contentA+      let contentB = T.unlines+            [ "{-# OPTIONS_GHC -Wunused-imports #-}"+            , "module ModuleB where"+            , "import ModuleA (A(..), stuffB)"+            , "main = print stuffB"+            ]+      docB <- openDoc' "ModuleB.hs" "haskell" contentB+      _ <- waitForDiagnostics+      [CACodeAction action@CodeAction { _title = actionTitle }]+          <- getCodeActions docB (Range (Position 2 0) (Position 2 5))+      liftIO $ "Remove A from import" @=? actionTitle+      executeCodeAction action+      contentAfterAction <- documentContents docB+      let expectedContentAfterAction = T.unlines+            [ "{-# OPTIONS_GHC -Wunused-imports #-}"+            , "module ModuleB where"+            , "import ModuleA (stuffB)"+            , "main = print stuffB"+            ]+      liftIO $ expectedContentAfterAction @=? contentAfterAction   ] +extendImportTests :: TestTree+extendImportTests = testGroup "extend import actions"+  [ testSession "extend single line import with value" $ template+      (T.unlines+            [ "module ModuleA where"+            , "stuffA :: Double"+            , "stuffA = 0.00750"+            , "stuffB :: Integer"+            , "stuffB = 123"+            ])+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA as A (stuffB)"+            , "main = print (stuffA, stuffB)"+            ])+      (Range (Position 3 17) (Position 3 18))+      "Add stuffA to the import list of ModuleA"+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA as A (stuffA, stuffB)"+            , "main = print (stuffA, stuffB)"+            ])+  , testSession "extend single line import with type" $ template+      (T.unlines+            [ "module ModuleA where"+            , "type A = Double"+            ])+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA ()"+            , "b :: A"+            , "b = 0"+            ])+      (Range (Position 2 5) (Position 2 5))+      "Add A to the import list of ModuleA"+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA (A)"+            , "b :: A"+            , "b = 0"+            ])+  ,  (`xfail` "known broken") $ testSession "extend single line import with constructor" $ template+      (T.unlines+            [ "module ModuleA where"+            , "data A = Constructor"+            ])+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA (A)"+            , "b :: A"+            , "b = Constructor"+            ])+      (Range (Position 2 5) (Position 2 5))+      "Add Constructor to the import list of ModuleA"+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA (A(Constructor))"+            , "b :: A"+            , "b = Constructor"+            ])+  , testSession "extend single line qualified import with value" $ template+      (T.unlines+            [ "module ModuleA where"+            , "stuffA :: Double"+            , "stuffA = 0.00750"+            , "stuffB :: Integer"+            , "stuffB = 123"+            ])+      (T.unlines+            [ "module ModuleB where"+            , "import qualified ModuleA as A (stuffB)"+            , "main = print (A.stuffA, A.stuffB)"+            ])+      (Range (Position 3 17) (Position 3 18))+      "Add stuffA to the import list of ModuleA"+      (T.unlines+            [ "module ModuleB where"+            , "import qualified ModuleA as A (stuffA, stuffB)"+            , "main = print (A.stuffA, A.stuffB)"+            ])+  , testSession "extend multi line import with value" $ template+      (T.unlines+            [ "module ModuleA where"+            , "stuffA :: Double"+            , "stuffA = 0.00750"+            , "stuffB :: Integer"+            , "stuffB = 123"+            ])+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA (stuffB"+            , "               )"+            , "main = print (stuffA, stuffB)"+            ])+      (Range (Position 3 17) (Position 3 18))+      "Add stuffA to the import list of ModuleA"+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA (stuffA, stuffB"+            , "               )"+            , "main = print (stuffA, stuffB)"+            ])+  ]+  where+    template contentA contentB range expectedAction expectedContentB = do+      _docA <- openDoc' "ModuleA.hs" "haskell" contentA+      docB <- openDoc' "ModuleB.hs" "haskell" contentB+      _ <- waitForDiagnostics+      CACodeAction action@CodeAction { _title = actionTitle } : _+                  <- sortOn (\(CACodeAction CodeAction{_title=x}) -> x) <$>+                     getCodeActions docB range+      liftIO $ expectedAction @=? actionTitle+      executeCodeAction action+      contentAfterAction <- documentContents docB+      liftIO $ expectedContentB @=? contentAfterAction++fixConstructorImportTests :: TestTree+fixConstructorImportTests = testGroup "fix import actions"+  [ testSession "fix constructor import" $ template+      (T.unlines+            [ "module ModuleA where"+            , "data A = Constructor"+            ])+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA(Constructor)"+            ])+      (Range (Position 1 10) (Position 1 11))+      "Fix import of A(Constructor)"+      (T.unlines+            [ "module ModuleB where"+            , "import ModuleA(A(Constructor))"+            ])+  ]+  where+    template contentA contentB range expectedAction expectedContentB = do+      _docA <- openDoc' "ModuleA.hs" "haskell" contentA+      docB  <- openDoc' "ModuleB.hs" "haskell" contentB+      _diags <- waitForDiagnostics+      CACodeAction action@CodeAction { _title = actionTitle } : _+                  <- sortOn (\(CACodeAction CodeAction{_title=x}) -> x) <$>+                     getCodeActions docB range+      liftIO $ expectedAction @=? actionTitle+      executeCodeAction action+      contentAfterAction <- documentContents docB+      liftIO $ expectedContentB @=? contentAfterAction+ importRenameActionTests :: TestTree importRenameActionTests = testGroup "import rename actions"   [ testSession "Data.Mape -> Data.Map"   $ check "Map"@@ -668,14 +926,14 @@  addSigActionTests :: TestTree addSigActionTests = let-  header = T.unlines [ "{-# OPTIONS_GHC -Wmissing-signatures #-}"-                     , "module Sigs where"]-  before  def     = T.unlines [header,      def]-  after'  def sig = T.unlines [header, sig, def]+  header = "{-# OPTIONS_GHC -Wmissing-signatures #-}"+  moduleH = "module Sigs where"+  before def     = T.unlines [header, moduleH,      def]+  after' def sig = T.unlines [header, moduleH, sig, def]    def >:: sig = testSession (T.unpack def) $ do     let originalCode = before def-    let expectedCode = after'  def sig+    let expectedCode = after' def sig     doc <- openDoc' "Sigs.hs" "haskell" originalCode     _ <- waitForDiagnostics     actionsOrCommands <- getCodeActions doc (Range (Position 3 1) (Position 3 maxBound))@@ -685,14 +943,53 @@     liftIO $ expectedCode @=? modifiedCode   in   testGroup "add signature"-  [ "abc = True"             >:: "abc :: Bool"-  , "foo a b = a + b"        >:: "foo :: Num a => a -> a -> a"-  , "bar a b = show $ a + b" >:: "bar :: (Show a, Num a) => a -> a -> String"-  , "(!!!) a b = a > b"      >:: "(!!!) :: Ord a => a -> a -> Bool"-  , "a >>>> b = a + b"       >:: "(>>>>) :: Num a => a -> a -> a"-  , "a `haha` b = a b"       >:: "haha :: (t1 -> t2) -> t1 -> t2"-  ]+    [ "abc = True"             >:: "abc :: Bool"+    , "foo a b = a + b"        >:: "foo :: Num a => a -> a -> a"+    , "bar a b = show $ a + b" >:: "bar :: (Show a, Num a) => a -> a -> String"+    , "(!!!) a b = a > b"      >:: "(!!!) :: Ord a => a -> a -> Bool"+    , "a >>>> b = a + b"       >:: "(>>>>) :: Num a => a -> a -> a"+    , "a `haha` b = a b"       >:: "haha :: (t1 -> t2) -> t1 -> t2"+    ] +addSigLensesTests :: TestTree+addSigLensesTests = let+  missing = "{-# OPTIONS_GHC -Wmissing-signatures -Wunused-matches #-}"+  notMissing = "{-# OPTIONS_GHC -Wunused-matches #-}"+  moduleH = "module Sigs where"+  other = T.unlines ["f :: Integer -> Integer", "f x = 3"]+  before  withMissing def+    = T.unlines $ (if withMissing then (missing :) else (notMissing :)) [moduleH, def, other]+  after'  withMissing def sig+    = T.unlines $ (if withMissing then (missing :) else (notMissing :)) [moduleH, sig, def, other]++  sigSession withMissing def sig = testSession (T.unpack def) $ do+    let originalCode = before withMissing def+    let expectedCode = after' withMissing def sig+    doc <- openDoc' "Sigs.hs" "haskell" originalCode+    [CodeLens {_command = Just c}] <- getCodeLenses doc+    executeCommand c+    modifiedCode <- getDocumentEdit doc+    liftIO $ expectedCode @=? modifiedCode+  in+  testGroup "add signature"+    [ testGroup "with warnings enabled"+      [ sigSession True "abc = True"             "abc :: Bool"+      , sigSession True "foo a b = a + b"        "foo :: Num a => a -> a -> a"+      , sigSession True "bar a b = show $ a + b" "bar :: (Show a, Num a) => a -> a -> String"+      , sigSession True "(!!!) a b = a > b"      "(!!!) :: Ord a => a -> a -> Bool"+      , sigSession True "a >>>> b = a + b"       "(>>>>) :: Num a => a -> a -> a"+      , sigSession True "a `haha` b = a b"       "haha :: (t1 -> t2) -> t1 -> t2"+      ]+    , testGroup "with warnings disabled"+      [ sigSession False "abc = True"             "abc :: Bool"+      , sigSession False "foo a b = a + b"        "foo :: Num a => a -> a -> a"+      , sigSession False "bar a b = show $ a + b" "bar :: (Show a, Num a) => a -> a -> String"+      , sigSession False "(!!!) a b = a > b"      "(!!!) :: Ord a => a -> a -> Bool"+      , sigSession False "a >>>> b = a + b"       "(>>>>) :: Num a => a -> a -> a"+      , sigSession False "a `haha` b = a b"       "haha :: (t1 -> t2) -> t1 -> t2"+      ]+    ]+ findDefinitionAndHoverTests :: TestTree findDefinitionAndHoverTests = let @@ -707,6 +1004,8 @@     check (ExpectRange expectedRange) = do       assertNDefinitionsFound 1 defs       assertRangeCorrect (head defs) expectedRange+    check ExpectNoDefinitions = do+      assertNDefinitionsFound 0 defs     check ExpectExternFail = liftIO $ assertFailure "Expecting to fail to find in external file"     check _ = pure () -- all other expectations not relevant to getDefinition @@ -721,13 +1020,14 @@      check expected =       case hover of-        Nothing -> liftIO $ assertFailure "no hover found"+        Nothing -> unless (expected == ExpectNoHover) $ liftIO $ assertFailure "no hover found"         Just Hover{_contents = (HoverContents MarkupContent{_value = msg})                   ,_range    = rangeInHover } ->           case expected of             ExpectRange  expectedRange -> checkHoverRange expectedRange rangeInHover msg             ExpectHoverRange expectedRange -> checkHoverRange expectedRange rangeInHover msg             ExpectHoverText snippets -> liftIO $ traverse_ (`assertFoundIn` msg) snippets+            ExpectNoHover -> liftIO $ assertFailure $ "Expected no hover but got " <> show hover             _ -> pure () -- all other expectations not relevant to hover         _ -> liftIO $ assertFailure $ "test not expecting this kind of hover info" <> show hover @@ -775,42 +1075,78 @@   aaaL14 = Position 14 20  ;  aaa    = [mkR   7  0    7  3]   dcL7   = Position  7 11  ;  tcDC   = [mkR   3 23    5 16]   dcL12  = Position 12 11  ;-  xtcL5  = Position  5 11  ;  xtc    = [ExpectExternFail]-  tcL6   = Position  6 11  ;  tcData = [mkR   3  0    5 16]+  xtcL5  = Position  5 11  ;  xtc    = [ExpectExternFail,   ExpectHoverText ["Int", "Defined in ‘GHC.Types’"]]+  tcL6   = Position  6 11  ;  tcData = [mkR   3  0    5 16, ExpectHoverText ["TypeConstructor", "GotoHover.hs:4:1"]]   vvL16  = Position 16 12  ;  vv     = [mkR  16  4   16  6]   opL16  = Position 16 15  ;  op     = [mkR  17  2   17  4]   opL18  = Position 18 22  ;  opp    = [mkR  18 13   18 17]   aL18   = Position 18 20  ;  apmp   = [mkR  18 10   18 11]   b'L19  = Position 19 13  ;  bp     = [mkR  19  6   19  7]-  xvL20  = Position 20  8  ;  xvMsg  = [ExpectHoverText ["Data.Text.pack", ":: String -> Text"], ExpectExternFail]-  clL23  = Position 23 11  ;  cls    = [mkR  21  0   22 20]+  xvL20  = Position 20  8  ;  xvMsg  = [ExpectExternFail,   ExpectHoverText ["Data.Text.pack", ":: String -> Text"]]+  clL23  = Position 23 11  ;  cls    = [mkR  21  0   22 20, ExpectHoverText ["MyClass", "GotoHover.hs:22:1"]]   clL25  = Position 25  9-  eclL15 = Position 15  8  ;  ecls   = [ExpectHoverText ["Num"], ExpectExternFail]+  eclL15 = Position 15  8  ;  ecls   = [ExpectExternFail, ExpectHoverText ["Num", "Defined in ‘GHC.Num’"]]+  dnbL29 = Position 29 18  ;  dnb    = [ExpectHoverText [":: ()"],   mkR  29 12   29 21]+  dnbL30 = Position 30 23+  lcbL33 = Position 33 26  ;  lcb    = [ExpectHoverText [":: Char"], mkR  33 26   33 27]+  lclL33 = Position 33 22+  mclL36 = Position 36  1  ;  mcl    = [mkR  36  0   36 14]+  mclL37 = Position 37  1+  spaceL37 = Position 37  24 ; space = [ExpectNoDefinitions, ExpectHoverText [":: Char"]]+  docL41 = Position 41  1  ;  doc    = [ExpectHoverText ["Recognizable docs: kpqz"]]+                           ;  constr = [ExpectHoverText ["Monad m =>"]]+  eitL40 = Position 40 28  ;  kindE  = [ExpectHoverText [":: * -> * -> *\n"]]+  intL40 = Position 40 34  ;  kindI  = [ExpectHoverText [":: *\n"]]+  tvrL40 = Position 40 37  ;  kindV  = [ExpectHoverText [":: * -> *\n"]]+  intL41 = Position 41 20  ;  litI   = [ExpectHoverText ["7518"]]+  chrL36 = Position 36 25  ;  litC   = [ExpectHoverText ["'t'"]]+  txtL8  = Position  8 14  ;  litT   = [ExpectHoverText ["\"dfgv\""]]+  lstL43 = Position 43 12  ;  litL   = [ExpectHoverText ["[ 8391 :: Int, 6268 ]"]]+  outL45 = Position 45  3  ;  outSig = [ExpectHoverText ["outer", "Bool"], mkR 46 0 46 5]+  innL48 = Position 48  5  ;  innSig = [ExpectHoverText ["inner", "Char"], mkR 49 2 49 7]   in   mkFindTests   --     def    hover  look   expect   [ test yes    yes    fffL4  fff    "field in record definition"-  , test broken broken fffL8  fff    "field in record construction"-  , test yes    yes    fffL14 fff    "field name used as accessor"   -- 120 in Calculate.hs-  , test yes    yes    aaaL14 aaa    "top-level name"                -- 120-  , test broken broken dcL7   tcDC   "record data constructor"-  , test yes    yes    dcL12  tcDC   "plain  data constructor"       -- 121-  , test yes    broken tcL6   tcData "type constructor"              -- 147-  , test broken broken xtcL5  xtc    "type constructor from other package"-  , test broken yes    xvL20  xvMsg  "value from other package"      -- 120-  , test yes    yes    vvL16  vv     "plain parameter"               -- 120-  , test yes    yes    aL18   apmp   "pattern match name"            -- 120-  , test yes    yes    opL16  op     "top-level operator"            -- 120, 123-  , test yes    yes    opL18  opp    "parameter operator"            -- 120-  , test yes    yes    b'L19  bp     "name in backticks"             -- 120-  , test yes    broken clL23  cls    "class in instance declaration"-  , test yes    broken clL25  cls    "class in signature"            -- 147-  , test broken broken eclL15 ecls   "external class in signature"+  , test broken broken fffL8  fff    "field in record construction     #71"+  , test yes    yes    fffL14 fff    "field name used as accessor"          -- 120 in Calculate.hs+  , test yes    yes    aaaL14 aaa    "top-level name"                       -- 120+  , test yes    yes    dcL7   tcDC   "data constructor record         #247"+  , test yes    yes    dcL12  tcDC   "data constructor plain"               -- 121+  , test yes    yes    tcL6   tcData "type constructor                #248" -- 147+  , test broken yes    xtcL5  xtc    "type constructor external   #248,249"+  , test broken yes    xvL20  xvMsg  "value external package          #249" -- 120+  , test yes    yes    vvL16  vv     "plain parameter"                      -- 120+  , test yes    yes    aL18   apmp   "pattern match name"                   -- 120+  , test yes    yes    opL16  op     "top-level operator"                   -- 120, 123+  , test yes    yes    opL18  opp    "parameter operator"                   -- 120+  , test yes    yes    b'L19  bp     "name in backticks"                    -- 120+  , test yes    yes    clL23  cls    "class in instance declaration   #250"+  , test yes    yes    clL25  cls    "class in signature              #250" -- 147+  , test broken yes    eclL15 ecls   "external class in signature #249,250"+  , test yes    yes    dnbL29 dnb    "do-notation   bind"                   -- 137+  , test yes    yes    dnbL30 dnb    "do-notation lookup"+  , test yes    yes    lcbL33 lcb    "listcomp   bind"                      -- 137+  , test yes    yes    lclL33 lcb    "listcomp lookup"+  , test yes    yes    mclL36 mcl    "top-level fn 1st clause"+  , test yes    yes    mclL37 mcl    "top-level fn 2nd clause         #246"+  , test yes    yes    spaceL37 space "top-level fn on space #315"+  , test no     broken docL41 doc    "documentation                     #7"+  , test no     broken eitL40 kindE  "kind of Either                  #273"+  , test no     broken intL40 kindI  "kind of Int                     #273"+  , test no     broken tvrL40 kindV  "kind of (* -> *) type variable  #273"+  , test no     broken intL41 litI   "literal Int  in hover info      #274"+  , test no     broken chrL36 litC   "literal Char in hover info      #274"+  , test no     broken txtL8  litT   "literal Text in hover info      #274"+  , test no     broken lstL43 litL   "literal List in hover info      #274"+  , test no     broken docL41 constr "type constraint in hover info   #283"+  , test broken broken outL45 outSig "top-level signature             #310"+  , test broken broken innL48 innSig "inner     signature             #310"   ]   where yes, broken :: (TestTree -> Maybe TestTree)         yes    = Just -- test should run and pass         broken = Just . (`xfail` "known broken")-      --  no = const Nothing -- don't run this test at all+        no = const Nothing -- don't run this test at all  pluginTests :: TestTree pluginTests = testSessionWait "plugins" $ do@@ -834,6 +1170,50 @@       )     ] +cppTests :: TestTree+cppTests =+  testCase "cpp" $ do+    let content =+          T.unlines+            [ "{-# LANGUAGE CPP #-}",+              "module Testing where",+              "#ifdef FOO",+              "foo = 42"+            ]+    -- The error locations differ depending on which C-preprocessor is used.+    -- Some give the column number and others don't (hence -1). Assert either+    -- of them.+    (run $ expectError content (2, -1))+      `catch` ( \e -> do+                  let _ = e :: HUnitFailure+                  run $ expectError content (2, 1)+              )+  where+    expectError :: T.Text -> Cursor -> Session ()+    expectError content cursor = do+      _ <- openDoc' "Testing.hs" "haskell" content+      expectDiagnostics+        [ ( "Testing.hs",+            [(DsError, cursor, "error: unterminated")]+          )+        ]+      expectNoMoreDiagnostics 0.5++preprocessorTests :: TestTree+preprocessorTests = testSessionWait "preprocessor" $ do+  let content =+        T.unlines+          [ "{-# OPTIONS_GHC -F -pgmF=ghcide-test-preprocessor #-}"+          , "module Testing where"+          , "y = x + z" -- plugin replaces x with y, making this have only one diagnostic+          ]+  _ <- openDoc' "Testing.hs" "haskell" content+  expectDiagnostics+    [ ( "Testing.hs",+        [(DsError, (2, 8), "Variable not in scope: z")]+      )+    ]+ thTests :: TestTree thTests =   testGroup@@ -864,6 +1244,240 @@         expectDiagnostics [ ( "B.hs", [(DsError, (6, 29), "Variable not in scope: n")] ) ]     ] +completionTests :: TestTree+completionTests+  = testGroup "completion"+    [ testSessionWait "variable" $ do+        let source = T.unlines ["module A where", "f = hea"]+        docId <- openDoc' "A.hs" "haskell" source+        compls <- getCompletions docId (Position 1 7)+        liftIO $ map dropDocs compls @?= +          [complItem "head" (Just CiFunction) (Just "[a] -> a")]+        let [CompletionItem { _documentation = headDocs}] = compls+        checkDocText "head" headDocs [ "Defined in 'Prelude'"+#if MIN_GHC_API_VERSION(8,6,0)+                                     , "Extract the first element of a list"+#endif+                                     ]+    , testSessionWait "constructor" $ do+        let source = T.unlines ["module A where", "f = Tru"]+        docId <- openDoc' "A.hs" "haskell" source+        compls <- getCompletions docId (Position 1 7)+        liftIO $ map dropDocs compls @?= +          [ complItem "True" (Just CiConstructor) (Just "Bool")+#if MIN_GHC_API_VERSION(8,6,0)+          , complItem "truncate" (Just CiFunction) (Just "(RealFrac a, Integral b) => a -> b")+#else+          , complItem "truncate" (Just CiFunction) (Just "RealFrac a => forall b. Integral b => a -> b") +#endif+          ]+    , testSessionWait "type" $ do+        let source = T.unlines ["{-# OPTIONS_GHC -Wall #-}", "module A () where", "f :: ()", "f = ()"]+        docId <- openDoc' "A.hs" "haskell" source+        expectDiagnostics [ ("A.hs", [(DsWarning, (3,0), "not used")]) ]+        changeDoc docId [TextDocumentContentChangeEvent Nothing Nothing $ T.unlines ["{-# OPTIONS_GHC -Wall #-}", "module A () where", "f :: Bo", "f = True"]]+        compls <- getCompletions docId (Position 2 7)+        liftIO $ map dropDocs compls @?=+            [ complItem "Bounded" (Just CiClass) (Just "* -> Constraint")+            , complItem "Bool" (Just CiStruct) (Just "*") ]+        let [ CompletionItem { _documentation = boundedDocs},+              CompletionItem { _documentation = boolDocs } ] = compls+        checkDocText "Bounded" boundedDocs [ "Defined in 'Prelude'"+#if MIN_GHC_API_VERSION(8,6,0)+                                           , "name the upper and lower limits"+#endif+                                           ]+        checkDocText "Bool" boolDocs [ "Defined in 'Prelude'" ]+    , testSessionWait "qualified" $ do+        let source = T.unlines ["{-# OPTIONS_GHC -Wunused-binds #-}", "module A () where", "f = ()"]+        docId <- openDoc' "A.hs" "haskell" source+        expectDiagnostics [ ("A.hs", [(DsWarning, (2, 0), "not used")]) ]+        changeDoc docId [TextDocumentContentChangeEvent Nothing Nothing $ T.unlines ["{-# OPTIONS_GHC -Wunused-binds #-}", "module A () where", "f = Prelude.hea"]]+        compls <- getCompletions docId (Position 2 15)+        liftIO $ map dropDocs compls @?= +          [complItem "head" (Just CiFunction) (Just "[a] -> a")]+        let [CompletionItem { _documentation = headDocs}] = compls+        checkDocText "head" headDocs [ "Defined in 'Prelude'"+#if MIN_GHC_API_VERSION(8,6,0)+                                     , "Extract the first element of a list"+#endif+                                     ]+    ]+  where+    dropDocs :: CompletionItem -> CompletionItem+    dropDocs ci = ci { _documentation = Nothing }+    complItem label kind ty = CompletionItem+      { _label = label+      , _kind = kind+      , _detail = (":: " <>) <$> ty+      , _documentation = Nothing+      , _deprecated = Nothing+      , _preselect = Nothing+      , _sortText = Nothing+      , _filterText = Nothing+      , _insertText = Nothing+      , _insertTextFormat = Just PlainText+      , _textEdit = Nothing+      , _additionalTextEdits = Nothing+      , _commitCharacters = Nothing+      , _command = Nothing+      , _xdata = Nothing+      }+    getDocText (CompletionDocString s) = s+    getDocText (CompletionDocMarkup (MarkupContent _ s)) = s+    checkDocText thing Nothing _+      = liftIO $ assertFailure $ "docs for " ++ thing ++ " not found"+    checkDocText thing (Just doc) items+      = liftIO $ assertBool ("docs for " ++ thing ++ " contain the strings") $+          all (`T.isInfixOf` getDocText doc) items++outlineTests :: TestTree+outlineTests = testGroup+  "outline"+  [ testSessionWait "type class" $ do+    let source = T.unlines ["module A where", "class A a where a :: a -> Bool"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [ moduleSymbol+          "A"+          (R 0 7 0 8)+          [ classSymbol "A a"+                        (R 1 0 1 30)+                        [docSymbol' "a" SkMethod (R 1 16 1 30) (R 1 16 1 17)]+          ]+      ]+  , testSessionWait "type class instance " $ do+    let source = T.unlines ["class A a where", "instance A () where"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [ classSymbol "A a" (R 0 0 0 15) []+      , docSymbol "A ()" SkInterface (R 1 0 1 19)+      ]+  , testSessionWait "type family" $ do+    let source = T.unlines ["{-# language TypeFamilies #-}", "type family A"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left [docSymbolD "A" "type family" SkClass (R 1 0 1 13)]+  , testSessionWait "type family instance " $ do+    let source = T.unlines+          [ "{-# language TypeFamilies #-}"+          , "type family A a"+          , "type instance A () = ()"+          ]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [ docSymbolD "A a"   "type family" SkClass     (R 1 0 1 15)+      , docSymbol "A ()" SkInterface (R 2 0 2 23)+      ]+  , testSessionWait "data family" $ do+    let source = T.unlines ["{-# language TypeFamilies #-}", "data family A"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left [docSymbolD "A" "data family" SkClass (R 1 0 1 11)]+  , testSessionWait "data family instance " $ do+    let source = T.unlines+          [ "{-# language TypeFamilies #-}"+          , "data family A a"+          , "data instance A () = A ()"+          ]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [ docSymbolD "A a"   "data family" SkClass     (R 1 0 1 11)+      , docSymbol "A ()" SkInterface (R 2 0 2 25)+      ]+  , testSessionWait "constant" $ do+    let source = T.unlines ["a = ()"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [docSymbol "a" SkFunction (R 0 0 0 6)]+  , testSessionWait "pattern" $ do+    let source = T.unlines ["Just foo = Just 21"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [docSymbol "Just foo" SkFunction (R 0 0 0 18)]+  , testSessionWait "pattern with type signature" $ do+    let source = T.unlines ["{-# language ScopedTypeVariables #-}", "a :: () = ()"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [docSymbol "a :: ()" SkFunction (R 1 0 1 12)]+  , testSessionWait "function" $ do+    let source = T.unlines ["a x = ()"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left [docSymbol "a" SkFunction (R 0 0 0 8)]+  , testSessionWait "type synonym" $ do+    let source = T.unlines ["type A = Bool"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [docSymbol' "A" SkTypeParameter (R 0 0 0 13) (R 0 5 0 6)]+  , testSessionWait "datatype" $ do+    let source = T.unlines ["data A = C"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [ docSymbolWithChildren "A"+                              SkStruct+                              (R 0 0 0 10)+                              [docSymbol "C" SkConstructor (R 0 9 0 10)]+      ]+  , testSessionWait "import" $ do+    let source = T.unlines ["import Data.Maybe"]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left+      [docSymbol "import Data.Maybe" SkModule (R 0 0 0 17)]+  , testSessionWait "foreign import" $ do+    let source = T.unlines+          [ "{-# language ForeignFunctionInterface #-}"+          , "foreign import ccall \"a\" a :: Int"+          ]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left [docSymbolD "a" "import" SkObject (R 1 0 1 33)]+  , testSessionWait "foreign export" $ do+    let source = T.unlines+          [ "{-# language ForeignFunctionInterface #-}"+          , "foreign export ccall odd :: Int -> Bool"+          ]+    docId   <- openDoc' "A.hs" "haskell" source+    symbols <- getDocumentSymbols docId+    liftIO $ symbols @?= Left [docSymbolD "odd" "export" SkObject (R 1 0 1 39)]+  ]+ where+  docSymbol name kind loc =+    DocumentSymbol name Nothing kind Nothing loc loc Nothing+  docSymbol' name kind loc selectionLoc =+    DocumentSymbol name Nothing kind Nothing loc selectionLoc Nothing+  docSymbolD name detail kind loc =+    DocumentSymbol name (Just detail) kind Nothing loc loc Nothing+  docSymbolWithChildren name kind loc cc =+    DocumentSymbol name Nothing kind Nothing loc loc (Just $ List cc)+  moduleSymbol name loc cc = DocumentSymbol name+                                            Nothing+                                            SkFile+                                            Nothing+                                            (R 0 0 maxBound 0)+                                            loc+                                            (Just $ List cc)+  classSymbol name loc cc = DocumentSymbol name+                                           (Just "class")+                                           SkClass+                                           Nothing+                                           loc+                                           loc+                                           (Just $ List cc)++pattern R :: Int -> Int -> Int -> Int -> Range+pattern R x y x' y' = Range (Position x y) (Position x' y')+ xfail :: TestTree -> String -> TestTree xfail = flip expectFailBecause @@ -873,7 +1487,10 @@   | ExpectHoverRange Range -- Only hover should report this range   | ExpectHoverText [T.Text] -- the hover message must contain these snippets   | ExpectExternFail -- definition lookup in other file expected to fail+  | ExpectNoDefinitions+  | ExpectNoHover --  | ExpectExtern -- TODO: as above, but expected to succeed: need some more info in here, once we have some working examples+  deriving Eq  mkR :: Int -> Int -> Int -> Int -> Expect mkR startLine startColumn endLine endColumn = ExpectRange $ mkRange startLine startColumn endLine endColumn@@ -918,11 +1535,20 @@   runSessionWithConfig conf cmd fullCaps { _window = Just $ WindowClientCapabilities $ Just True } dir s   where     conf = defaultConfig-      -- If you uncomment this you can see all messages+      -- If you uncomment this you can see all logging       -- which can be quite useful for debugging.-      -- { logMessages = True, logColor = False, logStdErr = True }+      -- { logStdErr = True, logColor = False }+      -- If you really want to, you can also see all messages+      -- { logMessages = True, logColor = False }  openTestDataDoc :: FilePath -> Session TextDocumentIdentifier openTestDataDoc path = do   source <- liftIO $ readFileUtf8 $ "test/data" </> path   openDoc' path "haskell" source++unitTests :: TestTree+unitTests = do+  testGroup "Unit"+     [ testCase "empty file path" $+         uriToFilePath' (fromNormalizedUri $ filePathToUri' "") @?= Just ""+     ]
+ test/preprocessor/Main.hs view
@@ -0,0 +1,10 @@++module Main(main) where++import System.Environment++main :: IO ()+main = do+    _:input:output:_ <- getArgs+    let f = map (\x -> if x == 'x' then 'y' else x)+    writeFile output . f =<< readFile input